Compare commits
141 Commits
2025-02-07
...
2025-04-03
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b1fca404b7 | ||
|
|
b6e6328e6a | ||
|
|
ecf0327378 | ||
|
|
787c551f5f | ||
|
|
d662152088 | ||
|
|
2fcdb52157 | ||
|
|
70f2a32fad | ||
|
|
37356317a4 | ||
|
|
08f3a56285 | ||
|
|
5af71d1c2e | ||
|
|
1ada5ea424 | ||
|
|
91ee6097d2 | ||
|
|
a4b0cddcf8 | ||
|
|
9bc6ae1567 | ||
|
|
c71685b261 | ||
|
|
0ae7d01234 | ||
|
|
345606846f | ||
|
|
9decf78d2d | ||
|
|
a6f2e69e1a | ||
|
|
76fa87c63e | ||
|
|
2e01dfd23a | ||
|
|
99376e75d6 | ||
|
|
be28d50997 | ||
|
|
d03f5388d4 | ||
|
|
48123c8822 | ||
|
|
0fa744f6ec | ||
|
|
42301d4f1a | ||
|
|
6c19254abd | ||
|
|
b5c5d221c4 | ||
|
|
c219d8bdbb | ||
|
|
4812508afc | ||
|
|
57b9f0e54c | ||
|
|
0d2061365c | ||
|
|
4d8a124822 | ||
|
|
a7641a571f | ||
|
|
bd28e04635 | ||
|
|
37382dea44 | ||
|
|
57a8960841 | ||
|
|
6b4ae8308a | ||
|
|
2739550087 | ||
|
|
4ada011632 | ||
|
|
c99afe7956 | ||
|
|
b58b85dc0f | ||
|
|
a407c8b956 | ||
|
|
1851f71850 | ||
|
|
e4f40a82a2 | ||
|
|
b9900e67a6 | ||
|
|
7d558edb3e | ||
|
|
eb4b1c2a07 | ||
|
|
087f88146d | ||
|
|
3a11ccb854 | ||
|
|
068465143b | ||
|
|
1f0846297f | ||
|
|
4a0e0ed954 | ||
|
|
4b7d1ebb59 | ||
|
|
ec536126b9 | ||
|
|
9b00bdcaea | ||
|
|
da17c68830 | ||
|
|
badfb483b2 | ||
|
|
fafe636b7c | ||
|
|
ef15aa2dcd | ||
|
|
2d44c9ad2f | ||
|
|
0a1d0f650f | ||
|
|
2ba7c1ff9a | ||
|
|
0ecf6298a3 | ||
|
|
1d11bb19b8 | ||
|
|
ff7f31ca33 | ||
|
|
6bb9ae92bf | ||
|
|
5238087ddf | ||
|
|
08bb18cefe | ||
|
|
ecbdd32a2d | ||
|
|
2f415dcc6e | ||
|
|
8fc1b22889 | ||
|
|
85a50ce9d5 | ||
|
|
b706e26a32 | ||
|
|
15725c67c7 | ||
|
|
d8da4473e2 | ||
|
|
e1964f21de | ||
|
|
87c5d07807 | ||
|
|
3d0f4868df | ||
|
|
56bd11794e | ||
|
|
e541b9d572 | ||
|
|
a7e8c1f59b | ||
|
|
ec452aabe2 | ||
|
|
2a9599eed8 | ||
|
|
58a2b7ff39 | ||
|
|
448903efe3 | ||
|
|
d76e8f9146 | ||
|
|
3620664a9f | ||
|
|
7ff43f15fc | ||
|
|
e271e6ecf8 | ||
|
|
93d28717e0 | ||
|
|
6df97a156f | ||
|
|
05d06f9016 | ||
|
|
e8574641b0 | ||
|
|
9ac13018c6 | ||
|
|
06b25f1cfc | ||
|
|
6f5d369416 | ||
|
|
4543038fd8 | ||
|
|
345f8b772c | ||
|
|
47311b1dfd | ||
|
|
21e22ed5fb | ||
|
|
0d6497fcdc | ||
|
|
bfaeeb5aea | ||
|
|
b46667f6db | ||
|
|
49932ee6f8 | ||
|
|
57e37e8f4d | ||
|
|
af68a95964 | ||
|
|
959a268f91 | ||
|
|
77a3515470 | ||
|
|
6a008acb2b | ||
|
|
247e1aff83 | ||
|
|
3df4efebaa | ||
|
|
5c8d1f3cff | ||
|
|
01d5e58a5f | ||
|
|
a28300ac42 | ||
|
|
0666483756 | ||
|
|
abca5514af | ||
|
|
2247c66ea6 | ||
|
|
3b638598ad | ||
|
|
63e3e3ceb1 | ||
|
|
23f4c9c4e4 | ||
|
|
356b00e8c7 | ||
|
|
8916e049bd | ||
|
|
287b4a5597 | ||
|
|
d77ee00e70 | ||
|
|
18ac4c2bd4 | ||
|
|
2dc614c6b7 | ||
|
|
7c9bf75393 | ||
|
|
d1102939a2 | ||
|
|
cb060f43b5 | ||
|
|
80bd783d54 | ||
|
|
82b147d235 | ||
|
|
6944f5f81c | ||
|
|
8cbfe85ed4 | ||
|
|
eb2c71d381 | ||
|
|
86161185d9 | ||
|
|
79bf3adb2a | ||
|
|
07ca243d48 | ||
|
|
edaca772c5 | ||
|
|
b30b72b7c3 |
@@ -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>Ubuntu 20.04 LTS</kbd> <sub><i>Focal Fossa</i></sub>
|
||||
• <kbd>Debian 12</kbd> <sub><i>Bookworm</i></sub>
|
||||
• <kbd>Debian 11</kbd> <sub><i>Bullseye</i></sub>
|
||||
• <kbd>Fedora 41</kbd>
|
||||
• <kbd>Fedora 40</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>
|
||||
|
||||
|
||||
|
||||
153
.github/CONTRIBUTING.md
vendored
@@ -7,32 +7,33 @@
|
||||
<br>
|
||||
|
||||
# Contributing to Cockatrice #
|
||||
First off, thanks for taking the time to contribute to our project! 🎉 ❤ ️✨
|
||||
|
||||
The following is a set of guidelines for contributing to Cockatrice. These are
|
||||
mostly guidelines, not rules. Use your best judgment, and feel free to propose
|
||||
changes to this document in a pull request.
|
||||
First off, thanks for taking the time and considering to lend a helping hand to our project! 🎉 ❤ ️✨
|
||||
|
||||
> [!NOTE]
|
||||
> The following is a set of guidelines for contributing to Cockatrice.
|
||||
> These are mostly guidelines, not rules. Use your best judgment, and feel free to
|
||||
> propose changes to this document in a pull request.
|
||||
>
|
||||
> [](
|
||||
> https://discord.gg/3Z9yzmA)
|
||||
> If you'd like to ask questions, get advice, or just want to say "Hi",
|
||||
> the Cockatrice Development Team uses [Discord](https://discord.gg/ZASRzKu)
|
||||
> for communications and you can reach out in the `#dev` channel.
|
||||
|
||||
|
||||
# Recommended Setups #
|
||||
|
||||
For those developers who like the Linux or MacOS environment, many of our
|
||||
For those developers on **Linux** or **macOS** environment, many of our
|
||||
developers like working with a nifty program called [CLion](
|
||||
https://www.jetbrains.com/clion/). The program's a great asset and one of the
|
||||
best tools you'll find on these systems, but you're welcomed to use any IDE
|
||||
you most enjoy.
|
||||
https://www.jetbrains.com/clion/). The program is a great asset and one of the
|
||||
best tools you'll find on these systems.
|
||||
|
||||
Developers who like Windows development tend to find [Visual Studio](
|
||||
Developers on **Windows** systems tend to find [Visual Studio](
|
||||
https://www.visualstudio.com/) the best tool for the job.
|
||||
|
||||
[](https://discord.gg/ZASRzKu)
|
||||
[](https://gitter.im/Cockatrice/Cockatrice)
|
||||
|
||||
If you'd like to ask questions, get advice, or just want to say hi,
|
||||
the Cockatrice Development Team uses [Discord](https://discord.gg/ZASRzKu)
|
||||
for communications in the #dev channel. If you're not into Discord, we also
|
||||
have a [Gitter](https://gitter.im/Cockatrice/Cockatrice) channel available,
|
||||
albeit slightly less active.
|
||||
But you're welcomed to use any IDE you enjoy most of course!
|
||||
|
||||
|
||||
# Code Style Guide #
|
||||
@@ -54,7 +55,7 @@ The message will look like this:
|
||||
*** Then commit and push those changes to this branch. ***
|
||||
*** Check our CONTRIBUTING.md file for more details. ***
|
||||
*** ***
|
||||
*** Thank you ❤️ ***
|
||||
*** Thank you ❤️ ***
|
||||
*** ***
|
||||
***********************************************************
|
||||
```
|
||||
@@ -64,7 +65,7 @@ information on our formatting guidelines.
|
||||
|
||||
### Compatibility ###
|
||||
|
||||
Cockatrice is currently compiled on all platforms using <kbd>C++11</kbd>.
|
||||
Cockatrice is currently compiled on all platforms using <kbd>C++20</kbd>.
|
||||
You'll notice <kbd>C++03</kbd> code throughout the codebase. Please feel free
|
||||
to help convert it over!
|
||||
|
||||
@@ -78,11 +79,12 @@ or other appropriate conversion.
|
||||
### Formatting ###
|
||||
|
||||
The handy tool `clang-format` can format your code for you, it is available for
|
||||
almost any environment. A special `.clang-format` configuration file is
|
||||
almost any environment. A special [`.clang-format`](
|
||||
https://github.com/Cockatrice/Cockatrice/blob/master/.clang-format) configuration file is
|
||||
included in the project and is used to format your code.
|
||||
|
||||
We've also included a bash script, `format.sh`, that will use clang-format to
|
||||
format all files in your pr in one go. Use `./format.sh --help` to show a full
|
||||
format all files in your PR in one go. Use `./format.sh --help` to show a full
|
||||
help page.
|
||||
|
||||
To run clang-format on a single source file simply use the command
|
||||
@@ -90,10 +92,10 @@ To run clang-format on a single source file simply use the command
|
||||
clang-format with a specific version number appended,
|
||||
`find /usr/bin -name clang-format*` should find it for you)
|
||||
|
||||
See [the clang-format documentation](
|
||||
See the [clang-format documentation](
|
||||
https://clang.llvm.org/docs/ClangFormat.html) for more information about the tool.
|
||||
|
||||
#### Header files ####
|
||||
#### Header Files ####
|
||||
|
||||
Use header files with the extension `.h` and source files with the extension
|
||||
`.cpp`.
|
||||
@@ -168,10 +170,10 @@ braces around single line statements is preferred.
|
||||
See the following example:
|
||||
```c++
|
||||
int main()
|
||||
{ // function or class: own line
|
||||
if (someCondition) { // control statement: same line
|
||||
doSomething(); // single line statement, braces preferred
|
||||
} else if (someOtherCondition1) { // else goes on the same line as a closing brace
|
||||
{ // function or class: own line
|
||||
if (someCondition) { // control statement: same line
|
||||
doSomething(); // single line statement, braces preferred
|
||||
} else if (someOtherCondition1) { // else goes on the same line as a closing brace
|
||||
for (int i = 0; i < 100; i++) {
|
||||
doSomethingElse();
|
||||
}
|
||||
@@ -234,7 +236,7 @@ mutating objects.)
|
||||
When pointers can't be avoided, try to use a smart pointer of some sort, such
|
||||
as `QScopedPointer`, or, less preferably, `QSharedPointer`.
|
||||
|
||||
### Database migrations ###
|
||||
### Database Migrations ###
|
||||
|
||||
The servatrice database's schema can be found at `servatrice/servatrice.sql`.
|
||||
Everytime the schema gets modified, some other steps are due:
|
||||
@@ -255,7 +257,7 @@ Ensure that the migration produces the expected effects; e.g. if you add a
|
||||
new column, make sure the migration places it in the same order as
|
||||
servatrice.sql.
|
||||
|
||||
### Protocol buffer ###
|
||||
### Protocol Buffer ###
|
||||
|
||||
Cockatrice and Servatrice exchange data using binary messages. The syntax of
|
||||
these messages is defined in the `proto` files in the `common/pb` folder. These
|
||||
@@ -268,6 +270,7 @@ new clients incompatible to the old server and vice versa.
|
||||
You can find more information on how we use Protobuf on [our wiki!](
|
||||
https://github.com/Cockatrice/Cockatrice/wiki/Client-server-protocol)
|
||||
|
||||
|
||||
# Reviewing Pull Requests #
|
||||
|
||||
After you have finished your changes to the project you should put them on a
|
||||
@@ -286,6 +289,7 @@ all changes have been approved your pull request will be squashed into a single
|
||||
commit and merged into the master branch by a team member. Your change will then
|
||||
be included in the next release 👍
|
||||
|
||||
|
||||
# Translations #
|
||||
|
||||
Basic workflow for translations:
|
||||
@@ -294,16 +298,16 @@ Basic workflow for translations:
|
||||
3. Maintainer verifies and merges the change;
|
||||
4. Transifex picks up the new files from GitHub automatically;
|
||||
5. Translators translate the new untranslated strings on Transifex;
|
||||
6. Before a release, a maintainer fetches the updated translations from Transifex.
|
||||
6. Before a release, a maintainer fetches the newest translations from Transifex.
|
||||
|
||||
### Using Translations (for developers) ###
|
||||
|
||||
All user interface strings inside Cockatrice's source code must be written
|
||||
in English (US).
|
||||
Translations to other languages are managed using [Transifex](
|
||||
https://www.transifex.com/projects/p/cockatrice/).
|
||||
https://transifex.com/cockatrice/cockatrice/).
|
||||
|
||||
Adding a new string to translate is as easy as adding the string in the
|
||||
Adding a new string for translation is as easy as adding the string in the
|
||||
`tr("")` function, the string will be picked up as translatable automatically
|
||||
and translated as needed.
|
||||
For example, setting the text of a label in the way that the string
|
||||
@@ -315,9 +319,9 @@ nameLabel.setText(tr("My name is:"));
|
||||
To translate a string that would have plural forms you can add the amount to
|
||||
the tr() call, also you can add an extra string as a hint for translators:
|
||||
```c++
|
||||
QString message = tr("Everyone draws %n cards", "pop up message", amount);
|
||||
QString message = tr("Everyone draws %n cards", "english hint for translators", amount);
|
||||
```
|
||||
See [QT's wiki on translations](
|
||||
See [Qt's wiki on translations](
|
||||
https://doc.qt.io/qt-5/i18n-source-translation.html#handling-plurals)
|
||||
|
||||
If you're about to propose a change that adds or modifies any translatable
|
||||
@@ -325,7 +329,7 @@ string in the code, you don't need to take care of adding the new strings to
|
||||
the translation files.<br>
|
||||
We have an automated process to update our language source files on a schedule
|
||||
and provide the translators on Transifex with the new contents.<br>
|
||||
Maintainers can also manually trigger this on demand.
|
||||
Maintainers can also manually trigger this workflow on demand via GitHub Actions.
|
||||
|
||||
### Maintaining Translations (for maintainers) ###
|
||||
|
||||
@@ -389,27 +393,27 @@ Now you are ready to commit your changes and open a PR.
|
||||
</details>
|
||||
|
||||
Once the changes get merged, Transifex will pick up the modified files
|
||||
automatically (checked every few hours) and update their online editor where
|
||||
automatically (checked every few hours) and update the web editor where
|
||||
translators will be able to translate the new strings right in the browser.
|
||||
|
||||
### Releasing Translations (for maintainers) ###
|
||||
|
||||
Before rushing out a new release, a maintainer should fetch the most up to date
|
||||
Before publishing a new release, a maintainer should fetch the most up to date
|
||||
translations from Transifex and commit them into the Cockatrice source code.
|
||||
This can be done manually from the Transifex web interface, but it's quite time
|
||||
consuming.
|
||||
|
||||
As an alternative, you can install the Transifex CLI:
|
||||
|
||||
http://docs.transifex.com/developer/client/
|
||||
We utilize the official GitHub integration to push all languages that are 100%
|
||||
translated from Transifex to our GitHub repo automatically.
|
||||
On top, it runs on a quarterly schedule to update changes for incomplete languages.
|
||||
A synchronisation/update can also be triggered manually from the Transifex web interface
|
||||
and a translation treshold can be set.
|
||||
|
||||
As an alternative, you can install the [Transifex CLI](https://developers.transifex.com/docs/cli).
|
||||
You'll then be able to use a git-like cli command to push and pull translations
|
||||
from Transifex to the source code and vice versa.
|
||||
|
||||
### Adding Translations (for translators) ###
|
||||
|
||||
As a translator you can help translate the new strings on [Transifex](
|
||||
https://www.transifex.com/projects/p/cockatrice/).
|
||||
As a translator, you can help to translate new strings on [Transifex](
|
||||
https://www.transifex.com/projects/p/cockatrice/) to your native language.
|
||||
Please have a look at the specific [FAQ for translators](
|
||||
https://github.com/Cockatrice/Cockatrice/wiki/Translation-FAQ).
|
||||
|
||||
@@ -419,9 +423,9 @@ https://github.com/Cockatrice/Cockatrice/wiki/Translation-FAQ).
|
||||
### Publishing A New Release ###
|
||||
|
||||
We use [GitHub Releases](https://github.com/Cockatrice/Cockatrice/releases) to
|
||||
publish new stable versions and betas.
|
||||
Whenever a git tag is pushed to the repository github will create a draft
|
||||
release and upload binaries automatically.
|
||||
publish new stable versions and beta releases.
|
||||
Whenever a git tag is pushed to the repository, GitHub will create a draft
|
||||
release and upload binaries from our CI automatically.
|
||||
|
||||
To create a tag, simply do the following:
|
||||
```bash
|
||||
@@ -433,18 +437,16 @@ git push $UPSTREAM $TAG_NAME
|
||||
```
|
||||
|
||||
You should define the variables as such:
|
||||
```
|
||||
`$UPSTREAM` - the remote for git@github.com:Cockatrice/Cockatrice.git
|
||||
`$TAG_NAME` should be formatted as:
|
||||
- `YYYY-MM-DD-Release-MAJ.MIN.PATCH` for **stable releases**
|
||||
- `YYYY-MM-DD-Development-MAJ.MIN.PATCH-beta.X` for **beta releases**<br>
|
||||
With *MAJ.MIN.PATCH* being the NEXT release version!
|
||||
```
|
||||
- `$UPSTREAM`: the remote for `git@github.com:Cockatrice/Cockatrice.git`
|
||||
- `$TAG_NAME` should be formatted as:
|
||||
- `YYYY-MM-DD-Release-MAJ.MIN.PATCH` for **stable releases**
|
||||
- `YYYY-MM-DD-Development-MAJ.MIN.PATCH-beta.X` for **beta releases**<br>
|
||||
With <kbd>MAJ</kbd>.<kbd>MIN</kbd>.<kbd>PATCH</kbd> being the NEXT release version!
|
||||
|
||||
This will cause a tagged release to be established on the GitHub repository,
|
||||
with the binaries being added to the release whenever they are ready.
|
||||
The release is initially a draft, where the path notes can be edited and after
|
||||
all is good and ready it can be published on GitHub.
|
||||
with the binaries being added to the release whenever they are done building in CI.
|
||||
The release is initially a draft, where the release notes can be edited and after
|
||||
all is checked and ready, it can be published as GitHub release.
|
||||
If you use a SemVer tag including "beta", the release that will be created at
|
||||
GitHub will be marked as a "Pre-release" automatically.
|
||||
The target of the `.../latest` URL will not be changed in that case, it always
|
||||
@@ -457,7 +459,7 @@ revoke the tag by doing the following:
|
||||
git push --delete upstream $TAG_NAME
|
||||
git tag -d $TAG_NAME
|
||||
```
|
||||
You can also do this on GitHub, you'll also want to delete the new release.
|
||||
You can also do this on GitHub, you'll also want to delete the false release.
|
||||
|
||||
In the first lines of [CMakeLists.txt](
|
||||
https://github.com/Cockatrice/Cockatrice/blob/master/CMakeLists.txt)
|
||||
@@ -468,25 +470,24 @@ coming from the tag title, it's good practice to increment the ones at CMake
|
||||
after every full release to stress that master is ahead of the last stable
|
||||
release.
|
||||
The preferred flow of operation is:
|
||||
* Just before a release, make sure the version number in CMakeLists.txt is set
|
||||
to the same release version you are about to tag.
|
||||
* This is also the time to change the pretty name in CMakeLists.txt called
|
||||
GIT_TAG_RELEASENAME and commit and push these changes.
|
||||
* Tag the release following the previously described syntax in order to get it
|
||||
correctly built and deployed by CI.
|
||||
* Wait for the configure step to create the release and update the patch
|
||||
notes.
|
||||
* Check on the github actions page for build progress which should be the top
|
||||
listed [here](
|
||||
- Just before a release, make sure the version number in CMakeLists.txt is set
|
||||
to the same release version you are about to tag.
|
||||
- This is also the time to change the pretty name in CMakeLists.txt called
|
||||
`GIT_TAG_RELEASENAME` and commit and push these changes.
|
||||
- Tag the release following the previously described syntax in order to get it
|
||||
correctly built and deployed by CI.
|
||||
- Wait for the configuration step to create the release and update the patch
|
||||
notes.
|
||||
- Check on the GitHub Actions page for build progress which should be the top
|
||||
listed [here](
|
||||
https://github.com/Cockatrice/Cockatrice/actions?query=event%3Apush+-branch%3Amaster
|
||||
).
|
||||
* When the build has been completed you can verify all uploaded files on the
|
||||
release are in order and hit the publish button.
|
||||
* After the release is complete, update the CMake version number again to the
|
||||
next targeted beta version, typically increasing `PROJECT_VERSION_PATCH` by
|
||||
one.
|
||||
- When the build has been completed, you can verify if all uploaded files on the
|
||||
draft release are included and hit the publish button.
|
||||
- After the release is complete, update the CMake version number again to the
|
||||
next targeted beta version, typically increasing `PROJECT_VERSION_PATCH` by
|
||||
one.
|
||||
|
||||
When releasing a new stable version, all previous beta releases (and tags)
|
||||
should be deleted. This is needed for Cockatrice to update users of the "Beta"
|
||||
release channel correctly to the latest stable version as well.
|
||||
should be deleted as well.
|
||||
This can be done the same way as revoking tags, mentioned above.
|
||||
|
||||
2
.github/workflows/desktop-build.yml
vendored
@@ -330,7 +330,7 @@ jobs:
|
||||
if: matrix.make_package
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: macOS${{matrix.target}}${{ matrix.soc == 'Intel' && '_Intel' || '' }}${{ matrix.type == 'Debug' && '_Debug' || '' }}-dmg
|
||||
name: macOS${{matrix.target}}${{ matrix.soc == 'Intel' && '_Intel' || '' }}${{ matrix.type == 'Debug' && '_Debug' || '' }}-package
|
||||
path: ${{steps.build.outputs.path}}
|
||||
if-no-files-found: error
|
||||
|
||||
|
||||
@@ -74,11 +74,11 @@ endif()
|
||||
|
||||
# A project name is needed for CPack
|
||||
# Version can be overriden by git tags, see cmake/getversion.cmake
|
||||
project("Cockatrice" VERSION 2.10.0)
|
||||
project("Cockatrice" VERSION 2.10.2)
|
||||
|
||||
# Set release name if not provided via env/cmake var
|
||||
if(NOT DEFINED GIT_TAG_RELEASENAME)
|
||||
set(GIT_TAG_RELEASENAME "Rings of the Wild")
|
||||
set(GIT_TAG_RELEASENAME "Omenpath")
|
||||
endif()
|
||||
|
||||
# Use c++20 for all targets
|
||||
|
||||
@@ -26,6 +26,6 @@ RUN cmake .. -DWITH_SERVER=1 -DWITH_CLIENT=0 -DWITH_ORACLE=0 -DWITH_DBCONVERTER=
|
||||
|
||||
WORKDIR /home/servatrice
|
||||
|
||||
EXPOSE 4747 4748
|
||||
EXPOSE 4748
|
||||
|
||||
ENTRYPOINT [ "servatrice", "--log-to-console" ]
|
||||
|
||||
@@ -128,9 +128,9 @@ 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 4747:4747/tcp -t servatrice:latest`<br>
|
||||
`docker run -i -p 4748:4748 -t servatrice:latest`<br>
|
||||
|
||||
>Note: Running this command exposes the TCP port 4747 of the docker container<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).
|
||||
@@ -145,7 +145,7 @@ docker-compose build # Build the Servatrice image using the same Dockerfile a
|
||||
docker-compose up # Setup and run both the MySQL server and Servatrice.
|
||||
```
|
||||
|
||||
>Note: Similar to the above Docker setup, this will expose TCP ports 4747 and 4748.
|
||||
>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.
|
||||
|
||||
|
||||
@@ -4,196 +4,9 @@
|
||||
|
||||
project(Cockatrice VERSION "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}")
|
||||
|
||||
set(cockatrice_SOURCES
|
||||
src/game/cards/abstract_card_drag_item.cpp
|
||||
src/game/cards/abstract_card_item.cpp
|
||||
src/client/game_logic/abstract_client.cpp
|
||||
src/game/board/abstract_counter.cpp
|
||||
src/game/board/abstract_graphics_item.cpp
|
||||
src/game/board/arrow_item.cpp
|
||||
src/game/board/arrow_target.cpp
|
||||
src/game/cards/card_database.cpp
|
||||
src/game/cards/card_database_manager.cpp
|
||||
src/game/cards/card_database_model.cpp
|
||||
src/game/cards/card_database_parser/card_database_parser.cpp
|
||||
src/game/cards/card_database_parser/cockatrice_xml_3.cpp
|
||||
src/game/cards/card_database_parser/cockatrice_xml_4.cpp
|
||||
src/game/cards/card_drag_item.cpp
|
||||
src/game/filters/filter_card.cpp
|
||||
src/client/ui/widgets/cards/card_info_frame_widget.cpp
|
||||
src/client/ui/widgets/cards/card_info_picture_widget.cpp
|
||||
src/client/ui/widgets/cards/card_info_text_widget.cpp
|
||||
src/client/ui/widgets/cards/card_info_display_widget.cpp
|
||||
src/client/ui/widgets/cards/card_size_widget.cpp
|
||||
src/game/cards/card_item.cpp
|
||||
src/game/cards/card_list.cpp
|
||||
src/game/zones/card_zone.cpp
|
||||
src/server/chat_view/chat_view.cpp
|
||||
src/game/board/counter_general.cpp
|
||||
src/deck/custom_line_edit.cpp
|
||||
src/deck/deck_loader.cpp
|
||||
src/deck/deck_list_model.cpp
|
||||
src/deck/deck_stats_interface.cpp
|
||||
src/dialogs/dlg_connect.cpp
|
||||
src/dialogs/dlg_convert_deck_to_cod_format.cpp
|
||||
src/dialogs/dlg_create_token.cpp
|
||||
src/dialogs/dlg_create_game.cpp
|
||||
src/dialogs/dlg_edit_avatar.cpp
|
||||
src/dialogs/dlg_edit_password.cpp
|
||||
src/dialogs/dlg_edit_tokens.cpp
|
||||
src/dialogs/dlg_edit_user.cpp
|
||||
src/dialogs/dlg_filter_games.cpp
|
||||
src/dialogs/dlg_forgot_password_challenge.cpp
|
||||
src/dialogs/dlg_forgot_password_request.cpp
|
||||
src/dialogs/dlg_forgot_password_reset.cpp
|
||||
src/dialogs/dlg_load_deck_from_clipboard.cpp
|
||||
src/dialogs/dlg_load_remote_deck.cpp
|
||||
src/dialogs/dlg_manage_sets.cpp
|
||||
src/dialogs/dlg_move_top_cards_until.cpp
|
||||
src/dialogs/dlg_register.cpp
|
||||
src/dialogs/dlg_roll_dice.cpp
|
||||
src/dialogs/dlg_settings.cpp
|
||||
src/dialogs/dlg_tip_of_the_day.cpp
|
||||
src/dialogs/dlg_update.cpp
|
||||
src/dialogs/dlg_view_log.cpp
|
||||
src/dialogs/dlg_load_deck.cpp
|
||||
src/game/deckview/deck_view.cpp
|
||||
src/game/deckview/deck_view_container.cpp
|
||||
src/game/filters/filter_string.cpp
|
||||
src/game/filters/filter_builder.cpp
|
||||
src/game/filters/filter_tree.cpp
|
||||
src/game/filters/filter_tree_model.cpp
|
||||
src/client/ui/layouts/flow_layout.cpp
|
||||
src/client/ui/widgets/general/layout_containers/flow_widget.cpp
|
||||
src/game/game_scene.cpp
|
||||
src/game/game_selector.cpp
|
||||
src/game/games_model.cpp
|
||||
src/game/game_view.cpp
|
||||
src/client/get_text_with_max.cpp
|
||||
src/game/hand_counter.cpp
|
||||
src/server/handle_public_servers.cpp
|
||||
src/game/zones/hand_zone.cpp
|
||||
src/client/game_logic/key_signals.cpp
|
||||
src/client/ui/line_edit_completer.cpp
|
||||
src/server/local_client.cpp
|
||||
src/server/local_server.cpp
|
||||
src/server/local_server_interface.cpp
|
||||
src/utility/logger.cpp
|
||||
src/client/ui/widgets/cards/card_info_picture_enlarged_widget.cpp
|
||||
src/client/ui/widgets/cards/card_info_picture_with_text_overlay_widget.cpp
|
||||
src/client/ui/widgets/general/display/banner_widget.cpp
|
||||
src/client/ui/widgets/general/display/labeled_input.cpp
|
||||
src/client/ui/widgets/general/display/dynamic_font_size_label.cpp
|
||||
src/client/ui/widgets/general/display/dynamic_font_size_push_button.cpp
|
||||
src/client/ui/widgets/general/display/shadow_background_label.cpp
|
||||
src/main.cpp
|
||||
src/server/message_log_widget.cpp
|
||||
src/client/ui/layouts/overlap_layout.cpp
|
||||
src/client/ui/widgets/general/layout_containers/overlap_widget.cpp
|
||||
src/client/ui/widgets/general/layout_containers/overlap_control_widget.cpp
|
||||
src/server/pending_command.cpp
|
||||
src/game/phase.cpp
|
||||
src/client/ui/phases_toolbar.cpp
|
||||
src/client/ui/picture_loader/picture_loader.cpp
|
||||
src/client/ui/picture_loader/picture_loader_worker.cpp
|
||||
src/client/ui/picture_loader/picture_to_load.cpp
|
||||
src/game/zones/pile_zone.cpp
|
||||
src/client/ui/pixel_map_generator.cpp
|
||||
src/game/player/player.cpp
|
||||
src/game/player/player_list_widget.cpp
|
||||
src/game/player/player_target.cpp
|
||||
src/client/ui/widgets/printing_selector/all_zones_card_amount_widget.cpp
|
||||
src/client/ui/widgets/printing_selector/card_amount_widget.cpp
|
||||
src/client/ui/widgets/printing_selector/printing_selector.cpp
|
||||
src/client/ui/widgets/printing_selector/printing_selector_card_display_widget.cpp
|
||||
src/client/ui/widgets/printing_selector/printing_selector_card_overlay_widget.cpp
|
||||
src/client/ui/widgets/printing_selector/printing_selector_card_search_widget.cpp
|
||||
src/client/ui/widgets/printing_selector/printing_selector_card_selection_widget.cpp
|
||||
src/client/ui/widgets/printing_selector/printing_selector_card_sorting_widget.cpp
|
||||
src/client/ui/widgets/printing_selector/printing_selector_view_options_toolbar_widget.cpp
|
||||
src/client/ui/widgets/printing_selector/printing_selector_view_options_widget.cpp
|
||||
src/client/ui/widgets/printing_selector/set_name_and_collectors_number_display_widget.cpp
|
||||
src/client/network/release_channel.cpp
|
||||
src/client/network/client_update_checker.cpp
|
||||
src/server/remote/remote_client.cpp
|
||||
src/server/remote/remote_decklist_tree_widget.cpp
|
||||
src/server/remote/remote_replay_list_tree_widget.cpp
|
||||
src/client/network/replay_timeline_widget.cpp
|
||||
src/game/zones/select_zone.cpp
|
||||
src/utility/sequence_edit.cpp
|
||||
src/client/network/sets_model.cpp
|
||||
src/settings/card_database_settings.cpp
|
||||
src/settings/download_settings.cpp
|
||||
src/settings/game_filters_settings.cpp
|
||||
src/settings/layouts_settings.cpp
|
||||
src/settings/message_settings.cpp
|
||||
src/settings/recents_settings.cpp
|
||||
src/settings/servers_settings.cpp
|
||||
src/settings/settings_manager.cpp
|
||||
src/settings/cache_settings.cpp
|
||||
src/settings/shortcuts_settings.cpp
|
||||
src/settings/shortcut_treeview.cpp
|
||||
src/settings/card_override_settings.cpp
|
||||
src/settings/debug_settings.cpp
|
||||
src/client/sound_engine.cpp
|
||||
src/client/network/spoiler_background_updater.cpp
|
||||
src/game/zones/stack_zone.cpp
|
||||
src/client/tabs/tab.cpp
|
||||
src/client/tabs/tab_account.cpp
|
||||
src/client/tabs/tab_admin.cpp
|
||||
src/client/tabs/tab_deck_editor.cpp
|
||||
src/client/tabs/tab_deck_storage.cpp
|
||||
src/client/tabs/tab_game.cpp
|
||||
src/client/tabs/tab_logs.cpp
|
||||
src/client/tabs/tab_message.cpp
|
||||
src/client/tabs/tab_replays.cpp
|
||||
src/client/tabs/tab_room.cpp
|
||||
src/client/tabs/tab_server.cpp
|
||||
src/client/tabs/tab_supervisor.cpp
|
||||
src/client/tabs/api/edhrec/tab_edhrec.cpp
|
||||
src/client/tabs/api/edhrec/edhrec_commander_api_response_display_widget.cpp
|
||||
src/client/tabs/api/edhrec/edhrec_commander_api_response_card_details_display_widget.cpp
|
||||
src/client/tabs/api/edhrec/edhrec_commander_api_response_card_list_display_widget.cpp
|
||||
src/client/tabs/api/edhrec/edhrec_commander_api_response_commander_details_display_widget.cpp
|
||||
src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_archidekt_links.cpp
|
||||
src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_average_deck_statistics.cpp
|
||||
src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_card_details.cpp
|
||||
src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_card_list.cpp
|
||||
src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_card_container.cpp
|
||||
src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_card_prices.cpp
|
||||
src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_commander_details.cpp
|
||||
src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response.cpp
|
||||
src/game/zones/table_zone.cpp
|
||||
src/client/tapped_out_interface.cpp
|
||||
src/client/ui/theme_manager.cpp
|
||||
src/client/ui/tip_of_the_day.cpp
|
||||
src/client/translate_counter_name.cpp
|
||||
src/client/update_downloader.cpp
|
||||
src/server/user/user_context_menu.cpp
|
||||
src/server/user/user_info_connection.cpp
|
||||
src/server/user/user_info_box.cpp
|
||||
src/server/user/user_list_manager.cpp
|
||||
src/server/user/user_list_widget.cpp
|
||||
src/client/ui/window_main.cpp
|
||||
src/game/zones/view_zone_widget.cpp
|
||||
src/game/zones/view_zone.cpp
|
||||
src/client/tabs/visual_deck_storage/tab_deck_storage_visual.cpp
|
||||
src/client/ui/widgets/cards/deck_preview_card_picture_widget.cpp
|
||||
src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_color_identity_widget.cpp
|
||||
src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_color_identity_filter_widget.cpp
|
||||
src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_tag_addition_widget.cpp
|
||||
src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_tag_display_widget.cpp
|
||||
src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_tag_dialog.cpp
|
||||
src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_tag_item_widget.cpp
|
||||
src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_deck_tags_display_widget.cpp
|
||||
src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_widget.cpp
|
||||
src/client/ui/widgets/visual_deck_storage/visual_deck_storage_widget.cpp
|
||||
src/client/ui/widgets/visual_deck_storage/visual_deck_storage_folder_display_widget.cpp
|
||||
src/client/ui/widgets/visual_deck_storage/visual_deck_storage_search_widget.cpp
|
||||
src/client/ui/widgets/visual_deck_storage/visual_deck_storage_sort_widget.cpp
|
||||
src/client/ui/widgets/visual_deck_storage/visual_deck_storage_tag_filter_widget.cpp
|
||||
${VERSION_STRING_CPP}
|
||||
)
|
||||
file(GLOB_RECURSE cockatrice_CPP_FILES CONFIGURE_DEPENDS ${CMAKE_SOURCE_DIR}/cockatrice/src/*.cpp)
|
||||
|
||||
set(cockatrice_SOURCES ${cockatrice_CPP_FILES} ${VERSION_STRING_CPP})
|
||||
|
||||
add_subdirectory(sounds)
|
||||
add_subdirectory(themes)
|
||||
@@ -338,6 +151,13 @@ if(APPLE)
|
||||
PATTERN "tls/*.dylib"
|
||||
)
|
||||
|
||||
install(
|
||||
DIRECTORY "${CMAKE_BINARY_DIR}/cockatrice/"
|
||||
DESTINATION ${qtconf_dest_dir}/
|
||||
FILES_MATCHING
|
||||
PATTERN "*.ini"
|
||||
)
|
||||
|
||||
install(
|
||||
CODE "
|
||||
file(WRITE \"\${CMAKE_INSTALL_PREFIX}/${qtconf_dest_dir}/qt.conf\" \"[Paths]
|
||||
@@ -372,6 +192,13 @@ if(WIN32)
|
||||
PATTERN "*.dll"
|
||||
)
|
||||
|
||||
install(
|
||||
DIRECTORY "${CMAKE_BINARY_DIR}/cockatrice/"
|
||||
DESTINATION ./
|
||||
FILES_MATCHING
|
||||
PATTERN "*.ini"
|
||||
)
|
||||
|
||||
# Qt plugins: audio (Qt5), iconengines, imageformats, multimedia (Qt6) platforms, printsupport (Qt5), styles, tls (Qt6)
|
||||
install(
|
||||
DIRECTORY "${QT_PLUGINS_DIR}/"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<RCC>
|
||||
<qresource prefix="/" >
|
||||
<qresource prefix="/">
|
||||
<file>resources/cardback.svg</file>
|
||||
<file>resources/cockatrice.svg</file>
|
||||
<file>resources/hand.svg</file>
|
||||
@@ -13,9 +13,12 @@
|
||||
<file>resources/icons/arrow_top_green.svg</file>
|
||||
<file>resources/icons/arrow_up_green.svg</file>
|
||||
<file>resources/icons/clearsearch.svg</file>
|
||||
<file>resources/icons/cogwheel.svg</file>
|
||||
<file>resources/icons/conceded.svg</file>
|
||||
<file>resources/icons/decrement.svg</file>
|
||||
<file>resources/icons/delete.svg</file>
|
||||
<file>resources/icons/dropdown_collapsed.svg</file>
|
||||
<file>resources/icons/dropdown_expanded.svg</file>
|
||||
<file>resources/icons/forgot_password.svg</file>
|
||||
<file>resources/icons/increment.svg</file>
|
||||
<file>resources/icons/info.svg</file>
|
||||
@@ -24,7 +27,9 @@
|
||||
<file>resources/icons/pencil.svg</file>
|
||||
<file>resources/icons/player.svg</file>
|
||||
<file>resources/icons/ready_start.svg</file>
|
||||
<file>resources/icons/reload.svg</file>
|
||||
<file>resources/icons/remove_row.svg</file>
|
||||
<file>resources/icons/rename.svg</file>
|
||||
<file>resources/icons/scales.svg</file>
|
||||
<file>resources/icons/search.svg</file>
|
||||
<file>resources/icons/settings.svg</file>
|
||||
@@ -35,6 +40,12 @@
|
||||
<file>resources/icons/update.png</file>
|
||||
<file>resources/icons/view.svg</file>
|
||||
|
||||
<file>resources/icons/mana/B.svg</file>
|
||||
<file>resources/icons/mana/G.svg</file>
|
||||
<file>resources/icons/mana/R.svg</file>
|
||||
<file>resources/icons/mana/U.svg</file>
|
||||
<file>resources/icons/mana/W.svg</file>
|
||||
|
||||
<file>resources/config/general.svg</file>
|
||||
<file>resources/config/appearance.svg</file>
|
||||
<file>resources/config/interface.svg</file>
|
||||
@@ -342,19 +353,29 @@
|
||||
|
||||
<!-- ADD TIP OF THE DAY IMAGES HERE -->
|
||||
<file>resources/tips/images/accounts_tab.png</file>
|
||||
<file>resources/tips/images/add_card.png</file>
|
||||
<file>resources/tips/images/arrows.png</file>
|
||||
<file>resources/tips/images/card_select.png</file>
|
||||
<file>resources/tips/images/cockatrice_register.png</file>
|
||||
<file>resources/tips/images/cockatrice_wiki.png</file>
|
||||
<file>resources/tips/images/coin_flip.png</file>
|
||||
<file>resources/tips/images/counter_expression.png</file>
|
||||
<file>resources/tips/images/discord.png</file>
|
||||
<file>resources/tips/images/edhrec.png</file>
|
||||
<file>resources/tips/images/expand_card_view.png</file>
|
||||
<file>resources/tips/images/face_down.png</file>
|
||||
<file>resources/tips/images/filter_games.png</file>
|
||||
<file>resources/tips/images/github_logo.png</file>
|
||||
<file>resources/tips/images/highlight_cards.png</file>
|
||||
<file>resources/tips/images/pawns.png</file>
|
||||
<file>resources/tips/images/setpt.png</file>
|
||||
<file>resources/tips/images/shortcuts.png</file>
|
||||
<file>resources/tips/images/syntax_help.png</file>
|
||||
<file>resources/tips/images/themes.png</file>
|
||||
<file>resources/tips/images/tip_of_the_day.png</file>
|
||||
<file>resources/tips/images/token.png</file>
|
||||
<file>resources/tips/images/updates.png</file>
|
||||
<file>resources/tips/images/visual_deck_tags.png</file>
|
||||
<file>resources/tips/tips_of_the_day.xml</file>
|
||||
|
||||
<file>resources/help/search.md</file>
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
[Rules]
|
||||
# Uncomment a rule to disable logging for that category
|
||||
# The default log level is info
|
||||
*.debug = false
|
||||
|
||||
# Uncomment a rule to disable logging for that category,
|
||||
# or set .debug = true for that category to see debug level logs
|
||||
|
||||
# main = false
|
||||
# qt_translator = false
|
||||
@@ -44,11 +48,15 @@
|
||||
# cockatrice_xml.* = false
|
||||
# cockatrice_xml.xml_3_parser = false
|
||||
# cockatrice_xml.xml_4_parser = false
|
||||
# card_info = false
|
||||
# card_list = false
|
||||
|
||||
# flow_layout = false
|
||||
flow_widget.* = false
|
||||
#flow_layout = false
|
||||
#flow_widget = false
|
||||
#flow_widget.size = false
|
||||
|
||||
# card_info_picture_widget = false
|
||||
|
||||
# pixel_map_generator = false
|
||||
|
||||
# filter_string = false
|
||||
# filter_string = false
|
||||
|
||||
29
cockatrice/resources/icons/cogwheel.svg
Normal file
@@ -0,0 +1,29 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
<svg xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" width="102.5024mm" height="102.24421mm"
|
||||
viewBox="0 0 102.5024 102.24421" version="1.1"
|
||||
id="svg1"
|
||||
inkscape:version="1.4 (e7c3feb100, 2024-10-09)" sodipodi:docname="cog_wheel.svg"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<sodipodi:namedview id="namedview1" pagecolor="#ffffff" bordercolor="#000000" borderopacity="0.25"
|
||||
inkscape:showpageshadow="2" inkscape:pageopacity="0.0" inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1" inkscape:document-units="mm" inkscape:zoom="0.66101291"
|
||||
inkscape:cx="146.74449" inkscape:cy="193.64221" inkscape:window-width="1829"
|
||||
inkscape:window-height="951" inkscape:window-x="0" inkscape:window-y="0"
|
||||
inkscape:window-maximized="1" inkscape:current-layer="layer1"/>
|
||||
<defs id="defs1"/>
|
||||
<g inkscape:label="Layer 1" inkscape:groupmode="layer" id="layer1" transform="translate(-66.148059,-97.377896)">
|
||||
<g id="g1" transform="translate(-165.09777,-82.009607)">
|
||||
<g id="g2" transform="matrix(0.26458333,0,0,0.26458333,64.983323,161.37858)">
|
||||
<path style="fill:#333333;stroke-width:94.4882"
|
||||
d="m 721.34301,94.413051 40.00966,29.763289 29.51933,-9.27053 14.63768,-46.840589 36.83817,0.48792 13.41787,46.108699 28.29952,9.5145 40.74155,-28.299519 29.0314,21.468599 -15.85748,47.08454 16.58937,23.66426 48.30432,-2.68358 12.93,34.88648 -41.71741,27.07971 -0.48792,29.27536 40.49763,26.10387 -10.2464,34.15459 -48.54834,-0.97584 -18.78502,23.42029 15.85749,45.37681 -28.29952,21.71256 -41.22947,-29.27536 -28.54348,9.75846 -13.66184,47.57246 -38.30194,-0.73188 -14.63768,-48.30435 -27.07971,-9.27054 -40.00966,29.27537 -30.25121,-20.49276 17.32125,-46.35266 -17.80918,-24.64009 -49.76812,3.17149 -11.22222,-34.64251 40.74155,-27.07971 0.48792,-30.00725 -41.71739,-27.32367 12.19807,-32.93479 48.30435,2.68358 20.24879,-24.6401 -17.32126,-47.08455 z"
|
||||
id="path1"/>
|
||||
<circle style="fill:#f9f9f9;stroke-width:72.1317" id="path2" cx="822.09906" cy="261.28262"
|
||||
r="83.434792"/>
|
||||
<circle style="fill:#4d4d4d;stroke-width:40.5147" id="circle2" cx="822.09906" cy="261.28262"
|
||||
r="46.863361"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.5 KiB |
10
cockatrice/resources/icons/dropdown_collapsed.svg
Normal file
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 12.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 51448) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [
|
||||
<!ENTITY ns_svg "http://www.w3.org/2000/svg">
|
||||
<!ENTITY ns_xlink "http://www.w3.org/1999/xlink">
|
||||
]>
|
||||
<svg version="1.1" id="Layer_1" xmlns="&ns_svg;" xmlns:xlink="&ns_xlink;" width="460.5" height="531.74"
|
||||
viewBox="0 0 460.5 531.74" overflow="visible" enable-background="new 0 0 460.5 531.74" xml:space="preserve">
|
||||
<polygon fill="#918d8d" points="0.5,0.866 459.5,265.87 0.5,530.874 "/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 657 B |
10
cockatrice/resources/icons/dropdown_expanded.svg
Normal file
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 12.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 51448) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [
|
||||
<!ENTITY ns_svg "http://www.w3.org/2000/svg">
|
||||
<!ENTITY ns_xlink "http://www.w3.org/1999/xlink">
|
||||
]>
|
||||
<svg version="1.1" id="Layer_1" xmlns="&ns_svg;" xmlns:xlink="&ns_xlink;" width="531.74" height="460.5"
|
||||
viewBox="0 0 531.74 460.5" overflow="visible" enable-background="new 0 0 531.74 460.5" xml:space="preserve">
|
||||
<polygon fill="#918d8d" points="530.874,0.5 265.87,459.5 0.866,0.5 "/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 657 B |
47
cockatrice/resources/icons/mana/B.svg
Normal file
@@ -0,0 +1,47 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
width="169.34801mm"
|
||||
height="169.34801mm"
|
||||
viewBox="0 0 600 600"
|
||||
version="1.1"
|
||||
id="svg1"
|
||||
sodipodi:docname="B.svg"
|
||||
inkscape:export-filename="B.svg"
|
||||
inkscape:export-xdpi="96"
|
||||
inkscape:export-ydpi="96"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs1"/>
|
||||
<sodipodi:namedview
|
||||
id="namedview1"
|
||||
pagecolor="#505050"
|
||||
bordercolor="#eeeeee"
|
||||
borderopacity="1"
|
||||
inkscape:showpageshadow="0"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#505050"
|
||||
inkscape:document-units="mm">
|
||||
<inkscape:page
|
||||
x="0"
|
||||
y="0"
|
||||
width="600"
|
||||
height="600"
|
||||
id="page2"
|
||||
margin="0"
|
||||
bleed="0"/>
|
||||
</sodipodi:namedview>
|
||||
<path
|
||||
id="Selection"
|
||||
fill="none"
|
||||
stroke="#000000"
|
||||
stroke-width="1"
|
||||
d="m 275,0.21 c 0,0 42,0 42,0 0,0 19,1.51 19,1.51 29.13,3.58 56.99,10.83 84,22.43 33.64,14.45 59.89,32.91 86.91,57.41 17.38,15.76 34.67,37.56 47.14,57.44 22.78,36.31 37.1,74.56 43.22,117 1.28,8.88 2.72,19.08 2.73,28 0,0 0,34 0,34 0,0 -0.91,10 -0.91,10 -2.36,32.29 -11.93,67.59 -25.4,97 -27.01,58.98 -70.49,105.42 -126.69,137.57 -34.3,19.63 -79.56,33.64 -119,36.52 0,0 -12,0.91 -12,0.91 0,0 -34,0 -34,0 0,0 -10,-0.91 -10,-0.91 -26.57,-1.94 -53.09,-8.27 -78,-17.67 C 163.49,569.9 134.12,553.46 109,532.57 79.87,508.33 54.41,478.39 36.31,445 -5.08,368.65 -11.86,276.57 18.05,195 47.85,113.75 113.21,48.92 194,18.42 213.55,11.04 236.32,5.43 257,2.43 Z"
|
||||
style="display:inline;fill:#ababab;fill-opacity:1"/>
|
||||
<path
|
||||
d="m 299.67889,84.945431 c -118.26949,0 -214.122988,83.892719 -214.122988,187.357619 0,58.63289 30.863828,110.90901 79.041488,145.28579 8.02961,5.77129 12.71356,15.13917 11.29165,25.0089 l -7.86234,55.37086 c -1.17098,8.02962 5.01851,15.22281 13.13176,15.22281 h 64.98968 v -46.83939 c 0,-3.68025 3.01111,-6.69135 6.69135,-6.69135 h 13.38268 c 3.68024,0 6.69135,3.0111 6.69135,6.69135 v 46.83939 h 53.53075 v -46.83939 c 0,-3.68025 3.01111,-6.69135 6.69134,-6.69135 h 13.38269 c 3.68023,0 6.69134,3.0111 6.69134,6.69135 v 46.83939 h 64.98967 c 8.11327,0 14.30275,-7.19319 13.13177,-15.22281 l -7.86234,-55.37086 c -1.4219,-9.78609 3.1784,-19.23761 11.29165,-25.0089 48.17767,-34.37678 79.0415,-86.6529 79.0415,-145.28579 0,-103.4649 -95.8535,-187.357619 -214.123,-187.357619 z M 219.38277,352.59917 c -29.52555,0 -53.53075,-24.0052 -53.53075,-53.53075 0,-29.52555 24.0052,-53.53075 53.53075,-53.53075 29.52555,0 53.53075,24.0052 53.53075,53.53075 0,29.52555 -24.0052,53.53075 -53.53075,53.53075 z m 160.59225,0 c -29.52555,0 -53.53075,-24.0052 -53.53075,-53.53075 0,-29.52555 24.0052,-53.53075 53.53075,-53.53075 29.52555,0 53.53075,24.0052 53.53075,53.53075 0,29.52555 -24.0052,53.53075 -53.53075,53.53075 z"
|
||||
id="path1"
|
||||
style="fill:#4d4d4d;fill-opacity:1;stroke:#000000;stroke-width:0.836419;stroke-opacity:1"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.2 KiB |
47
cockatrice/resources/icons/mana/G.svg
Normal file
@@ -0,0 +1,47 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
width="169.34801mm"
|
||||
height="169.34801mm"
|
||||
viewBox="0 0 600 600"
|
||||
version="1.1"
|
||||
id="svg1"
|
||||
sodipodi:docname="B.svg"
|
||||
inkscape:export-filename="U.svg"
|
||||
inkscape:export-xdpi="96"
|
||||
inkscape:export-ydpi="96"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs1"/>
|
||||
<sodipodi:namedview
|
||||
id="namedview1"
|
||||
pagecolor="#505050"
|
||||
bordercolor="#eeeeee"
|
||||
borderopacity="1"
|
||||
inkscape:showpageshadow="0"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#505050"
|
||||
inkscape:document-units="mm">
|
||||
<inkscape:page
|
||||
x="0"
|
||||
y="0"
|
||||
width="600"
|
||||
height="600"
|
||||
id="page2"
|
||||
margin="0"
|
||||
bleed="0"/>
|
||||
</sodipodi:namedview>
|
||||
<path
|
||||
id="Selection"
|
||||
fill="none"
|
||||
stroke="#000000"
|
||||
stroke-width="1"
|
||||
d="m 275,0.21 c 0,0 42,0 42,0 0,0 19,1.51 19,1.51 29.13,3.58 56.99,10.83 84,22.43 33.64,14.45 59.89,32.91 86.91,57.41 17.38,15.76 34.67,37.56 47.14,57.44 22.78,36.31 37.1,74.56 43.22,117 1.28,8.88 2.72,19.08 2.73,28 0,0 0,34 0,34 0,0 -0.91,10 -0.91,10 -2.36,32.29 -11.93,67.59 -25.4,97 -27.01,58.98 -70.49,105.42 -126.69,137.57 -34.3,19.63 -79.56,33.64 -119,36.52 0,0 -12,0.91 -12,0.91 0,0 -34,0 -34,0 0,0 -10,-0.91 -10,-0.91 -26.57,-1.94 -53.09,-8.27 -78,-17.67 C 163.49,569.9 134.12,553.46 109,532.57 79.87,508.33 54.41,478.39 36.31,445 -5.08,368.65 -11.86,276.57 18.05,195 47.85,113.75 113.21,48.92 194,18.42 213.55,11.04 236.32,5.43 257,2.43 Z"
|
||||
style="display:inline;fill:#5f9f56;fill-opacity:1"/>
|
||||
<path
|
||||
d="m 488.52401,428.04591 -80.36527,-83.50228 h 30.81218 c 9.06362,0 17.08104,-4.61393 20.90363,-12.05153 3.82262,-7.41912 2.56521,-15.92714 -3.29949,-22.19282 l -78.59482,-83.87142 h 29.06188 c 9.15413,0 17.40291,-4.93688 20.98411,-12.55902 3.54092,-7.50219 1.87106,-16.23169 -4.26525,-22.22053 L 312.83499,83.240199 c -6.06588,-5.951919 -17.38282,-5.951919 -23.45873,0 L 178.44019,191.64831 c -6.13629,5.98884 -7.79612,14.71834 -4.26523,22.22053 3.58119,7.62214 11.84001,12.55902 20.99414,12.55902 h 29.06189 l -78.60489,83.88987 c -5.84454,6.25645 -7.10199,14.75525 -3.28944,22.18361 3.82262,7.42836 11.82996,12.04229 20.90363,12.04229 h 30.81222 l -80.36527,83.50228 c -6.03569,6.26567 -7.40379,14.84754 -3.58117,22.38663 3.7723,7.42839 12.07138,12.22682 21.13497,12.22682 h 129.63669 c 0,0 -36.12127,66.38546 0,62.75824 h 80.45578 c 33.50708,5.78651 0,-62.75824 0,-62.75824 h 129.63668 c 9.06361,0 17.36266,-4.79843 21.13499,-12.22682 3.82262,-7.53909 2.45452,-16.12096 -3.58117,-22.38663 z"
|
||||
id="ENVIR"
|
||||
style="fill:#3f6e3a;fill-opacity:1;stroke:#000000;stroke-width:1.09833;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.1 KiB |
47
cockatrice/resources/icons/mana/R.svg
Normal file
@@ -0,0 +1,47 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
width="169.34801mm"
|
||||
height="169.34801mm"
|
||||
viewBox="0 0 600 600"
|
||||
version="1.1"
|
||||
id="svg1"
|
||||
sodipodi:docname="B.svg"
|
||||
inkscape:export-filename="B.svg"
|
||||
inkscape:export-xdpi="96"
|
||||
inkscape:export-ydpi="96"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs1"/>
|
||||
<sodipodi:namedview
|
||||
id="namedview1"
|
||||
pagecolor="#505050"
|
||||
bordercolor="#eeeeee"
|
||||
borderopacity="1"
|
||||
inkscape:showpageshadow="0"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#505050"
|
||||
inkscape:document-units="mm">
|
||||
<inkscape:page
|
||||
x="0"
|
||||
y="0"
|
||||
width="600"
|
||||
height="600"
|
||||
id="page2"
|
||||
margin="0"
|
||||
bleed="0"/>
|
||||
</sodipodi:namedview>
|
||||
<path
|
||||
id="Selection"
|
||||
fill="none"
|
||||
stroke="#000000"
|
||||
stroke-width="1"
|
||||
d="m 275,0.21 c 0,0 42,0 42,0 0,0 19,1.51 19,1.51 29.13,3.58 56.99,10.83 84,22.43 33.64,14.45 59.89,32.91 86.91,57.41 17.38,15.76 34.67,37.56 47.14,57.44 22.78,36.31 37.1,74.56 43.22,117 1.28,8.88 2.72,19.08 2.73,28 0,0 0,34 0,34 0,0 -0.91,10 -0.91,10 -2.36,32.29 -11.93,67.59 -25.4,97 -27.01,58.98 -70.49,105.42 -126.69,137.57 -34.3,19.63 -79.56,33.64 -119,36.52 0,0 -12,0.91 -12,0.91 0,0 -34,0 -34,0 0,0 -10,-0.91 -10,-0.91 -26.57,-1.94 -53.09,-8.27 -78,-17.67 C 163.49,569.9 134.12,553.46 109,532.57 79.87,508.33 54.41,478.39 36.31,445 -5.08,368.65 -11.86,276.57 18.05,195 47.85,113.75 113.21,48.92 194,18.42 213.55,11.04 236.32,5.43 257,2.43 Z"
|
||||
style="display:inline;fill:#c95e46;fill-opacity:1"/>
|
||||
<path
|
||||
style="display:inline;fill:#7e3525;fill-opacity:1;stroke:#000000;stroke-width:0.999129;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 278.25473,537.27772 c -22.36635,-1.29637 -48.78648,-8.31223 -69.74195,-18.51997 -47.30139,-23.04123 -75.19872,-59.72004 -85.45343,-112.35234 -2.77041,-14.21911 -2.51753,-48.32296 0.4755,-64.1246 4.84373,-25.57244 17.23939,-55.03329 30.65191,-72.85059 3.74877,-4.97991 16.33133,-18.11906 30.23479,-31.57223 13.09764,-12.67346 26.64745,-26.67202 30.11069,-31.10792 16.66894,-21.35045 26.46602,-44.71272 29.68313,-70.78286 1.93489,-15.67951 0.37962,-45.867231 -3.5802,-69.491071 -0.58906,-3.51428 0.36584,-3.52724 11.47726,-0.15583 36.51454,11.0792 67.37028,34.791071 89.32423,68.643431 9.96686,15.36862 20.07787,39.31263 24.78919,58.70364 2.23257,9.18886 2.53252,44.73878 0.46396,54.98571 -0.75708,3.75024 -1.10086,7.09425 -0.76396,7.43114 1.5021,1.5021 14.63666,-6.65479 21.98002,-13.65016 10.59844,-10.09621 14.32693,-17.61354 15.94714,-32.15254 l 1.16788,-10.47988 3.4626,3.95724 c 26.80954,30.6392 41.75268,56.11528 54.11775,92.26358 11.02075,32.21825 14.93902,62.79297 12.47422,97.33768 -1.88493,26.41774 -10.16486,50.6958 -24.95555,73.17369 -6.31625,9.59901 -30.86306,34.05172 -40.40914,40.25423 -23.44452,15.23295 -50.15355,25.25695 -77.26806,28.99903 -12.48732,1.72336 -38.04339,2.42637 -54.18798,1.49062 z m 48.34948,-30.34196 c 30.78066,-8.0491 53.53846,-30.12825 59.69792,-57.9177 2.48916,-11.23027 2.26087,-34.25269 -0.43747,-44.11787 -5.3998,-19.74181 -9.72931,-26.61651 -30.6237,-48.62658 -16.92925,-17.8332 -21.73398,-24.64464 -25.45673,-36.0889 -2.38622,-7.33554 -2.63126,-9.53814 -2.57644,-23.15876 0.0334,-8.27872 0.486,-17.4607 1.00593,-20.40441 0.75887,-4.29636 0.66928,-5.45812 -0.45409,-5.88919 -2.12997,-0.81736 -16.25071,4.45522 -21.90917,8.1807 -25.69349,16.91641 -42.62753,52.30879 -40.22917,84.07948 0.39046,5.17228 0.2831,8.79277 -0.26071,8.79277 -1.78935,0 -13.49435,-13.46378 -15.34792,-17.65406 -1.03171,-2.33236 -2.15967,-6.42651 -2.50657,-9.09812 -1.00143,-7.71233 -1.60778,-8.14619 -5.46339,-3.90915 -13.31687,14.63425 -24.58113,35.69359 -29.53283,55.21379 -11.75615,46.34414 2.14731,84.38956 37.55813,102.77397 16.56338,8.59931 29.2427,11.25369 51.26679,10.73261 12.58064,-0.29766 17.41119,-0.85366 25.26942,-2.90858 z"
|
||||
id="path8"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.2 KiB |
48
cockatrice/resources/icons/mana/U.svg
Normal file
@@ -0,0 +1,48 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
width="169.34801mm"
|
||||
height="169.34801mm"
|
||||
viewBox="0 0 600 600"
|
||||
version="1.1"
|
||||
id="svg1"
|
||||
sodipodi:docname="U.svg"
|
||||
inkscape:export-filename="U.png"
|
||||
inkscape:export-xdpi="96"
|
||||
inkscape:export-ydpi="96"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs1"/>
|
||||
<sodipodi:namedview
|
||||
id="namedview1"
|
||||
pagecolor="#505050"
|
||||
bordercolor="#eeeeee"
|
||||
borderopacity="1"
|
||||
inkscape:showpageshadow="0"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#505050"
|
||||
inkscape:document-units="mm">
|
||||
<inkscape:page
|
||||
x="0"
|
||||
y="0"
|
||||
width="600"
|
||||
height="600"
|
||||
id="page2"
|
||||
margin="0"
|
||||
bleed="0"/>
|
||||
</sodipodi:namedview>
|
||||
<path
|
||||
id="Selection"
|
||||
fill="none"
|
||||
stroke="#000000"
|
||||
stroke-width="1"
|
||||
d="m 275,0.21 c 0,0 42,0 42,0 0,0 19,1.51 19,1.51 29.13,3.58 56.99,10.83 84,22.43 33.64,14.45 59.89,32.91 86.91,57.41 17.38,15.76 34.67,37.56 47.14,57.44 22.78,36.31 37.1,74.56 43.22,117 1.28,8.88 2.72,19.08 2.73,28 0,0 0,34 0,34 0,0 -0.91,10 -0.91,10 -2.36,32.29 -11.93,67.59 -25.4,97 -27.01,58.98 -70.49,105.42 -126.69,137.57 -34.3,19.63 -79.56,33.64 -119,36.52 0,0 -12,0.91 -12,0.91 0,0 -34,0 -34,0 0,0 -10,-0.91 -10,-0.91 -26.57,-1.94 -53.09,-8.27 -78,-17.67 C 163.49,569.9 134.12,553.46 109,532.57 79.87,508.33 54.41,478.39 36.31,445 -5.08,368.65 -11.86,276.57 18.05,195 47.85,113.75 113.21,48.92 194,18.42 213.55,11.04 236.32,5.43 257,2.43 Z"
|
||||
style="display:inline;fill:#4a8fd8;fill-opacity:1"/>
|
||||
<path
|
||||
style="display:inline;fill:#3266af;fill-opacity:1;stroke:#000000;stroke-width:1.06525;stroke-opacity:1"
|
||||
d="m 275.20608,536.5693 c -58.69596,-9.5447 -107.01545,-50.69962 -125.66779,-107.0344 -9.25532,-27.95342 -10.39669,-58.82111 -3.22092,-87.1088 5.31307,-20.94475 9.06296,-28.00552 81.87785,-154.17 38.54798,-66.79109 70.40461,-121.77997 70.79251,-122.19751 1.00535,-1.08215 140.3595,240.63696 145.26785,251.97682 8.6735,20.03855 12.6923,39.82747 12.6923,62.49805 0,44.25387 -16.70161,83.24077 -48.85852,114.0514 -21.46938,20.57055 -45.69209,33.39124 -76.46586,40.47213 -12.83139,2.95245 -42.64639,3.75165 -56.41742,1.51231 z"
|
||||
id="path2"
|
||||
sodipodi:nodetypes="ssssssssss"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.7 KiB |
47
cockatrice/resources/icons/mana/W.svg
Normal file
@@ -0,0 +1,47 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
width="169.34801mm"
|
||||
height="169.34801mm"
|
||||
viewBox="0 0 600 600"
|
||||
version="1.1"
|
||||
id="svg1"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
inkscape:export-xdpi="96"
|
||||
inkscape:export-ydpi="96"
|
||||
sodipodi:docname="W.svg"
|
||||
inkscape:export-filename="W.png"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs1"/>
|
||||
<sodipodi:namedview
|
||||
id="namedview1"
|
||||
pagecolor="#505050"
|
||||
bordercolor="#eeeeee"
|
||||
borderopacity="1"
|
||||
inkscape:showpageshadow="0"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#505050"
|
||||
inkscape:document-units="mm">
|
||||
<inkscape:page
|
||||
x="0"
|
||||
y="0"
|
||||
width="600"
|
||||
height="600"
|
||||
id="page2"
|
||||
margin="0"
|
||||
bleed="0"/>
|
||||
</sodipodi:namedview>
|
||||
<path
|
||||
id="Selection"
|
||||
fill="none"
|
||||
stroke="#000000"
|
||||
stroke-width="1"
|
||||
d="m 275,0.21 c 0,0 42,0 42,0 0,0 19,1.51 19,1.51 29.13,3.58 56.99,10.83 84,22.43 33.64,14.45 59.89,32.91 86.91,57.41 17.38,15.76 34.67,37.56 47.14,57.44 22.78,36.31 37.1,74.56 43.22,117 1.28,8.88 2.72,19.08 2.73,28 0,0 0,34 0,34 0,0 -0.91,10 -0.91,10 -2.36,32.29 -11.93,67.59 -25.4,97 -27.01,58.98 -70.49,105.42 -126.69,137.57 -34.3,19.63 -79.56,33.64 -119,36.52 0,0 -12,0.91 -12,0.91 0,0 -34,0 -34,0 0,0 -10,-0.91 -10,-0.91 -26.57,-1.94 -53.09,-8.27 -78,-17.67 C 163.49,569.9 134.12,553.46 109,532.57 79.87,508.33 54.41,478.39 36.31,445 -5.08,368.65 -11.86,276.57 18.05,195 47.85,113.75 113.21,48.92 194,18.42 213.55,11.04 236.32,5.43 257,2.43 Z"
|
||||
style="display:inline;fill:#ffefb3;fill-opacity:1"/>
|
||||
<path
|
||||
id="circle8"
|
||||
d="M 387.28106,302.711 A 85.962512,85.962512 0 0 1 301.31852,388.67353 85.962512,85.962512 0 0 1 215.35597,302.711 85.962512,85.962512 0 0 1 301.31852,216.74844 85.962512,85.962512 0 0 1 387.28106,302.711 Z M 191.75349,358.6114 143.05798,460.97151 245.4181,412.27601 A 121.73882,121.73882 0 0 1 191.75349,358.6114 Z M 178.33753,302.711 c 0,-13.16763 2.23687,-25.83857 5.96271,-38.01232 L 77.71657,302.711 184.30024,340.7233 a 119.25435,119.25435 0 0 1 -5.96271,-38.0123 z m 178.8814,109.56501 102.36011,48.6955 -48.6955,-102.36011 a 121.73882,121.73882 0 0 1 -53.66461,53.66461 z m -55.90041,13.41595 c -13.16762,0 -26.0869,-2.23687 -38.01232,-5.9627 l 38.01232,106.58368 38.01231,-106.58368 a 119.25435,119.25435 0 0 1 -38.01231,5.9627 z m 109.56502,-178.88139 48.6955,-102.36012 -102.36011,48.69551 a 122.98106,122.98106 0 0 1 53.66461,53.66461 z M 245.66643,193.14596 143.30658,144.45045 192.00209,246.81057 a 121.73882,121.73882 0 0 1 53.66434,-53.66461 z M 524.92046,302.711 418.33679,264.69868 a 119.25435,119.25435 0 0 1 0,76.02462 z M 301.31852,179.73 c 13.16761,0 26.0869,2.23688 38.01231,5.96271 L 301.31852,79.109036 263.3062,185.69271 A 119.25435,119.25435 0 0 1 301.31852,179.73 Z"
|
||||
style="fill:#b4a35e;fill-opacity:1;stroke:#000000;stroke-width:1.09833;stroke-dasharray:none;stroke-opacity:1"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.3 KiB |
7
cockatrice/resources/icons/reload.svg
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" x="0px" y="0px"
|
||||
viewBox="0 0 1000 1000" enable-background="new 0 0 1000 1000" xml:space="preserve">
|
||||
<metadata> Svg Vector Icons : http://www.onlinewebfonts.com/icon </metadata>
|
||||
<g><path d="M586.9,373.6l95.6-84.4c-49.6-39.2-129.4-79.1-198.2-79.1c-134.9,0-248.2,90.5-280.2,212.8L10.6,374.5C64.4,168.3,255.2,15.8,482.5,15.8c126.7,0,258.8,63.7,345.4,141.4l90.8-80.1L990,479.6L586.9,373.6z M317.5,710.8c49.6,39.2,129.4,79.1,198.2,79.1c134.9,0,248.2-90.5,280.2-212.8l193.5,48.5c-53.7,206.2-244.6,358.7-471.9,358.7c-126.7,0-258.8-63.7-345.4-141.4l-90.8,80.1L10,520.4l403.1,106L317.5,710.8z"/></g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 797 B |
10
cockatrice/resources/icons/rename.svg
Normal file
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="1200pt" height="1200pt" version="1.1" viewBox="0 0 1200 1200" xmlns="http://www.w3.org/2000/svg">
|
||||
<path transform="scale(50)" d="m17 16h4v-8h-4" fill="none" stroke="#000" stroke-miterlimit="10" stroke-width="2"/>
|
||||
<path transform="scale(50)" d="m12 8h-9v8h9" fill="none" stroke="#000" stroke-miterlimit="10" stroke-width="2"/>
|
||||
<path transform="scale(50)" d="m6 12h5" fill="none" stroke="#000" stroke-miterlimit="10" stroke-width="2"/>
|
||||
<path transform="scale(50)" d="m20 4h-2c-1.1 0-2 0.9-2 2v12c0 1.1 0.9 2 2 2h2" fill="none" stroke="#000"
|
||||
stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2"/>
|
||||
<path transform="scale(50)" d="m12 20h2c1.1 0 2-0.9 2-2v-12c0-1.1-0.9-2-2-2h-2" fill="none" stroke="#000"
|
||||
stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 875 B |
BIN
cockatrice/resources/tips/images/add_card.png
Normal file
|
After Width: | Height: | Size: 9.7 KiB |
BIN
cockatrice/resources/tips/images/card_select.png
Normal file
|
After Width: | Height: | Size: 7.6 KiB |
BIN
cockatrice/resources/tips/images/edhrec.png
Normal file
|
After Width: | Height: | Size: 7.4 KiB |
BIN
cockatrice/resources/tips/images/expand_card_view.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 227 KiB After Width: | Height: | Size: 70 KiB |
BIN
cockatrice/resources/tips/images/highlight_cards.png
Normal file
|
After Width: | Height: | Size: 24 KiB |
BIN
cockatrice/resources/tips/images/pawns.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 29 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 39 KiB |
BIN
cockatrice/resources/tips/images/syntax_help.png
Normal file
|
After Width: | Height: | Size: 38 KiB |
BIN
cockatrice/resources/tips/images/token.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
cockatrice/resources/tips/images/updates.png
Normal file
|
After Width: | Height: | Size: 6.0 KiB |
BIN
cockatrice/resources/tips/images/visual_deck_tags.png
Normal file
|
After Width: | Height: | Size: 136 KiB |
@@ -1,99 +1,222 @@
|
||||
<tips>
|
||||
<tip>
|
||||
<title>Tip of the Day</title>
|
||||
<text>Tip of the Day is a new feature to Cockatrice that allows users to get information about the newest features of the program and some of the most commonly asked questions!</text>
|
||||
<image>tip_of_the_day.png</image>
|
||||
<date>2018-03-01</date>
|
||||
</tip>
|
||||
<tip>
|
||||
<title>Suggesting New Tips</title>
|
||||
<text>You can suggest new Tips of the Day by reaching out to the development team on <a href="https://discord.gg/3Z9yzmA">Discord</a>!</text>
|
||||
<image>discord.png</image>
|
||||
<date>2023-10-18</date>
|
||||
</tip>
|
||||
<tip>
|
||||
<title>Reporting Bugs</title>
|
||||
<text>If you encounter a bug while using Cockatrice, you can report the bug to the development team via <a href="https://github.com/cockatrice/cockatrice/issues">GitHub<a></text>
|
||||
<image>github_logo.png</image>
|
||||
<date>2018-03-01</date>
|
||||
</tip>
|
||||
<tip>
|
||||
<title>FAQ/Troubleshooting Wiki</title>
|
||||
<text>You can find answers to the most common questions and some helpful Cockatrice toubleshooting over on the <a href="https://github.com/cockatrice/cockatrice/wiki">GitHub wiki<a></text>
|
||||
<image>cockatrice_wiki.png</image>
|
||||
<date>2018-03-01</date>
|
||||
</tip>
|
||||
<tip>
|
||||
<title>Register for a Server</title>
|
||||
<text>Click on either Cockatrice (Windows) or Actions (Mac) and then Register to server... When the dialogue appears, fill out the desired server information.</text>
|
||||
<image>cockatrice_register.png</image>
|
||||
<date>2018-03-01</date>
|
||||
</tip>
|
||||
<tip>
|
||||
<title>Drawing Arrows</title>
|
||||
<text>You can draw arrows of different color by holding a combination of keys!
|
||||
Right Click: Red Arrow
|
||||
Shift + Right Click: Green Arrow
|
||||
Alt + Right Click: Blue Arrow
|
||||
Cmd + Right Click: Yellow Arrow
|
||||
</text>
|
||||
<image>arrows.png</image>
|
||||
<date>2018-03-01</date>
|
||||
</tip>
|
||||
<tip>
|
||||
<title>Filtering Games</title>
|
||||
<text>Don't see all the active games? Want to see a smaller selection? Use the Game Filters to change your horizon</text>
|
||||
<image>filter_games.png</image>
|
||||
<date>2018-03-01</date>
|
||||
</tip>
|
||||
<tip>
|
||||
<title>Upload Custom Avatar</title>
|
||||
<text>Want to show off your hippo avatar? Need to update your password? Check out the Accounts Tab for more info!</text>
|
||||
<image>accounts_tab.png</image>
|
||||
<date>2018-03-01</date>
|
||||
</tip>
|
||||
<tip>
|
||||
<title>Common Shortcuts</title>
|
||||
<text>You can find a full list of shortcuts <a href="https://github.com/Cockatrice/Cockatrice/wiki/Custom-Keyboard-Shortcuts">on the wiki</a>, but a short list:
|
||||
<br>Roll a die: CTRL + I
|
||||
<br>Mulligan: CTRL + M
|
||||
<br>Draw a card: CTRL + D
|
||||
<br>Undo a draw: CTRL + SHIFT + D
|
||||
<br>View Sideboard: CTRL + F3
|
||||
<br>Change Life: CTRL + L
|
||||
<br>All shortcuts can be customized via Settings->Shortcuts!
|
||||
</text>
|
||||
<image>shortcuts.png</image>
|
||||
<date>2018-03-01</date>
|
||||
</tip>
|
||||
<tip>
|
||||
<title>Changing Themes</title>
|
||||
<text>Did you know Cockatrice has custom themes? You can either <a href="https://github.com/Cockatrice/Cockatrice/wiki/Themes">create one yourself</a> or use one of the several pre-loaded ones! Go to Settings->Appearance and try them out!</text>
|
||||
<image>themes.png</image>
|
||||
<date>2018-03-01</date>
|
||||
</tip>
|
||||
<tip>
|
||||
<title>Flip of the Coin</title>
|
||||
<text>You can flip a coin instead of rolling a die by rolling a 2 sided die instead!</text>
|
||||
<image>coin_flip.png</image>
|
||||
<date>2018-03-01</date>
|
||||
</tip>
|
||||
<tip>
|
||||
<title>Face Down Cards</title>
|
||||
<text>You can hold Shift while dragging or clicking on a card to have it enter play face down</text>
|
||||
<image>face_down.png</image>
|
||||
<date>2018-03-01</date>
|
||||
</tip>
|
||||
<tip>
|
||||
<title>Counter expressions</title>
|
||||
<text>When setting a counter value, you can type a math expression in the box and the counter will be set to the result.<br>The "x" variable contains the current counter value.</text>
|
||||
<image>counter_expression.png</image>
|
||||
<date>2019-02-02</date>
|
||||
</tip>
|
||||
<tip>
|
||||
<title>Power and Toughness</title>
|
||||
<text>You can add and subtract to a creature's stats.<br>With a card selected, set the power and toughness ( default: ctrl + p ) and enter +3/-1 to increase power by three while decreasing toughness by one.<br>You can also reset it to the original value ( default: ctrl + alt + 0 ).</text>
|
||||
<image>setpt.png</image>
|
||||
<date>2019-03-02</date>
|
||||
</tip>
|
||||
<tip>
|
||||
<title>Tip of the Day</title>
|
||||
<text>Tip of the Day is a feature to Cockatrice that allows users to get information about the newest features of the program and some of the most commonly asked questions!
|
||||
Check back in with major updates for new tips to be added or old tips to be updated as features are added or expanded upon!
|
||||
</text>
|
||||
<image>tip_of_the_day.png</image>
|
||||
<date>2025-02-10</date>
|
||||
</tip>
|
||||
<tip>
|
||||
<title>Join the Community</title>
|
||||
<text>You can join the community to find games, interact with other players, suggest new 'Tips of the Day' and provide user feedback to the development or support teams on <a href="https://discord.gg/3Z9yzmA">Discord</a>!</text>
|
||||
<image>discord.png</image>
|
||||
<date>2025-02-09</date>
|
||||
</tip>
|
||||
<tip>
|
||||
<title>Reporting Bugs and Requesting Features</title>
|
||||
<text>If you encounter a bug while using Cockatrice, you can report the bug to and request the feature from the development team via <a href="https://github.com/cockatrice/cockatrice/issues">GitHub<a></text>
|
||||
<image>github_logo.png</image>
|
||||
<date>2025-02-09</date>
|
||||
</tip>
|
||||
<tip>
|
||||
<title>FAQ/Troubleshooting Wiki</title>
|
||||
<text>You can find answers to the most common questions and some helpful Cockatrice troubleshooting over on the <a href="https://github.com/cockatrice/cockatrice/wiki">GitHub wiki<a></text>
|
||||
<image>cockatrice_wiki.png</image>
|
||||
<date>2018-03-01</date>
|
||||
</tip>
|
||||
<tip>
|
||||
<title>Register for a Server</title>
|
||||
<text>Click on either Cockatrice (Windows) or Actions (Mac) and then Register to server... When the dialogue appears, fill out the desired server information.</text>
|
||||
<image>cockatrice_register.png</image>
|
||||
<date>2018-03-01</date>
|
||||
</tip>
|
||||
<tip>
|
||||
<title>Filtering Games</title>
|
||||
<text>Don't see an open game or want to see a smaller selection? Use the Game Filters to change your horizon!</text>
|
||||
<image>filter_games.png</image>
|
||||
<date>2025-02-09</date>
|
||||
</tip>
|
||||
<tip>
|
||||
<title>Common Shortcuts</title>
|
||||
<text>You can find a full list of default shortcuts <a href="https://github.com/Cockatrice/Cockatrice/wiki/Custom-Keyboard-Shortcuts">on the wiki</a> or in Settings -> Shortcuts, but a short list:
|
||||
<ul>
|
||||
<li>Roll a die: CMD/CTRL + I</li>
|
||||
<li>Mulligan: CMD/CTRL + M</li>
|
||||
<li>Draw 1 / X card(s): CMD/CTRL + D / E</li>
|
||||
<li>Undo a draw: CMD/CTRL + SHIFT + D</li>
|
||||
<li>View Library / Sideboard: F3 / CMD/CTRL + F3</li>
|
||||
<li>Change Life: CMD/CTRL + L</li>
|
||||
<li>Show Card Info: Middle Mouse Click</li>
|
||||
</ul>
|
||||
All shortcuts can be customized via Cockatrice -> Settings -> Shortcuts!
|
||||
</text>
|
||||
<image>shortcuts.png</image>
|
||||
<date>2025-02-09</date>
|
||||
</tip>
|
||||
<tip>
|
||||
<title>Power and Toughness</title>
|
||||
<text>You can add and subtract to a creature's stats.
|
||||
With a card selected, with the following keybindings:
|
||||
|
||||
<ul>
|
||||
<li>Set P/T to any value: CTRL + P</li>
|
||||
<li>+1 or -1 to both to P/T: CMD/CTRL + ALT + '+' or '-'</li>
|
||||
<li>+1 or -1 to Power Only: CMD/CTRL + '+' or '-'</li>
|
||||
<li>+1 or -1 to Toughness Only: ALT + '+' or '-'</li>
|
||||
<li>Set P/T to Default: CMD/CTRL + ALT + 0</li>
|
||||
</ul>
|
||||
All shortcuts can be customized via Cockatrice -> Settings -> Shortcuts!
|
||||
</text>
|
||||
<image>setpt.png</image>
|
||||
<date>2025-02-09</date>
|
||||
</tip>
|
||||
<tip>
|
||||
<title>Drawing Arrows</title>
|
||||
<text>You can draw arrows of different color by holding a combination of keys!
|
||||
|
||||
<ul>
|
||||
<li>Right Click: Red Arrow</li>
|
||||
<li>SHIFT + Right Click: Green Arrow</li>
|
||||
<li>ALT + Right Click: Blue Arrow</li>
|
||||
<li>CMD/CTRL + Right Click: Yellow Arrow</li>
|
||||
</ul>
|
||||
</text>
|
||||
<image>arrows.png</image>
|
||||
<date>2018-03-01</date>
|
||||
</tip>
|
||||
<tip>
|
||||
<title>General Etiquette</title>
|
||||
<text>When playing on Cockatrice there are general "rules of engagement" for a better play experience for all players.
|
||||
The following are some of the expectations a player should reasonably expect for how to operate smooth and enjoyable games:
|
||||
<ul>
|
||||
<li>Have a "Rule 0" conversation pre-game with your fellow players on the expectations of the game including deck strength, house rules (e.g. no take backs and "may is not must"), etc.</li>
|
||||
<li>When rolling your die for turn order, it is often best to create a token with the text of your roll (using CTRL + T and typing the number into "Name") and then tap this token when created to show that you have resolved your mulligans and are ready to start the game.</li>
|
||||
<li>Use the phases and steps trackers on the left of the client to show others where in the turn you are and announce when moving between them. (Double Click for the action of this button)</li>
|
||||
<li>When your turn is over leave it on your end step and DO NOT HIT PASS. Allow other players to respond if they can, and then if not, let the next player pass to themselves.</li>
|
||||
<li>Please treat others kindly and respect as per <a href="https://cockatrice.us/terms.php">our Terms of Service</a>.</li>
|
||||
</ul>
|
||||
</text>
|
||||
<image>token.png</image>
|
||||
<date>2025-02-10</date>
|
||||
</tip>
|
||||
<tip>
|
||||
<title>Flip of the Coin</title>
|
||||
<text>You can flip a coin instead of rolling a die by rolling a 2 sided die instead!</text>
|
||||
<image>coin_flip.png</image>
|
||||
<date>2018-03-01</date>
|
||||
</tip>
|
||||
<tip>
|
||||
<title>Upload Custom Avatar</title>
|
||||
<text>Want to show off your hippo avatar? Need to update your password? Check out the Accounts Tab for more info!</text>
|
||||
<image>accounts_tab.png</image>
|
||||
<date>2018-03-01</date>
|
||||
</tip>
|
||||
<tip>
|
||||
<title>Player Icon Key</title>
|
||||
<text>Besides your name is a player icon, this is a key for what they mean:
|
||||
<ul>
|
||||
<li>Flag: Country of Origin Player Selected</li>
|
||||
<li>Purple Heart: Donator (<a href="https://cockatrice.us/donate">Help support us and donate here!</a>)</li>
|
||||
<li>Gold Star: VIP (Special Admin Given Role)</li>
|
||||
<li>Brown Gavel (Hammer): Judge / Rule's Lawyer</li>
|
||||
<li>Black/White Pawn: Moderator (Support for Client Issues)</li>
|
||||
<li>Red: Administrator (Sever Operators)</li>
|
||||
</ul>
|
||||
</text>
|
||||
<image>pawns.png</image>
|
||||
<date>2025-02-09</date>
|
||||
</tip>
|
||||
<tip>
|
||||
<title>Changing Themes</title>
|
||||
<text>Did you know Cockatrice has custom themes? You can either <a href="https://github.com/Cockatrice/Cockatrice/wiki/Themes">create one yourself</a> or use one of the several preloaded ones! Go to Settings->Appearance and try them out!</text>
|
||||
<image>themes.png</image>
|
||||
<date>2018-03-01</date>
|
||||
</tip>
|
||||
<tip>
|
||||
<title>Face Down Cards</title>
|
||||
<text>You can hold Shift while dragging or clicking on a card to have it enter play face down.
|
||||
You can also hold CTRL + SHIFT and click and drag from your library to move bottom card face down as well!
|
||||
</text>
|
||||
<image>face_down.png</image>
|
||||
<date>2025-02-09</date>
|
||||
</tip>
|
||||
<tip>
|
||||
<title>Counter expressions</title>
|
||||
<text>When setting a counter value, you can type a math expression in the box and the counter will be set to the result.<br>The "x" variable contains the current counter value.</text>
|
||||
<image>counter_expression.png</image>
|
||||
<date>2019-02-02</date>
|
||||
</tip>
|
||||
<tip>
|
||||
<title>Select Multiple Cards</title>
|
||||
<text>You can click and drag in any zone in order to highlight all cards within the created box.
|
||||
You can also hold CMD/CTRL and clik or click and drag to maintain other previously selected cards while adding others!
|
||||
You can move, alter and attach multiple highlighted cards at the same time!
|
||||
Other useful multi-select keybindings:
|
||||
<ul>
|
||||
<li>Select All Cards in Zone: CMD/CTRL + A</li>
|
||||
<li>Select All Cards in Column: CMD/CTRL + SHIFT + C</li>
|
||||
<li>Select All Cards in Row: CMD/CTRL + SHIFT + X</li>
|
||||
</ul>
|
||||
</text>
|
||||
<image>highlight_cards.png</image>
|
||||
<date>2025-02-09</date>
|
||||
</tip>
|
||||
<tip>
|
||||
<title>Auto Cascade and Search Parameters</title>
|
||||
<text>You can use the default keybind of CMD/CTRL + SHIFT + Y in order to perform auto cascade or similar effects from your library using Scryfall search syntax.
|
||||
If you are unfamiliar with the syntax you may find it by opening a deck in deck editor and clicking the "i" next to the search bar in order to pull up a list of syntax commands.
|
||||
This same syntax can be used in the deck editor search bar as well to help you find the best cards for your decks!
|
||||
</text>
|
||||
<image>syntax_help.png</image>
|
||||
<date>2025-02-09</date>
|
||||
</tip>
|
||||
<tip>
|
||||
<title>Visual Deck Storage and Tags</title>
|
||||
<text>You can now view your saved decks visually with integrated folder,tags and filter support!
|
||||
Pro Tip: If you want to use emoji's press WinKey + '.' or CTRL + CMD + SPACE on Windows or Mac respectively!
|
||||
</text>
|
||||
<image>visual_deck_tags.png</image>
|
||||
<date>2025-02-09</date>
|
||||
</tip>
|
||||
<tip>
|
||||
<title>Card Selector And Card Preferences</title>
|
||||
<text>Cockatrice's deck editor now has the ability for players to bling out their decks by selecting the arts for cards on a per-card basis!
|
||||
You can also have multiple printings of the same card in your deck if you so choose!
|
||||
Not only that, other players will see the arts you have chosen by default!
|
||||
If you want to disable this feature for other players: Settings -> Appearance -> Select "Override all card art with personal set preference"
|
||||
</text>
|
||||
<image>card_select.png</image>
|
||||
<date>2025-02-10</date>
|
||||
</tip>
|
||||
<tip>
|
||||
<title>EDHREC Integration</title>
|
||||
<text>We now have in-client integration for EDHREC, which allows you to right-click on any card name in the deck editor in order to bring up the contextual menu for the card.</text>
|
||||
<image>edhrec.png</image>
|
||||
<date>2025-02-10</date>
|
||||
</tip>
|
||||
<tip>
|
||||
<title>Add Cards to Deck</title>
|
||||
<text>You can now right-click anywhere on a card in any card info view in order to add it to any decks you have open in your tabs.</text>
|
||||
<image>add_card.png</image>
|
||||
<date>2025-02-10</date>
|
||||
</tip>
|
||||
<tip>
|
||||
<title>Update Client and Card Database</title>
|
||||
<text>You can update your client by going to Help and selecting whichever you wish to update.
|
||||
Client Update: Updates the client (if available) for new features, fixes and changes.
|
||||
Card Update: Updates card sources for spoilers and new card printings.
|
||||
Updating your card sources can often fix issues of cards not working or displaying properly, if not, go to Cockatrice -> Settings -> Card Sources -> Update Spoilers
|
||||
</text>
|
||||
<image>updates.png</image>
|
||||
<date>2025-02-10</date>
|
||||
</tip>
|
||||
<tip>
|
||||
<title>Expand Card View Window</title>
|
||||
<text>Double click on the title bar of the card view window to expand it! Double click again to shrink it back down.
|
||||
You can configure the initial and expanded heights of the window in the settings, under "Appearance".
|
||||
</text>
|
||||
<image>expand_card_view.png</image>
|
||||
<date>2025-02-27</date>
|
||||
</tip>
|
||||
</tips>
|
||||
|
||||
201
cockatrice/src/client/menus/deck_editor/deck_editor_menu.cpp
Normal file
@@ -0,0 +1,201 @@
|
||||
#include "deck_editor_menu.h"
|
||||
|
||||
#include "../../../settings/cache_settings.h"
|
||||
#include "../../../settings/shortcuts_settings.h"
|
||||
|
||||
DeckEditorMenu::DeckEditorMenu(QWidget *parent, AbstractTabDeckEditor *_deckEditor)
|
||||
: QMenu(parent), deckEditor(_deckEditor)
|
||||
{
|
||||
aNewDeck = new QAction(QString(), this);
|
||||
connect(aNewDeck, SIGNAL(triggered()), deckEditor, SLOT(actNewDeck()));
|
||||
|
||||
aLoadDeck = new QAction(QString(), this);
|
||||
connect(aLoadDeck, SIGNAL(triggered()), deckEditor, SLOT(actLoadDeck()));
|
||||
|
||||
loadRecentDeckMenu = new QMenu(this);
|
||||
connect(&SettingsCache::instance().recents(), &RecentsSettings::recentlyOpenedDeckPathsChanged, this,
|
||||
&DeckEditorMenu::updateRecentlyOpened);
|
||||
|
||||
aClearRecents = new QAction(QString(), this);
|
||||
connect(aClearRecents, &QAction::triggered, this, &DeckEditorMenu::actClearRecents);
|
||||
|
||||
updateRecentlyOpened();
|
||||
|
||||
aSaveDeck = new QAction(QString(), this);
|
||||
connect(aSaveDeck, SIGNAL(triggered()), deckEditor, SLOT(actSaveDeck()));
|
||||
|
||||
aSaveDeckAs = new QAction(QString(), this);
|
||||
connect(aSaveDeckAs, SIGNAL(triggered()), deckEditor, SLOT(actSaveDeckAs()));
|
||||
|
||||
aLoadDeckFromClipboard = new QAction(QString(), this);
|
||||
connect(aLoadDeckFromClipboard, SIGNAL(triggered()), deckEditor, SLOT(actLoadDeckFromClipboard()));
|
||||
|
||||
aEditDeckInClipboard = new QAction(QString(), this);
|
||||
connect(aEditDeckInClipboard, SIGNAL(triggered()), deckEditor, SLOT(actEditDeckInClipboard()));
|
||||
|
||||
aEditDeckInClipboardRaw = new QAction(QString(), this);
|
||||
connect(aEditDeckInClipboardRaw, SIGNAL(triggered()), deckEditor, SLOT(actEditDeckInClipboardRaw()));
|
||||
|
||||
aSaveDeckToClipboard = new QAction(QString(), this);
|
||||
connect(aSaveDeckToClipboard, SIGNAL(triggered()), deckEditor, SLOT(actSaveDeckToClipboard()));
|
||||
|
||||
aSaveDeckToClipboardNoSetInfo = new QAction(QString(), this);
|
||||
connect(aSaveDeckToClipboardNoSetInfo, SIGNAL(triggered()), deckEditor, SLOT(actSaveDeckToClipboardNoSetInfo()));
|
||||
|
||||
aSaveDeckToClipboardRaw = new QAction(QString(), this);
|
||||
connect(aSaveDeckToClipboardRaw, SIGNAL(triggered()), deckEditor, SLOT(actSaveDeckToClipboardRaw()));
|
||||
|
||||
aSaveDeckToClipboardRawNoSetInfo = new QAction(QString(), this);
|
||||
connect(aSaveDeckToClipboardRawNoSetInfo, SIGNAL(triggered()), deckEditor,
|
||||
SLOT(actSaveDeckToClipboardRawNoSetInfo()));
|
||||
|
||||
aPrintDeck = new QAction(QString(), this);
|
||||
connect(aPrintDeck, SIGNAL(triggered()), deckEditor, SLOT(actPrintDeck()));
|
||||
|
||||
aExportDeckDecklist = new QAction(QString(), this);
|
||||
connect(aExportDeckDecklist, SIGNAL(triggered()), deckEditor, SLOT(actExportDeckDecklist()));
|
||||
|
||||
aExportDeckDecklistXyz = new QAction(QString(), this);
|
||||
connect(aExportDeckDecklistXyz, SIGNAL(triggered()), deckEditor, SLOT(actExportDeckDecklistXyz()));
|
||||
|
||||
aAnalyzeDeckDeckstats = new QAction(QString(), this);
|
||||
connect(aAnalyzeDeckDeckstats, SIGNAL(triggered()), deckEditor, SLOT(actAnalyzeDeckDeckstats()));
|
||||
|
||||
aAnalyzeDeckTappedout = new QAction(QString(), this);
|
||||
connect(aAnalyzeDeckTappedout, SIGNAL(triggered()), deckEditor, SLOT(actAnalyzeDeckTappedout()));
|
||||
|
||||
analyzeDeckMenu = new QMenu(this);
|
||||
analyzeDeckMenu->addAction(aExportDeckDecklist);
|
||||
analyzeDeckMenu->addAction(aExportDeckDecklistXyz);
|
||||
analyzeDeckMenu->addSeparator();
|
||||
analyzeDeckMenu->addAction(aAnalyzeDeckDeckstats);
|
||||
analyzeDeckMenu->addAction(aAnalyzeDeckTappedout);
|
||||
|
||||
aClose = new QAction(QString(), this);
|
||||
connect(aClose, &QAction::triggered, deckEditor, &AbstractTabDeckEditor::closeRequest);
|
||||
|
||||
editDeckInClipboardMenu = new QMenu(this);
|
||||
editDeckInClipboardMenu->addAction(aEditDeckInClipboard);
|
||||
editDeckInClipboardMenu->addAction(aEditDeckInClipboardRaw);
|
||||
|
||||
saveDeckToClipboardMenu = new QMenu(this);
|
||||
saveDeckToClipboardMenu->addAction(aSaveDeckToClipboard);
|
||||
saveDeckToClipboardMenu->addAction(aSaveDeckToClipboardNoSetInfo);
|
||||
saveDeckToClipboardMenu->addAction(aSaveDeckToClipboardRaw);
|
||||
saveDeckToClipboardMenu->addAction(aSaveDeckToClipboardRawNoSetInfo);
|
||||
|
||||
addAction(aNewDeck);
|
||||
addAction(aLoadDeck);
|
||||
addMenu(loadRecentDeckMenu);
|
||||
addAction(aSaveDeck);
|
||||
addAction(aSaveDeckAs);
|
||||
addSeparator();
|
||||
addAction(aLoadDeckFromClipboard);
|
||||
addMenu(editDeckInClipboardMenu);
|
||||
addMenu(saveDeckToClipboardMenu);
|
||||
addSeparator();
|
||||
addAction(aPrintDeck);
|
||||
addMenu(analyzeDeckMenu);
|
||||
addSeparator();
|
||||
addAction(deckEditor->filterDockWidget->aClearFilterOne);
|
||||
addAction(deckEditor->filterDockWidget->aClearFilterAll);
|
||||
addSeparator();
|
||||
addAction(aClose);
|
||||
|
||||
retranslateUi();
|
||||
connect(&SettingsCache::instance().shortcuts(), SIGNAL(shortCutChanged()), this, SLOT(refreshShortcuts()));
|
||||
refreshShortcuts();
|
||||
}
|
||||
|
||||
void DeckEditorMenu::setSaveStatus(bool newStatus)
|
||||
{
|
||||
aSaveDeck->setEnabled(newStatus);
|
||||
aSaveDeckAs->setEnabled(newStatus);
|
||||
aSaveDeckToClipboard->setEnabled(newStatus);
|
||||
aSaveDeckToClipboardNoSetInfo->setEnabled(newStatus);
|
||||
aSaveDeckToClipboardRaw->setEnabled(newStatus);
|
||||
aSaveDeckToClipboardRawNoSetInfo->setEnabled(newStatus);
|
||||
saveDeckToClipboardMenu->setEnabled(newStatus);
|
||||
aPrintDeck->setEnabled(newStatus);
|
||||
analyzeDeckMenu->setEnabled(newStatus);
|
||||
}
|
||||
|
||||
void DeckEditorMenu::updateRecentlyOpened()
|
||||
{
|
||||
loadRecentDeckMenu->clear();
|
||||
for (const auto &deckPath : SettingsCache::instance().recents().getRecentlyOpenedDeckPaths()) {
|
||||
QAction *aRecentlyOpenedDeck = new QAction(deckPath, this);
|
||||
loadRecentDeckMenu->addAction(aRecentlyOpenedDeck);
|
||||
connect(aRecentlyOpenedDeck, &QAction::triggered, deckEditor,
|
||||
[=, this] { deckEditor->actOpenRecent(aRecentlyOpenedDeck->text()); });
|
||||
}
|
||||
loadRecentDeckMenu->addSeparator();
|
||||
loadRecentDeckMenu->addAction(aClearRecents);
|
||||
aClearRecents->setEnabled(SettingsCache::instance().recents().getRecentlyOpenedDeckPaths().length() > 0);
|
||||
}
|
||||
|
||||
void DeckEditorMenu::actClearRecents()
|
||||
{
|
||||
SettingsCache::instance().recents().clearRecentlyOpenedDeckPaths();
|
||||
}
|
||||
|
||||
void DeckEditorMenu::retranslateUi()
|
||||
{
|
||||
setTitle(tr("&Deck Editor"));
|
||||
aNewDeck->setText(tr("&New deck"));
|
||||
aLoadDeck->setText(tr("&Load deck..."));
|
||||
loadRecentDeckMenu->setTitle(tr("Load recent deck..."));
|
||||
aClearRecents->setText(tr("Clear"));
|
||||
aSaveDeck->setText(tr("&Save deck"));
|
||||
aSaveDeckAs->setText(tr("Save deck &as..."));
|
||||
|
||||
aLoadDeckFromClipboard->setText(tr("Load deck from cl&ipboard..."));
|
||||
|
||||
editDeckInClipboardMenu->setTitle(tr("Edit deck in clipboard"));
|
||||
aEditDeckInClipboard->setText(tr("Annotated"));
|
||||
aEditDeckInClipboardRaw->setText(tr("Not Annotated"));
|
||||
|
||||
saveDeckToClipboardMenu->setTitle(tr("Save deck to clipboard"));
|
||||
aSaveDeckToClipboard->setText(tr("Annotated"));
|
||||
aSaveDeckToClipboardNoSetInfo->setText(tr("Annotated (No set info)"));
|
||||
aSaveDeckToClipboardRaw->setText(tr("Not Annotated"));
|
||||
aSaveDeckToClipboardRawNoSetInfo->setText(tr("Not Annotated (No set info)"));
|
||||
|
||||
aPrintDeck->setText(tr("&Print deck..."));
|
||||
|
||||
analyzeDeckMenu->setTitle(tr("&Send deck to online service"));
|
||||
aExportDeckDecklist->setText(tr("Create decklist (decklist.org)"));
|
||||
aExportDeckDecklistXyz->setText(tr("Create decklist (decklist.xyz)"));
|
||||
aAnalyzeDeckDeckstats->setText(tr("Analyze deck (deckstats.net)"));
|
||||
aAnalyzeDeckTappedout->setText(tr("Analyze deck (tappedout.net)"));
|
||||
|
||||
aClose->setText(tr("&Close"));
|
||||
}
|
||||
|
||||
void DeckEditorMenu::refreshShortcuts()
|
||||
{
|
||||
ShortcutsSettings &shortcuts = SettingsCache::instance().shortcuts();
|
||||
aNewDeck->setShortcuts(shortcuts.getShortcut("TabDeckEditor/aNewDeck"));
|
||||
aLoadDeck->setShortcuts(shortcuts.getShortcut("TabDeckEditor/aLoadDeck"));
|
||||
aSaveDeck->setShortcuts(shortcuts.getShortcut("TabDeckEditor/aSaveDeck"));
|
||||
aSaveDeckAs->setShortcuts(shortcuts.getShortcut("TabDeckEditor/aSaveDeckAs"));
|
||||
aLoadDeckFromClipboard->setShortcuts(shortcuts.getShortcut("TabDeckEditor/aLoadDeckFromClipboard"));
|
||||
aEditDeckInClipboard->setShortcuts(shortcuts.getShortcut("TabDeckEditor/aEditDeckInClipboard"));
|
||||
aEditDeckInClipboardRaw->setShortcuts(shortcuts.getShortcut("TabDeckEditor/aEditDeckInClipboardRaw"));
|
||||
aPrintDeck->setShortcuts(shortcuts.getShortcut("TabDeckEditor/aPrintDeck"));
|
||||
|
||||
aExportDeckDecklist->setShortcuts(shortcuts.getShortcut("TabDeckEditor/aExportDeckDecklist"));
|
||||
aExportDeckDecklistXyz->setShortcuts(shortcuts.getShortcut("TabDeckEditor/aExportDeckDecklistXyz"));
|
||||
aAnalyzeDeckDeckstats->setShortcuts(shortcuts.getShortcut("TabDeckEditor/aAnalyzeDeck"));
|
||||
aAnalyzeDeckTappedout->setShortcuts(shortcuts.getShortcut("TabDeckEditor/aAnalyzeDeckTappedout"));
|
||||
|
||||
aClose->setShortcuts(shortcuts.getShortcut("TabDeckEditor/aClose"));
|
||||
|
||||
aSaveDeckToClipboard->setShortcuts(shortcuts.getShortcut("TabDeckEditor/aSaveDeckToClipboard"));
|
||||
aSaveDeckToClipboardNoSetInfo->setShortcuts(shortcuts.getShortcut("TabDeckEditor/aSaveDeckToClipboardNoSetInfo"));
|
||||
aSaveDeckToClipboardRaw->setShortcuts(shortcuts.getShortcut("TabDeckEditor/aSaveDeckToClipboardRaw"));
|
||||
aSaveDeckToClipboardRawNoSetInfo->setShortcuts(
|
||||
shortcuts.getShortcut("TabDeckEditor/aSaveDeckToClipboardRawNoSetInfo"));
|
||||
|
||||
aClose->setShortcuts(shortcuts.getShortcut("TabDeckEditor/aClose"));
|
||||
}
|
||||
32
cockatrice/src/client/menus/deck_editor/deck_editor_menu.h
Normal file
@@ -0,0 +1,32 @@
|
||||
#ifndef DECK_EDITOR_MENU_H
|
||||
#define DECK_EDITOR_MENU_H
|
||||
|
||||
#include "../../tabs/abstract_tab_deck_editor.h"
|
||||
|
||||
#include <QMenu>
|
||||
|
||||
class AbstractTabDeckEditor;
|
||||
class DeckEditorMenu : public QMenu
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit DeckEditorMenu(QWidget *parent, AbstractTabDeckEditor *deckEditor);
|
||||
|
||||
AbstractTabDeckEditor *deckEditor;
|
||||
|
||||
QAction *aNewDeck, *aLoadDeck, *aClearRecents, *aSaveDeck, *aSaveDeckAs, *aLoadDeckFromClipboard,
|
||||
*aEditDeckInClipboard, *aEditDeckInClipboardRaw, *aSaveDeckToClipboard, *aSaveDeckToClipboardNoSetInfo,
|
||||
*aSaveDeckToClipboardRaw, *aSaveDeckToClipboardRawNoSetInfo, *aPrintDeck, *aExportDeckDecklist,
|
||||
*aExportDeckDecklistXyz, *aAnalyzeDeckDeckstats, *aAnalyzeDeckTappedout, *aClose;
|
||||
QMenu *loadRecentDeckMenu, *analyzeDeckMenu, *editDeckInClipboardMenu, *saveDeckToClipboardMenu;
|
||||
|
||||
void setSaveStatus(bool newStatus);
|
||||
|
||||
public slots:
|
||||
void updateRecentlyOpened();
|
||||
void actClearRecents();
|
||||
void retranslateUi();
|
||||
void refreshShortcuts();
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -11,6 +11,11 @@
|
||||
#include <QSysInfo>
|
||||
#include <QtGlobal>
|
||||
|
||||
#if defined(Q_OS_MACOS)
|
||||
#include <sys/sysctl.h>
|
||||
#include <sys/types.h>
|
||||
#endif
|
||||
|
||||
#define STABLERELEASE_URL "https://api.github.com/repos/Cockatrice/Cockatrice/releases/latest"
|
||||
#define STABLEMANUALDOWNLOAD_URL "https://github.com/Cockatrice/Cockatrice/releases/latest"
|
||||
#define STABLETAG_URL "https://api.github.com/repos/Cockatrice/Cockatrice/git/refs/tags/"
|
||||
@@ -33,31 +38,42 @@ 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);
|
||||
}
|
||||
|
||||
// Different release channel checking functions for different operating systems
|
||||
#if defined(Q_OS_MACOS)
|
||||
bool ReleaseChannel::downloadMatchesCurrentOS(const QString &fileName)
|
||||
{
|
||||
static QRegularExpression version_regex("macOS-(\\d+)\\.(\\d+)");
|
||||
#if defined(Q_OS_MACOS)
|
||||
static QRegularExpression version_regex("macOS(\\d+)");
|
||||
auto match = version_regex.match(fileName);
|
||||
if (!match.hasMatch()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto getSystemVersion = [] {
|
||||
// QSysInfo does not go through translation layers
|
||||
// We need to use sysctl to reliably detect the underlying architecture
|
||||
char arch[255];
|
||||
size_t len = sizeof(arch);
|
||||
if (sysctlbyname("machdep.cpu.brand_string", arch, &len, nullptr, 0) == 0) {
|
||||
// Intel mac is only supported on macOS 13 versions
|
||||
if (QString::fromUtf8(arch).contains("Intel")) {
|
||||
return 13;
|
||||
}
|
||||
}
|
||||
|
||||
return QSysInfo::productVersion().split(".")[0].toInt();
|
||||
};
|
||||
|
||||
// older(smaller) releases are compatible with a newer or the same system version
|
||||
int sys_maj = QSysInfo::productVersion().split(".")[0].toInt();
|
||||
int sys_min = QSysInfo::productVersion().split(".")[1].toInt();
|
||||
int sys_maj = getSystemVersion();
|
||||
int rel_maj = match.captured(1).toInt();
|
||||
int rel_min = match.captured(2).toInt();
|
||||
return rel_maj < sys_maj || (rel_maj == sys_maj && rel_min <= sys_min);
|
||||
}
|
||||
return rel_maj == sys_maj;
|
||||
|
||||
#elif defined(Q_OS_WIN)
|
||||
bool ReleaseChannel::downloadMatchesCurrentOS(const QString &fileName)
|
||||
{
|
||||
#if Q_PROCESSOR_WORDSIZE == 4
|
||||
return fileName.contains("32bit");
|
||||
#elif Q_PROCESSOR_WORDSIZE == 8
|
||||
@@ -68,16 +84,15 @@ bool ReleaseChannel::downloadMatchesCurrentOS(const QString &fileName)
|
||||
return fileName.contains("Win10");
|
||||
}
|
||||
#else
|
||||
Q_UNUSED(fileName);
|
||||
return false;
|
||||
#endif
|
||||
|
||||
#else // If the OS doesn't fit one of the above #defines, then it will never match
|
||||
Q_UNUSED(fileName);
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
#else
|
||||
bool ReleaseChannel::downloadMatchesCurrentOS(const QString &)
|
||||
{
|
||||
// If the OS doesn't fit one of the above #defines, then it will never match
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
QString StableReleaseChannel::getManualDownloadUrl() const
|
||||
{
|
||||
@@ -101,7 +116,7 @@ void StableReleaseChannel::releaseListFinished()
|
||||
QJsonDocument jsonResponse = QJsonDocument::fromJson(reply->readAll(), &parseError);
|
||||
reply->deleteLater();
|
||||
if (parseError.error != QJsonParseError::NoError) {
|
||||
qWarning() << "No reply received from the release update server.";
|
||||
qCWarning(ReleaseChannelLog) << "No reply received from the release update server.";
|
||||
emit error(tr("No reply received from the release update server."));
|
||||
return;
|
||||
}
|
||||
@@ -109,7 +124,7 @@ void StableReleaseChannel::releaseListFinished()
|
||||
QVariantMap resultMap = jsonResponse.toVariant().toMap();
|
||||
if (!(resultMap.contains("name") && resultMap.contains("html_url") && resultMap.contains("tag_name") &&
|
||||
resultMap.contains("published_at"))) {
|
||||
qWarning() << "Invalid received from the release update server:" << resultMap;
|
||||
qCWarning(ReleaseChannelLog) << "Invalid received from the release update server:" << resultMap;
|
||||
emit error(tr("Invalid reply received from the release update server."));
|
||||
return;
|
||||
}
|
||||
@@ -123,37 +138,29 @@ void StableReleaseChannel::releaseListFinished()
|
||||
|
||||
if (resultMap.contains("assets")) {
|
||||
auto rawAssets = resultMap["assets"].toList();
|
||||
// [(name, url)]
|
||||
QVector<std::pair<QString, QString>> assets;
|
||||
std::transform(rawAssets.begin(), rawAssets.end(), std::back_inserter(assets), [](QVariant _asset) {
|
||||
QVariantMap asset = _asset.toMap();
|
||||
for (const auto &rawAsset : rawAssets) {
|
||||
QVariantMap asset = rawAsset.toMap();
|
||||
QString name = asset["name"].toString();
|
||||
QString url = asset["browser_download_url"].toString();
|
||||
return std::make_pair(name, url);
|
||||
});
|
||||
|
||||
auto _releaseAsset = std::find_if(assets.begin(), assets.end(), [](std::pair<QString, QString> nameAndUrl) {
|
||||
return downloadMatchesCurrentOS(nameAndUrl.first);
|
||||
});
|
||||
|
||||
if (_releaseAsset != assets.end()) {
|
||||
std::pair<QString, QString> releaseAsset = *_releaseAsset;
|
||||
auto releaseUrl = releaseAsset.second;
|
||||
lastRelease->setDownloadUrl(releaseUrl);
|
||||
if (downloadMatchesCurrentOS(name)) {
|
||||
lastRelease->setDownloadUrl(url);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
@@ -165,24 +172,24 @@ void StableReleaseChannel::tagListFinished()
|
||||
QJsonDocument jsonResponse = QJsonDocument::fromJson(reply->readAll(), &parseError);
|
||||
reply->deleteLater();
|
||||
if (parseError.error != QJsonParseError::NoError) {
|
||||
qWarning() << "No reply received from the tag update server.";
|
||||
qCWarning(ReleaseChannelLog) << "No reply received from the tag update server.";
|
||||
emit error(tr("No reply received from the tag update server."));
|
||||
return;
|
||||
}
|
||||
|
||||
QVariantMap resultMap = jsonResponse.toVariant().toMap();
|
||||
if (!(resultMap.contains("object") && resultMap["object"].toMap().contains("sha"))) {
|
||||
qWarning() << "Invalid received from the tag update server.";
|
||||
qCWarning(ReleaseChannelLog) << "Invalid received from the tag update server.";
|
||||
emit error(tr("Invalid reply received from the tag update server."));
|
||||
return;
|
||||
}
|
||||
|
||||
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);
|
||||
@@ -225,7 +232,7 @@ void BetaReleaseChannel::releaseListFinished()
|
||||
QVariantMap resultMap = array.at(0).toObject().toVariantMap();
|
||||
|
||||
if (array.empty() || resultMap.empty()) {
|
||||
qWarning() << "No reply received from the release update server:" << QString(jsonData);
|
||||
qCWarning(ReleaseChannelLog) << "No reply received from the release update server:" << QString(jsonData);
|
||||
emit error(tr("No reply received from the release update server."));
|
||||
return;
|
||||
}
|
||||
@@ -234,7 +241,7 @@ void BetaReleaseChannel::releaseListFinished()
|
||||
if (!resultMap.contains("assets") || !resultMap.contains("author") || !resultMap.contains("tag_name") ||
|
||||
!resultMap.contains("target_commitish") || !resultMap.contains("assets_url") ||
|
||||
!resultMap.contains("published_at")) {
|
||||
qWarning() << "Invalid received from the release update server:" << resultMap;
|
||||
qCWarning(ReleaseChannelLog) << "Invalid received from the release update server:" << resultMap;
|
||||
emit error(tr("Invalid reply received from the release update server."));
|
||||
return;
|
||||
}
|
||||
@@ -249,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);
|
||||
}
|
||||
@@ -267,7 +274,7 @@ void BetaReleaseChannel::fileListFinished()
|
||||
QJsonDocument jsonResponse = QJsonDocument::fromJson(reply->readAll(), &parseError);
|
||||
reply->deleteLater();
|
||||
if (parseError.error != QJsonParseError::NoError) {
|
||||
qWarning() << "No reply received from the file update server.";
|
||||
qCWarning(ReleaseChannelLog) << "No reply received from the file update server.";
|
||||
emit error(tr("No reply received from the file update server."));
|
||||
return;
|
||||
}
|
||||
@@ -275,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;
|
||||
@@ -292,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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +28,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 +67,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 +81,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 +101,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 +139,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 +159,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 +202,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 +221,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();
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
569
cockatrice/src/client/tabs/abstract_tab_deck_editor.cpp
Normal file
@@ -0,0 +1,569 @@
|
||||
#include "abstract_tab_deck_editor.h"
|
||||
|
||||
#include "../../client/game_logic/abstract_client.h"
|
||||
#include "../../client/tapped_out_interface.h"
|
||||
#include "../../client/ui/widgets/cards/card_info_frame_widget.h"
|
||||
#include "../../deck/deck_stats_interface.h"
|
||||
#include "../../dialogs/dlg_load_deck.h"
|
||||
#include "../../dialogs/dlg_load_deck_from_clipboard.h"
|
||||
#include "../../game/cards/card_database_manager.h"
|
||||
#include "../../game/cards/card_database_model.h"
|
||||
#include "../../server/pending_command.h"
|
||||
#include "../../settings/cache_settings.h"
|
||||
#include "../ui/picture_loader/picture_loader.h"
|
||||
#include "../ui/pixel_map_generator.h"
|
||||
#include "pb/command_deck_upload.pb.h"
|
||||
#include "pb/response.pb.h"
|
||||
#include "tab_supervisor.h"
|
||||
#include "trice_limits.h"
|
||||
|
||||
#include <QAction>
|
||||
#include <QApplication>
|
||||
#include <QClipboard>
|
||||
#include <QCloseEvent>
|
||||
#include <QDebug>
|
||||
#include <QDesktopServices>
|
||||
#include <QDir>
|
||||
#include <QFileDialog>
|
||||
#include <QHeaderView>
|
||||
#include <QLineEdit>
|
||||
#include <QMenuBar>
|
||||
#include <QMessageBox>
|
||||
#include <QPrintPreviewDialog>
|
||||
#include <QProcessEnvironment>
|
||||
#include <QPushButton>
|
||||
#include <QRegularExpression>
|
||||
#include <QSplitter>
|
||||
#include <QTextStream>
|
||||
#include <QTreeView>
|
||||
#include <QUrl>
|
||||
|
||||
AbstractTabDeckEditor::AbstractTabDeckEditor(TabSupervisor *_tabSupervisor) : Tab(_tabSupervisor)
|
||||
{
|
||||
setDockOptions(QMainWindow::AnimatedDocks | QMainWindow::AllowNestedDocks | QMainWindow::AllowTabbedDocks);
|
||||
|
||||
databaseDisplayDockWidget = new DeckEditorDatabaseDisplayWidget(this);
|
||||
deckDockWidget = new DeckEditorDeckDockWidget(this);
|
||||
cardInfoDockWidget = new DeckEditorCardInfoDockWidget(this);
|
||||
filterDockWidget = new DeckEditorFilterDockWidget(this);
|
||||
printingSelectorDockWidget = new DeckEditorPrintingSelectorDockWidget(this);
|
||||
|
||||
connect(deckDockWidget, &DeckEditorDeckDockWidget::deckChanged, this, &AbstractTabDeckEditor::onDeckChanged);
|
||||
connect(deckDockWidget, &DeckEditorDeckDockWidget::cardChanged, this, &AbstractTabDeckEditor::updateCard);
|
||||
connect(this, &AbstractTabDeckEditor::decrementCard, deckDockWidget, &DeckEditorDeckDockWidget::actDecrementCard);
|
||||
connect(databaseDisplayDockWidget, &DeckEditorDatabaseDisplayWidget::cardChanged, this,
|
||||
&AbstractTabDeckEditor::updateCard);
|
||||
connect(databaseDisplayDockWidget, &DeckEditorDatabaseDisplayWidget::addCardToMainDeck, this,
|
||||
&AbstractTabDeckEditor::actAddCard);
|
||||
connect(databaseDisplayDockWidget, &DeckEditorDatabaseDisplayWidget::addCardToSideboard, this,
|
||||
&AbstractTabDeckEditor::actAddCardToSideboard);
|
||||
connect(databaseDisplayDockWidget, &DeckEditorDatabaseDisplayWidget::decrementCardFromMainDeck, this,
|
||||
&AbstractTabDeckEditor::actDecrementCard);
|
||||
connect(databaseDisplayDockWidget, &DeckEditorDatabaseDisplayWidget::decrementCardFromSideboard, this,
|
||||
&AbstractTabDeckEditor::actDecrementCardFromSideboard);
|
||||
|
||||
connect(filterDockWidget, &DeckEditorFilterDockWidget::clearAllDatabaseFilters, databaseDisplayDockWidget,
|
||||
&DeckEditorDatabaseDisplayWidget::clearAllDatabaseFilters);
|
||||
|
||||
connect(&SettingsCache::instance().shortcuts(), SIGNAL(shortCutChanged()), this, SLOT(refreshShortcuts()));
|
||||
}
|
||||
|
||||
void AbstractTabDeckEditor::updateCard(CardInfoPtr _card)
|
||||
{
|
||||
cardInfoDockWidget->updateCard(_card);
|
||||
printingSelectorDockWidget->printingSelector->setCard(_card, DECK_ZONE_MAIN);
|
||||
}
|
||||
|
||||
void AbstractTabDeckEditor::onDeckChanged()
|
||||
{
|
||||
setModified(!isBlankNewDeck());
|
||||
deckMenu->setSaveStatus(!isBlankNewDeck());
|
||||
}
|
||||
|
||||
void AbstractTabDeckEditor::addCardHelper(const CardInfoPtr info, QString zoneName)
|
||||
{
|
||||
if (!info)
|
||||
return;
|
||||
if (info->getIsToken())
|
||||
zoneName = DECK_ZONE_TOKENS;
|
||||
|
||||
QModelIndex newCardIndex = deckDockWidget->deckModel->addPreferredPrintingCard(info->getName(), zoneName, false);
|
||||
// recursiveExpand(newCardIndex);
|
||||
deckDockWidget->deckView->clearSelection();
|
||||
deckDockWidget->deckView->setCurrentIndex(newCardIndex);
|
||||
setModified(true);
|
||||
databaseDisplayDockWidget->searchEdit->setSelection(0, databaseDisplayDockWidget->searchEdit->text().length());
|
||||
}
|
||||
|
||||
void AbstractTabDeckEditor::actAddCard(CardInfoPtr info)
|
||||
{
|
||||
if (QApplication::keyboardModifiers() & Qt::ControlModifier)
|
||||
actAddCardToSideboard(info);
|
||||
else
|
||||
addCardHelper(info, DECK_ZONE_MAIN);
|
||||
deckMenu->setSaveStatus(true);
|
||||
}
|
||||
|
||||
void AbstractTabDeckEditor::actAddCardToSideboard(CardInfoPtr info)
|
||||
{
|
||||
addCardHelper(info, DECK_ZONE_SIDE);
|
||||
deckMenu->setSaveStatus(true);
|
||||
}
|
||||
|
||||
void AbstractTabDeckEditor::actDecrementCard(CardInfoPtr info)
|
||||
{
|
||||
emit decrementCard(info, DECK_ZONE_MAIN);
|
||||
}
|
||||
|
||||
void AbstractTabDeckEditor::actDecrementCardFromSideboard(CardInfoPtr info)
|
||||
{
|
||||
emit decrementCard(info, DECK_ZONE_SIDE);
|
||||
}
|
||||
|
||||
void AbstractTabDeckEditor::actSwapCard(CardInfoPtr info, QString zoneName)
|
||||
{
|
||||
QString providerId = CardDatabaseManager::getInstance()->getSetInfoForCard(info).getProperty("uuid");
|
||||
QString collectorNumber = CardDatabaseManager::getInstance()->getSetInfoForCard(info).getProperty("num");
|
||||
deckDockWidget->swapCard(
|
||||
deckDockWidget->deckModel->findCard(info->getName(), zoneName, providerId, collectorNumber));
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the deck in this tab.
|
||||
* @param deck The deck. Takes ownership of the object
|
||||
*/
|
||||
void AbstractTabDeckEditor::openDeck(DeckLoader *deck)
|
||||
{
|
||||
setDeck(deck);
|
||||
|
||||
if (!deck->getLastFileName().isEmpty()) {
|
||||
SettingsCache::instance().recents().updateRecentlyOpenedDeckPaths(deck->getLastFileName());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the currently active deck for this tab
|
||||
* @param _deck The deck. Takes ownership of the object
|
||||
*/
|
||||
void AbstractTabDeckEditor::setDeck(DeckLoader *_deck)
|
||||
{
|
||||
deckDockWidget->setDeck(_deck);
|
||||
PictureLoader::cacheCardPixmaps(
|
||||
CardDatabaseManager::getInstance()->getCardsByNameAndProviderId(getDeckList()->getCardListWithProviderId()));
|
||||
setModified(false);
|
||||
|
||||
// If they load a deck, make the deck list appear
|
||||
aDeckDockVisible->setChecked(true);
|
||||
deckDockWidget->setVisible(aDeckDockVisible->isChecked());
|
||||
}
|
||||
|
||||
DeckLoader *AbstractTabDeckEditor::getDeckList() const
|
||||
{
|
||||
return deckDockWidget->getDeckList();
|
||||
}
|
||||
|
||||
void AbstractTabDeckEditor::setModified(bool _modified)
|
||||
{
|
||||
modified = _modified;
|
||||
emit tabTextChanged(this, getTabText());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns true if this tab is a blank newly opened tab, as if it was just created with the `New Deck` action.
|
||||
*/
|
||||
bool AbstractTabDeckEditor::isBlankNewDeck() const
|
||||
{
|
||||
DeckLoader *deck = getDeckList();
|
||||
return !modified && deck->hasNotBeenLoaded();
|
||||
}
|
||||
|
||||
void AbstractTabDeckEditor::actNewDeck()
|
||||
{
|
||||
auto deckOpenLocation = confirmOpen(false);
|
||||
|
||||
if (deckOpenLocation == CANCELLED) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (deckOpenLocation == NEW_TAB) {
|
||||
emit openDeckEditor(nullptr);
|
||||
return;
|
||||
}
|
||||
|
||||
cleanDeckAndResetModified();
|
||||
}
|
||||
|
||||
void AbstractTabDeckEditor::cleanDeckAndResetModified()
|
||||
{
|
||||
deckMenu->setSaveStatus(false);
|
||||
deckDockWidget->cleanDeck();
|
||||
setModified(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Displays the save confirmation dialogue that is shown before loading a deck, if required. Takes into
|
||||
* account the `openDeckInNewTab` settting.
|
||||
*
|
||||
* @param openInSameTabIfBlank Open the deck in the same tab instead of a new tab if the current tab is completely
|
||||
* blank. Only relevant when the `openDeckInNewTab` setting is enabled.
|
||||
*
|
||||
* @returns An enum that indicates if and where to load the deck
|
||||
*/
|
||||
AbstractTabDeckEditor::DeckOpenLocation AbstractTabDeckEditor::confirmOpen(const bool openInSameTabIfBlank)
|
||||
{
|
||||
// handle `openDeckInNewTab` setting
|
||||
if (SettingsCache::instance().getOpenDeckInNewTab()) {
|
||||
if (openInSameTabIfBlank && isBlankNewDeck()) {
|
||||
return SAME_TAB;
|
||||
} else {
|
||||
return NEW_TAB;
|
||||
}
|
||||
}
|
||||
|
||||
// early return if deck is unmodified
|
||||
if (!modified) {
|
||||
return SAME_TAB;
|
||||
}
|
||||
|
||||
// do the save confirmation dialogue
|
||||
tabSupervisor->setCurrentWidget(this);
|
||||
|
||||
QMessageBox *msgBox = createSaveConfirmationWindow();
|
||||
QPushButton *newTabButton = msgBox->addButton(tr("Open in new tab"), QMessageBox::ApplyRole);
|
||||
|
||||
int ret = msgBox->exec();
|
||||
|
||||
// `exec()` returns an opaque value if a non-standard button was clicked.
|
||||
// Directly check if newTabButton was clicked before switching over the standard buttons.
|
||||
if (msgBox->clickedButton() == newTabButton) {
|
||||
return NEW_TAB;
|
||||
}
|
||||
|
||||
switch (ret) {
|
||||
case QMessageBox::Save:
|
||||
return actSaveDeck() ? SAME_TAB : CANCELLED;
|
||||
case QMessageBox::Discard:
|
||||
return SAME_TAB;
|
||||
default:
|
||||
return CANCELLED;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Creates the base save confirmation dialogue box.
|
||||
*
|
||||
* @returns A QMessageBox that can be further modified
|
||||
*/
|
||||
QMessageBox *AbstractTabDeckEditor::createSaveConfirmationWindow()
|
||||
{
|
||||
QMessageBox *msgBox = new QMessageBox(this);
|
||||
msgBox->setIcon(QMessageBox::Warning);
|
||||
msgBox->setWindowTitle(tr("Are you sure?"));
|
||||
msgBox->setText(tr("The decklist has been modified.\nDo you want to save the changes?"));
|
||||
msgBox->setStandardButtons(QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel);
|
||||
return msgBox;
|
||||
}
|
||||
|
||||
void AbstractTabDeckEditor::actLoadDeck()
|
||||
{
|
||||
auto deckOpenLocation = confirmOpen();
|
||||
|
||||
if (deckOpenLocation == CANCELLED) {
|
||||
return;
|
||||
}
|
||||
|
||||
DlgLoadDeck dialog(this);
|
||||
if (!dialog.exec())
|
||||
return;
|
||||
|
||||
QString fileName = dialog.selectedFiles().at(0);
|
||||
openDeckFromFile(fileName, deckOpenLocation);
|
||||
deckDockWidget->updateBannerCardComboBox();
|
||||
}
|
||||
|
||||
void AbstractTabDeckEditor::actOpenRecent(const QString &fileName)
|
||||
{
|
||||
auto deckOpenLocation = confirmOpen();
|
||||
|
||||
if (deckOpenLocation == CANCELLED) {
|
||||
return;
|
||||
}
|
||||
|
||||
openDeckFromFile(fileName, deckOpenLocation);
|
||||
}
|
||||
|
||||
/**
|
||||
* Actually opens the deck from file
|
||||
* @param fileName The path of the deck to open
|
||||
* @param deckOpenLocation Which tab to open the deck
|
||||
*/
|
||||
void AbstractTabDeckEditor::openDeckFromFile(const QString &fileName, DeckOpenLocation deckOpenLocation)
|
||||
{
|
||||
DeckLoader::FileFormat fmt = DeckLoader::getFormatFromName(fileName);
|
||||
|
||||
auto *l = new DeckLoader;
|
||||
if (l->loadFromFile(fileName, fmt, true)) {
|
||||
if (deckOpenLocation == NEW_TAB) {
|
||||
emit openDeckEditor(l);
|
||||
l->deleteLater();
|
||||
} else {
|
||||
deckMenu->setSaveStatus(false);
|
||||
openDeck(l);
|
||||
}
|
||||
} else {
|
||||
l->deleteLater();
|
||||
QMessageBox::critical(this, tr("Error"), tr("Could not open deck at %1").arg(fileName));
|
||||
}
|
||||
deckMenu->setSaveStatus(true);
|
||||
}
|
||||
|
||||
bool AbstractTabDeckEditor::actSaveDeck()
|
||||
{
|
||||
DeckLoader *const deck = getDeckList();
|
||||
if (deck->getLastRemoteDeckId() != -1) {
|
||||
QString deckString = deck->writeToString_Native();
|
||||
if (deckString.length() > MAX_FILE_LENGTH) {
|
||||
QMessageBox::critical(this, tr("Error"), tr("Could not save remote deck"));
|
||||
return false;
|
||||
}
|
||||
|
||||
Command_DeckUpload cmd;
|
||||
cmd.set_deck_id(static_cast<google::protobuf::uint32>(deck->getLastRemoteDeckId()));
|
||||
cmd.set_deck_list(deckString.toStdString());
|
||||
|
||||
PendingCommand *pend = AbstractClient::prepareSessionCommand(cmd);
|
||||
connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this,
|
||||
SLOT(saveDeckRemoteFinished(Response)));
|
||||
tabSupervisor->getClient()->sendCommand(pend);
|
||||
|
||||
return true;
|
||||
} else if (deck->getLastFileName().isEmpty())
|
||||
return actSaveDeckAs();
|
||||
else if (deck->saveToFile(deck->getLastFileName(), deck->getLastFileFormat())) {
|
||||
setModified(false);
|
||||
return true;
|
||||
}
|
||||
QMessageBox::critical(
|
||||
this, tr("Error"),
|
||||
tr("The deck could not be saved.\nPlease check that the directory is writable and try again."));
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AbstractTabDeckEditor::actSaveDeckAs()
|
||||
{
|
||||
QFileDialog dialog(this, tr("Save deck"));
|
||||
dialog.setDirectory(SettingsCache::instance().getDeckPath());
|
||||
dialog.setAcceptMode(QFileDialog::AcceptSave);
|
||||
dialog.setDefaultSuffix("cod");
|
||||
dialog.setNameFilters(DeckLoader::FILE_NAME_FILTERS);
|
||||
dialog.selectFile(getDeckList()->getName().trimmed());
|
||||
if (!dialog.exec())
|
||||
return false;
|
||||
|
||||
QString fileName = dialog.selectedFiles().at(0);
|
||||
DeckLoader::FileFormat fmt = DeckLoader::getFormatFromName(fileName);
|
||||
|
||||
if (!getDeckList()->saveToFile(fileName, fmt)) {
|
||||
QMessageBox::critical(
|
||||
this, tr("Error"),
|
||||
tr("The deck could not be saved.\nPlease check that the directory is writable and try again."));
|
||||
return false;
|
||||
}
|
||||
setModified(false);
|
||||
|
||||
SettingsCache::instance().recents().updateRecentlyOpenedDeckPaths(fileName);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void AbstractTabDeckEditor::saveDeckRemoteFinished(const Response &response)
|
||||
{
|
||||
if (response.response_code() != Response::RespOk)
|
||||
QMessageBox::critical(this, tr("Error"), tr("The deck could not be saved."));
|
||||
else
|
||||
setModified(false);
|
||||
}
|
||||
|
||||
void AbstractTabDeckEditor::actLoadDeckFromClipboard()
|
||||
{
|
||||
auto deckOpenLocation = confirmOpen();
|
||||
|
||||
if (deckOpenLocation == CANCELLED) {
|
||||
return;
|
||||
}
|
||||
|
||||
DlgLoadDeckFromClipboard dlg(this);
|
||||
if (!dlg.exec())
|
||||
return;
|
||||
|
||||
if (deckOpenLocation == NEW_TAB) {
|
||||
emit openDeckEditor(dlg.getDeckList());
|
||||
} else {
|
||||
setDeck(dlg.getDeckList());
|
||||
setModified(true);
|
||||
}
|
||||
|
||||
deckMenu->setSaveStatus(true);
|
||||
}
|
||||
|
||||
void AbstractTabDeckEditor::editDeckInClipboard(bool annotated)
|
||||
{
|
||||
DlgEditDeckInClipboard dlg(*getDeckList(), annotated, this);
|
||||
if (!dlg.exec())
|
||||
return;
|
||||
|
||||
setDeck(dlg.getDeckList());
|
||||
setModified(true);
|
||||
|
||||
deckMenu->setSaveStatus(true);
|
||||
}
|
||||
|
||||
void AbstractTabDeckEditor::actEditDeckInClipboard()
|
||||
{
|
||||
editDeckInClipboard(true);
|
||||
}
|
||||
|
||||
void AbstractTabDeckEditor::actEditDeckInClipboardRaw()
|
||||
{
|
||||
editDeckInClipboard(false);
|
||||
}
|
||||
|
||||
void AbstractTabDeckEditor::actSaveDeckToClipboard()
|
||||
{
|
||||
getDeckList()->saveToClipboard(true, true);
|
||||
}
|
||||
|
||||
void AbstractTabDeckEditor::actSaveDeckToClipboardNoSetInfo()
|
||||
{
|
||||
getDeckList()->saveToClipboard(true, false);
|
||||
}
|
||||
|
||||
void AbstractTabDeckEditor::actSaveDeckToClipboardRaw()
|
||||
{
|
||||
getDeckList()->saveToClipboard(false, true);
|
||||
}
|
||||
|
||||
void AbstractTabDeckEditor::actSaveDeckToClipboardRawNoSetInfo()
|
||||
{
|
||||
getDeckList()->saveToClipboard(false, false);
|
||||
}
|
||||
|
||||
void AbstractTabDeckEditor::actPrintDeck()
|
||||
{
|
||||
auto *dlg = new QPrintPreviewDialog(this);
|
||||
connect(dlg, SIGNAL(paintRequested(QPrinter *)), deckDockWidget->deckModel, SLOT(printDeckList(QPrinter *)));
|
||||
dlg->exec();
|
||||
}
|
||||
|
||||
void AbstractTabDeckEditor::exportToDecklistWebsite(DeckLoader::DecklistWebsite website)
|
||||
{
|
||||
// check if deck is not null
|
||||
if (DeckLoader *const deck = getDeckList()) {
|
||||
// Get the decklist url string from the deck loader class.
|
||||
QString decklistUrlString = deck->exportDeckToDecklist(website);
|
||||
// Check to make sure the string isn't empty.
|
||||
if (QString::compare(decklistUrlString, "", Qt::CaseInsensitive) == 0) {
|
||||
// Show an error if the deck is empty, and return.
|
||||
QMessageBox::critical(this, tr("Error"), tr("There are no cards in your deck to be exported"));
|
||||
return;
|
||||
}
|
||||
// Encode the string recieved from the model to make sure all characters are encoded.
|
||||
// first we put it into a qurl object
|
||||
QUrl decklistUrl = QUrl(decklistUrlString);
|
||||
// we get the correctly encoded url.
|
||||
decklistUrlString = decklistUrl.toEncoded();
|
||||
// We open the url in the user's default browser
|
||||
QDesktopServices::openUrl(decklistUrlString);
|
||||
} else {
|
||||
// if there's no deck loader object, return an error
|
||||
QMessageBox::critical(this, tr("Error"), tr("No deck was selected to be exported."));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Exports the deck to www.decklist.org (the old website)
|
||||
*/
|
||||
void AbstractTabDeckEditor::actExportDeckDecklist()
|
||||
{
|
||||
exportToDecklistWebsite(DeckLoader::DecklistOrg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Exports the deck to www.decklist.xyz (the new website)
|
||||
*/
|
||||
void AbstractTabDeckEditor::actExportDeckDecklistXyz()
|
||||
{
|
||||
exportToDecklistWebsite(DeckLoader::DecklistXyz);
|
||||
}
|
||||
|
||||
void AbstractTabDeckEditor::actAnalyzeDeckDeckstats()
|
||||
{
|
||||
auto *interface = new DeckStatsInterface(*databaseDisplayDockWidget->databaseModel->getDatabase(),
|
||||
this); // it deletes itself when done
|
||||
interface->analyzeDeck(getDeckList());
|
||||
}
|
||||
|
||||
void AbstractTabDeckEditor::actAnalyzeDeckTappedout()
|
||||
{
|
||||
auto *interface = new TappedOutInterface(*databaseDisplayDockWidget->databaseModel->getDatabase(),
|
||||
this); // it deletes itself when done
|
||||
interface->analyzeDeck(getDeckList());
|
||||
}
|
||||
|
||||
void AbstractTabDeckEditor::filterTreeChanged(FilterTree *filterTree)
|
||||
{
|
||||
databaseDisplayDockWidget->setFilterTree(filterTree);
|
||||
}
|
||||
|
||||
// Method uses to sync docks state with menu items state
|
||||
bool AbstractTabDeckEditor::eventFilter(QObject *o, QEvent *e)
|
||||
{
|
||||
if (e->type() == QEvent::Close) {
|
||||
if (o == cardInfoDockWidget) {
|
||||
aCardInfoDockVisible->setChecked(false);
|
||||
aCardInfoDockFloating->setEnabled(false);
|
||||
} else if (o == deckDockWidget) {
|
||||
aDeckDockVisible->setChecked(false);
|
||||
aDeckDockFloating->setEnabled(false);
|
||||
} else if (o == filterDockWidget) {
|
||||
aFilterDockVisible->setChecked(false);
|
||||
aFilterDockFloating->setEnabled(false);
|
||||
} else if (o == printingSelectorDockWidget) {
|
||||
aPrintingSelectorDockVisible->setChecked(false);
|
||||
aPrintingSelectorDockFloating->setEnabled(false);
|
||||
}
|
||||
}
|
||||
if (o == this && e->type() == QEvent::Hide) {
|
||||
LayoutsSettings &layouts = SettingsCache::instance().layouts();
|
||||
layouts.setDeckEditorLayoutState(saveState());
|
||||
layouts.setDeckEditorGeometry(saveGeometry());
|
||||
layouts.setDeckEditorCardSize(cardInfoDockWidget->size());
|
||||
layouts.setDeckEditorFilterSize(filterDockWidget->size());
|
||||
layouts.setDeckEditorDeckSize(deckDockWidget->size());
|
||||
layouts.setDeckEditorPrintingSelectorSize(printingSelectorDockWidget->size());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AbstractTabDeckEditor::confirmClose()
|
||||
{
|
||||
if (modified) {
|
||||
tabSupervisor->setCurrentWidget(this);
|
||||
int ret = createSaveConfirmationWindow()->exec();
|
||||
if (ret == QMessageBox::Save)
|
||||
return actSaveDeck();
|
||||
else if (ret == QMessageBox::Cancel)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void AbstractTabDeckEditor::closeRequest(bool forced)
|
||||
{
|
||||
if (!forced && !confirmClose()) {
|
||||
return;
|
||||
}
|
||||
|
||||
emit deckEditorClosing(this);
|
||||
close();
|
||||
}
|
||||
155
cockatrice/src/client/tabs/abstract_tab_deck_editor.h
Normal file
@@ -0,0 +1,155 @@
|
||||
#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,191 +1,41 @@
|
||||
#ifndef WINDOW_DECKEDITOR_H
|
||||
#define WINDOW_DECKEDITOR_H
|
||||
|
||||
#include "../../deck/custom_line_edit.h"
|
||||
#include "../../game/cards/card_database.h"
|
||||
#include "../game_logic/key_signals.h"
|
||||
#include "../ui/widgets/printing_selector/printing_selector.h"
|
||||
#include "tab.h"
|
||||
|
||||
#include <QAbstractItemModel>
|
||||
#include <QDir>
|
||||
#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 QTreeView;
|
||||
|
||||
class CardInfoFrameWidget;
|
||||
class QTextEdit;
|
||||
class QLabel;
|
||||
class DeckLoader;
|
||||
class Response;
|
||||
class FilterTreeModel;
|
||||
class FilterBuilder;
|
||||
class QComboBox;
|
||||
class QGroupBox;
|
||||
class QMessageBox;
|
||||
class QHBoxLayout;
|
||||
class QVBoxLayout;
|
||||
class QPushButton;
|
||||
class QDockWidget;
|
||||
|
||||
class TabDeckEditor : public Tab
|
||||
class TabDeckEditor : public AbstractTabDeckEditor
|
||||
{
|
||||
Q_OBJECT
|
||||
private slots:
|
||||
void updateName(const QString &name);
|
||||
void updateComments();
|
||||
void updateBannerCardComboBox();
|
||||
void setBannerCard(int);
|
||||
void updateHash();
|
||||
void updateCardInfoLeft(const QModelIndex ¤t, const QModelIndex &previous);
|
||||
void updateCardInfoRight(const QModelIndex ¤t, const QModelIndex &previous);
|
||||
void updatePrintingSelectorDatabase(const QModelIndex ¤t, const QModelIndex &previous);
|
||||
void updatePrintingSelectorDeckView(const QModelIndex ¤t, const QModelIndex &previous);
|
||||
void updateSearch(const QString &search);
|
||||
void databaseCustomMenu(QPoint point);
|
||||
void decklistCustomMenu(QPoint point);
|
||||
void updateRecentlyOpened();
|
||||
|
||||
void actNewDeck();
|
||||
void actLoadDeck();
|
||||
void actOpenRecent(const QString &fileName);
|
||||
void actClearRecents();
|
||||
bool actSaveDeck();
|
||||
bool actSaveDeckAs();
|
||||
void actLoadDeckFromClipboard();
|
||||
void actSaveDeckToClipboard();
|
||||
void actSaveDeckToClipboardNoSetNameAndNumber();
|
||||
void actSaveDeckToClipboardRaw();
|
||||
void actSaveDeckToClipboardRawNoSetNameAndNumber();
|
||||
void actPrintDeck();
|
||||
void actExportDeckDecklist();
|
||||
void actAnalyzeDeckDeckstats();
|
||||
void actAnalyzeDeckTappedout();
|
||||
|
||||
void actClearFilterAll();
|
||||
void actClearFilterOne();
|
||||
|
||||
void actSwapCard();
|
||||
void actAddCard();
|
||||
void actAddCardToSideboard();
|
||||
void actRemoveCard();
|
||||
void actIncrement();
|
||||
void actDecrement();
|
||||
void actDecrementCard();
|
||||
void actDecrementCardFromSideboard();
|
||||
void copyDatabaseCellContents();
|
||||
|
||||
void saveDeckRemoteFinished(const Response &r);
|
||||
void filterViewCustomContextMenu(const QPoint &point);
|
||||
void filterRemove(QAction *action);
|
||||
|
||||
void loadLayout();
|
||||
void restartLayout();
|
||||
void freeDocksSize();
|
||||
void refreshShortcuts();
|
||||
protected slots:
|
||||
void loadLayout() override;
|
||||
void restartLayout() override;
|
||||
void freeDocksSize() override;
|
||||
void refreshShortcuts() override;
|
||||
|
||||
bool eventFilter(QObject *o, QEvent *e) override;
|
||||
void dockVisibleTriggered();
|
||||
void dockFloatingTriggered();
|
||||
void dockTopLevelChanged(bool topLevel);
|
||||
void saveDbHeaderState();
|
||||
void setSaveStatus(bool newStatus);
|
||||
void showSearchSyntaxHelp();
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief Which tab to open the new deck in
|
||||
*/
|
||||
enum DeckOpenLocation
|
||||
{
|
||||
CANCELLED,
|
||||
SAME_TAB,
|
||||
NEW_TAB
|
||||
};
|
||||
|
||||
DeckOpenLocation confirmOpen(const bool openInSameTabIfBlank = true);
|
||||
QMessageBox *createSaveConfirmationWindow();
|
||||
|
||||
bool isBlankNewDeck() const;
|
||||
CardInfoPtr currentCardInfo() const;
|
||||
void offsetCountAtIndex(const QModelIndex &idx, int offset);
|
||||
void decrementCardHelper(QString zoneName);
|
||||
bool swapCard(const QModelIndex &idx);
|
||||
void recursiveExpand(const QModelIndex &index);
|
||||
void openDeckFromFile(const QString &fileName, DeckOpenLocation deckOpenLocation);
|
||||
|
||||
QModelIndexList getSelectedCardNodes() const;
|
||||
|
||||
CardDatabaseModel *databaseModel;
|
||||
CardDatabaseDisplayModel *databaseDisplayModel;
|
||||
DeckListModel *deckModel;
|
||||
QTreeView *databaseView;
|
||||
|
||||
QTreeView *deckView;
|
||||
KeySignals deckViewKeySignals;
|
||||
CardInfoFrameWidget *cardInfo;
|
||||
PrintingSelector *printingSelector;
|
||||
SearchLineEdit *searchEdit;
|
||||
KeySignals searchKeySignals;
|
||||
|
||||
QLabel *nameLabel;
|
||||
LineEditUnfocusable *nameEdit;
|
||||
QLabel *commentsLabel;
|
||||
QTextEdit *commentsEdit;
|
||||
QLabel *bannerCardLabel;
|
||||
QComboBox *bannerCardComboBox;
|
||||
QLabel *hashLabel1;
|
||||
LineEditUnfocusable *hashLabel;
|
||||
FilterTreeModel *filterModel;
|
||||
QTreeView *filterView;
|
||||
KeySignals filterViewKeySignals;
|
||||
QWidget *filterBox;
|
||||
|
||||
QMenu *deckMenu, *viewMenu, *cardInfoDockMenu, *deckDockMenu, *filterDockMenu, *printingSelectorDockMenu,
|
||||
*analyzeDeckMenu, *saveDeckToClipboardMenu, *loadRecentDeckMenu;
|
||||
QAction *aNewDeck, *aLoadDeck, *aClearRecents, *aSaveDeck, *aSaveDeckAs, *aLoadDeckFromClipboard,
|
||||
*aSaveDeckToClipboard, *aSaveDeckToClipboardNoSetNameAndNumber, *aSaveDeckToClipboardRaw,
|
||||
*aSaveDeckToClipboardRawNoSetNameAndNumber, *aPrintDeck, *aExportDeckDecklist, *aAnalyzeDeckDeckstats,
|
||||
*aAnalyzeDeckTappedout, *aClose;
|
||||
QAction *aClearFilterAll, *aClearFilterOne;
|
||||
QAction *aAddCard, *aAddCardToSideboard, *aRemoveCard, *aIncrement, *aDecrement, *aSwapCard;
|
||||
QAction *aResetLayout;
|
||||
QAction *aCardInfoDockVisible, *aCardInfoDockFloating, *aDeckDockVisible, *aDeckDockFloating, *aFilterDockVisible,
|
||||
*aFilterDockFloating, *aPrintingSelectorDockVisible, *aPrintingSelectorDockFloating;
|
||||
|
||||
bool modified;
|
||||
QVBoxLayout *centralFrame;
|
||||
QHBoxLayout *searchLayout;
|
||||
QDockWidget *cardInfoDock;
|
||||
QDockWidget *deckDock;
|
||||
QDockWidget *filterDock;
|
||||
QDockWidget *printingSelectorDock;
|
||||
QWidget *centralWidget;
|
||||
void dockVisibleTriggered() override;
|
||||
void dockFloatingTriggered() override;
|
||||
void dockTopLevelChanged(bool topLevel) override;
|
||||
|
||||
public:
|
||||
explicit TabDeckEditor(TabSupervisor *_tabSupervisor);
|
||||
void retranslateUi() override;
|
||||
QString getTabText() const override;
|
||||
void setDeck(DeckLoader *_deckLoader);
|
||||
void setModified(bool _windowModified);
|
||||
bool confirmClose();
|
||||
void createDeckDock();
|
||||
void createCardInfoDock();
|
||||
void createFiltersDock();
|
||||
void createPrintingSelectorDock();
|
||||
void createMenus();
|
||||
void createCentralFrame();
|
||||
void updateCardInfo(CardInfoPtr _card);
|
||||
void addCardHelper(CardInfoPtr info, QString zoneName);
|
||||
void createMenus() override;
|
||||
|
||||
public slots:
|
||||
void closeRequest(bool forced = false) override;
|
||||
void showPrintingSelector();
|
||||
signals:
|
||||
void openDeckEditor(const DeckLoader *deckLoader);
|
||||
void deckEditorClosing(TabDeckEditor *tab);
|
||||
void showPrintingSelector() override;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -106,6 +106,9 @@ TabDeckStorage::TabDeckStorage(TabSupervisor *_tabSupervisor,
|
||||
aOpenLocalDeck = new QAction(this);
|
||||
aOpenLocalDeck->setIcon(QPixmap("theme:icons/pencil"));
|
||||
connect(aOpenLocalDeck, SIGNAL(triggered()), this, SLOT(actOpenLocalDeck()));
|
||||
aRenameLocal = new QAction(this);
|
||||
aRenameLocal->setIcon(QPixmap("theme:icons/rename"));
|
||||
connect(aRenameLocal, &QAction::triggered, this, &TabDeckStorage::actRenameLocal);
|
||||
aUpload = new QAction(this);
|
||||
aUpload->setIcon(QPixmap("theme:icons/arrow_right_green"));
|
||||
connect(aUpload, SIGNAL(triggered()), this, SLOT(actUpload()));
|
||||
@@ -136,6 +139,7 @@ TabDeckStorage::TabDeckStorage(TabSupervisor *_tabSupervisor,
|
||||
|
||||
// Add actions to toolbars
|
||||
leftToolBar->addAction(aOpenLocalDeck);
|
||||
leftToolBar->addAction(aRenameLocal);
|
||||
leftToolBar->addAction(aUpload);
|
||||
leftToolBar->addAction(aNewLocalFolder);
|
||||
leftToolBar->addAction(aDeleteLocalDeck);
|
||||
@@ -164,6 +168,7 @@ void TabDeckStorage::retranslateUi()
|
||||
rightGroupBox->setTitle(tr("Server deck storage"));
|
||||
|
||||
aOpenLocalDeck->setText(tr("Open in deck editor"));
|
||||
aRenameLocal->setText(tr("Rename deck or folder"));
|
||||
aUpload->setText(tr("Upload deck"));
|
||||
aOpenRemoteDeck->setText(tr("Open in deck editor"));
|
||||
aDownload->setText(tr("Download deck"));
|
||||
@@ -241,12 +246,40 @@ void TabDeckStorage::actOpenLocalDeck()
|
||||
if (!deckLoader.loadFromFile(filePath, DeckLoader::CockatriceFormat, true))
|
||||
continue;
|
||||
|
||||
SettingsCache::instance().recents().updateRecentlyOpenedDeckPaths(filePath);
|
||||
|
||||
emit openDeckEditor(&deckLoader);
|
||||
}
|
||||
}
|
||||
|
||||
void TabDeckStorage::actRenameLocal()
|
||||
{
|
||||
QModelIndexList curLefts = localDirView->selectionModel()->selectedRows();
|
||||
for (const auto &curLeft : curLefts) {
|
||||
const QFileInfo info = localDirModel->fileInfo(curLeft);
|
||||
|
||||
const QString oldName = info.baseName();
|
||||
const QString title = info.isDir() ? tr("Rename local folder") : tr("Rename local file");
|
||||
|
||||
bool ok;
|
||||
QString newName = QInputDialog::getText(this, title, tr("New name:"), QLineEdit::Normal, oldName, &ok);
|
||||
if (!ok) { // terminate all remaining selections if user cancels
|
||||
return;
|
||||
}
|
||||
if (newName.isEmpty() || oldName == newName) {
|
||||
continue;
|
||||
}
|
||||
|
||||
QString newFileName = newName;
|
||||
if (!info.suffix().isEmpty()) {
|
||||
newFileName += "." + info.suffix();
|
||||
}
|
||||
const QString newFilePath = QFileInfo(info.dir(), newFileName).filePath();
|
||||
|
||||
if (!QFile::rename(info.filePath(), newFilePath)) {
|
||||
QMessageBox::critical(this, tr("Error"), tr("Rename failed"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TabDeckStorage::actUpload()
|
||||
{
|
||||
QModelIndexList curLefts = localDirView->selectionModel()->selectedRows();
|
||||
|
||||
@@ -28,7 +28,7 @@ private:
|
||||
RemoteDeckList_TreeWidget *serverDirView;
|
||||
QGroupBox *leftGroupBox, *rightGroupBox;
|
||||
|
||||
QAction *aOpenLocalDeck, *aUpload, *aNewLocalFolder, *aDeleteLocalDeck;
|
||||
QAction *aOpenLocalDeck, *aRenameLocal, *aUpload, *aNewLocalFolder, *aDeleteLocalDeck;
|
||||
QAction *aOpenDecksFolder;
|
||||
QAction *aOpenRemoteDeck, *aDownload, *aNewFolder, *aDeleteRemoteDeck;
|
||||
QString getTargetPath() const;
|
||||
@@ -47,6 +47,8 @@ private slots:
|
||||
void actLocalDoubleClick(const QModelIndex &curLeft);
|
||||
void actOpenLocalDeck();
|
||||
|
||||
void actRenameLocal();
|
||||
|
||||
void actUpload();
|
||||
void uploadFinished(const Response &r, const CommandContainer &commandContainer);
|
||||
|
||||
|
||||
@@ -119,7 +119,6 @@ TabGame::TabGame(TabSupervisor *_tabSupervisor, GameReplay *_replay)
|
||||
refreshShortcuts();
|
||||
messageLog->logReplayStarted(gameInfo.game_id());
|
||||
|
||||
this->installEventFilter(this);
|
||||
QTimer::singleShot(0, this, SLOT(loadLayout()));
|
||||
}
|
||||
|
||||
@@ -164,7 +163,6 @@ TabGame::TabGame(TabSupervisor *_tabSupervisor,
|
||||
for (int i = gameInfo.game_types_size() - 1; i >= 0; i--)
|
||||
gameTypes.append(roomGameTypes.find(gameInfo.game_types(i)).value());
|
||||
|
||||
this->installEventFilter(this);
|
||||
QTimer::singleShot(0, this, SLOT(loadLayout()));
|
||||
}
|
||||
|
||||
@@ -506,10 +504,12 @@ void TabGame::actConcede()
|
||||
bool TabGame::leaveGame()
|
||||
{
|
||||
if (!gameClosed) {
|
||||
if (!spectator)
|
||||
if (!spectator) {
|
||||
tabSupervisor->setCurrentWidget(this);
|
||||
if (QMessageBox::question(this, tr("Leave game"), tr("Are you sure you want to leave this game?"),
|
||||
QMessageBox::Yes | QMessageBox::No, QMessageBox::No) != QMessageBox::Yes)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!replay)
|
||||
sendGameCommand(Command_LeaveGame());
|
||||
@@ -753,7 +753,7 @@ void TabGame::processGameEventContainer(const GameEventContainer &cont,
|
||||
default: {
|
||||
Player *player = players.value(playerId, 0);
|
||||
if (!player) {
|
||||
qCDebug(TabGameLog) << "unhandled game event: invalid player id";
|
||||
qCWarning(TabGameLog) << "unhandled game event: invalid player id";
|
||||
break;
|
||||
}
|
||||
player->processGameEvent(eventType, event, context, options);
|
||||
@@ -836,6 +836,7 @@ void TabGame::startGame(bool _resuming)
|
||||
while (i.hasNext()) {
|
||||
i.next();
|
||||
i.value()->setReadyStart(false);
|
||||
i.value()->setVisualDeckStorageExists(false);
|
||||
i.value()->hide();
|
||||
}
|
||||
|
||||
@@ -928,8 +929,8 @@ void TabGame::eventGameStateChanged(const Event_GameStateChanged &event,
|
||||
DeckViewContainer *deckViewContainer = deckViewContainers.value(playerId);
|
||||
if (playerInfo.has_deck_list()) {
|
||||
DeckLoader newDeck(QString::fromStdString(playerInfo.deck_list()));
|
||||
PictureLoader::cacheCardPixmaps(
|
||||
CardDatabaseManager::getInstance()->getCards(newDeck.getCardList()));
|
||||
PictureLoader::cacheCardPixmaps(CardDatabaseManager::getInstance()->getCardsByNameAndProviderId(
|
||||
newDeck.getCardListWithProviderId()));
|
||||
deckViewContainer->setDeck(newDeck);
|
||||
player->setDeck(newDeck);
|
||||
}
|
||||
@@ -1750,6 +1751,27 @@ void TabGame::createMessageDock(bool bReplay)
|
||||
connect(messageLayoutDock, SIGNAL(topLevelChanged(bool)), this, SLOT(dockTopLevelChanged(bool)));
|
||||
}
|
||||
|
||||
void TabGame::hideEvent(QHideEvent *event)
|
||||
{
|
||||
LayoutsSettings &layouts = SettingsCache::instance().layouts();
|
||||
if (replay) {
|
||||
layouts.setReplayPlayAreaState(saveState());
|
||||
layouts.setReplayPlayAreaGeometry(saveGeometry());
|
||||
layouts.setReplayCardInfoSize(cardInfoDock->size());
|
||||
layouts.setReplayMessageLayoutSize(messageLayoutDock->size());
|
||||
layouts.setReplayPlayerListSize(playerListDock->size());
|
||||
layouts.setReplayReplaySize(replayDock->size());
|
||||
} else {
|
||||
layouts.setGamePlayAreaState(saveState());
|
||||
layouts.setGamePlayAreaGeometry(saveGeometry());
|
||||
layouts.setGameCardInfoSize(cardInfoDock->size());
|
||||
layouts.setGameMessageLayoutSize(messageLayoutDock->size());
|
||||
layouts.setGamePlayerListSize(playerListDock->size());
|
||||
}
|
||||
|
||||
Tab::hideEvent(event);
|
||||
}
|
||||
|
||||
// Method uses to sync docks state with menu items state
|
||||
bool TabGame::eventFilter(QObject *o, QEvent *e)
|
||||
{
|
||||
@@ -1769,23 +1791,6 @@ bool TabGame::eventFilter(QObject *o, QEvent *e)
|
||||
}
|
||||
}
|
||||
|
||||
if (o == this && e->type() == QEvent::Hide) {
|
||||
LayoutsSettings &layouts = SettingsCache::instance().layouts();
|
||||
if (replay) {
|
||||
layouts.setReplayPlayAreaState(saveState());
|
||||
layouts.setReplayPlayAreaGeometry(saveGeometry());
|
||||
layouts.setReplayCardInfoSize(cardInfoDock->size());
|
||||
layouts.setReplayMessageLayoutSize(messageLayoutDock->size());
|
||||
layouts.setReplayPlayerListSize(playerListDock->size());
|
||||
layouts.setReplayReplaySize(replayDock->size());
|
||||
} else {
|
||||
layouts.setGamePlayAreaState(saveState());
|
||||
layouts.setGamePlayAreaGeometry(saveGeometry());
|
||||
layouts.setGameCardInfoSize(cardInfoDock->size());
|
||||
layouts.setGameMessageLayoutSize(messageLayoutDock->size());
|
||||
layouts.setGamePlayerListSize(playerListDock->size());
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -208,6 +208,7 @@ private slots:
|
||||
void actResetLayout();
|
||||
void freeDocksSize();
|
||||
|
||||
void hideEvent(QHideEvent *event) override;
|
||||
bool eventFilter(QObject *o, QEvent *e) override;
|
||||
void dockVisibleTriggered();
|
||||
void dockFloatingTriggered();
|
||||
|
||||
@@ -145,7 +145,7 @@ void TabMessage::showSystemPopup(const Event_UserMessage &event)
|
||||
event.message().c_str());
|
||||
connect(trayIcon, SIGNAL(messageClicked()), this, SLOT(messageClicked()));
|
||||
} else {
|
||||
qCDebug(TabMessageLog) << "Error: trayIcon is NULL. TabMessage::showSystemPopup failed";
|
||||
qCWarning(TabMessageLog) << "Error: trayIcon is NULL. TabMessage::showSystemPopup failed";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -99,7 +99,7 @@ TabReplays::TabReplays(TabSupervisor *_tabSupervisor, AbstractClient *_client, c
|
||||
connect(aOpenLocalReplay, SIGNAL(triggered()), this, SLOT(actOpenLocalReplay()));
|
||||
connect(localDirView, SIGNAL(doubleClicked(const QModelIndex &)), this, SLOT(actOpenLocalReplay()));
|
||||
aRenameLocal = new QAction(this);
|
||||
aRenameLocal->setIcon(QPixmap("theme:icons/pencil"));
|
||||
aRenameLocal->setIcon(QPixmap("theme:icons/rename"));
|
||||
connect(aRenameLocal, &QAction::triggered, this, &TabReplays::actRenameLocal);
|
||||
aNewLocalFolder = new QAction(this);
|
||||
aNewLocalFolder->setIcon(qApp->style()->standardIcon(QStyle::SP_FileDialogNewFolder));
|
||||
|
||||
@@ -201,7 +201,7 @@ void TabSupervisor::retranslateUi()
|
||||
QListIterator<TabGame *> replayIterator(replayTabs);
|
||||
while (replayIterator.hasNext())
|
||||
tabs.append(replayIterator.next());
|
||||
QListIterator<TabDeckEditor *> deckEditorIterator(deckEditorTabs);
|
||||
QListIterator<AbstractTabDeckEditor *> deckEditorIterator(deckEditorTabs);
|
||||
while (deckEditorIterator.hasNext())
|
||||
tabs.append(deckEditorIterator.next());
|
||||
QMapIterator<QString, TabMessage *> messageIterator(messageTabs);
|
||||
@@ -232,22 +232,45 @@ void TabSupervisor::refreshShortcuts()
|
||||
aTabLog->setShortcuts(shortcuts.getShortcut("Tabs/aTabLog"));
|
||||
}
|
||||
|
||||
bool TabSupervisor::closeRequest()
|
||||
void TabSupervisor::closeEvent(QCloseEvent *event)
|
||||
{
|
||||
// This will accept the event, which we may then override.
|
||||
QTabWidget::closeEvent(event);
|
||||
|
||||
if (getGameCount()) {
|
||||
if (QMessageBox::question(this, tr("Are you sure?"),
|
||||
tr("There are still open games. Are you sure you want to quit?"),
|
||||
QMessageBox::Yes | QMessageBox::No, QMessageBox::No) == QMessageBox::No) {
|
||||
return false;
|
||||
event->ignore();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
for (TabDeckEditor *tab : deckEditorTabs) {
|
||||
if (!tab->confirmClose())
|
||||
return false;
|
||||
for (AbstractTabDeckEditor *tab : deckEditorTabs) {
|
||||
if (!tab->confirmClose()) {
|
||||
event->ignore();
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
// Close the game tabs in order to make sure they store their layout.
|
||||
QSet<int> gameTabsToRemove;
|
||||
for (auto it = gameTabs.begin(), end = gameTabs.end(); it != end; ++it) {
|
||||
if (it.value()->close()) {
|
||||
// Hotfix: the tab owns the `QMenu`s so they need to be cleared,
|
||||
// otherwise we end up with use-after-free bugs.
|
||||
if (it.value() == currentWidget()) {
|
||||
emit setMenu();
|
||||
}
|
||||
|
||||
gameTabsToRemove.insert(it.key());
|
||||
} else {
|
||||
event->ignore();
|
||||
}
|
||||
}
|
||||
|
||||
for (auto tabId : gameTabsToRemove) {
|
||||
gameTabs.remove(tabId);
|
||||
}
|
||||
}
|
||||
|
||||
AbstractClient *TabSupervisor::getClient() const
|
||||
@@ -285,9 +308,15 @@ void TabSupervisor::initStartupTabs()
|
||||
{
|
||||
addDeckEditorTab(nullptr);
|
||||
|
||||
checkAndTrigger(aTabVisualDeckStorage, SettingsCache::instance().getTabVisualDeckStorageOpen());
|
||||
checkAndTrigger(aTabDeckStorage, SettingsCache::instance().getTabDeckStorageOpen());
|
||||
checkAndTrigger(aTabReplays, SettingsCache::instance().getTabReplaysOpen());
|
||||
if (SettingsCache::instance().getTabVisualDeckStorageOpen()) {
|
||||
openTabVisualDeckStorage();
|
||||
}
|
||||
if (SettingsCache::instance().getTabDeckStorageOpen()) {
|
||||
openTabDeckStorage();
|
||||
}
|
||||
if (SettingsCache::instance().getTabReplaysOpen()) {
|
||||
openTabReplays();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -360,8 +389,12 @@ void TabSupervisor::start(const ServerInfo_User &_userInfo)
|
||||
tabsMenu->addAction(aTabServer);
|
||||
tabsMenu->addAction(aTabAccount);
|
||||
|
||||
checkAndTrigger(aTabServer, SettingsCache::instance().getTabServerOpen());
|
||||
checkAndTrigger(aTabAccount, SettingsCache::instance().getTabAccountOpen());
|
||||
if (SettingsCache::instance().getTabServerOpen()) {
|
||||
openTabServer();
|
||||
}
|
||||
if (SettingsCache::instance().getTabAccountOpen()) {
|
||||
openTabAccount();
|
||||
}
|
||||
|
||||
updatePingTime(0, -1);
|
||||
|
||||
@@ -370,8 +403,12 @@ void TabSupervisor::start(const ServerInfo_User &_userInfo)
|
||||
tabsMenu->addAction(aTabAdmin);
|
||||
tabsMenu->addAction(aTabLog);
|
||||
|
||||
checkAndTrigger(aTabAdmin, SettingsCache::instance().getTabAdminOpen());
|
||||
checkAndTrigger(aTabLog, SettingsCache::instance().getTabLogOpen());
|
||||
if (SettingsCache::instance().getTabAdminOpen()) {
|
||||
openTabAdmin();
|
||||
}
|
||||
if (SettingsCache::instance().getTabLogOpen()) {
|
||||
openTabLog();
|
||||
}
|
||||
}
|
||||
|
||||
retranslateUi();
|
||||
@@ -453,114 +490,163 @@ void TabSupervisor::actTabVisualDeckStorage(bool checked)
|
||||
{
|
||||
SettingsCache::instance().setTabVisualDeckStorageOpen(checked);
|
||||
if (checked && !tabVisualDeckStorage) {
|
||||
tabVisualDeckStorage = new TabDeckStorageVisual(this);
|
||||
myAddTab(tabVisualDeckStorage, aTabVisualDeckStorage);
|
||||
connect(tabVisualDeckStorage, &Tab::closed, this, [this] {
|
||||
tabVisualDeckStorage = nullptr;
|
||||
aTabVisualDeckStorage->setChecked(false);
|
||||
});
|
||||
openTabVisualDeckStorage();
|
||||
setCurrentWidget(tabVisualDeckStorage);
|
||||
} else if (!checked && tabVisualDeckStorage) {
|
||||
tabVisualDeckStorage->closeRequest();
|
||||
}
|
||||
}
|
||||
|
||||
void TabSupervisor::openTabVisualDeckStorage()
|
||||
{
|
||||
tabVisualDeckStorage = new TabDeckStorageVisual(this);
|
||||
myAddTab(tabVisualDeckStorage, aTabVisualDeckStorage);
|
||||
connect(tabVisualDeckStorage, &Tab::closed, this, [this] {
|
||||
tabVisualDeckStorage = nullptr;
|
||||
aTabVisualDeckStorage->setChecked(false);
|
||||
});
|
||||
aTabVisualDeckStorage->setChecked(true);
|
||||
}
|
||||
|
||||
void TabSupervisor::actTabServer(bool checked)
|
||||
{
|
||||
SettingsCache::instance().setTabServerOpen(checked);
|
||||
if (checked && !tabServer) {
|
||||
tabServer = new TabServer(this, client);
|
||||
connect(tabServer, &TabServer::roomJoined, this, &TabSupervisor::addRoomTab);
|
||||
myAddTab(tabServer, aTabServer);
|
||||
connect(tabServer, &Tab::closed, this, [this] {
|
||||
tabServer = nullptr;
|
||||
aTabServer->setChecked(false);
|
||||
});
|
||||
openTabServer();
|
||||
setCurrentWidget(tabServer);
|
||||
} else if (!checked && tabServer) {
|
||||
tabServer->closeRequest();
|
||||
}
|
||||
}
|
||||
|
||||
void TabSupervisor::openTabServer()
|
||||
{
|
||||
tabServer = new TabServer(this, client);
|
||||
connect(tabServer, &TabServer::roomJoined, this, &TabSupervisor::addRoomTab);
|
||||
myAddTab(tabServer, aTabServer);
|
||||
connect(tabServer, &Tab::closed, this, [this] {
|
||||
tabServer = nullptr;
|
||||
aTabServer->setChecked(false);
|
||||
});
|
||||
aTabServer->setChecked(true);
|
||||
}
|
||||
|
||||
void TabSupervisor::actTabAccount(bool checked)
|
||||
{
|
||||
SettingsCache::instance().setTabAccountOpen(checked);
|
||||
if (checked && !tabAccount) {
|
||||
tabAccount = new TabAccount(this, client, *userInfo);
|
||||
connect(tabAccount, &TabAccount::openMessageDialog, this, &TabSupervisor::addMessageTab);
|
||||
connect(tabAccount, &TabAccount::userJoined, this, &TabSupervisor::processUserJoined);
|
||||
connect(tabAccount, &TabAccount::userLeft, this, &TabSupervisor::processUserLeft);
|
||||
myAddTab(tabAccount, aTabAccount);
|
||||
connect(tabAccount, &Tab::closed, this, [this] {
|
||||
tabAccount = nullptr;
|
||||
aTabAccount->setChecked(false);
|
||||
});
|
||||
openTabAccount();
|
||||
setCurrentWidget(tabAccount);
|
||||
} else if (!checked && tabAccount) {
|
||||
tabAccount->closeRequest();
|
||||
}
|
||||
}
|
||||
|
||||
void TabSupervisor::openTabAccount()
|
||||
{
|
||||
tabAccount = new TabAccount(this, client, *userInfo);
|
||||
connect(tabAccount, &TabAccount::openMessageDialog, this, &TabSupervisor::addMessageTab);
|
||||
connect(tabAccount, &TabAccount::userJoined, this, &TabSupervisor::processUserJoined);
|
||||
connect(tabAccount, &TabAccount::userLeft, this, &TabSupervisor::processUserLeft);
|
||||
myAddTab(tabAccount, aTabAccount);
|
||||
connect(tabAccount, &Tab::closed, this, [this] {
|
||||
tabAccount = nullptr;
|
||||
aTabAccount->setChecked(false);
|
||||
});
|
||||
aTabAccount->setChecked(true);
|
||||
}
|
||||
|
||||
void TabSupervisor::actTabDeckStorage(bool checked)
|
||||
{
|
||||
SettingsCache::instance().setTabDeckStorageOpen(checked);
|
||||
if (checked && !tabDeckStorage) {
|
||||
tabDeckStorage = new TabDeckStorage(this, client, userInfo);
|
||||
connect(tabDeckStorage, &TabDeckStorage::openDeckEditor, this, &TabSupervisor::addDeckEditorTab);
|
||||
myAddTab(tabDeckStorage, aTabDeckStorage);
|
||||
connect(tabDeckStorage, &Tab::closed, this, [this] {
|
||||
tabDeckStorage = nullptr;
|
||||
aTabDeckStorage->setChecked(false);
|
||||
});
|
||||
openTabDeckStorage();
|
||||
setCurrentWidget(tabDeckStorage);
|
||||
} else if (!checked && tabDeckStorage) {
|
||||
tabDeckStorage->closeRequest();
|
||||
}
|
||||
}
|
||||
|
||||
void TabSupervisor::openTabDeckStorage()
|
||||
{
|
||||
tabDeckStorage = new TabDeckStorage(this, client, userInfo);
|
||||
connect(tabDeckStorage, &TabDeckStorage::openDeckEditor, this, &TabSupervisor::addDeckEditorTab);
|
||||
myAddTab(tabDeckStorage, aTabDeckStorage);
|
||||
connect(tabDeckStorage, &Tab::closed, this, [this] {
|
||||
tabDeckStorage = nullptr;
|
||||
aTabDeckStorage->setChecked(false);
|
||||
});
|
||||
aTabDeckStorage->setChecked(true);
|
||||
}
|
||||
|
||||
void TabSupervisor::actTabReplays(bool checked)
|
||||
{
|
||||
SettingsCache::instance().setTabReplaysOpen(checked);
|
||||
if (checked && !tabReplays) {
|
||||
tabReplays = new TabReplays(this, client, userInfo);
|
||||
connect(tabReplays, &TabReplays::openReplay, this, &TabSupervisor::openReplay);
|
||||
myAddTab(tabReplays, aTabReplays);
|
||||
connect(tabReplays, &Tab::closed, this, [this] {
|
||||
tabReplays = nullptr;
|
||||
aTabReplays->setChecked(false);
|
||||
});
|
||||
openTabReplays();
|
||||
setCurrentWidget(tabReplays);
|
||||
} else if (!checked && tabReplays) {
|
||||
tabReplays->closeRequest();
|
||||
}
|
||||
}
|
||||
|
||||
void TabSupervisor::openTabReplays()
|
||||
{
|
||||
tabReplays = new TabReplays(this, client, userInfo);
|
||||
connect(tabReplays, &TabReplays::openReplay, this, &TabSupervisor::openReplay);
|
||||
myAddTab(tabReplays, aTabReplays);
|
||||
connect(tabReplays, &Tab::closed, this, [this] {
|
||||
tabReplays = nullptr;
|
||||
aTabReplays->setChecked(false);
|
||||
});
|
||||
aTabReplays->setChecked(true);
|
||||
}
|
||||
|
||||
void TabSupervisor::actTabAdmin(bool checked)
|
||||
{
|
||||
SettingsCache::instance().setTabAdminOpen(checked);
|
||||
if (checked && !tabAdmin) {
|
||||
tabAdmin = new TabAdmin(this, client, (userInfo->user_level() & ServerInfo_User::IsAdmin));
|
||||
connect(tabAdmin, &TabAdmin::adminLockChanged, this, &TabSupervisor::adminLockChanged);
|
||||
myAddTab(tabAdmin, aTabAdmin);
|
||||
connect(tabAdmin, &Tab::closed, this, [this] {
|
||||
tabAdmin = nullptr;
|
||||
aTabAdmin->setChecked(false);
|
||||
});
|
||||
openTabAdmin();
|
||||
setCurrentWidget(tabAdmin);
|
||||
} else if (!checked && tabAdmin) {
|
||||
tabAdmin->closeRequest();
|
||||
}
|
||||
}
|
||||
|
||||
void TabSupervisor::openTabAdmin()
|
||||
{
|
||||
tabAdmin = new TabAdmin(this, client, (userInfo->user_level() & ServerInfo_User::IsAdmin));
|
||||
connect(tabAdmin, &TabAdmin::adminLockChanged, this, &TabSupervisor::adminLockChanged);
|
||||
myAddTab(tabAdmin, aTabAdmin);
|
||||
connect(tabAdmin, &Tab::closed, this, [this] {
|
||||
tabAdmin = nullptr;
|
||||
aTabAdmin->setChecked(false);
|
||||
});
|
||||
aTabAdmin->setChecked(true);
|
||||
}
|
||||
|
||||
void TabSupervisor::actTabLog(bool checked)
|
||||
{
|
||||
SettingsCache::instance().setTabLogOpen(checked);
|
||||
if (checked && !tabLog) {
|
||||
tabLog = new TabLog(this, client);
|
||||
myAddTab(tabLog, aTabLog);
|
||||
connect(tabLog, &Tab::closed, this, [this] {
|
||||
tabLog = nullptr;
|
||||
aTabAdmin->setChecked(false);
|
||||
});
|
||||
openTabLog();
|
||||
setCurrentWidget(tabLog);
|
||||
} else if (!checked && tabLog) {
|
||||
tabLog->closeRequest();
|
||||
}
|
||||
}
|
||||
|
||||
void TabSupervisor::openTabLog()
|
||||
{
|
||||
tabLog = new TabLog(this, client);
|
||||
myAddTab(tabLog, aTabLog);
|
||||
connect(tabLog, &Tab::closed, this, [this] {
|
||||
tabLog = nullptr;
|
||||
aTabAdmin->setChecked(false);
|
||||
});
|
||||
aTabAdmin->setChecked(true);
|
||||
}
|
||||
|
||||
void TabSupervisor::updatePingTime(int value, int max)
|
||||
{
|
||||
if (!tabServer)
|
||||
@@ -701,13 +787,17 @@ void TabSupervisor::talkLeft(TabMessage *tab)
|
||||
removeTab(indexOf(tab));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new deck editor tab
|
||||
* @param deckToOpen The deck to open in the tab. Creates a copy of the DeckLoader instance.
|
||||
*/
|
||||
TabDeckEditor *TabSupervisor::addDeckEditorTab(const DeckLoader *deckToOpen)
|
||||
{
|
||||
auto *tab = new TabDeckEditor(this);
|
||||
if (deckToOpen)
|
||||
tab->setDeck(new DeckLoader(*deckToOpen));
|
||||
connect(tab, &TabDeckEditor::deckEditorClosing, this, &TabSupervisor::deckEditorClosed);
|
||||
connect(tab, &TabDeckEditor::openDeckEditor, this, &TabSupervisor::addDeckEditorTab);
|
||||
tab->openDeck(new DeckLoader(*deckToOpen));
|
||||
connect(tab, &AbstractTabDeckEditor::deckEditorClosing, this, &TabSupervisor::deckEditorClosed);
|
||||
connect(tab, &AbstractTabDeckEditor::openDeckEditor, this, &TabSupervisor::addDeckEditorTab);
|
||||
myAddTab(tab);
|
||||
deckEditorTabs.append(tab);
|
||||
setCurrentWidget(tab);
|
||||
@@ -726,7 +816,7 @@ TabEdhRec *TabSupervisor::addEdhrecTab(const CardInfoPtr &cardToQuery, bool isCo
|
||||
return tab;
|
||||
}
|
||||
|
||||
void TabSupervisor::deckEditorClosed(TabDeckEditor *tab)
|
||||
void TabSupervisor::deckEditorClosed(AbstractTabDeckEditor *tab)
|
||||
{
|
||||
if (tab == currentWidget())
|
||||
emit setMenu();
|
||||
@@ -766,7 +856,7 @@ void TabSupervisor::processGameEventContainer(const GameEventContainer &cont)
|
||||
if (tab)
|
||||
tab->processGameEventContainer(cont, qobject_cast<AbstractClient *>(sender()), {});
|
||||
else
|
||||
qCDebug(TabSupervisorLog) << "gameEvent: invalid gameId";
|
||||
qCInfo(TabSupervisorLog) << "gameEvent: invalid gameId" << cont.game_id();
|
||||
}
|
||||
|
||||
void TabSupervisor::processUserMessageEvent(const Event_UserMessage &event)
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
#include "../../deck/deck_loader.h"
|
||||
#include "../../server/user/user_list_proxy.h"
|
||||
#include "abstract_tab_deck_editor.h"
|
||||
#include "api/edhrec/tab_edhrec.h"
|
||||
#include "visual_deck_storage/tab_deck_storage_visual.h"
|
||||
|
||||
@@ -87,7 +88,7 @@ private:
|
||||
QMap<int, TabGame *> gameTabs;
|
||||
QList<TabGame *> replayTabs;
|
||||
QMap<QString, TabMessage *> messageTabs;
|
||||
QList<TabDeckEditor *> deckEditorTabs;
|
||||
QList<AbstractTabDeckEditor *> deckEditorTabs;
|
||||
bool isLocalGame;
|
||||
|
||||
QAction *aTabDeckEditor, *aTabVisualDeckStorage, *aTabServer, *aTabAccount, *aTabDeckStorage, *aTabReplays,
|
||||
@@ -132,12 +133,12 @@ public:
|
||||
{
|
||||
return roomTabs;
|
||||
}
|
||||
QList<TabDeckEditor *> getDeckEditorTabs() const
|
||||
QList<AbstractTabDeckEditor *> getDeckEditorTabs() const
|
||||
{
|
||||
return deckEditorTabs;
|
||||
}
|
||||
bool getAdminLocked() const;
|
||||
bool closeRequest();
|
||||
void closeEvent(QCloseEvent *event) override;
|
||||
bool switchToGameTabIfAlreadyExists(const int gameId);
|
||||
static void actShowPopup(const QString &message);
|
||||
signals:
|
||||
@@ -162,6 +163,14 @@ private slots:
|
||||
void actTabAdmin(bool checked);
|
||||
void actTabLog(bool checked);
|
||||
|
||||
void openTabVisualDeckStorage();
|
||||
void openTabServer();
|
||||
void openTabAccount();
|
||||
void openTabDeckStorage();
|
||||
void openTabReplays();
|
||||
void openTabAdmin();
|
||||
void openTabLog();
|
||||
|
||||
void updateCurrent(int index);
|
||||
void updatePingTime(int value, int max);
|
||||
void gameJoined(const Event_GameJoined &event);
|
||||
@@ -174,7 +183,7 @@ private slots:
|
||||
void processUserLeft(const QString &userName);
|
||||
void processUserJoined(const ServerInfo_User &userInfo);
|
||||
void talkLeft(TabMessage *tab);
|
||||
void deckEditorClosed(TabDeckEditor *tab);
|
||||
void deckEditorClosed(AbstractTabDeckEditor *tab);
|
||||
void tabUserEvent(bool globalEvent);
|
||||
void updateTabText(Tab *tab, const QString &newTabText);
|
||||
void processRoomEvent(const RoomEvent &event);
|
||||
@@ -183,4 +192,4 @@ private slots:
|
||||
void processNotifyUserEvent(const Event_NotifyUser &event);
|
||||
};
|
||||
|
||||
#endif
|
||||
#endif
|
||||
|
||||
@@ -6,14 +6,17 @@
|
||||
#include "../tab_supervisor.h"
|
||||
#include "pb/command_deck_del.pb.h"
|
||||
|
||||
#include <QMessageBox>
|
||||
#include <QMouseEvent>
|
||||
|
||||
TabDeckStorageVisual::TabDeckStorageVisual(TabSupervisor *_tabSupervisor)
|
||||
: Tab(_tabSupervisor), visualDeckStorageWidget(new VisualDeckStorageWidget(this))
|
||||
{
|
||||
connect(this, &TabDeckStorageVisual::openDeckEditor, tabSupervisor, &TabSupervisor::addDeckEditorTab);
|
||||
connect(visualDeckStorageWidget, &VisualDeckStorageWidget::deckPreviewDoubleClicked, this,
|
||||
connect(visualDeckStorageWidget, &VisualDeckStorageWidget::deckLoadRequested, this,
|
||||
&TabDeckStorageVisual::actOpenLocalDeck);
|
||||
connect(visualDeckStorageWidget, &VisualDeckStorageWidget::openDeckEditor, this,
|
||||
&TabDeckStorageVisual::openDeckEditor);
|
||||
|
||||
auto *widget = new QWidget(this);
|
||||
auto *layout = new QVBoxLayout(widget);
|
||||
@@ -22,10 +25,11 @@ TabDeckStorageVisual::TabDeckStorageVisual(TabSupervisor *_tabSupervisor)
|
||||
layout->addWidget(visualDeckStorageWidget);
|
||||
}
|
||||
|
||||
void TabDeckStorageVisual::actOpenLocalDeck(QMouseEvent * /*event*/, DeckPreviewWidget *instance)
|
||||
void TabDeckStorageVisual::actOpenLocalDeck(const QString &filePath)
|
||||
{
|
||||
DeckLoader deckLoader;
|
||||
if (!deckLoader.loadFromFile(instance->filePath, DeckLoader::getFormatFromName(instance->filePath), true)) {
|
||||
if (!deckLoader.loadFromFile(filePath, DeckLoader::getFormatFromName(filePath), true)) {
|
||||
QMessageBox::critical(this, tr("Error"), tr("Could not open deck at %1").arg(filePath));
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ public:
|
||||
}
|
||||
|
||||
public slots:
|
||||
void actOpenLocalDeck(QMouseEvent * /*event*/, DeckPreviewWidget *instance);
|
||||
void actOpenLocalDeck(const QString &filePath);
|
||||
|
||||
signals:
|
||||
void openDeckEditor(const DeckLoader *deckLoader);
|
||||
|
||||
@@ -33,7 +33,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 +57,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);
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
#include <QWidget>
|
||||
#include <qstyle.h>
|
||||
|
||||
inline Q_LOGGING_CATEGORY(FlowLayoutLog, "flow_layout");
|
||||
inline Q_LOGGING_CATEGORY(FlowLayoutLog, "flow_layout", QtInfoMsg);
|
||||
|
||||
class FlowLayout : public QLayout
|
||||
{
|
||||
|
||||
@@ -37,51 +37,92 @@ PictureLoader::~PictureLoader()
|
||||
|
||||
void PictureLoader::getCardBackPixmap(QPixmap &pixmap, QSize size)
|
||||
{
|
||||
QString backCacheKey = "_trice_card_back_" + QString::number(size.width()) + QString::number(size.height());
|
||||
QString backCacheKey = "_trice_card_back_" + QString::number(size.width()) + "x" + QString::number(size.height());
|
||||
if (!QPixmapCache::find(backCacheKey, &pixmap)) {
|
||||
qCDebug(PictureLoaderLog) << "PictureLoader: cache fail for" << backCacheKey;
|
||||
pixmap = QPixmap("theme:cardback").scaled(size, Qt::KeepAspectRatio, Qt::SmoothTransformation);
|
||||
qCDebug(PictureLoaderLog) << "PictureLoader: cache miss for" << backCacheKey;
|
||||
QPixmap tmpPixmap("theme:cardback");
|
||||
|
||||
if (tmpPixmap.isNull()) {
|
||||
qCWarning(PictureLoaderLog) << "Failed to load 'theme:cardback'! Using fallback pixmap.";
|
||||
tmpPixmap = QPixmap(size);
|
||||
tmpPixmap.fill(Qt::gray); // Fallback to a gray pixmap
|
||||
} else {
|
||||
qCDebug(PictureLoaderLog) << "Successfully loaded 'theme:cardback'.";
|
||||
}
|
||||
|
||||
pixmap = tmpPixmap.scaled(size, Qt::KeepAspectRatio, Qt::SmoothTransformation);
|
||||
QPixmapCache::insert(backCacheKey, pixmap);
|
||||
}
|
||||
}
|
||||
|
||||
void PictureLoader::getCardBackLoadingInProgressPixmap(QPixmap &pixmap, QSize size)
|
||||
{
|
||||
QString backCacheKey = "_trice_card_back_" + QString::number(size.width()) + QString::number(size.height());
|
||||
QString backCacheKey =
|
||||
"_trice_card_back_inprogress_" + QString::number(size.width()) + "x" + QString::number(size.height());
|
||||
if (!QPixmapCache::find(backCacheKey, &pixmap)) {
|
||||
qCDebug(PictureLoaderCardBackCacheFailLog) << "PictureLoader: cache fail for" << backCacheKey;
|
||||
pixmap = QPixmap("theme:cardback").scaled(size, Qt::KeepAspectRatio, Qt::SmoothTransformation);
|
||||
qCDebug(PictureLoaderCardBackCacheFailLog) << "PictureLoader: cache miss for" << backCacheKey;
|
||||
QPixmap tmpPixmap("theme:cardback");
|
||||
|
||||
if (tmpPixmap.isNull()) {
|
||||
qCWarning(PictureLoaderLog) << "Failed to load 'theme:cardback' for in-progress state! Using fallback.";
|
||||
tmpPixmap = QPixmap(size);
|
||||
tmpPixmap.fill(Qt::blue); // Fallback with blue color
|
||||
} else {
|
||||
qCDebug(PictureLoaderCardBackCacheFailLog) << "Successfully loaded 'theme:cardback' for in-progress state.";
|
||||
}
|
||||
|
||||
pixmap = tmpPixmap.scaled(size, Qt::KeepAspectRatio, Qt::SmoothTransformation);
|
||||
QPixmapCache::insert(backCacheKey, pixmap);
|
||||
}
|
||||
}
|
||||
|
||||
void PictureLoader::getCardBackLoadingFailedPixmap(QPixmap &pixmap, QSize size)
|
||||
{
|
||||
QString backCacheKey = "_trice_card_back_" + QString::number(size.width()) + QString::number(size.height());
|
||||
QString backCacheKey =
|
||||
"_trice_card_back_failed_" + QString::number(size.width()) + "x" + QString::number(size.height());
|
||||
if (!QPixmapCache::find(backCacheKey, &pixmap)) {
|
||||
qCDebug(PictureLoaderCardBackCacheFailLog) << "PictureLoader: cache fail for" << backCacheKey;
|
||||
pixmap = QPixmap("theme:cardback").scaled(size, Qt::KeepAspectRatio, Qt::SmoothTransformation);
|
||||
qCDebug(PictureLoaderCardBackCacheFailLog) << "PictureLoader: cache miss for" << backCacheKey;
|
||||
QPixmap tmpPixmap("theme:cardback");
|
||||
|
||||
if (tmpPixmap.isNull()) {
|
||||
qCWarning(PictureLoaderLog) << "Failed to load 'theme:cardback' for failed state! Using fallback.";
|
||||
tmpPixmap = QPixmap(size);
|
||||
tmpPixmap.fill(Qt::red); // Fallback with red color
|
||||
} else {
|
||||
qCDebug(PictureLoaderCardBackCacheFailLog) << "Successfully loaded 'theme:cardback' for failed state.";
|
||||
}
|
||||
|
||||
pixmap = tmpPixmap.scaled(size, Qt::KeepAspectRatio, Qt::SmoothTransformation);
|
||||
QPixmapCache::insert(backCacheKey, pixmap);
|
||||
}
|
||||
}
|
||||
|
||||
void PictureLoader::getPixmap(QPixmap &pixmap, CardInfoPtr card, QSize size)
|
||||
{
|
||||
if (card == nullptr) {
|
||||
if (!card) {
|
||||
qCWarning(PictureLoaderLog) << "getPixmap called with null card!";
|
||||
return;
|
||||
}
|
||||
|
||||
// search for an exact size copy of the picture in cache
|
||||
QString key = card->getPixmapCacheKey();
|
||||
QString sizeKey = key + QLatin1Char('_') + QString::number(size.width()) + QString::number(size.height());
|
||||
if (QPixmapCache::find(sizeKey, &pixmap))
|
||||
return;
|
||||
QString sizeKey = key + QLatin1Char('_') + QString::number(size.width()) + "x" + QString::number(size.height());
|
||||
|
||||
if (QPixmapCache::find(sizeKey, &pixmap)) {
|
||||
return; // Use cached version
|
||||
}
|
||||
|
||||
// load the image and create a copy of the correct size
|
||||
QPixmap bigPixmap;
|
||||
if (QPixmapCache::find(key, &bigPixmap)) {
|
||||
if (bigPixmap.isNull()) {
|
||||
qCDebug(PictureLoaderLog) << "Cached pixmap for key" << key << "is NULL!";
|
||||
return;
|
||||
}
|
||||
|
||||
QScreen *screen = qApp->primaryScreen();
|
||||
qreal dpr = screen->devicePixelRatio();
|
||||
qreal dpr = screen ? screen->devicePixelRatio() : 1.0;
|
||||
qCDebug(PictureLoaderLog) << "Scaling cached image for" << card->getName();
|
||||
|
||||
pixmap = bigPixmap.scaled(size * dpr, Qt::KeepAspectRatio, Qt::SmoothTransformation);
|
||||
pixmap.setDevicePixelRatio(dpr);
|
||||
QPixmapCache::insert(sizeKey, pixmap);
|
||||
@@ -154,3 +195,31 @@ void PictureLoader::picsPathChanged()
|
||||
{
|
||||
QPixmapCache::clear();
|
||||
}
|
||||
|
||||
bool PictureLoader::hasCustomArt()
|
||||
{
|
||||
auto picsPath = SettingsCache::instance().getPicsPath();
|
||||
QDirIterator it(picsPath, QDir::Dirs | QDir::NoDotAndDotDot);
|
||||
|
||||
// Check if there is at least one non-directory file in the pics path, other
|
||||
// than in the "downloadedPics" subdirectory.
|
||||
while (it.hasNext()) {
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(6, 3, 0))
|
||||
QFileInfo dir(it.nextFileInfo());
|
||||
#else
|
||||
// nextFileInfo() is only available in Qt 6.3+, for previous versions, we build
|
||||
// the QFileInfo from a QString which requires more system calls.
|
||||
QFileInfo dir(it.next());
|
||||
#endif
|
||||
|
||||
if (it.fileName() == "downloadedPics")
|
||||
continue;
|
||||
|
||||
QDirIterator subIt(it.filePath(), QDir::Files, QDirIterator::Subdirectories | QDirIterator::FollowSymlinks);
|
||||
if (subIt.hasNext()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -36,6 +36,7 @@ public:
|
||||
static void clearPixmapCache(CardInfoPtr card);
|
||||
static void clearPixmapCache();
|
||||
static void cacheCardPixmaps(QList<CardInfoPtr> cards);
|
||||
static bool hasCustomArt();
|
||||
|
||||
public slots:
|
||||
static void clearNetworkCache();
|
||||
|
||||
@@ -16,11 +16,14 @@ QStringList PictureLoaderWorker::md5Blacklist = QStringList() << "db0c48db407a90
|
||||
PictureLoaderWorker::PictureLoaderWorker()
|
||||
: QObject(nullptr), picsPath(SettingsCache::instance().getPicsPath()),
|
||||
customPicsPath(SettingsCache::instance().getCustomPicsPath()),
|
||||
picDownload(SettingsCache::instance().getPicDownload()), downloadRunning(false), loadQueueRunning(false)
|
||||
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
|
||||
@@ -83,11 +86,24 @@ void PictureLoaderWorker::processLoadQueue()
|
||||
qCDebug(PictureLoaderWorkerLog).nospace()
|
||||
<< "[card: " << cardName << " set: " << setName << "]: Trying to load picture";
|
||||
|
||||
if (CardDatabaseManager::getInstance()->isProviderIdForPreferredPrinting(
|
||||
cardName, cardBeingLoaded.getCard()->getPixmapCacheKey())) {
|
||||
if (cardImageExistsOnDisk(setName, correctedCardName)) {
|
||||
continue;
|
||||
}
|
||||
// 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()
|
||||
@@ -100,23 +116,26 @@ void PictureLoaderWorker::processLoadQueue()
|
||||
}
|
||||
}
|
||||
|
||||
bool PictureLoaderWorker::cardImageExistsOnDisk(QString &setName, QString &correctedCardname)
|
||||
bool PictureLoaderWorker::cardImageExistsOnDisk(QString &setName, QString &correctedCardname, bool searchCustomPics)
|
||||
{
|
||||
QImage image;
|
||||
QImageReader imgReader;
|
||||
imgReader.setDecideFormatFromContent(true);
|
||||
QList<QString> picsPaths = QList<QString>();
|
||||
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 (searchCustomPics) {
|
||||
QDirIterator it(customPicsPath, QDirIterator::Subdirectories | QDirIterator::FollowSymlinks);
|
||||
|
||||
if (thisFileInfo.isFile() &&
|
||||
(thisFileInfo.fileName() == correctedCardname || thisFileInfo.completeBaseName() == correctedCardname ||
|
||||
thisFileInfo.baseName() == correctedCardname)) {
|
||||
picsPaths << thisPath; // Card found in the CUSTOM directory, somewhere
|
||||
// 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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -194,7 +213,7 @@ void PictureLoaderWorker::picDownloadFailed()
|
||||
loadQueue.prepend(cardBeingDownloaded);
|
||||
mutex.unlock();
|
||||
} else {
|
||||
qCDebug(PictureLoaderWorkerLog).nospace()
|
||||
qCWarning(PictureLoaderWorkerLog).nospace()
|
||||
<< "[card: " << cardBeingDownloaded.getCard()->getCorrectedName()
|
||||
<< " set: " << cardBeingDownloaded.getSetName() << "]: Picture NOT found, "
|
||||
<< (picDownload ? "download failed" : "downloads disabled")
|
||||
@@ -405,7 +424,7 @@ void PictureLoaderWorker::picDownloadFinished(QNetworkReply *reply)
|
||||
}
|
||||
|
||||
if (logSuccessMessage) {
|
||||
qCDebug(PictureLoaderWorkerLog).nospace()
|
||||
qCInfo(PictureLoaderWorkerLog).nospace()
|
||||
<< "[card: " << cardBeingDownloaded.getCard()->getName() << " set: " << cardBeingDownloaded.getSetName()
|
||||
<< "]: Image successfully " << (isFromCache ? "loaded from cached" : "downloaded from") << " url "
|
||||
<< reply->url().toDisplayString();
|
||||
@@ -451,7 +470,12 @@ void PictureLoaderWorker::picsPathChanged()
|
||||
customPicsPath = SettingsCache::instance().getCustomPicsPath();
|
||||
}
|
||||
|
||||
void PictureLoaderWorker::setOverrideAllCardArtWithPersonalPreference(bool _overrideAllCardArtWithPersonalPreference)
|
||||
{
|
||||
overrideAllCardArtWithPersonalPreference = _overrideAllCardArtWithPersonalPreference;
|
||||
}
|
||||
|
||||
void PictureLoaderWorker::clearNetworkCache()
|
||||
{
|
||||
networkManager->cache()->clear();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,8 +42,16 @@ private:
|
||||
PictureToLoad cardBeingLoaded;
|
||||
PictureToLoad cardBeingDownloaded;
|
||||
bool picDownload, downloadRunning, loadQueueRunning;
|
||||
bool overrideAllCardArtWithPersonalPreference;
|
||||
void startNextPicDownload();
|
||||
bool cardImageExistsOnDisk(QString &setName, QString &correctedCardName);
|
||||
|
||||
/** 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);
|
||||
@@ -58,6 +66,7 @@ private slots:
|
||||
|
||||
void picDownloadChanged();
|
||||
void picsPathChanged();
|
||||
void setOverrideAllCardArtWithPersonalPreference(bool _overrideAllCardArtWithPersonalPreference);
|
||||
public slots:
|
||||
void processLoadQueue();
|
||||
|
||||
|
||||
@@ -180,6 +180,12 @@ QMap<QString, QPixmap> CountryPixmapGenerator::pmCache;
|
||||
* @param idName id that the tag has to match
|
||||
* @param attrValue the value to update the attribute to
|
||||
*/
|
||||
static void setAttrRecur(QDomElement &elem,
|
||||
const QString &tagName,
|
||||
const QString &attrName,
|
||||
const QString &idName,
|
||||
const QString &attrValue);
|
||||
|
||||
void setAttrRecur(QDomElement &elem,
|
||||
const QString &tagName,
|
||||
const QString &attrName,
|
||||
@@ -357,6 +363,21 @@ QPixmap LockPixmapGenerator::generatePixmap(int height)
|
||||
|
||||
QMap<int, QPixmap> LockPixmapGenerator::pmCache;
|
||||
|
||||
QPixmap DropdownIconPixmapGenerator::generatePixmap(int height, bool expanded)
|
||||
{
|
||||
QString key = QString::number(expanded) + ":" + QString::number(height);
|
||||
if (pmCache.contains(key))
|
||||
return pmCache.value(key);
|
||||
|
||||
QString name = expanded ? "dropdown_expanded" : "dropdown_collapsed";
|
||||
QPixmap pixmap = tryLoadImage("theme:icons/" + name, QSize(height, height), true);
|
||||
|
||||
pmCache.insert(key, pixmap);
|
||||
return pixmap;
|
||||
}
|
||||
|
||||
QMap<QString, QPixmap> DropdownIconPixmapGenerator::pmCache;
|
||||
|
||||
QPixmap loadColorAdjustedPixmap(const QString &name)
|
||||
{
|
||||
if (qApp->palette().windowText().color().lightness() > 200) {
|
||||
|
||||
@@ -106,6 +106,19 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
class DropdownIconPixmapGenerator
|
||||
{
|
||||
private:
|
||||
static QMap<QString, QPixmap> pmCache;
|
||||
|
||||
public:
|
||||
static QPixmap generatePixmap(int height, bool expanded);
|
||||
static void clear()
|
||||
{
|
||||
pmCache.clear();
|
||||
}
|
||||
};
|
||||
|
||||
QPixmap loadColorAdjustedPixmap(const QString &name);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -32,7 +32,7 @@ void ThemeManager::ensureThemeDirectoryExists()
|
||||
{
|
||||
if (SettingsCache::instance().getThemeName().isEmpty() ||
|
||||
!getAvailableThemes().contains(SettingsCache::instance().getThemeName())) {
|
||||
qCDebug(ThemeManagerLog) << "Theme name not set, setting default value";
|
||||
qCInfo(ThemeManagerLog) << "Theme name not set, setting default value";
|
||||
SettingsCache::instance().setThemeName(NONE_THEME_NAME);
|
||||
}
|
||||
}
|
||||
@@ -105,7 +105,7 @@ QBrush ThemeManager::loadExtraBrush(QString fileName, QBrush &fallbackBrush)
|
||||
void ThemeManager::themeChangedSlot()
|
||||
{
|
||||
QString themeName = SettingsCache::instance().getThemeName();
|
||||
qCDebug(ThemeManagerLog) << "Theme changed:" << themeName;
|
||||
qCInfo(ThemeManagerLog) << "Theme changed:" << themeName;
|
||||
|
||||
QString dirPath = getAvailableThemes().value(themeName);
|
||||
QDir dir = dirPath;
|
||||
@@ -120,10 +120,10 @@ void ThemeManager::themeChangedSlot()
|
||||
if (dirPath.isEmpty()) {
|
||||
// set default values
|
||||
QDir::setSearchPaths("theme", DEFAULT_RESOURCE_PATHS);
|
||||
handBgBrush = HANDZONE_BG_DEFAULT;
|
||||
tableBgBrush = TABLEZONE_BG_DEFAULT;
|
||||
playerBgBrush = PLAYERZONE_BG_DEFAULT;
|
||||
stackBgBrush = STACKZONE_BG_DEFAULT;
|
||||
brushes[Role::Hand] = HANDZONE_BG_DEFAULT;
|
||||
brushes[Role::Table] = TABLEZONE_BG_DEFAULT;
|
||||
brushes[Role::Player] = PLAYERZONE_BG_DEFAULT;
|
||||
brushes[Role::Stack] = STACKZONE_BG_DEFAULT;
|
||||
} else {
|
||||
// resources
|
||||
QStringList resources;
|
||||
@@ -132,73 +132,58 @@ void ThemeManager::themeChangedSlot()
|
||||
|
||||
// zones bg
|
||||
dir.cd("zones");
|
||||
handBgBrush = loadBrush(HANDZONE_BG_NAME, HANDZONE_BG_DEFAULT);
|
||||
tableBgBrush = loadBrush(TABLEZONE_BG_NAME, TABLEZONE_BG_DEFAULT);
|
||||
playerBgBrush = loadBrush(PLAYERZONE_BG_NAME, PLAYERZONE_BG_DEFAULT);
|
||||
stackBgBrush = loadBrush(STACKZONE_BG_NAME, STACKZONE_BG_DEFAULT);
|
||||
brushes[Role::Hand] = loadBrush(HANDZONE_BG_NAME, HANDZONE_BG_DEFAULT);
|
||||
brushes[Role::Table] = loadBrush(TABLEZONE_BG_NAME, TABLEZONE_BG_DEFAULT);
|
||||
brushes[Role::Player] = loadBrush(PLAYERZONE_BG_NAME, PLAYERZONE_BG_DEFAULT);
|
||||
brushes[Role::Stack] = loadBrush(STACKZONE_BG_NAME, STACKZONE_BG_DEFAULT);
|
||||
}
|
||||
for (auto &brushCache : brushesCache) {
|
||||
brushCache.clear();
|
||||
}
|
||||
tableBgBrushesCache.clear();
|
||||
stackBgBrushesCache.clear();
|
||||
playerBgBrushesCache.clear();
|
||||
handBgBrushesCache.clear();
|
||||
|
||||
QPixmapCache::clear();
|
||||
|
||||
emit themeChanged();
|
||||
}
|
||||
|
||||
QBrush ThemeManager::getExtraTableBgBrush(QString extraNumber, QBrush &fallbackBrush)
|
||||
static QString roleBgName(ThemeManager::Role role)
|
||||
{
|
||||
QBrush returnBrush;
|
||||
switch (role) {
|
||||
case ThemeManager::Hand:
|
||||
return HANDZONE_BG_NAME;
|
||||
|
||||
if (!tableBgBrushesCache.contains(extraNumber.toInt())) {
|
||||
returnBrush = loadExtraBrush(TABLEZONE_BG_NAME + extraNumber, fallbackBrush);
|
||||
tableBgBrushesCache.insert(extraNumber.toInt(), returnBrush);
|
||||
} else {
|
||||
returnBrush = tableBgBrushesCache.value(extraNumber.toInt());
|
||||
case ThemeManager::Player:
|
||||
return PLAYERZONE_BG_NAME;
|
||||
|
||||
case ThemeManager::Stack:
|
||||
return STACKZONE_BG_NAME;
|
||||
|
||||
case ThemeManager::Table:
|
||||
return TABLEZONE_BG_NAME;
|
||||
|
||||
default:
|
||||
Q_ASSERT(false);
|
||||
}
|
||||
|
||||
return returnBrush;
|
||||
}
|
||||
|
||||
QBrush ThemeManager::getExtraStackBgBrush(QString extraNumber, QBrush &fallbackBrush)
|
||||
QBrush &ThemeManager::getBgBrush(Role role)
|
||||
{
|
||||
QBrush returnBrush;
|
||||
|
||||
if (!stackBgBrushesCache.contains(extraNumber.toInt())) {
|
||||
returnBrush = loadExtraBrush(STACKZONE_BG_NAME + extraNumber, fallbackBrush);
|
||||
stackBgBrushesCache.insert(extraNumber.toInt(), returnBrush);
|
||||
} else {
|
||||
returnBrush = stackBgBrushesCache.value(extraNumber.toInt());
|
||||
}
|
||||
|
||||
return returnBrush;
|
||||
return brushes[role];
|
||||
}
|
||||
|
||||
QBrush ThemeManager::getExtraPlayerBgBrush(QString extraNumber, QBrush &fallbackBrush)
|
||||
QBrush ThemeManager::getExtraBgBrush(Role role, int zoneId)
|
||||
{
|
||||
QBrush returnBrush;
|
||||
|
||||
if (!playerBgBrushesCache.contains(extraNumber.toInt())) {
|
||||
returnBrush = loadExtraBrush(PLAYERZONE_BG_NAME + extraNumber, fallbackBrush);
|
||||
playerBgBrushesCache.insert(extraNumber.toInt(), returnBrush);
|
||||
} else {
|
||||
returnBrush = playerBgBrushesCache.value(extraNumber.toInt());
|
||||
if (zoneId <= 0) {
|
||||
return getBgBrush(role);
|
||||
}
|
||||
|
||||
return returnBrush;
|
||||
}
|
||||
QBrushMap &brushCache = brushesCache[role];
|
||||
|
||||
QBrush ThemeManager::getExtraHandBgBrush(QString extraNumber, QBrush &fallbackBrush)
|
||||
{
|
||||
QBrush returnBrush;
|
||||
|
||||
if (!handBgBrushesCache.contains(extraNumber.toInt())) {
|
||||
returnBrush = loadExtraBrush(HANDZONE_BG_NAME + extraNumber, fallbackBrush);
|
||||
handBgBrushesCache.insert(extraNumber.toInt(), returnBrush);
|
||||
} else {
|
||||
returnBrush = handBgBrushesCache.value(extraNumber.toInt());
|
||||
if (!brushCache.contains(zoneId)) {
|
||||
QBrush brush = loadExtraBrush(roleBgName(role) + QString::number(zoneId), getBgBrush(role));
|
||||
brushCache.insert(zoneId, brush);
|
||||
return brush;
|
||||
}
|
||||
|
||||
return returnBrush;
|
||||
return brushCache.value(zoneId);
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <QObject>
|
||||
#include <QPixmap>
|
||||
#include <QString>
|
||||
#include <array>
|
||||
|
||||
inline Q_LOGGING_CATEGORY(ThemeManagerLog, "theme_manager");
|
||||
|
||||
@@ -22,13 +23,23 @@ class ThemeManager : public QObject
|
||||
public:
|
||||
ThemeManager(QObject *parent = nullptr);
|
||||
|
||||
enum Role
|
||||
{
|
||||
MinRole = 0,
|
||||
Hand = MinRole,
|
||||
Stack,
|
||||
Table,
|
||||
Player,
|
||||
MaxRole = Player,
|
||||
};
|
||||
|
||||
private:
|
||||
QBrush handBgBrush, stackBgBrush, tableBgBrush, playerBgBrush;
|
||||
std::array<QBrush, Role::MaxRole + 1> brushes;
|
||||
QStringMap availableThemes;
|
||||
/*
|
||||
Internal cache for multiple backgrounds
|
||||
*/
|
||||
QBrushMap tableBgBrushesCache, stackBgBrushesCache, playerBgBrushesCache, handBgBrushesCache;
|
||||
std::array<QBrushMap, Role::MaxRole + 1> brushesCache;
|
||||
|
||||
protected:
|
||||
void ensureThemeDirectoryExists();
|
||||
@@ -36,27 +47,10 @@ protected:
|
||||
QBrush loadExtraBrush(QString fileName, QBrush &fallbackBrush);
|
||||
|
||||
public:
|
||||
QBrush &getHandBgBrush()
|
||||
{
|
||||
return handBgBrush;
|
||||
}
|
||||
QBrush &getStackBgBrush()
|
||||
{
|
||||
return stackBgBrush;
|
||||
}
|
||||
QBrush &getTableBgBrush()
|
||||
{
|
||||
return tableBgBrush;
|
||||
}
|
||||
QBrush &getPlayerBgBrush()
|
||||
{
|
||||
return playerBgBrush;
|
||||
}
|
||||
QStringMap &getAvailableThemes();
|
||||
QBrush getExtraTableBgBrush(QString extraNumber, QBrush &fallbackBrush);
|
||||
QBrush getExtraStackBgBrush(QString extraNumber, QBrush &fallbackBrush);
|
||||
QBrush getExtraPlayerBgBrush(QString extraNumber, QBrush &fallbackBrush);
|
||||
QBrush getExtraHandBgBrush(QString extraNumber, QBrush &fallbackBrush);
|
||||
|
||||
QBrush &getBgBrush(Role zone);
|
||||
QBrush getExtraBgBrush(Role zone, int zoneId = 0);
|
||||
protected slots:
|
||||
void themeChangedSlot();
|
||||
signals:
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
#include "color_identity_widget.h"
|
||||
|
||||
#include "../../../../../settings/cache_settings.h"
|
||||
#include "mana_symbol_widget.h"
|
||||
|
||||
#include <QHBoxLayout>
|
||||
#include <QLabel>
|
||||
#include <QPixmap>
|
||||
#include <QRegularExpression>
|
||||
#include <QResizeEvent>
|
||||
#include <QSize>
|
||||
|
||||
ColorIdentityWidget::ColorIdentityWidget(QWidget *parent, CardInfoPtr _card) : QWidget(parent), card(_card)
|
||||
{
|
||||
layout = new QHBoxLayout(this);
|
||||
layout->setSpacing(5); // Small spacing between icons
|
||||
layout->setContentsMargins(0, 0, 0, 0);
|
||||
layout->setAlignment(Qt::AlignCenter); // Ensure icons are centered
|
||||
setLayout(layout);
|
||||
|
||||
// Define the full WUBRG set (White, Blue, Black, Red, Green)
|
||||
QString fullColorIdentity = "WUBRG";
|
||||
|
||||
if (card) {
|
||||
manaCost = card->getColors(); // Get mana cost string
|
||||
QStringList symbols = parseColorIdentity(manaCost); // Parse mana cost string
|
||||
|
||||
populateManaSymbolWidgets();
|
||||
}
|
||||
connect(&SettingsCache::instance(), &SettingsCache::visualDeckStorageDrawUnusedColorIdentitiesChanged, this,
|
||||
&ColorIdentityWidget::toggleUnusedVisibility);
|
||||
}
|
||||
|
||||
ColorIdentityWidget::ColorIdentityWidget(QWidget *parent, QString _manaCost)
|
||||
: QWidget(parent), card(nullptr), manaCost(_manaCost)
|
||||
{
|
||||
layout = new QHBoxLayout(this);
|
||||
layout->setSpacing(5); // Small spacing between icons
|
||||
layout->setContentsMargins(0, 0, 0, 0);
|
||||
layout->setAlignment(Qt::AlignCenter); // Ensure icons are centered
|
||||
setLayout(layout);
|
||||
|
||||
populateManaSymbolWidgets();
|
||||
|
||||
connect(&SettingsCache::instance(), &SettingsCache::visualDeckStorageDrawUnusedColorIdentitiesChanged, this,
|
||||
&ColorIdentityWidget::toggleUnusedVisibility);
|
||||
}
|
||||
|
||||
void ColorIdentityWidget::populateManaSymbolWidgets()
|
||||
{
|
||||
// Define the full WUBRG set (White, Blue, Black, Red, Green)
|
||||
QString fullColorIdentity = "WUBRG";
|
||||
QStringList symbols = parseColorIdentity(manaCost); // Parse mana cost string
|
||||
|
||||
if (SettingsCache::instance().getVisualDeckStorageDrawUnusedColorIdentities()) {
|
||||
for (const QString symbol : fullColorIdentity) {
|
||||
auto *manaSymbol = new ManaSymbolWidget(this, symbol, symbols.contains(symbol));
|
||||
layout->addWidget(manaSymbol);
|
||||
}
|
||||
} else {
|
||||
for (const QString &symbol : symbols) {
|
||||
auto *manaSymbol = new ManaSymbolWidget(this, symbol, symbols.contains(symbol));
|
||||
layout->addWidget(manaSymbol);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ColorIdentityWidget::toggleUnusedVisibility()
|
||||
{
|
||||
if (layout != nullptr) {
|
||||
QLayoutItem *item;
|
||||
while ((item = layout->takeAt(0)) != nullptr) {
|
||||
item->widget()->deleteLater(); // Delete the widget
|
||||
delete item; // Delete the layout item
|
||||
}
|
||||
}
|
||||
populateManaSymbolWidgets();
|
||||
}
|
||||
|
||||
void ColorIdentityWidget::resizeEvent(QResizeEvent *event)
|
||||
{
|
||||
QWidget::resizeEvent(event);
|
||||
QList<ManaSymbolWidget *> manaSymbols = findChildren<ManaSymbolWidget *>();
|
||||
|
||||
if (!manaSymbols.isEmpty()) {
|
||||
int totalWidth = event->size().width();
|
||||
int totalHeight = totalWidth / 6; // Set height to 1/4 of the width
|
||||
setFixedHeight(totalHeight);
|
||||
|
||||
int spacing = layout->spacing();
|
||||
int count = manaSymbols.size();
|
||||
int availableWidth = totalWidth - (spacing * (count - 1));
|
||||
int iconSize = qMin(availableWidth / count, totalHeight); // Ensure icons fit within the new height
|
||||
|
||||
for (ManaSymbolWidget *manaSymbol : manaSymbols) {
|
||||
manaSymbol->setFixedSize(iconSize, iconSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QStringList ColorIdentityWidget::parseColorIdentity(const QString &cmc)
|
||||
{
|
||||
QStringList symbols;
|
||||
|
||||
// Handle split costs (e.g., "3U // 4UU")
|
||||
QStringList splitCosts = cmc.split(" // ");
|
||||
for (const QString &part : splitCosts) {
|
||||
QRegularExpression regex(R"(\{([^}]+)\}|(\d+)|([WUBRGCSPX]))");
|
||||
QRegularExpressionMatchIterator matches = regex.globalMatch(part);
|
||||
while (matches.hasNext()) {
|
||||
QRegularExpressionMatch match = matches.next();
|
||||
if (match.captured(1).isEmpty()) { // If no `{}` group was captured, check other groups
|
||||
if (!match.captured(2).isEmpty()) {
|
||||
symbols.append(match.captured(2)); // Number match
|
||||
} else {
|
||||
symbols.append(match.captured(3)); // Single mana letter match
|
||||
}
|
||||
} else {
|
||||
symbols.append(match.captured(1)); // `{}` enclosed match
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return symbols;
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
#ifndef COLOR_IDENTITY_WIDGET_H
|
||||
#define COLOR_IDENTITY_WIDGET_H
|
||||
|
||||
#include "../../../../../game/cards/card_database.h"
|
||||
|
||||
#include <QHBoxLayout>
|
||||
#include <QWidget>
|
||||
|
||||
class ColorIdentityWidget : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit ColorIdentityWidget(QWidget *parent, CardInfoPtr card);
|
||||
explicit ColorIdentityWidget(QWidget *parent, QString manaCost);
|
||||
void populateManaSymbolWidgets();
|
||||
|
||||
QStringList parseColorIdentity(const QString &manaString);
|
||||
|
||||
public slots:
|
||||
void resizeEvent(QResizeEvent *event) override;
|
||||
void toggleUnusedVisibility();
|
||||
|
||||
private:
|
||||
CardInfoPtr card;
|
||||
QString manaCost;
|
||||
QHBoxLayout *layout;
|
||||
};
|
||||
|
||||
#endif // COLOR_IDENTITY_WIDGET_H
|
||||
@@ -0,0 +1,76 @@
|
||||
#include "mana_cost_widget.h"
|
||||
|
||||
#include "mana_symbol_widget.h"
|
||||
|
||||
#include <QHBoxLayout>
|
||||
#include <QLabel>
|
||||
#include <QPixmap>
|
||||
#include <QResizeEvent>
|
||||
#include <QSize>
|
||||
#include <qregularexpression.h>
|
||||
|
||||
ManaCostWidget::ManaCostWidget(QWidget *parent, CardInfoPtr _card) : QWidget(parent), card(_card)
|
||||
{
|
||||
layout = new QHBoxLayout(this);
|
||||
layout->setSpacing(5); // Small spacing between icons
|
||||
layout->setContentsMargins(0, 0, 0, 0);
|
||||
setLayout(layout);
|
||||
|
||||
setFixedHeight(50); // Fixed height
|
||||
|
||||
if (card) {
|
||||
QString manaCost = card->getManaCost(); // Get mana cost string
|
||||
QStringList symbols = parseManaCost(manaCost); // Parse mana cost string
|
||||
|
||||
for (const QString &symbol : symbols) {
|
||||
ManaSymbolWidget *manaSymbol = new ManaSymbolWidget(this, symbol, true, false);
|
||||
layout->addWidget(manaSymbol);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ManaCostWidget::resizeEvent(QResizeEvent *event)
|
||||
{
|
||||
QWidget::resizeEvent(event);
|
||||
QList<ManaSymbolWidget *> manaSymbols = findChildren<ManaSymbolWidget *>();
|
||||
|
||||
if (!manaSymbols.isEmpty()) {
|
||||
int totalWidth = event->size().width();
|
||||
int spacing = layout->spacing();
|
||||
int count = manaSymbols.size();
|
||||
|
||||
// Available width minus total spacing
|
||||
int availableWidth = totalWidth - (spacing * (count - 1));
|
||||
int iconSize = qMin(50, availableWidth / count);
|
||||
|
||||
for (ManaSymbolWidget *manaSymbol : manaSymbols) {
|
||||
manaSymbol->setFixedSize(iconSize, iconSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QStringList ManaCostWidget::parseManaCost(const QString &cmc)
|
||||
{
|
||||
QStringList symbols;
|
||||
|
||||
// Handle split costs (e.g., "3U // 4UU")
|
||||
QStringList splitCosts = cmc.split(" // ");
|
||||
for (const QString &part : splitCosts) {
|
||||
QRegularExpression regex(R"(\{([^}]+)\}|(\d+)|([WUBRGCSPX]))");
|
||||
QRegularExpressionMatchIterator matches = regex.globalMatch(part);
|
||||
while (matches.hasNext()) {
|
||||
QRegularExpressionMatch match = matches.next();
|
||||
if (match.captured(1).isEmpty()) { // If no `{}` group was captured, check other groups
|
||||
if (!match.captured(2).isEmpty()) {
|
||||
symbols.append(match.captured(2)); // Number match
|
||||
} else {
|
||||
symbols.append(match.captured(3)); // Single mana letter match
|
||||
}
|
||||
} else {
|
||||
symbols.append(match.captured(1)); // `{}` enclosed match
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return symbols;
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
#ifndef MANA_COST_WIDGET_H
|
||||
#define MANA_COST_WIDGET_H
|
||||
|
||||
#include "../../../../../game/cards/card_database.h"
|
||||
|
||||
#include <QHBoxLayout>
|
||||
#include <QWidget>
|
||||
|
||||
class ManaCostWidget : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit ManaCostWidget(QWidget *parent, CardInfoPtr card);
|
||||
|
||||
QStringList parseManaCost(const QString &manaString);
|
||||
public slots:
|
||||
void resizeEvent(QResizeEvent *event) override;
|
||||
|
||||
private:
|
||||
CardInfoPtr card;
|
||||
QHBoxLayout *layout;
|
||||
};
|
||||
|
||||
#endif // MANA_COST_WIDGET_H
|
||||
@@ -0,0 +1,70 @@
|
||||
#include "mana_symbol_widget.h"
|
||||
|
||||
#include "../../../../../settings/cache_settings.h"
|
||||
|
||||
#include <QResizeEvent>
|
||||
|
||||
ManaSymbolWidget::ManaSymbolWidget(QWidget *parent, QString _symbol, bool _isActive, bool _mayBeToggled)
|
||||
: QLabel(parent), symbol(_symbol), isActive(_isActive), mayBeToggled(_mayBeToggled)
|
||||
{
|
||||
loadManaIcon();
|
||||
setPixmap(manaIcon.scaled(50, 50, Qt::KeepAspectRatio, Qt::SmoothTransformation));
|
||||
setMaximumWidth(50);
|
||||
|
||||
// Initialize opacity effect
|
||||
opacityEffect = new QGraphicsOpacityEffect(this);
|
||||
setGraphicsEffect(opacityEffect);
|
||||
updateOpacity();
|
||||
|
||||
connect(&SettingsCache::instance(), &SettingsCache::visualDeckStorageUnusedColorIdentitiesOpacityChanged, this,
|
||||
&ManaSymbolWidget::updateOpacity);
|
||||
}
|
||||
|
||||
void ManaSymbolWidget::setColorActive(bool active)
|
||||
{
|
||||
if (isActive != active) {
|
||||
isActive = active;
|
||||
updateOpacity();
|
||||
emit colorToggled(getSymbolChar(), isActive);
|
||||
}
|
||||
}
|
||||
|
||||
void ManaSymbolWidget::updateOpacity()
|
||||
{
|
||||
qreal opacity;
|
||||
if (mayBeToggled) {
|
||||
// UI elements that users can click on shouldn't be transparent.
|
||||
opacity = isActive ? 1.0 : 0.5;
|
||||
} else {
|
||||
// It's just for display, they can do whatever they want.
|
||||
opacity = isActive ? 1.0 : SettingsCache::instance().getVisualDeckStorageUnusedColorIdentitiesOpacity() / 100.0;
|
||||
}
|
||||
opacityEffect->setOpacity(opacity);
|
||||
}
|
||||
|
||||
void ManaSymbolWidget::mousePressEvent(QMouseEvent *event)
|
||||
{
|
||||
Q_UNUSED(event);
|
||||
if (mayBeToggled) {
|
||||
isActive = !isActive;
|
||||
updateOpacity();
|
||||
emit colorToggled(getSymbolChar(), isActive);
|
||||
}
|
||||
}
|
||||
|
||||
void ManaSymbolWidget::resizeEvent(QResizeEvent *event)
|
||||
{
|
||||
QLabel::resizeEvent(event);
|
||||
setPixmap(manaIcon.scaled(event->size(), Qt::KeepAspectRatio, Qt::SmoothTransformation));
|
||||
}
|
||||
|
||||
void ManaSymbolWidget::loadManaIcon()
|
||||
{
|
||||
QString filename = "theme:icons/mana/";
|
||||
|
||||
if (symbol == "W" || symbol == "U" || symbol == "B" || symbol == "R" || symbol == "G") {
|
||||
filename += symbol + ".svg";
|
||||
}
|
||||
|
||||
manaIcon = QPixmap(filename);
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
#ifndef MANA_SYMBOL_WIDGET_H
|
||||
#define MANA_SYMBOL_WIDGET_H
|
||||
|
||||
#include <QGraphicsOpacityEffect>
|
||||
#include <QLabel>
|
||||
|
||||
class ManaSymbolWidget : public QLabel
|
||||
{
|
||||
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
ManaSymbolWidget(QWidget *parent, QString symbol, bool isActive = true, bool mayBeToggled = false);
|
||||
void setColorActive(bool active);
|
||||
void updateOpacity();
|
||||
bool isColorActive() const;
|
||||
QString getSymbol() const
|
||||
{
|
||||
return symbol;
|
||||
};
|
||||
QChar getSymbolChar() const
|
||||
{
|
||||
return symbol[0];
|
||||
};
|
||||
|
||||
void loadManaIcon();
|
||||
|
||||
public slots:
|
||||
void resizeEvent(QResizeEvent *event) override;
|
||||
void mousePressEvent(QMouseEvent *event) override;
|
||||
|
||||
signals:
|
||||
void colorToggled(QChar symbol, bool isActive);
|
||||
|
||||
private:
|
||||
QString symbol;
|
||||
QPixmap manaIcon;
|
||||
bool isActive;
|
||||
bool mayBeToggled;
|
||||
QGraphicsOpacityEffect *opacityEffect;
|
||||
};
|
||||
|
||||
#endif // MANA_SYMBOL_WIDGET_H
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "card_info_picture_enlarged_widget.h"
|
||||
|
||||
#include "../../../../settings/cache_settings.h"
|
||||
#include "../../picture_loader/picture_loader.h"
|
||||
|
||||
#include <QPainterPath>
|
||||
@@ -17,6 +18,12 @@ CardInfoPictureEnlargedWidget::CardInfoPictureEnlargedWidget(QWidget *parent)
|
||||
{
|
||||
setWindowFlags(Qt::ToolTip); // Keeps this widget on top of everything
|
||||
setAttribute(Qt::WA_TranslucentBackground);
|
||||
|
||||
connect(&SettingsCache::instance(), &SettingsCache::roundCardCornersChanged, this, [this](bool _roundCardCorners) {
|
||||
Q_UNUSED(_roundCardCorners);
|
||||
|
||||
update();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -79,7 +86,8 @@ void CardInfoPictureEnlargedWidget::paintEvent(QPaintEvent *event)
|
||||
QPoint topLeft{(width() - scaledSize.width()) / 2, (height() - scaledSize.height()) / 2};
|
||||
|
||||
// Define the radius for rounded corners
|
||||
qreal radius = 0.05 * scaledSize.width(); // Adjust the radius as needed for rounded corners
|
||||
// Adjust the radius as needed for rounded corners
|
||||
qreal radius = SettingsCache::instance().getRoundCardCorners() ? 0.05 * scaledSize.width() : 0.;
|
||||
|
||||
QStylePainter painter(this);
|
||||
// Fill the background with transparent color to ensure rounded corners are rendered properly
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
#include "../../../../game/cards/card_database_manager.h"
|
||||
#include "../../../../game/cards/card_item.h"
|
||||
#include "../../../../settings/cache_settings.h"
|
||||
#include "../../../tabs/tab_deck_editor.h"
|
||||
#include "../../../tabs/tab_supervisor.h"
|
||||
#include "../../picture_loader/picture_loader.h"
|
||||
#include "../../window_main.h"
|
||||
@@ -44,6 +43,12 @@ CardInfoPictureWidget::CardInfoPictureWidget(QWidget *parent, const bool hoverTo
|
||||
hoverTimer = new QTimer(this);
|
||||
hoverTimer->setSingleShot(true);
|
||||
connect(hoverTimer, &QTimer::timeout, this, &CardInfoPictureWidget::showEnlargedPixmap);
|
||||
|
||||
connect(&SettingsCache::instance(), &SettingsCache::roundCardCornersChanged, this, [this](bool _roundCardCorners) {
|
||||
Q_UNUSED(_roundCardCorners);
|
||||
|
||||
update();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -166,21 +171,37 @@ void CardInfoPictureWidget::paintEvent(QPaintEvent *event)
|
||||
}
|
||||
}
|
||||
|
||||
// Adjust scaling after rotation
|
||||
const QSize availableSize = size(); // Size of the widget
|
||||
const QSize scaledSize = transformedPixmap.size().scaled(availableSize, Qt::KeepAspectRatio);
|
||||
// Handle DPI scaling
|
||||
qreal dpr = devicePixelRatio(); // Get the actual scaling factor
|
||||
QSize availableSize = size() * dpr; // Convert to physical pixel size
|
||||
|
||||
const QRect targetRect{(availableSize.width() - scaledSize.width()) / 2,
|
||||
(availableSize.height() - scaledSize.height()) / 2, scaledSize.width(), scaledSize.height()};
|
||||
// Compute final scaled size
|
||||
QSize pixmapSize = transformedPixmap.size();
|
||||
QSize scaledSize = pixmapSize.scaled(availableSize, Qt::KeepAspectRatio);
|
||||
|
||||
const qreal radius = 0.05 * scaledSize.width();
|
||||
// Pre-scale the pixmap once before drawing
|
||||
QPixmap finalPixmap = transformedPixmap.scaled(scaledSize, Qt::KeepAspectRatio, Qt::SmoothTransformation);
|
||||
finalPixmap.setDevicePixelRatio(dpr); // Ensure correct display on high-DPI screens
|
||||
|
||||
// Compute target rectangle with explicit integer conversion
|
||||
int targetX = static_cast<int>((availableSize.width() - scaledSize.width()) / (2 * dpr));
|
||||
int targetY = static_cast<int>((availableSize.height() - scaledSize.height()) / (2 * dpr));
|
||||
int targetW = static_cast<int>(scaledSize.width() / dpr);
|
||||
int targetH = static_cast<int>(scaledSize.height() / dpr);
|
||||
QRect targetRect{targetX, targetY, targetW, targetH};
|
||||
|
||||
// Compute rounded corner radius
|
||||
// Ensure consistent rounding
|
||||
qreal radius = SettingsCache::instance().getRoundCardCorners() ? 0.05 * static_cast<qreal>(targetRect.width()) : 0.;
|
||||
|
||||
// Draw the pixmap with rounded corners
|
||||
QStylePainter painter(this);
|
||||
QPainterPath shape;
|
||||
shape.addRoundedRect(targetRect, radius, radius);
|
||||
painter.setClipPath(shape);
|
||||
painter.drawPixmap(targetRect, transformedPixmap.scaled(scaledSize, Qt::KeepAspectRatio, Qt::SmoothTransformation));
|
||||
|
||||
// Draw the pre-scaled pixmap directly
|
||||
painter.drawPixmap(targetRect, finalPixmap);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -291,12 +312,27 @@ QMenu *CardInfoPictureWidget::createViewRelatedCardsMenu()
|
||||
return viewRelatedCards;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the single instance of the MainWindow in this application.
|
||||
*/
|
||||
static MainWindow *findMainWindow()
|
||||
{
|
||||
for (auto widget : QApplication::topLevelWidgets()) {
|
||||
if (auto mainWindow = qobject_cast<MainWindow *>(widget)) {
|
||||
return mainWindow;
|
||||
}
|
||||
}
|
||||
// This code should be unreachable
|
||||
qCritical() << "Could not find MainWindow in QApplication::topLevelWidgets";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
QMenu *CardInfoPictureWidget::createAddToOpenDeckMenu()
|
||||
{
|
||||
auto addToOpenDeckMenu = new QMenu(tr("Add card to deck"));
|
||||
|
||||
auto *mainWindow = qobject_cast<MainWindow *>(window());
|
||||
QList<TabDeckEditor *> deckEditorTabs = mainWindow->getTabSupervisor()->getDeckEditorTabs();
|
||||
auto mainWindow = findMainWindow();
|
||||
QList<AbstractTabDeckEditor *> deckEditorTabs = mainWindow->getTabSupervisor()->getDeckEditorTabs();
|
||||
|
||||
if (deckEditorTabs.isEmpty()) {
|
||||
addToOpenDeckMenu->setEnabled(false);
|
||||
@@ -308,14 +344,14 @@ QMenu *CardInfoPictureWidget::createAddToOpenDeckMenu()
|
||||
|
||||
QAction *addCard = addCardMenu->addAction(tr("Mainboard"));
|
||||
connect(addCard, &QAction::triggered, this, [this, deckEditorTab] {
|
||||
deckEditorTab->updateCardInfo(info);
|
||||
deckEditorTab->addCardHelper(info, DECK_ZONE_MAIN);
|
||||
deckEditorTab->updateCard(info);
|
||||
deckEditorTab->actAddCard(info);
|
||||
});
|
||||
|
||||
QAction *addCardSideboard = addCardMenu->addAction(tr("Sideboard"));
|
||||
connect(addCardSideboard, &QAction::triggered, this, [this, deckEditorTab] {
|
||||
deckEditorTab->updateCardInfo(info);
|
||||
deckEditorTab->addCardHelper(info, DECK_ZONE_SIDE);
|
||||
deckEditorTab->updateCard(info);
|
||||
deckEditorTab->actAddCardToSideboard(info);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
#include <QTimer>
|
||||
#include <QWidget>
|
||||
|
||||
inline Q_LOGGING_CATEGORY(CardInfoPictureWidgetLog, "card_info_picture_widget");
|
||||
|
||||
class AbstractCardItem;
|
||||
class QMenu;
|
||||
|
||||
|
||||
@@ -11,8 +11,8 @@
|
||||
* This widget allows users to dynamically change the card size in a linked FlowWidget
|
||||
* and updates the application's settings accordingly.
|
||||
*/
|
||||
CardSizeWidget::CardSizeWidget(QWidget *parent, FlowWidget *flowWidget, int defaultValue)
|
||||
: parent(parent), flowWidget(flowWidget)
|
||||
CardSizeWidget::CardSizeWidget(QWidget *parent, FlowWidget *_flowWidget, int defaultValue)
|
||||
: parent(parent), flowWidget(_flowWidget)
|
||||
{
|
||||
cardSizeLayout = new QHBoxLayout(this);
|
||||
cardSizeLayout->setContentsMargins(9, 0, 9, 0);
|
||||
|
||||
@@ -38,6 +38,9 @@ void DeckPreviewCardPictureWidget::mousePressEvent(QMouseEvent *event)
|
||||
if (event->button() == Qt::LeftButton) {
|
||||
lastMouseEvent = event;
|
||||
singleClickTimer->start(QApplication::doubleClickInterval());
|
||||
} else {
|
||||
emit imageClicked(event, this);
|
||||
event->accept();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
#include "deck_editor_card_info_dock_widget.h"
|
||||
|
||||
#include "../cards/card_info_frame_widget.h"
|
||||
|
||||
#include <QVBoxLayout>
|
||||
|
||||
DeckEditorCardInfoDockWidget::DeckEditorCardInfoDockWidget(AbstractTabDeckEditor *parent)
|
||||
: QDockWidget(parent), deckEditor(parent)
|
||||
{
|
||||
setObjectName("cardInfoDock");
|
||||
|
||||
setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea);
|
||||
setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetFloatable | QDockWidget::DockWidgetMovable);
|
||||
|
||||
createCardInfoDock();
|
||||
|
||||
retranslateUi();
|
||||
}
|
||||
|
||||
void DeckEditorCardInfoDockWidget::createCardInfoDock()
|
||||
{
|
||||
cardInfo = new CardInfoFrameWidget();
|
||||
cardInfo->setObjectName("cardInfo");
|
||||
auto *cardInfoFrame = new QVBoxLayout;
|
||||
cardInfoFrame->setObjectName("cardInfoFrame");
|
||||
cardInfoFrame->addWidget(cardInfo);
|
||||
|
||||
auto *cardInfoDockContents = new QWidget();
|
||||
cardInfoDockContents->setObjectName("cardInfoDockContents");
|
||||
cardInfoDockContents->setLayout(cardInfoFrame);
|
||||
setWidget(cardInfoDockContents);
|
||||
|
||||
installEventFilter(deckEditor);
|
||||
connect(this, &QDockWidget::topLevelChanged, deckEditor, &AbstractTabDeckEditor::dockTopLevelChanged);
|
||||
}
|
||||
|
||||
void DeckEditorCardInfoDockWidget::updateCard(CardInfoPtr _card)
|
||||
{
|
||||
cardInfo->setCard(_card);
|
||||
}
|
||||
|
||||
void DeckEditorCardInfoDockWidget::retranslateUi()
|
||||
{
|
||||
setWindowTitle(tr("Card Info"));
|
||||
cardInfo->retranslateUi();
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
#ifndef DECK_EDITOR_CARD_INFO_DOCK_WIDGET_H
|
||||
#define DECK_EDITOR_CARD_INFO_DOCK_WIDGET_H
|
||||
|
||||
#include "../../../tabs/abstract_tab_deck_editor.h"
|
||||
#include "../cards/card_info_frame_widget.h"
|
||||
|
||||
#include <QDockWidget>
|
||||
|
||||
class AbstractTabDeckEditor;
|
||||
class DeckEditorCardInfoDockWidget : public QDockWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit DeckEditorCardInfoDockWidget(AbstractTabDeckEditor *parent);
|
||||
void createCardInfoDock();
|
||||
void retranslateUi();
|
||||
|
||||
AbstractTabDeckEditor *deckEditor;
|
||||
CardInfoFrameWidget *cardInfo;
|
||||
|
||||
public slots:
|
||||
void updateCard(CardInfoPtr _card);
|
||||
};
|
||||
|
||||
#endif // DECK_EDITOR_CARD_INFO_DOCK_WIDGET_H
|
||||
@@ -0,0 +1,282 @@
|
||||
#include "deck_editor_database_display_widget.h"
|
||||
|
||||
#include "../../../../game/cards/card_database_manager.h"
|
||||
#include "../../../../settings/cache_settings.h"
|
||||
#include "../../../tabs/abstract_tab_deck_editor.h"
|
||||
#include "../../../tabs/tab_supervisor.h"
|
||||
#include "../../../ui/pixel_map_generator.h"
|
||||
|
||||
#include <QClipboard>
|
||||
#include <QFile>
|
||||
#include <QHeaderView>
|
||||
#include <QMenu>
|
||||
#include <QTextBrowser>
|
||||
#include <QToolButton>
|
||||
#include <QTreeView>
|
||||
|
||||
static bool canBeCommander(const CardInfoPtr &cardInfo)
|
||||
{
|
||||
return ((cardInfo->getCardType().contains("Legendary", Qt::CaseInsensitive) &&
|
||||
cardInfo->getCardType().contains("Creature", Qt::CaseInsensitive))) ||
|
||||
cardInfo->getText().contains("can be your commander", Qt::CaseInsensitive);
|
||||
}
|
||||
|
||||
DeckEditorDatabaseDisplayWidget::DeckEditorDatabaseDisplayWidget(AbstractTabDeckEditor *parent)
|
||||
: QWidget(parent), deckEditor(parent)
|
||||
{
|
||||
setObjectName("centralWidget");
|
||||
|
||||
centralFrame = new QVBoxLayout(this);
|
||||
centralFrame->setObjectName("centralFrame");
|
||||
setLayout(centralFrame);
|
||||
|
||||
searchEdit = new SearchLineEdit();
|
||||
searchEdit->setObjectName("searchEdit");
|
||||
searchEdit->setPlaceholderText(tr("Search by card name (or search expressions)"));
|
||||
searchEdit->setClearButtonEnabled(true);
|
||||
searchEdit->addAction(loadColorAdjustedPixmap("theme:icons/search"), QLineEdit::LeadingPosition);
|
||||
auto help = searchEdit->addAction(QPixmap("theme:icons/info"), QLineEdit::TrailingPosition);
|
||||
searchEdit->installEventFilter(&searchKeySignals);
|
||||
|
||||
setFocusProxy(searchEdit);
|
||||
setFocusPolicy(Qt::ClickFocus);
|
||||
|
||||
searchKeySignals.setObjectName("searchKeySignals");
|
||||
connect(searchEdit, SIGNAL(textChanged(const QString &)), this, SLOT(updateSearch(const QString &)));
|
||||
connect(&searchKeySignals, &KeySignals::onEnter, this, &DeckEditorDatabaseDisplayWidget::actAddCardToMainDeck);
|
||||
connect(&searchKeySignals, &KeySignals::onCtrlAltEqual, this,
|
||||
&DeckEditorDatabaseDisplayWidget::actAddCardToMainDeck);
|
||||
connect(&searchKeySignals, &KeySignals::onCtrlAltRBracket, this,
|
||||
&DeckEditorDatabaseDisplayWidget::actAddCardToSideboard);
|
||||
connect(&searchKeySignals, &KeySignals::onCtrlAltMinus, this,
|
||||
&DeckEditorDatabaseDisplayWidget::actDecrementCardFromMainDeck);
|
||||
connect(&searchKeySignals, &KeySignals::onCtrlAltLBracket, this,
|
||||
&DeckEditorDatabaseDisplayWidget::actDecrementCardFromSideboard);
|
||||
connect(&searchKeySignals, &KeySignals::onCtrlAltEnter, this,
|
||||
&DeckEditorDatabaseDisplayWidget::actAddCardToSideboard);
|
||||
connect(&searchKeySignals, &KeySignals::onCtrlEnter, this, &DeckEditorDatabaseDisplayWidget::actAddCardToSideboard);
|
||||
connect(&searchKeySignals, &KeySignals::onCtrlC, this, &DeckEditorDatabaseDisplayWidget::copyDatabaseCellContents);
|
||||
connect(help, &QAction::triggered, this, &DeckEditorDatabaseDisplayWidget::showSearchSyntaxHelp);
|
||||
|
||||
databaseModel = new CardDatabaseModel(CardDatabaseManager::getInstance(), true, this);
|
||||
databaseModel->setObjectName("databaseModel");
|
||||
databaseDisplayModel = new CardDatabaseDisplayModel(this);
|
||||
databaseDisplayModel->setObjectName("databaseDisplayModel");
|
||||
databaseDisplayModel->setSourceModel(databaseModel);
|
||||
databaseDisplayModel->setFilterKeyColumn(0);
|
||||
|
||||
databaseView = new QTreeView(this);
|
||||
databaseView->setObjectName("databaseView");
|
||||
databaseView->setFocusProxy(searchEdit);
|
||||
databaseView->setUniformRowHeights(true);
|
||||
databaseView->setRootIsDecorated(false);
|
||||
databaseView->setAlternatingRowColors(true);
|
||||
databaseView->setSortingEnabled(true);
|
||||
databaseView->sortByColumn(0, Qt::AscendingOrder);
|
||||
databaseView->setModel(databaseDisplayModel);
|
||||
databaseView->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
connect(databaseView, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(databaseCustomMenu(QPoint)));
|
||||
connect(databaseView->selectionModel(), &QItemSelectionModel::currentRowChanged, this,
|
||||
&DeckEditorDatabaseDisplayWidget::updateCard);
|
||||
connect(databaseView, &QTreeView::doubleClicked, this, &DeckEditorDatabaseDisplayWidget::actAddCardToMainDeck);
|
||||
|
||||
QByteArray dbHeaderState = SettingsCache::instance().layouts().getDeckEditorDbHeaderState();
|
||||
if (dbHeaderState.isNull()) {
|
||||
// first run
|
||||
databaseView->setColumnWidth(0, 200);
|
||||
} else {
|
||||
databaseView->header()->restoreState(dbHeaderState);
|
||||
}
|
||||
connect(databaseView->header(), SIGNAL(geometriesChanged()), this, SLOT(saveDbHeaderState()));
|
||||
|
||||
searchEdit->setTreeView(databaseView);
|
||||
|
||||
aAddCard = new QAction(QString(), this);
|
||||
aAddCard->setIcon(QPixmap("theme:icons/arrow_right_green"));
|
||||
connect(aAddCard, &QAction::triggered, this, &DeckEditorDatabaseDisplayWidget::actAddCardToMainDeck);
|
||||
auto *tbAddCard = new QToolButton(this);
|
||||
tbAddCard->setDefaultAction(aAddCard);
|
||||
|
||||
aAddCardToSideboard = new QAction(QString(), this);
|
||||
aAddCardToSideboard->setIcon(QPixmap("theme:icons/arrow_right_blue"));
|
||||
connect(aAddCardToSideboard, &QAction::triggered, this, &DeckEditorDatabaseDisplayWidget::actAddCardToSideboard);
|
||||
auto *tbAddCardToSideboard = new QToolButton(this);
|
||||
tbAddCardToSideboard->setDefaultAction(aAddCardToSideboard);
|
||||
|
||||
searchLayout = new QHBoxLayout;
|
||||
searchLayout->setObjectName("searchLayout");
|
||||
searchLayout->addWidget(searchEdit);
|
||||
searchLayout->addWidget(tbAddCard);
|
||||
searchLayout->addWidget(tbAddCardToSideboard);
|
||||
|
||||
centralFrame->addLayout(searchLayout);
|
||||
centralFrame->addWidget(databaseView);
|
||||
|
||||
retranslateUi();
|
||||
}
|
||||
|
||||
void DeckEditorDatabaseDisplayWidget::updateSearch(const QString &search)
|
||||
{
|
||||
databaseDisplayModel->setStringFilter(search);
|
||||
QModelIndexList sel = databaseView->selectionModel()->selectedRows();
|
||||
if (sel.isEmpty() && databaseDisplayModel->rowCount())
|
||||
databaseView->selectionModel()->setCurrentIndex(databaseDisplayModel->index(0, 0),
|
||||
QItemSelectionModel::SelectCurrent | QItemSelectionModel::Rows);
|
||||
}
|
||||
|
||||
void DeckEditorDatabaseDisplayWidget::clearAllDatabaseFilters()
|
||||
{
|
||||
databaseDisplayModel->clearFilterAll();
|
||||
searchEdit->setText("");
|
||||
}
|
||||
|
||||
void DeckEditorDatabaseDisplayWidget::updateCard(const QModelIndex ¤t, const QModelIndex & /*previous*/)
|
||||
{
|
||||
const QString cardName = current.sibling(current.row(), 0).data().toString();
|
||||
const QString cardProviderID = CardDatabaseManager::getInstance()->getPreferredPrintingProviderIdForCard(cardName);
|
||||
|
||||
if (!current.isValid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!current.model()->hasChildren(current.sibling(current.row(), 0))) {
|
||||
CardInfoPtr card = CardDatabaseManager::getInstance()->getCardByNameAndProviderId(cardName, cardProviderID);
|
||||
if (!card) {
|
||||
card = CardDatabaseManager::getInstance()->getCard(cardName);
|
||||
}
|
||||
emit cardChanged(card);
|
||||
}
|
||||
}
|
||||
|
||||
void DeckEditorDatabaseDisplayWidget::actAddCardToMainDeck()
|
||||
{
|
||||
emit addCardToMainDeck(currentCardInfo());
|
||||
}
|
||||
|
||||
void DeckEditorDatabaseDisplayWidget::actAddCardToSideboard()
|
||||
{
|
||||
emit addCardToSideboard(currentCardInfo());
|
||||
}
|
||||
|
||||
void DeckEditorDatabaseDisplayWidget::actDecrementCardFromMainDeck()
|
||||
{
|
||||
emit decrementCardFromMainDeck(currentCardInfo());
|
||||
}
|
||||
|
||||
void DeckEditorDatabaseDisplayWidget::actDecrementCardFromSideboard()
|
||||
{
|
||||
emit decrementCardFromSideboard(currentCardInfo());
|
||||
}
|
||||
|
||||
CardInfoPtr DeckEditorDatabaseDisplayWidget::currentCardInfo() const
|
||||
{
|
||||
const QModelIndex currentIndex = databaseView->selectionModel()->currentIndex();
|
||||
if (!currentIndex.isValid()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const QString cardName = currentIndex.sibling(currentIndex.row(), 0).data().toString();
|
||||
|
||||
return CardDatabaseManager::getInstance()->getCard(cardName);
|
||||
}
|
||||
|
||||
void DeckEditorDatabaseDisplayWidget::databaseCustomMenu(QPoint point)
|
||||
{
|
||||
QMenu menu;
|
||||
const CardInfoPtr info = currentCardInfo();
|
||||
|
||||
if (info) {
|
||||
// add to deck and sideboard options
|
||||
QAction *addToDeck, *addToSideboard, *selectPrinting, *edhRecCommander, *edhRecCard;
|
||||
addToDeck = menu.addAction(tr("Add to Deck"));
|
||||
addToSideboard = menu.addAction(tr("Add to Sideboard"));
|
||||
selectPrinting = menu.addAction(tr("Select Printing"));
|
||||
if (canBeCommander(info)) {
|
||||
edhRecCommander = menu.addAction(tr("Show on EDHREC (Commander)"));
|
||||
connect(edhRecCommander, &QAction::triggered, this,
|
||||
[this, info] { deckEditor->getTabSupervisor()->addEdhrecTab(info, true); });
|
||||
}
|
||||
edhRecCard = menu.addAction(tr("Show on EDHREC (Card)"));
|
||||
|
||||
connect(addToDeck, &QAction::triggered, this, &DeckEditorDatabaseDisplayWidget::actAddCardToMainDeck);
|
||||
connect(addToSideboard, &QAction::triggered, this, &DeckEditorDatabaseDisplayWidget::actAddCardToSideboard);
|
||||
connect(selectPrinting, &QAction::triggered, this, [this, info] { deckEditor->showPrintingSelector(); });
|
||||
connect(edhRecCard, &QAction::triggered, this,
|
||||
[this, info] { deckEditor->getTabSupervisor()->addEdhrecTab(info); });
|
||||
|
||||
// filling out the related cards submenu
|
||||
auto *relatedMenu = new QMenu(tr("Show Related cards"));
|
||||
menu.addMenu(relatedMenu);
|
||||
auto relatedCards = info->getAllRelatedCards();
|
||||
if (relatedCards.isEmpty()) {
|
||||
relatedMenu->setDisabled(true);
|
||||
} else {
|
||||
for (const CardRelation *rel : relatedCards) {
|
||||
const QString &relatedCardName = rel->getName();
|
||||
QAction *relatedCard = relatedMenu->addAction(relatedCardName);
|
||||
connect(
|
||||
relatedCard, &QAction::triggered, deckEditor->cardInfoDockWidget->cardInfo,
|
||||
[this, relatedCardName] { deckEditor->cardInfoDockWidget->cardInfo->setCard(relatedCardName); });
|
||||
}
|
||||
}
|
||||
menu.exec(databaseView->mapToGlobal(point));
|
||||
}
|
||||
}
|
||||
|
||||
void DeckEditorDatabaseDisplayWidget::copyDatabaseCellContents()
|
||||
{
|
||||
auto _data = databaseView->selectionModel()->currentIndex().data();
|
||||
QApplication::clipboard()->setText(_data.toString());
|
||||
}
|
||||
|
||||
void DeckEditorDatabaseDisplayWidget::saveDbHeaderState()
|
||||
{
|
||||
SettingsCache::instance().layouts().setDeckEditorDbHeaderState(databaseView->header()->saveState());
|
||||
}
|
||||
|
||||
void DeckEditorDatabaseDisplayWidget::showSearchSyntaxHelp()
|
||||
{
|
||||
|
||||
QFile file("theme:help/search.md");
|
||||
|
||||
if (!file.open(QFile::ReadOnly | QFile::Text)) {
|
||||
return;
|
||||
}
|
||||
|
||||
QTextStream in(&file);
|
||||
QString text = in.readAll();
|
||||
file.close();
|
||||
|
||||
// Poor Markdown Converter
|
||||
auto opts = QRegularExpression::MultilineOption;
|
||||
text = text.replace(QRegularExpression("^(###)(.*)", opts), "<h3>\\2</h3>")
|
||||
.replace(QRegularExpression("^(##)(.*)", opts), "<h2>\\2</h2>")
|
||||
.replace(QRegularExpression("^(#)(.*)", opts), "<h1>\\2</h1>")
|
||||
.replace(QRegularExpression("^------*", opts), "<hr />")
|
||||
.replace(QRegularExpression(R"(\[([^[]+)\]\(([^\)]+)\))", opts), R"(<a href='\2'>\1</a>)");
|
||||
|
||||
auto browser = new QTextBrowser;
|
||||
browser->setParent(this, Qt::Window | Qt::WindowTitleHint | Qt::WindowSystemMenuHint | Qt::WindowMinMaxButtonsHint |
|
||||
Qt::WindowCloseButtonHint | Qt::WindowFullscreenButtonHint);
|
||||
browser->setWindowTitle("Search Help");
|
||||
browser->setReadOnly(true);
|
||||
browser->setMinimumSize({500, 600});
|
||||
|
||||
QString sheet = QString("a { text-decoration: underline; color: rgb(71,158,252) };");
|
||||
browser->document()->setDefaultStyleSheet(sheet);
|
||||
|
||||
browser->setHtml(text);
|
||||
connect(browser, &QTextBrowser::anchorClicked, [this](const QUrl &link) { searchEdit->setText(link.fragment()); });
|
||||
browser->show();
|
||||
}
|
||||
|
||||
void DeckEditorDatabaseDisplayWidget::setFilterTree(FilterTree *filterTree)
|
||||
{
|
||||
databaseDisplayModel->setFilterTree(filterTree);
|
||||
}
|
||||
|
||||
void DeckEditorDatabaseDisplayWidget::retranslateUi()
|
||||
{
|
||||
aAddCard->setText(tr("Add card to &maindeck"));
|
||||
aAddCardToSideboard->setText(tr("Add card to &sideboard"));
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
#ifndef DECK_EDITOR_DATABASE_DISPLAY_WIDGET_H
|
||||
#define DECK_EDITOR_DATABASE_DISPLAY_WIDGET_H
|
||||
|
||||
#include "../../../../deck/custom_line_edit.h"
|
||||
#include "../../../../game/cards/card_database_model.h"
|
||||
#include "../../../game_logic/key_signals.h"
|
||||
#include "../../../tabs/abstract_tab_deck_editor.h"
|
||||
|
||||
#include <QHBoxLayout>
|
||||
#include <QWidget>
|
||||
|
||||
class AbstractTabDeckEditor;
|
||||
class DeckEditorDatabaseDisplayWidget : public QWidget
|
||||
{
|
||||
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit DeckEditorDatabaseDisplayWidget(AbstractTabDeckEditor *parent);
|
||||
AbstractTabDeckEditor *deckEditor;
|
||||
SearchLineEdit *searchEdit;
|
||||
CardDatabaseModel *databaseModel;
|
||||
CardDatabaseDisplayModel *databaseDisplayModel;
|
||||
|
||||
public slots:
|
||||
CardInfoPtr currentCardInfo() const;
|
||||
void setFilterTree(FilterTree *filterTree);
|
||||
void clearAllDatabaseFilters();
|
||||
|
||||
signals:
|
||||
void addCardToMainDeck(CardInfoPtr card);
|
||||
void addCardToSideboard(CardInfoPtr card);
|
||||
void decrementCardFromMainDeck(CardInfoPtr card);
|
||||
void decrementCardFromSideboard(CardInfoPtr card);
|
||||
void cardChanged(CardInfoPtr _card);
|
||||
|
||||
private:
|
||||
KeySignals searchKeySignals;
|
||||
QTreeView *databaseView;
|
||||
QHBoxLayout *searchLayout;
|
||||
QAction *aAddCard, *aAddCardToSideboard;
|
||||
QVBoxLayout *centralFrame;
|
||||
QWidget *centralWidget;
|
||||
|
||||
private slots:
|
||||
void showSearchSyntaxHelp();
|
||||
void retranslateUi();
|
||||
void updateSearch(const QString &search);
|
||||
void updateCard(const QModelIndex ¤t, const QModelIndex &);
|
||||
void actAddCardToMainDeck();
|
||||
void actAddCardToSideboard();
|
||||
void actDecrementCardFromMainDeck();
|
||||
void actDecrementCardFromSideboard();
|
||||
void databaseCustomMenu(QPoint point);
|
||||
void copyDatabaseCellContents();
|
||||
void saveDbHeaderState();
|
||||
};
|
||||
|
||||
#endif // DECK_EDITOR_DATABASE_DISPLAY_WIDGET_H
|
||||
@@ -0,0 +1,533 @@
|
||||
#include "deck_editor_deck_dock_widget.h"
|
||||
|
||||
#include "../../../../game/cards/card_database_manager.h"
|
||||
#include "../../../../settings/cache_settings.h"
|
||||
|
||||
#include <QComboBox>
|
||||
#include <QDockWidget>
|
||||
#include <QHeaderView>
|
||||
#include <QLabel>
|
||||
#include <QSplitter>
|
||||
#include <QTextEdit>
|
||||
#include <trice_limits.h>
|
||||
|
||||
DeckEditorDeckDockWidget::DeckEditorDeckDockWidget(AbstractTabDeckEditor *parent)
|
||||
: QDockWidget(parent), deckEditor(parent)
|
||||
{
|
||||
setObjectName("deckDock");
|
||||
|
||||
setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea);
|
||||
setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetFloatable | QDockWidget::DockWidgetMovable);
|
||||
|
||||
installEventFilter(deckEditor);
|
||||
connect(this, SIGNAL(topLevelChanged(bool)), deckEditor, SLOT(dockTopLevelChanged(bool)));
|
||||
createDeckDock();
|
||||
}
|
||||
|
||||
void DeckEditorDeckDockWidget::createDeckDock()
|
||||
{
|
||||
deckModel = new DeckListModel(this);
|
||||
deckModel->setObjectName("deckModel");
|
||||
connect(deckModel, SIGNAL(deckHashChanged()), this, SLOT(updateHash()));
|
||||
deckView = new QTreeView();
|
||||
deckView->setObjectName("deckView");
|
||||
deckView->setModel(deckModel);
|
||||
deckView->setUniformRowHeights(true);
|
||||
deckView->setSortingEnabled(true);
|
||||
deckView->sortByColumn(1, Qt::AscendingOrder);
|
||||
deckView->header()->setSectionResizeMode(QHeaderView::ResizeToContents);
|
||||
deckView->installEventFilter(&deckViewKeySignals);
|
||||
deckView->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
deckView->setSelectionMode(QAbstractItemView::ExtendedSelection);
|
||||
connect(deckView->selectionModel(), &QItemSelectionModel::currentRowChanged, this,
|
||||
&DeckEditorDeckDockWidget::updateCard);
|
||||
connect(deckView, SIGNAL(doubleClicked(const QModelIndex &)), this, SLOT(actSwapCard()));
|
||||
connect(deckView, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(decklistCustomMenu(QPoint)));
|
||||
connect(&deckViewKeySignals, SIGNAL(onShiftS()), this, SLOT(actSwapCard()));
|
||||
connect(&deckViewKeySignals, SIGNAL(onEnter()), this, SLOT(actIncrement()));
|
||||
connect(&deckViewKeySignals, SIGNAL(onCtrlAltEqual()), this, SLOT(actIncrement()));
|
||||
connect(&deckViewKeySignals, SIGNAL(onCtrlAltMinus()), this, SLOT(actDecrementSelection()));
|
||||
connect(&deckViewKeySignals, SIGNAL(onShiftRight()), this, SLOT(actIncrement()));
|
||||
connect(&deckViewKeySignals, SIGNAL(onShiftLeft()), this, SLOT(actDecrementSelection()));
|
||||
connect(&deckViewKeySignals, SIGNAL(onDelete()), this, SLOT(actRemoveCard()));
|
||||
|
||||
nameLabel = new QLabel();
|
||||
nameLabel->setObjectName("nameLabel");
|
||||
nameEdit = new LineEditUnfocusable;
|
||||
nameEdit->setMaxLength(MAX_NAME_LENGTH);
|
||||
nameEdit->setObjectName("nameEdit");
|
||||
nameLabel->setBuddy(nameEdit);
|
||||
connect(nameEdit, SIGNAL(textChanged(const QString &)), this, SLOT(updateName(const QString &)));
|
||||
commentsLabel = new QLabel();
|
||||
commentsLabel->setObjectName("commentsLabel");
|
||||
commentsEdit = new QTextEdit;
|
||||
commentsEdit->setAcceptRichText(false);
|
||||
commentsEdit->setMinimumHeight(nameEdit->minimumSizeHint().height());
|
||||
commentsEdit->setObjectName("commentsEdit");
|
||||
commentsLabel->setBuddy(commentsEdit);
|
||||
connect(commentsEdit, SIGNAL(textChanged()), this, SLOT(updateComments()));
|
||||
bannerCardLabel = new QLabel();
|
||||
bannerCardLabel->setObjectName("bannerCardLabel");
|
||||
bannerCardLabel->setText(tr("Banner Card"));
|
||||
bannerCardComboBox = new QComboBox(this);
|
||||
connect(deckModel, &DeckListModel::dataChanged, this, [this]() {
|
||||
// Delay the update to avoid race conditions
|
||||
QTimer::singleShot(100, this, &DeckEditorDeckDockWidget::updateBannerCardComboBox);
|
||||
});
|
||||
connect(bannerCardComboBox, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
|
||||
&DeckEditorDeckDockWidget::setBannerCard);
|
||||
|
||||
deckTagsDisplayWidget = new DeckPreviewDeckTagsDisplayWidget(this, deckModel->getDeckList());
|
||||
|
||||
aIncrement = new QAction(QString(), this);
|
||||
aIncrement->setIcon(QPixmap("theme:icons/increment"));
|
||||
connect(aIncrement, SIGNAL(triggered()), this, SLOT(actIncrement()));
|
||||
auto *tbIncrement = new QToolButton(this);
|
||||
tbIncrement->setDefaultAction(aIncrement);
|
||||
|
||||
aDecrement = new QAction(QString(), this);
|
||||
aDecrement->setIcon(QPixmap("theme:icons/decrement"));
|
||||
connect(aDecrement, SIGNAL(triggered()), this, SLOT(actDecrementSelection()));
|
||||
auto *tbDecrement = new QToolButton(this);
|
||||
tbDecrement->setDefaultAction(aDecrement);
|
||||
|
||||
aRemoveCard = new QAction(QString(), this);
|
||||
aRemoveCard->setIcon(QPixmap("theme:icons/remove_row"));
|
||||
connect(aRemoveCard, SIGNAL(triggered()), this, SLOT(actRemoveCard()));
|
||||
auto *tbRemoveCard = new QToolButton(this);
|
||||
tbRemoveCard->setDefaultAction(aRemoveCard);
|
||||
|
||||
aSwapCard = new QAction(QString(), this);
|
||||
aSwapCard->setIcon(QPixmap("theme:icons/swap"));
|
||||
connect(aSwapCard, SIGNAL(triggered()), this, SLOT(actSwapCard()));
|
||||
auto *tbSwapCard = new QToolButton(this);
|
||||
tbSwapCard->setDefaultAction(aSwapCard);
|
||||
|
||||
auto *upperLayout = new QGridLayout;
|
||||
upperLayout->setObjectName("upperLayout");
|
||||
upperLayout->setContentsMargins(11, 11, 11, 0);
|
||||
|
||||
upperLayout->addWidget(nameLabel, 0, 0);
|
||||
upperLayout->addWidget(nameEdit, 0, 1);
|
||||
|
||||
upperLayout->addWidget(commentsLabel, 1, 0);
|
||||
upperLayout->addWidget(commentsEdit, 1, 1);
|
||||
|
||||
upperLayout->addWidget(bannerCardLabel, 2, 0);
|
||||
upperLayout->addWidget(bannerCardComboBox, 2, 1);
|
||||
|
||||
upperLayout->addWidget(deckTagsDisplayWidget, 3, 1);
|
||||
|
||||
hashLabel1 = new QLabel();
|
||||
hashLabel1->setObjectName("hashLabel1");
|
||||
auto *hashSizePolicy = new QSizePolicy();
|
||||
hashSizePolicy->setHorizontalPolicy(QSizePolicy::Fixed);
|
||||
hashLabel1->setSizePolicy(*hashSizePolicy);
|
||||
hashLabel = new LineEditUnfocusable;
|
||||
hashLabel->setObjectName("hashLabel");
|
||||
hashLabel->setReadOnly(true);
|
||||
hashLabel->setFrame(false);
|
||||
|
||||
auto *lowerLayout = new QGridLayout;
|
||||
lowerLayout->setObjectName("lowerLayout");
|
||||
lowerLayout->addWidget(hashLabel1, 0, 0);
|
||||
lowerLayout->addWidget(hashLabel, 0, 1);
|
||||
lowerLayout->addWidget(tbIncrement, 0, 2);
|
||||
lowerLayout->addWidget(tbDecrement, 0, 3);
|
||||
lowerLayout->addWidget(tbRemoveCard, 0, 4);
|
||||
lowerLayout->addWidget(tbSwapCard, 0, 5);
|
||||
lowerLayout->addWidget(deckView, 1, 0, 1, 6);
|
||||
|
||||
// Create widgets for both layouts to make splitter work correctly
|
||||
auto *topWidget = new QWidget;
|
||||
topWidget->setLayout(upperLayout);
|
||||
auto *bottomWidget = new QWidget;
|
||||
bottomWidget->setLayout(lowerLayout);
|
||||
|
||||
auto *split = new QSplitter;
|
||||
split->setObjectName("deckSplitter");
|
||||
split->setOrientation(Qt::Vertical);
|
||||
split->setChildrenCollapsible(true);
|
||||
split->addWidget(topWidget);
|
||||
split->addWidget(bottomWidget);
|
||||
split->setStretchFactor(0, 1);
|
||||
split->setStretchFactor(1, 4);
|
||||
|
||||
auto *rightFrame = new QVBoxLayout;
|
||||
rightFrame->setObjectName("rightFrame");
|
||||
rightFrame->addWidget(split);
|
||||
|
||||
auto *deckDockContents = new QWidget();
|
||||
deckDockContents->setObjectName("deckDockContents");
|
||||
deckDockContents->setLayout(rightFrame);
|
||||
setWidget(deckDockContents);
|
||||
|
||||
refreshShortcuts();
|
||||
retranslateUi();
|
||||
}
|
||||
|
||||
CardInfoPtr DeckEditorDeckDockWidget::getCurrentCard()
|
||||
{
|
||||
QModelIndex current = deckView->selectionModel()->currentIndex();
|
||||
if (!current.isValid())
|
||||
return {};
|
||||
const QString cardName = current.sibling(current.row(), 1).data().toString();
|
||||
const QString cardProviderID = current.sibling(current.row(), 4).data().toString();
|
||||
const QModelIndex gparent = current.parent().parent();
|
||||
|
||||
if (!gparent.isValid()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const QString zoneName = gparent.sibling(gparent.row(), 1).data(Qt::EditRole).toString();
|
||||
|
||||
if (!current.model()->hasChildren(current.sibling(current.row(), 0))) {
|
||||
QString cardName = current.sibling(current.row(), 1).data().toString();
|
||||
QString providerId = current.sibling(current.row(), 4).data().toString();
|
||||
if (CardInfoPtr selectedCard =
|
||||
CardDatabaseManager::getInstance()->getCardByNameAndProviderId(cardName, providerId)) {
|
||||
return selectedCard;
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
void DeckEditorDeckDockWidget::updateCard(const QModelIndex /*¤t*/, const QModelIndex & /*previous*/)
|
||||
{
|
||||
if (CardInfoPtr card = getCurrentCard()) {
|
||||
emit cardChanged(card);
|
||||
}
|
||||
}
|
||||
|
||||
void DeckEditorDeckDockWidget::updateName(const QString &name)
|
||||
{
|
||||
deckModel->getDeckList()->setName(name);
|
||||
emit deckChanged();
|
||||
}
|
||||
|
||||
void DeckEditorDeckDockWidget::updateComments()
|
||||
{
|
||||
deckModel->getDeckList()->setComments(commentsEdit->toPlainText());
|
||||
emit deckChanged();
|
||||
}
|
||||
|
||||
void DeckEditorDeckDockWidget::updateHash()
|
||||
{
|
||||
hashLabel->setText(deckModel->getDeckList()->getDeckHash());
|
||||
emit deckChanged();
|
||||
}
|
||||
|
||||
void DeckEditorDeckDockWidget::updateBannerCardComboBox()
|
||||
{
|
||||
// Store the current text of the combo box
|
||||
QString currentText = bannerCardComboBox->currentText();
|
||||
|
||||
// Block signals temporarily
|
||||
bool wasBlocked = bannerCardComboBox->blockSignals(true);
|
||||
|
||||
// Clear the existing items in the combo box
|
||||
bannerCardComboBox->clear();
|
||||
|
||||
// Prepare the new items with deduplication
|
||||
QSet<QPair<QString, QString>> bannerCardSet;
|
||||
InnerDecklistNode *listRoot = deckModel->getDeckList()->getRoot();
|
||||
for (int i = 0; i < listRoot->size(); i++) {
|
||||
InnerDecklistNode *currentZone = dynamic_cast<InnerDecklistNode *>(listRoot->at(i));
|
||||
for (int j = 0; j < currentZone->size(); j++) {
|
||||
DecklistCardNode *currentCard = dynamic_cast<DecklistCardNode *>(currentZone->at(j));
|
||||
if (!currentCard)
|
||||
continue;
|
||||
|
||||
for (int k = 0; k < currentCard->getNumber(); ++k) {
|
||||
CardInfoPtr info = CardDatabaseManager::getInstance()->getCardByNameAndProviderId(
|
||||
currentCard->getName(), currentCard->getCardProviderId());
|
||||
if (info) {
|
||||
bannerCardSet.insert(
|
||||
QPair<QString, QString>(currentCard->getName(), currentCard->getCardProviderId()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QList<QPair<QString, QString>> pairList = bannerCardSet.values();
|
||||
|
||||
// Sort QList by the first() element of the QPair
|
||||
std::sort(pairList.begin(), pairList.end(), [](const QPair<QString, QString> &a, const QPair<QString, QString> &b) {
|
||||
return a.first.toLower() < b.first.toLower();
|
||||
});
|
||||
|
||||
for (const auto &pair : pairList) {
|
||||
QVariantMap dataMap;
|
||||
dataMap["name"] = pair.first;
|
||||
dataMap["uuid"] = pair.second;
|
||||
|
||||
bannerCardComboBox->addItem(pair.first, dataMap);
|
||||
}
|
||||
|
||||
// Try to restore the previous selection by finding the currentText
|
||||
int restoredIndex = bannerCardComboBox->findText(currentText);
|
||||
if (restoredIndex != -1) {
|
||||
bannerCardComboBox->setCurrentIndex(restoredIndex);
|
||||
} else {
|
||||
// Add a placeholder "-" and set it as the current selection
|
||||
int bannerIndex = bannerCardComboBox->findText(deckModel->getDeckList()->getBannerCard().first);
|
||||
if (bannerIndex != -1) {
|
||||
bannerCardComboBox->setCurrentIndex(bannerIndex);
|
||||
} else {
|
||||
bannerCardComboBox->insertItem(0, "-");
|
||||
bannerCardComboBox->setCurrentIndex(0);
|
||||
}
|
||||
}
|
||||
|
||||
// Restore the previous signal blocking state
|
||||
bannerCardComboBox->blockSignals(wasBlocked);
|
||||
}
|
||||
|
||||
void DeckEditorDeckDockWidget::setBannerCard(int /* changedIndex */)
|
||||
{
|
||||
QVariantMap itemData = bannerCardComboBox->itemData(bannerCardComboBox->currentIndex()).toMap();
|
||||
deckModel->getDeckList()->setBannerCard(
|
||||
QPair<QString, QString>(itemData["name"].toString(), itemData["uuid"].toString()));
|
||||
emit deckChanged();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the currently active deck for this tab
|
||||
* @param _deck The deck. Takes ownership of the object
|
||||
*/
|
||||
void DeckEditorDeckDockWidget::setDeck(DeckLoader *_deck)
|
||||
{
|
||||
deckModel->setDeckList(_deck);
|
||||
|
||||
nameEdit->setText(deckModel->getDeckList()->getName());
|
||||
commentsEdit->setText(deckModel->getDeckList()->getComments());
|
||||
bannerCardComboBox->setCurrentText(deckModel->getDeckList()->getBannerCard().first);
|
||||
updateBannerCardComboBox();
|
||||
updateHash();
|
||||
deckModel->sort(deckView->header()->sortIndicatorSection(), deckView->header()->sortIndicatorOrder());
|
||||
deckView->expandAll();
|
||||
deckView->expandAll();
|
||||
|
||||
deckTagsDisplayWidget->connectDeckList(deckModel->getDeckList());
|
||||
}
|
||||
|
||||
DeckLoader *DeckEditorDeckDockWidget::getDeckList()
|
||||
{
|
||||
return deckModel->getDeckList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the tab to the state for a blank new tab.
|
||||
*/
|
||||
void DeckEditorDeckDockWidget::cleanDeck()
|
||||
{
|
||||
deckModel->cleanList();
|
||||
nameEdit->setText(QString());
|
||||
commentsEdit->setText(QString());
|
||||
hashLabel->setText(QString());
|
||||
updateBannerCardComboBox();
|
||||
deckTagsDisplayWidget->connectDeckList(deckModel->getDeckList());
|
||||
}
|
||||
|
||||
void DeckEditorDeckDockWidget::recursiveExpand(const QModelIndex &index)
|
||||
{
|
||||
if (index.parent().isValid())
|
||||
recursiveExpand(index.parent());
|
||||
deckView->expand(index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the index of all the currently selected card nodes in the decklist table.
|
||||
* The list is in reverse order of the visual selection, so that rows can be deleted while iterating over them.
|
||||
*
|
||||
* @return A model index list containing all selected card nodes
|
||||
*/
|
||||
QModelIndexList DeckEditorDeckDockWidget::getSelectedCardNodes() const
|
||||
{
|
||||
auto selectedRows = deckView->selectionModel()->selectedRows();
|
||||
|
||||
const auto notLeafNode = [this](const auto &index) { return deckModel->hasChildren(index); };
|
||||
selectedRows.erase(std::remove_if(selectedRows.begin(), selectedRows.end(), notLeafNode), selectedRows.end());
|
||||
|
||||
std::reverse(selectedRows.begin(), selectedRows.end());
|
||||
return selectedRows;
|
||||
}
|
||||
|
||||
void DeckEditorDeckDockWidget::actIncrement()
|
||||
{
|
||||
auto selectedRows = getSelectedCardNodes();
|
||||
|
||||
for (const auto &index : selectedRows) {
|
||||
offsetCountAtIndex(index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
void DeckEditorDeckDockWidget::actSwapCard()
|
||||
{
|
||||
auto selectedRows = getSelectedCardNodes();
|
||||
|
||||
// hack to maintain the old reselection behavior when currently selected row of a single-selection gets deleted
|
||||
// TODO: remove the hack and also handle reselection when all rows of a multi-selection gets deleted
|
||||
if (selectedRows.length() == 1) {
|
||||
deckView->setSelectionMode(QAbstractItemView::SingleSelection);
|
||||
}
|
||||
|
||||
bool isModified = false;
|
||||
for (const auto ¤tIndex : selectedRows) {
|
||||
if (swapCard(currentIndex)) {
|
||||
isModified = true;
|
||||
}
|
||||
}
|
||||
|
||||
deckView->setSelectionMode(QAbstractItemView::ExtendedSelection);
|
||||
|
||||
if (isModified) {
|
||||
emit deckChanged();
|
||||
}
|
||||
|
||||
update();
|
||||
}
|
||||
|
||||
/**
|
||||
* Swaps the card at the index between the maindeck and sideboard
|
||||
*
|
||||
* @param currentIndex The index to swap.
|
||||
* @return True if the swap was successful
|
||||
*/
|
||||
bool DeckEditorDeckDockWidget::swapCard(const QModelIndex ¤tIndex)
|
||||
{
|
||||
if (!currentIndex.isValid())
|
||||
return false;
|
||||
const QString cardName = currentIndex.sibling(currentIndex.row(), 1).data().toString();
|
||||
const QString cardProviderID = currentIndex.sibling(currentIndex.row(), 4).data().toString();
|
||||
const QModelIndex gparent = currentIndex.parent().parent();
|
||||
|
||||
if (!gparent.isValid())
|
||||
return false;
|
||||
|
||||
const QString zoneName = gparent.sibling(gparent.row(), 1).data(Qt::EditRole).toString();
|
||||
offsetCountAtIndex(currentIndex, -1);
|
||||
const QString otherZoneName = zoneName == DECK_ZONE_MAIN ? DECK_ZONE_SIDE : DECK_ZONE_MAIN;
|
||||
|
||||
// Third argument (true) says create the card no matter what, even if not in DB
|
||||
QModelIndex newCardIndex = deckModel->addCard(
|
||||
cardName, CardDatabaseManager::getInstance()->getSpecificSetForCard(cardName, cardProviderID), otherZoneName,
|
||||
true);
|
||||
recursiveExpand(newCardIndex);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void DeckEditorDeckDockWidget::actDecrementCard(CardInfoPtr info, QString zoneName)
|
||||
{
|
||||
if (!info)
|
||||
return;
|
||||
if (info->getIsToken())
|
||||
zoneName = DECK_ZONE_TOKENS;
|
||||
|
||||
QString providerId = CardDatabaseManager::getInstance()->getSetInfoForCard(info).getProperty("uuid");
|
||||
QString collectorNumber = CardDatabaseManager::getInstance()->getSetInfoForCard(info).getProperty("num");
|
||||
|
||||
QModelIndex idx = deckModel->findCard(info->getName(), zoneName, providerId, collectorNumber);
|
||||
if (!idx.isValid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
deckView->clearSelection();
|
||||
deckView->setCurrentIndex(idx);
|
||||
offsetCountAtIndex(idx, -1);
|
||||
}
|
||||
|
||||
void DeckEditorDeckDockWidget::actDecrementSelection()
|
||||
{
|
||||
auto selectedRows = getSelectedCardNodes();
|
||||
|
||||
// hack to maintain the old reselection behavior when currently selected row of a single-selection gets deleted
|
||||
// TODO: remove the hack and also handle reselection when all rows of a multi-selection gets deleted
|
||||
if (selectedRows.length() == 1) {
|
||||
deckView->setSelectionMode(QAbstractItemView::SingleSelection);
|
||||
}
|
||||
|
||||
for (const auto &index : selectedRows) {
|
||||
offsetCountAtIndex(index, -1);
|
||||
}
|
||||
|
||||
deckView->setSelectionMode(QAbstractItemView::ExtendedSelection);
|
||||
}
|
||||
|
||||
void DeckEditorDeckDockWidget::actRemoveCard()
|
||||
{
|
||||
auto selectedRows = getSelectedCardNodes();
|
||||
|
||||
// hack to maintain the old reselection behavior when currently selected row of a single-selection gets deleted
|
||||
// TODO: remove the hack and also handle reselection when all rows of a multi-selection gets deleted
|
||||
if (selectedRows.length() == 1) {
|
||||
deckView->setSelectionMode(QAbstractItemView::SingleSelection);
|
||||
}
|
||||
|
||||
bool isModified = false;
|
||||
for (const auto &index : selectedRows) {
|
||||
if (!index.isValid() || deckModel->hasChildren(index)) {
|
||||
continue;
|
||||
}
|
||||
deckModel->removeRow(index.row(), index.parent());
|
||||
isModified = true;
|
||||
}
|
||||
|
||||
deckView->setSelectionMode(QAbstractItemView::ExtendedSelection);
|
||||
|
||||
if (isModified) {
|
||||
emit deckChanged();
|
||||
}
|
||||
}
|
||||
|
||||
void DeckEditorDeckDockWidget::offsetCountAtIndex(const QModelIndex &idx, int offset)
|
||||
{
|
||||
if (!idx.isValid() || deckModel->hasChildren(idx)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const QModelIndex numberIndex = idx.sibling(idx.row(), 0);
|
||||
const int count = deckModel->data(numberIndex, Qt::EditRole).toInt();
|
||||
const int new_count = count + offset;
|
||||
if (new_count <= 0)
|
||||
deckModel->removeRow(idx.row(), idx.parent());
|
||||
else
|
||||
deckModel->setData(numberIndex, new_count, Qt::EditRole);
|
||||
|
||||
emit deckChanged();
|
||||
}
|
||||
|
||||
void DeckEditorDeckDockWidget::decklistCustomMenu(QPoint point)
|
||||
{
|
||||
QMenu menu;
|
||||
const CardInfoPtr info = getCurrentCard();
|
||||
|
||||
QAction *selectPrinting = menu.addAction(tr("Select Printing"));
|
||||
|
||||
connect(selectPrinting, &QAction::triggered, deckEditor, &AbstractTabDeckEditor::showPrintingSelector);
|
||||
|
||||
menu.exec(deckView->mapToGlobal(point));
|
||||
}
|
||||
|
||||
void DeckEditorDeckDockWidget::refreshShortcuts()
|
||||
{
|
||||
ShortcutsSettings &shortcuts = SettingsCache::instance().shortcuts();
|
||||
aRemoveCard->setShortcuts(shortcuts.getShortcut("TabDeckEditor/aRemoveCard"));
|
||||
aIncrement->setShortcuts(shortcuts.getShortcut("TabDeckEditor/aIncrement"));
|
||||
aDecrement->setShortcuts(shortcuts.getShortcut("TabDeckEditor/aDecrement"));
|
||||
}
|
||||
|
||||
void DeckEditorDeckDockWidget::retranslateUi()
|
||||
{
|
||||
setWindowTitle(tr("Deck"));
|
||||
|
||||
nameLabel->setText(tr("Deck &name:"));
|
||||
commentsLabel->setText(tr("&Comments:"));
|
||||
hashLabel1->setText(tr("Hash:"));
|
||||
|
||||
aIncrement->setText(tr("&Increment number"));
|
||||
aDecrement->setText(tr("&Decrement number"));
|
||||
aRemoveCard->setText(tr("&Remove row"));
|
||||
aSwapCard->setText(tr("Swap card to/from sideboard"));
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
#ifndef DECK_EDITOR_DECK_DOCK_WIDGET_H
|
||||
#define DECK_EDITOR_DECK_DOCK_WIDGET_H
|
||||
|
||||
#include "../../../../deck/custom_line_edit.h"
|
||||
#include "../../../../game/cards/card_database.h"
|
||||
#include "../../../game_logic/key_signals.h"
|
||||
#include "../../../tabs/abstract_tab_deck_editor.h"
|
||||
#include "../visual_deck_storage/deck_preview/deck_preview_deck_tags_display_widget.h"
|
||||
|
||||
#include <QComboBox>
|
||||
#include <QDockWidget>
|
||||
#include <QLabel>
|
||||
#include <QTextEdit>
|
||||
#include <QTreeView>
|
||||
|
||||
class DeckListModel;
|
||||
class AbstractTabDeckEditor;
|
||||
class DeckEditorDeckDockWidget : public QDockWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit DeckEditorDeckDockWidget(AbstractTabDeckEditor *parent);
|
||||
DeckListModel *deckModel;
|
||||
QTreeView *deckView;
|
||||
QComboBox *bannerCardComboBox;
|
||||
void createDeckDock();
|
||||
CardInfoPtr getCurrentCard();
|
||||
void retranslateUi();
|
||||
QString getDeckName()
|
||||
{
|
||||
return nameEdit->text();
|
||||
}
|
||||
QString getSimpleDeckName()
|
||||
{
|
||||
return nameEdit->text().simplified();
|
||||
}
|
||||
|
||||
public slots:
|
||||
void cleanDeck();
|
||||
void updateBannerCardComboBox();
|
||||
void setDeck(DeckLoader *_deck);
|
||||
DeckLoader *getDeckList();
|
||||
void actIncrement();
|
||||
bool swapCard(const QModelIndex &idx);
|
||||
void actDecrementCard(CardInfoPtr info, QString zoneName);
|
||||
void actDecrementSelection();
|
||||
void actSwapCard();
|
||||
void actRemoveCard();
|
||||
void offsetCountAtIndex(const QModelIndex &idx, int offset);
|
||||
|
||||
signals:
|
||||
void deckChanged();
|
||||
void cardChanged(CardInfoPtr _card);
|
||||
|
||||
private:
|
||||
AbstractTabDeckEditor *deckEditor;
|
||||
KeySignals deckViewKeySignals;
|
||||
QLabel *nameLabel;
|
||||
LineEditUnfocusable *nameEdit;
|
||||
QLabel *commentsLabel;
|
||||
QTextEdit *commentsEdit;
|
||||
QLabel *bannerCardLabel;
|
||||
DeckPreviewDeckTagsDisplayWidget *deckTagsDisplayWidget;
|
||||
QLabel *hashLabel1;
|
||||
LineEditUnfocusable *hashLabel;
|
||||
|
||||
QAction *aRemoveCard, *aIncrement, *aDecrement, *aSwapCard;
|
||||
|
||||
void recursiveExpand(const QModelIndex &index);
|
||||
QModelIndexList getSelectedCardNodes() const;
|
||||
|
||||
private slots:
|
||||
void decklistCustomMenu(QPoint point);
|
||||
void updateCard(QModelIndex, const QModelIndex ¤t);
|
||||
void updateName(const QString &name);
|
||||
void updateComments();
|
||||
void setBannerCard(int);
|
||||
void updateHash();
|
||||
void refreshShortcuts();
|
||||
};
|
||||
|
||||
#endif // DECK_EDITOR_DECK_DOCK_WIDGET_H
|
||||
@@ -0,0 +1,143 @@
|
||||
#include "deck_editor_filter_dock_widget.h"
|
||||
|
||||
#include "../../../../game/cards/card_database_model.h"
|
||||
#include "../../../../game/filters/filter_builder.h"
|
||||
#include "../../../../game/filters/filter_tree_model.h"
|
||||
#include "../../../../settings/cache_settings.h"
|
||||
|
||||
#include <QGridLayout>
|
||||
#include <QMenu>
|
||||
#include <QToolButton>
|
||||
|
||||
DeckEditorFilterDockWidget::DeckEditorFilterDockWidget(AbstractTabDeckEditor *parent)
|
||||
: QDockWidget(parent), deckEditor(parent)
|
||||
{
|
||||
setObjectName("filterDock");
|
||||
|
||||
setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetFloatable | QDockWidget::DockWidgetMovable);
|
||||
|
||||
createFiltersDock();
|
||||
retranslateUi();
|
||||
}
|
||||
|
||||
void DeckEditorFilterDockWidget::createFiltersDock()
|
||||
{
|
||||
filterModel = new FilterTreeModel();
|
||||
filterModel->setObjectName("filterModel");
|
||||
deckEditor->filterTreeChanged(filterModel->filterTree());
|
||||
filterView = new QTreeView;
|
||||
filterView->setObjectName("filterView");
|
||||
filterView->setModel(filterModel);
|
||||
filterView->setUniformRowHeights(true);
|
||||
filterView->setHeaderHidden(true);
|
||||
filterView->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
filterView->installEventFilter(&filterViewKeySignals);
|
||||
connect(filterModel, SIGNAL(layoutChanged()), filterView, SLOT(expandAll()));
|
||||
connect(filterView, SIGNAL(customContextMenuRequested(const QPoint &)), this,
|
||||
SLOT(filterViewCustomContextMenu(const QPoint &)));
|
||||
connect(&filterViewKeySignals, SIGNAL(onDelete()), this, SLOT(actClearFilterOne()));
|
||||
|
||||
auto *filterBuilder = new FilterBuilder;
|
||||
filterBuilder->setObjectName("filterBuilder");
|
||||
connect(filterBuilder, SIGNAL(add(const CardFilter *)), filterModel, SLOT(addFilter(const CardFilter *)));
|
||||
|
||||
aClearFilterOne = new QAction(QString(), this);
|
||||
aClearFilterOne->setIcon(QPixmap("theme:icons/decrement"));
|
||||
connect(aClearFilterOne, SIGNAL(triggered()), this, SLOT(actClearFilterOne()));
|
||||
|
||||
aClearFilterAll = new QAction(QString(), this);
|
||||
aClearFilterAll->setIcon(QPixmap("theme:icons/clearsearch"));
|
||||
connect(aClearFilterAll, SIGNAL(triggered()), this, SLOT(actClearFilterAll()));
|
||||
|
||||
auto *filterDelOne = new QToolButton();
|
||||
filterDelOne->setObjectName("filterDelOne");
|
||||
filterDelOne->setDefaultAction(aClearFilterOne);
|
||||
filterDelOne->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
|
||||
|
||||
auto *filterDelAll = new QToolButton();
|
||||
filterDelAll->setObjectName("filterDelAll");
|
||||
filterDelAll->setDefaultAction(aClearFilterAll);
|
||||
filterDelAll->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
|
||||
|
||||
auto *filterLayout = new QGridLayout;
|
||||
filterLayout->setObjectName("filterLayout");
|
||||
filterLayout->setContentsMargins(0, 0, 0, 0);
|
||||
filterLayout->addWidget(filterBuilder, 0, 0, 1, 3);
|
||||
filterLayout->addWidget(filterView, 1, 0, 1, 3);
|
||||
filterLayout->addWidget(filterDelOne, 2, 0, 1, 1);
|
||||
filterLayout->addWidget(filterDelAll, 2, 2, 1, 1);
|
||||
|
||||
filterBox = new QWidget();
|
||||
filterBox->setObjectName("filterBox");
|
||||
filterBox->setLayout(filterLayout);
|
||||
|
||||
auto *filterFrame = new QVBoxLayout;
|
||||
filterFrame->setObjectName("filterFrame");
|
||||
filterFrame->addWidget(filterBox);
|
||||
|
||||
auto *filterDockContents = new QWidget(this);
|
||||
filterDockContents->setObjectName("filterDockContents");
|
||||
filterDockContents->setLayout(filterFrame);
|
||||
setWidget(filterDockContents);
|
||||
|
||||
installEventFilter(deckEditor);
|
||||
connect(this, &QDockWidget::topLevelChanged, deckEditor, &AbstractTabDeckEditor::dockTopLevelChanged);
|
||||
}
|
||||
|
||||
void DeckEditorFilterDockWidget::filterViewCustomContextMenu(const QPoint &point)
|
||||
{
|
||||
QMenu menu;
|
||||
QAction *action;
|
||||
QModelIndex idx;
|
||||
|
||||
idx = filterView->indexAt(point);
|
||||
if (!idx.isValid())
|
||||
return;
|
||||
|
||||
action = menu.addAction(QString("delete"));
|
||||
action->setData(point);
|
||||
connect(&menu, &QMenu::triggered, this, &DeckEditorFilterDockWidget::filterRemove);
|
||||
menu.exec(filterView->mapToGlobal(point));
|
||||
}
|
||||
|
||||
void DeckEditorFilterDockWidget::filterRemove(const QAction *action)
|
||||
{
|
||||
QPoint point;
|
||||
QModelIndex idx;
|
||||
|
||||
point = action->data().toPoint();
|
||||
idx = filterView->indexAt(point);
|
||||
if (!idx.isValid())
|
||||
return;
|
||||
|
||||
filterModel->removeRow(idx.row(), idx.parent());
|
||||
}
|
||||
|
||||
void DeckEditorFilterDockWidget::actClearFilterAll()
|
||||
{
|
||||
emit clearAllDatabaseFilters();
|
||||
}
|
||||
|
||||
void DeckEditorFilterDockWidget::actClearFilterOne()
|
||||
{
|
||||
QModelIndexList selIndexes = filterView->selectionModel()->selectedIndexes();
|
||||
for (QModelIndex idx : selIndexes) {
|
||||
filterModel->removeRow(idx.row(), idx.parent());
|
||||
}
|
||||
}
|
||||
|
||||
void DeckEditorFilterDockWidget::refreshShortcuts()
|
||||
{
|
||||
ShortcutsSettings &shortcuts = SettingsCache::instance().shortcuts();
|
||||
|
||||
aClearFilterAll->setShortcuts(shortcuts.getShortcut("TabDeckEditor/aClearFilterAll"));
|
||||
aClearFilterOne->setShortcuts(shortcuts.getShortcut("TabDeckEditor/aClearFilterOne"));
|
||||
}
|
||||
|
||||
void DeckEditorFilterDockWidget::retranslateUi()
|
||||
{
|
||||
setWindowTitle(tr("Filters"));
|
||||
|
||||
aClearFilterAll->setText(tr("&Clear all filters"));
|
||||
aClearFilterOne->setText(tr("Delete selected"));
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
#ifndef DECK_EDITOR_FILTER_DOCK_WIDGET_H
|
||||
#define DECK_EDITOR_FILTER_DOCK_WIDGET_H
|
||||
|
||||
#include "../../../game_logic/key_signals.h"
|
||||
#include "../../../tabs/abstract_tab_deck_editor.h"
|
||||
|
||||
#include <QDockWidget>
|
||||
#include <QTreeView>
|
||||
|
||||
class FilterTreeModel;
|
||||
class AbstractTabDeckEditor;
|
||||
class DeckEditorFilterDockWidget : public QDockWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit DeckEditorFilterDockWidget(AbstractTabDeckEditor *parent);
|
||||
void createFiltersDock();
|
||||
void retranslateUi();
|
||||
QAction *aClearFilterAll, *aClearFilterOne;
|
||||
|
||||
signals:
|
||||
void clearAllDatabaseFilters();
|
||||
|
||||
private:
|
||||
AbstractTabDeckEditor *deckEditor;
|
||||
FilterTreeModel *filterModel;
|
||||
QTreeView *filterView;
|
||||
KeySignals filterViewKeySignals;
|
||||
QWidget *filterBox;
|
||||
|
||||
private slots:
|
||||
void filterViewCustomContextMenu(const QPoint &point);
|
||||
void filterRemove(const QAction *action);
|
||||
void actClearFilterAll();
|
||||
void actClearFilterOne();
|
||||
void refreshShortcuts();
|
||||
};
|
||||
|
||||
#endif // DECK_EDITOR_FILTER_DOCK_WIDGET_H
|
||||
@@ -0,0 +1,41 @@
|
||||
#include "deck_editor_printing_selector_dock_widget.h"
|
||||
|
||||
#include "../../../tabs/abstract_tab_deck_editor.h"
|
||||
|
||||
#include <QVBoxLayout>
|
||||
|
||||
DeckEditorPrintingSelectorDockWidget::DeckEditorPrintingSelectorDockWidget(AbstractTabDeckEditor *parent)
|
||||
: QDockWidget(parent), deckEditor(parent)
|
||||
{
|
||||
setObjectName("printingSelectorDock");
|
||||
|
||||
setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea);
|
||||
setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetFloatable | QDockWidget::DockWidgetMovable);
|
||||
setFloating(false);
|
||||
|
||||
createPrintingSelectorDock();
|
||||
|
||||
retranslateUi();
|
||||
}
|
||||
|
||||
void DeckEditorPrintingSelectorDockWidget::createPrintingSelectorDock()
|
||||
{
|
||||
printingSelector = new PrintingSelector(this, deckEditor);
|
||||
printingSelector->setObjectName("printingSelector");
|
||||
auto *printingSelectorFrame = new QVBoxLayout;
|
||||
printingSelectorFrame->setObjectName("printingSelectorFrame");
|
||||
printingSelectorFrame->addWidget(printingSelector);
|
||||
|
||||
auto *printingSelectorDockContents = new QWidget();
|
||||
printingSelectorDockContents->setObjectName("printingSelectorDockContents");
|
||||
printingSelectorDockContents->setLayout(printingSelectorFrame);
|
||||
setWidget(printingSelectorDockContents);
|
||||
|
||||
installEventFilter(deckEditor);
|
||||
connect(this, &QDockWidget::topLevelChanged, deckEditor, &AbstractTabDeckEditor::dockTopLevelChanged);
|
||||
}
|
||||
|
||||
void DeckEditorPrintingSelectorDockWidget::retranslateUi()
|
||||
{
|
||||
setWindowTitle(tr("Printing Selector"));
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
#ifndef DECK_EDITOR_PRINTING_SELECTOR_DOCK_WIDGET_H
|
||||
#define DECK_EDITOR_PRINTING_SELECTOR_DOCK_WIDGET_H
|
||||
|
||||
#include "../../../tabs/abstract_tab_deck_editor.h"
|
||||
#include "../printing_selector/printing_selector.h"
|
||||
|
||||
#include <QDockWidget>
|
||||
|
||||
class TabDeckEditor;
|
||||
class DeckEditorPrintingSelectorDockWidget : public QDockWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit DeckEditorPrintingSelectorDockWidget(AbstractTabDeckEditor *parent);
|
||||
void createPrintingSelectorDock();
|
||||
void retranslateUi();
|
||||
PrintingSelector *printingSelector;
|
||||
|
||||
private:
|
||||
AbstractTabDeckEditor *deckEditor;
|
||||
};
|
||||
|
||||
#endif // DECK_EDITOR_PRINTING_SELECTOR_DOCK_WIDGET_H
|
||||
@@ -1,5 +1,7 @@
|
||||
#include "banner_widget.h"
|
||||
|
||||
#include "../../../../../client/ui/pixel_map_generator.h"
|
||||
|
||||
#include <QLinearGradient>
|
||||
#include <QMouseEvent>
|
||||
#include <QPainter>
|
||||
@@ -8,19 +10,26 @@
|
||||
BannerWidget::BannerWidget(QWidget *parent, const QString &text, Qt::Orientation orientation, int transparency)
|
||||
: QWidget(parent), gradientOrientation(orientation), transparency(qBound(0, transparency, 100))
|
||||
{
|
||||
auto layout = new QHBoxLayout(this);
|
||||
|
||||
iconLabel = new QLabel(this);
|
||||
iconLabel->setAlignment(Qt::AlignLeft | Qt::AlignVCenter);
|
||||
|
||||
// Create the banner label and set properties
|
||||
bannerLabel = new QLabel(text, this);
|
||||
bannerLabel->setAlignment(Qt::AlignCenter);
|
||||
bannerLabel->setStyleSheet("font-size: 24px; font-weight: bold; color: white;");
|
||||
|
||||
// Layout to center the banner label
|
||||
layout = new QVBoxLayout(this);
|
||||
layout->addWidget(iconLabel);
|
||||
layout->addWidget(bannerLabel);
|
||||
layout->addWidget(new QLabel(this)); // add dummy label to force text label to be centered
|
||||
setLayout(layout);
|
||||
|
||||
// Set minimum height for the widget
|
||||
setMinimumHeight(50);
|
||||
connect(this, &BannerWidget::buddyVisibilityChanged, this, &BannerWidget::toggleBuddyVisibility);
|
||||
|
||||
updateDropdownIconState();
|
||||
}
|
||||
|
||||
void BannerWidget::mousePressEvent(QMouseEvent *event)
|
||||
@@ -36,10 +45,33 @@ void BannerWidget::setText(const QString &text) const
|
||||
bannerLabel->setText(text);
|
||||
}
|
||||
|
||||
void BannerWidget::setClickable(bool _clickable)
|
||||
{
|
||||
clickable = _clickable;
|
||||
updateDropdownIconState();
|
||||
}
|
||||
|
||||
void BannerWidget::setBuddy(QWidget *_buddy)
|
||||
{
|
||||
buddy = _buddy;
|
||||
updateDropdownIconState();
|
||||
}
|
||||
|
||||
void BannerWidget::toggleBuddyVisibility() const
|
||||
{
|
||||
if (buddy) {
|
||||
buddy->setVisible(!buddy->isVisible());
|
||||
updateDropdownIconState();
|
||||
}
|
||||
}
|
||||
|
||||
void BannerWidget::updateDropdownIconState() const
|
||||
{
|
||||
if (clickable && buddy) {
|
||||
iconLabel->setPixmap(DropdownIconPixmapGenerator::generatePixmap(24, !buddy->isHidden()));
|
||||
} else {
|
||||
// we cannot directly hide the iconLabel, since it's needed to center the text; set an empty image instead
|
||||
iconLabel->setPixmap(QPixmap());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,33 +16,28 @@ public:
|
||||
int transparency = 80);
|
||||
void mousePressEvent(QMouseEvent *event) override;
|
||||
void setText(const QString &text) const;
|
||||
void setBuddy(QWidget *_buddy)
|
||||
{
|
||||
buddy = _buddy;
|
||||
}
|
||||
void setClickable(bool _clickable);
|
||||
void setBuddy(QWidget *_buddy);
|
||||
QString getText() const
|
||||
{
|
||||
return bannerLabel->text();
|
||||
}
|
||||
void setClickable(bool _clickable)
|
||||
{
|
||||
clickable = _clickable;
|
||||
}
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *event) override;
|
||||
|
||||
private:
|
||||
QVBoxLayout *layout;
|
||||
QLabel *iconLabel;
|
||||
QLabel *bannerLabel;
|
||||
Qt::Orientation gradientOrientation;
|
||||
int transparency; // Transparency percentage for the gradient
|
||||
QWidget *buddy;
|
||||
QWidget *buddy = nullptr;
|
||||
bool clickable = true;
|
||||
signals:
|
||||
void buddyVisibilityChanged();
|
||||
private slots:
|
||||
void toggleBuddyVisibility() const;
|
||||
void updateDropdownIconState() const;
|
||||
};
|
||||
|
||||
#endif // BANNER_WIDGET_H
|
||||
|
||||
@@ -7,8 +7,8 @@
|
||||
#include <QWidget>
|
||||
#include <qscrollarea.h>
|
||||
|
||||
inline Q_LOGGING_CATEGORY(FlowWidgetLog, "flow_widget");
|
||||
inline Q_LOGGING_CATEGORY(FlowWidgetSizeLog, "flow_widget.size");
|
||||
inline Q_LOGGING_CATEGORY(FlowWidgetLog, "flow_widget", QtInfoMsg);
|
||||
inline Q_LOGGING_CATEGORY(FlowWidgetSizeLog, "flow_widget.size", QtInfoMsg);
|
||||
|
||||
class FlowWidget final : public QWidget
|
||||
{
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
* @param setInfoForCard The set information for the card.
|
||||
*/
|
||||
AllZonesCardAmountWidget::AllZonesCardAmountWidget(QWidget *parent,
|
||||
TabDeckEditor *deckEditor,
|
||||
AbstractTabDeckEditor *deckEditor,
|
||||
DeckListModel *deckModel,
|
||||
QTreeView *deckView,
|
||||
QSlider *cardSizeSlider,
|
||||
|
||||
@@ -12,7 +12,7 @@ class AllZonesCardAmountWidget : public QWidget
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit AllZonesCardAmountWidget(QWidget *parent,
|
||||
TabDeckEditor *deckEditor,
|
||||
AbstractTabDeckEditor *deckEditor,
|
||||
DeckListModel *deckModel,
|
||||
QTreeView *deckView,
|
||||
QSlider *cardSizeSlider,
|
||||
@@ -31,7 +31,7 @@ public slots:
|
||||
|
||||
private:
|
||||
QVBoxLayout *layout;
|
||||
TabDeckEditor *deckEditor;
|
||||
AbstractTabDeckEditor *deckEditor;
|
||||
DeckListModel *deckModel;
|
||||
QTreeView *deckView;
|
||||
QSlider *cardSizeSlider;
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
* @param zoneName The zone name (e.g., DECK_ZONE_MAIN or DECK_ZONE_SIDE).
|
||||
*/
|
||||
CardAmountWidget::CardAmountWidget(QWidget *parent,
|
||||
TabDeckEditor *deckEditor,
|
||||
AbstractTabDeckEditor *deckEditor,
|
||||
DeckListModel *deckModel,
|
||||
QTreeView *deckView,
|
||||
QSlider *cardSizeSlider,
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
#include "../../../../deck/deck_list_model.h"
|
||||
#include "../../../../deck/deck_loader.h"
|
||||
#include "../../../../game/cards/card_database.h"
|
||||
#include "../../../tabs/tab_deck_editor.h"
|
||||
#include "../../../tabs/abstract_tab_deck_editor.h"
|
||||
#include "../general/display/dynamic_font_size_push_button.h"
|
||||
|
||||
#include <QHBoxLayout>
|
||||
@@ -18,7 +18,7 @@ class CardAmountWidget : public QWidget
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit CardAmountWidget(QWidget *parent,
|
||||
TabDeckEditor *deckEditor,
|
||||
AbstractTabDeckEditor *deckEditor,
|
||||
DeckListModel *deckModel,
|
||||
QTreeView *deckView,
|
||||
QSlider *cardSizeSlider,
|
||||
@@ -36,7 +36,7 @@ protected:
|
||||
void showEvent(QShowEvent *event) override;
|
||||
|
||||
private:
|
||||
TabDeckEditor *deckEditor;
|
||||
AbstractTabDeckEditor *deckEditor;
|
||||
DeckListModel *deckModel;
|
||||
QTreeView *deckView;
|
||||
QSlider *cardSizeSlider;
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
#include "printing_selector.h"
|
||||
|
||||
#include "../../../../settings/cache_settings.h"
|
||||
#include "../../picture_loader/picture_loader.h"
|
||||
#include "printing_selector_card_display_widget.h"
|
||||
#include "printing_selector_card_search_widget.h"
|
||||
#include "printing_selector_card_selection_widget.h"
|
||||
#include "printing_selector_card_sorting_widget.h"
|
||||
#include "printing_selector_view_options_toolbar_widget.h"
|
||||
|
||||
#include <QFrame>
|
||||
#include <QScrollBar>
|
||||
#include <qboxlayout.h>
|
||||
|
||||
/**
|
||||
* @brief Constructs a PrintingSelector widget to display and manage card printings.
|
||||
@@ -22,36 +24,72 @@
|
||||
* @param deckModel The DeckListModel instance that provides data for the deck's contents.
|
||||
* @param deckView The QTreeView instance used to display the deck and its contents.
|
||||
*/
|
||||
PrintingSelector::PrintingSelector(QWidget *parent,
|
||||
TabDeckEditor *deckEditor,
|
||||
DeckListModel *deckModel,
|
||||
QTreeView *deckView)
|
||||
: QWidget(parent), deckEditor(deckEditor), deckModel(deckModel), deckView(deckView)
|
||||
PrintingSelector::PrintingSelector(QWidget *parent, AbstractTabDeckEditor *_deckEditor)
|
||||
: QWidget(parent), deckEditor(_deckEditor), deckModel(deckEditor->deckDockWidget->deckModel),
|
||||
deckView(deckEditor->deckDockWidget->deckView)
|
||||
{
|
||||
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
||||
layout = new QVBoxLayout();
|
||||
layout = new QVBoxLayout(this);
|
||||
setLayout(layout);
|
||||
|
||||
if (PictureLoader::hasCustomArt()) {
|
||||
QFrame *warningFrame = new QFrame(this);
|
||||
warningFrame->setFrameShape(QFrame::StyledPanel);
|
||||
|
||||
warningLabel = new QLabel(this);
|
||||
warningLabel->setTextFormat(Qt::RichText);
|
||||
warningLabel->setWordWrap(true);
|
||||
|
||||
auto *warningLayout = new QVBoxLayout(warningFrame);
|
||||
warningFrame->setLayout(warningLayout);
|
||||
|
||||
warningLayout->addWidget(warningLabel);
|
||||
|
||||
layout->addWidget(warningFrame);
|
||||
} else {
|
||||
warningLabel = nullptr;
|
||||
}
|
||||
|
||||
widgetLoadingBufferTimer = new QTimer(this);
|
||||
|
||||
// Initialize toolbar and widgets
|
||||
viewOptionsToolbar = new PrintingSelectorViewOptionsToolbarWidget(this, this);
|
||||
layout->addWidget(viewOptionsToolbar);
|
||||
flowWidget = new FlowWidget(this, Qt::Horizontal, Qt::ScrollBarAlwaysOff, Qt::ScrollBarAsNeeded);
|
||||
|
||||
sortToolBar = new PrintingSelectorCardSortingWidget(this);
|
||||
sortToolBar->setVisible(SettingsCache::instance().getPrintingSelectorSortOptionsVisible());
|
||||
layout->addWidget(sortToolBar);
|
||||
sortToolBar->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
|
||||
|
||||
displayOptionsWidget = new SettingsButtonWidget(this);
|
||||
displayOptionsWidget->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Preferred);
|
||||
|
||||
// Create the checkbox for navigation buttons visibility
|
||||
navigationCheckBox = new QCheckBox(this);
|
||||
navigationCheckBox->setChecked(SettingsCache::instance().getPrintingSelectorNavigationButtonsVisible());
|
||||
connect(navigationCheckBox, &QCheckBox::QT_STATE_CHANGED, this,
|
||||
&PrintingSelector::toggleVisibilityNavigationButtons);
|
||||
connect(navigationCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(),
|
||||
&SettingsCache::setPrintingSelectorNavigationButtonsVisible);
|
||||
|
||||
cardSizeWidget =
|
||||
new CardSizeWidget(displayOptionsWidget, flowWidget, SettingsCache::instance().getPrintingSelectorCardSize());
|
||||
|
||||
displayOptionsWidget->addSettingsWidget(sortToolBar);
|
||||
displayOptionsWidget->addSettingsWidget(navigationCheckBox);
|
||||
displayOptionsWidget->addSettingsWidget(cardSizeWidget);
|
||||
|
||||
sortAndOptionsContainer = new QWidget(this);
|
||||
sortAndOptionsLayout = new QHBoxLayout(sortAndOptionsContainer);
|
||||
sortAndOptionsLayout->setSpacing(3);
|
||||
sortAndOptionsLayout->setContentsMargins(0, 0, 0, 0);
|
||||
sortAndOptionsContainer->setLayout(sortAndOptionsLayout);
|
||||
|
||||
searchBar = new PrintingSelectorCardSearchWidget(this);
|
||||
searchBar->setVisible(SettingsCache::instance().getPrintingSelectorSearchBarVisible());
|
||||
layout->addWidget(searchBar);
|
||||
|
||||
flowWidget = new FlowWidget(this, Qt::Horizontal, Qt::ScrollBarAlwaysOff, Qt::ScrollBarAsNeeded);
|
||||
sortAndOptionsLayout->addWidget(searchBar);
|
||||
sortAndOptionsLayout->addWidget(displayOptionsWidget);
|
||||
|
||||
layout->addWidget(sortAndOptionsContainer);
|
||||
|
||||
layout->addWidget(flowWidget);
|
||||
|
||||
cardSizeWidget = new CardSizeWidget(this, flowWidget, SettingsCache::instance().getPrintingSelectorCardSize());
|
||||
cardSizeWidget->setVisible(SettingsCache::instance().getPrintingSelectorCardSizeSliderVisible());
|
||||
layout->addWidget(cardSizeWidget);
|
||||
|
||||
cardSelectionBar = new PrintingSelectorCardSelectionWidget(this);
|
||||
cardSelectionBar->setVisible(SettingsCache::instance().getPrintingSelectorNavigationButtonsVisible());
|
||||
layout->addWidget(cardSelectionBar);
|
||||
@@ -59,6 +97,19 @@ PrintingSelector::PrintingSelector(QWidget *parent,
|
||||
// Connect deck model data change signal to update display
|
||||
connect(deckModel, &DeckListModel::rowsInserted, this, &PrintingSelector::printingsInDeckChanged);
|
||||
connect(deckModel, &DeckListModel::rowsRemoved, this, &PrintingSelector::printingsInDeckChanged);
|
||||
|
||||
retranslateUi();
|
||||
}
|
||||
|
||||
void PrintingSelector::retranslateUi()
|
||||
{
|
||||
navigationCheckBox->setText(tr("Display Navigation Buttons"));
|
||||
|
||||
if (warningLabel) {
|
||||
warningLabel->setText(
|
||||
tr("<b>Warning:</b> You appear to be using custom card art, which has known bugs when also "
|
||||
"using the printing selector in this version of Cockatrice."));
|
||||
}
|
||||
}
|
||||
|
||||
void PrintingSelector::printingsInDeckChanged()
|
||||
@@ -206,36 +257,6 @@ void PrintingSelector::getAllSetsForCurrentCard()
|
||||
widgetLoadingBufferTimer->start(0); // Process as soon as possible
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Toggles the visibility of the sorting options toolbar.
|
||||
*
|
||||
* @param _state The visibility state to set.
|
||||
*/
|
||||
void PrintingSelector::toggleVisibilitySortOptions(bool _state)
|
||||
{
|
||||
sortToolBar->setVisible(_state);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Toggles the visibility of the search bar.
|
||||
*
|
||||
* @param _state The visibility state to set.
|
||||
*/
|
||||
void PrintingSelector::toggleVisibilitySearchBar(bool _state)
|
||||
{
|
||||
searchBar->setVisible(_state);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Toggles the visibility of the card size slider.
|
||||
*
|
||||
* @param _state The visibility state to set.
|
||||
*/
|
||||
void PrintingSelector::toggleVisibilityCardSizeSlider(bool _state)
|
||||
{
|
||||
cardSizeWidget->setVisible(_state);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Toggles the visibility of the navigation buttons.
|
||||
*
|
||||
|
||||
@@ -5,7 +5,9 @@
|
||||
#include "../../../../game/cards/card_database.h"
|
||||
#include "../cards/card_size_widget.h"
|
||||
#include "../general/layout_containers/flow_widget.h"
|
||||
#include "../quick_settings/settings_button_widget.h"
|
||||
|
||||
#include <QCheckBox>
|
||||
#include <QLabel>
|
||||
#include <QTreeView>
|
||||
#include <QVBoxLayout>
|
||||
@@ -16,24 +18,23 @@
|
||||
class PrintingSelectorCardSearchWidget;
|
||||
class PrintingSelectorCardSelectionWidget;
|
||||
class PrintingSelectorCardSortingWidget;
|
||||
class PrintingSelectorViewOptionsToolbarWidget;
|
||||
class TabDeckEditor;
|
||||
class PrintingSelectorViewOptionsWidget;
|
||||
class AbstractTabDeckEditor;
|
||||
class PrintingSelector : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
PrintingSelector(QWidget *parent, TabDeckEditor *deckEditor, DeckListModel *deckModel, QTreeView *deckView);
|
||||
PrintingSelector(QWidget *parent, AbstractTabDeckEditor *deckEditor);
|
||||
|
||||
void setCard(const CardInfoPtr &newCard, const QString &_currentZone);
|
||||
void getAllSetsForCurrentCard();
|
||||
|
||||
public slots:
|
||||
void retranslateUi();
|
||||
void updateDisplay();
|
||||
void selectPreviousCard();
|
||||
void selectNextCard();
|
||||
void toggleVisibilitySortOptions(bool _state);
|
||||
void toggleVisibilitySearchBar(bool _state);
|
||||
void toggleVisibilityCardSizeSlider(bool _state);
|
||||
void toggleVisibilityNavigationButtons(bool _state);
|
||||
|
||||
private slots:
|
||||
@@ -41,13 +42,17 @@ private slots:
|
||||
|
||||
private:
|
||||
QVBoxLayout *layout;
|
||||
PrintingSelectorViewOptionsToolbarWidget *viewOptionsToolbar;
|
||||
SettingsButtonWidget *displayOptionsWidget;
|
||||
QWidget *sortAndOptionsContainer;
|
||||
QHBoxLayout *sortAndOptionsLayout;
|
||||
QCheckBox *navigationCheckBox;
|
||||
QLabel *warningLabel;
|
||||
PrintingSelectorCardSortingWidget *sortToolBar;
|
||||
PrintingSelectorCardSearchWidget *searchBar;
|
||||
FlowWidget *flowWidget;
|
||||
CardSizeWidget *cardSizeWidget;
|
||||
PrintingSelectorCardSelectionWidget *cardSelectionBar;
|
||||
TabDeckEditor *deckEditor;
|
||||
AbstractTabDeckEditor *deckEditor;
|
||||
DeckListModel *deckModel;
|
||||
QTreeView *deckView;
|
||||
CardInfoPtr selectedCard;
|
||||
|
||||
@@ -19,16 +19,16 @@
|
||||
* display.
|
||||
*
|
||||
* @param parent The parent widget for this display.
|
||||
* @param deckEditor The TabDeckEditor instance for deck management.
|
||||
* @param deckModel The DeckListModel instance providing deck data.
|
||||
* @param deckView The QTreeView instance displaying the deck.
|
||||
* @param cardSizeSlider The slider controlling the size of the displayed card.
|
||||
* @param rootCard The root card object, representing the card to be displayed.
|
||||
* @param setInfoForCard The set-specific information for the card being displayed.
|
||||
* @param currentZone The current zone in which the card is located.
|
||||
* @param _deckEditor The TabDeckEditor instance for deck management.
|
||||
* @param _deckModel The DeckListModel instance providing deck data.
|
||||
* @param _deckView The QTreeView instance displaying the deck.
|
||||
* @param _cardSizeSlider The slider controlling the size of the displayed card.
|
||||
* @param _rootCard The root card object, representing the card to be displayed.
|
||||
* @param _setInfoForCard The set-specific information for the card being displayed.
|
||||
* @param _currentZone The current zone in which the card is located.
|
||||
*/
|
||||
PrintingSelectorCardDisplayWidget::PrintingSelectorCardDisplayWidget(QWidget *parent,
|
||||
TabDeckEditor *_deckEditor,
|
||||
AbstractTabDeckEditor *_deckEditor,
|
||||
DeckListModel *_deckModel,
|
||||
QTreeView *_deckView,
|
||||
QSlider *_cardSizeSlider,
|
||||
|
||||
@@ -1,16 +1,13 @@
|
||||
#ifndef PRINTING_SELECTOR_CARD_DISPLAY_WIDGET_H
|
||||
#define PRINTING_SELECTOR_CARD_DISPLAY_WIDGET_H
|
||||
|
||||
#include "../../../../client/ui/widgets/cards/card_info_picture_widget.h"
|
||||
#include "../../../../deck/deck_list_model.h"
|
||||
#include "../../../../game/cards/card_database.h"
|
||||
#include "../../../tabs/tab_deck_editor.h"
|
||||
#include "../../../tabs/abstract_tab_deck_editor.h"
|
||||
#include "printing_selector_card_overlay_widget.h"
|
||||
#include "set_name_and_collectors_number_display_widget.h"
|
||||
|
||||
#include <QPainter>
|
||||
#include <QTreeView>
|
||||
#include <QVBoxLayout>
|
||||
#include <QWidget>
|
||||
|
||||
class PrintingSelectorCardDisplayWidget : public QWidget
|
||||
@@ -19,7 +16,7 @@ class PrintingSelectorCardDisplayWidget : public QWidget
|
||||
|
||||
public:
|
||||
PrintingSelectorCardDisplayWidget(QWidget *parent,
|
||||
TabDeckEditor *_deckEditor,
|
||||
AbstractTabDeckEditor *_deckEditor,
|
||||
DeckListModel *_deckModel,
|
||||
QTreeView *_deckView,
|
||||
QSlider *_cardSizeSlider,
|
||||
@@ -36,7 +33,7 @@ signals:
|
||||
private:
|
||||
QVBoxLayout *layout;
|
||||
SetNameAndCollectorsNumberDisplayWidget *setNameAndCollectorsNumberDisplayWidget;
|
||||
TabDeckEditor *deckEditor;
|
||||
AbstractTabDeckEditor *deckEditor;
|
||||
DeckListModel *deckModel;
|
||||
QTreeView *deckView;
|
||||
QSlider *cardSizeSlider;
|
||||
|
||||
@@ -17,15 +17,15 @@
|
||||
* as a widget that displays the card amounts in different zones (mainboard, sideboard, etc.).
|
||||
*
|
||||
* @param parent The parent widget for this overlay.
|
||||
* @param deckEditor The TabDeckEditor instance for deck management.
|
||||
* @param deckModel The DeckListModel instance providing deck data.
|
||||
* @param deckView The QTreeView instance displaying the deck.
|
||||
* @param cardSizeSlider The slider controlling the size of the card.
|
||||
* @param rootCard The root card object that contains information about the card.
|
||||
* @param setInfoForCard The set-specific information for the card being displayed.
|
||||
* @param _deckEditor The TabDeckEditor instance for deck management.
|
||||
* @param _deckModel The DeckListModel instance providing deck data.
|
||||
* @param _deckView The QTreeView instance displaying the deck.
|
||||
* @param _cardSizeSlider The slider controlling the size of the card.
|
||||
* @param _rootCard The root card object that contains information about the card.
|
||||
* @param _setInfoForCard The set-specific information for the card being displayed.
|
||||
*/
|
||||
PrintingSelectorCardOverlayWidget::PrintingSelectorCardOverlayWidget(QWidget *parent,
|
||||
TabDeckEditor *_deckEditor,
|
||||
AbstractTabDeckEditor *_deckEditor,
|
||||
DeckListModel *_deckModel,
|
||||
QTreeView *_deckView,
|
||||
QSlider *_cardSizeSlider,
|
||||
@@ -120,7 +120,7 @@ void PrintingSelectorCardOverlayWidget::enterEvent(QEvent *event)
|
||||
#endif
|
||||
{
|
||||
QWidget::enterEvent(event);
|
||||
deckEditor->updateCardInfo(setCard);
|
||||
deckEditor->updateCard(setCard);
|
||||
|
||||
// Check if either mainboard or sideboard amount is greater than 0
|
||||
if (allZonesCardAmountWidget->getMainboardAmount() > 0 || allZonesCardAmountWidget->getSideboardAmount() > 0) {
|
||||
@@ -199,7 +199,7 @@ void PrintingSelectorCardOverlayWidget::customMenu(QPoint point)
|
||||
const QString &relatedCardName = rel->getName();
|
||||
QAction *relatedCard = relatedMenu->addAction(relatedCardName);
|
||||
connect(relatedCard, &QAction::triggered, deckEditor, [this, relatedCardName] {
|
||||
deckEditor->updateCardInfo(CardDatabaseManager::getInstance()->getCard(relatedCardName));
|
||||
deckEditor->updateCard(CardDatabaseManager::getInstance()->getCard(relatedCardName));
|
||||
deckEditor->showPrintingSelector();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
#include "../../../../client/ui/widgets/cards/card_info_picture_widget.h"
|
||||
#include "../../../../deck/deck_list_model.h"
|
||||
#include "../../../../game/cards/card_database.h"
|
||||
#include "../../../tabs/tab_deck_editor.h"
|
||||
#include "../../../tabs/abstract_tab_deck_editor.h"
|
||||
#include "all_zones_card_amount_widget.h"
|
||||
#include "card_amount_widget.h"
|
||||
#include "set_name_and_collectors_number_display_widget.h"
|
||||
@@ -15,7 +15,7 @@ class PrintingSelectorCardOverlayWidget : public QWidget
|
||||
|
||||
public:
|
||||
explicit PrintingSelectorCardOverlayWidget(QWidget *parent,
|
||||
TabDeckEditor *_deckEditor,
|
||||
AbstractTabDeckEditor *_deckEditor,
|
||||
DeckListModel *_deckModel,
|
||||
QTreeView *_deckView,
|
||||
QSlider *_cardSizeSlider,
|
||||
@@ -39,7 +39,7 @@ signals:
|
||||
private:
|
||||
CardInfoPictureWidget *cardInfoPicture;
|
||||
AllZonesCardAmountWidget *allZonesCardAmountWidget;
|
||||
TabDeckEditor *deckEditor;
|
||||
AbstractTabDeckEditor *deckEditor;
|
||||
DeckListModel *deckModel;
|
||||
QTreeView *deckView;
|
||||
QSlider *cardSizeSlider;
|
||||
|
||||
@@ -18,10 +18,11 @@ const QStringList PrintingSelectorCardSortingWidget::SORT_OPTIONS = {SORT_OPTION
|
||||
*/
|
||||
PrintingSelectorCardSortingWidget::PrintingSelectorCardSortingWidget(PrintingSelector *parent) : parent(parent)
|
||||
{
|
||||
setMinimumWidth(300);
|
||||
sortToolBar = new QHBoxLayout(this);
|
||||
sortToolBar->setContentsMargins(9, 0, 9, 0);
|
||||
|
||||
sortOptionsSelector = new QComboBox(this);
|
||||
sortOptionsSelector->setFocusPolicy(Qt::StrongFocus);
|
||||
sortOptionsSelector->addItems(SORT_OPTIONS);
|
||||
sortOptionsSelector->setCurrentIndex(SettingsCache::instance().getPrintingSelectorSortOrder());
|
||||
connect(sortOptionsSelector, &QComboBox::currentTextChanged, this,
|
||||
|
||||
@@ -1,140 +0,0 @@
|
||||
#include "printing_selector_view_options_toolbar_widget.h"
|
||||
|
||||
#include <QHBoxLayout>
|
||||
#include <QLabel>
|
||||
#include <QPushButton>
|
||||
|
||||
/**
|
||||
* @class PrintingSelectorViewOptionsToolbarWidget
|
||||
* @brief A widget that provides a toolbar for view options with collapsible and expandable functionality.
|
||||
*
|
||||
* This widget allows the user to collapse or expand the view options for the PrintingSelector,
|
||||
* providing a more compact interface when collapsed and a full view of options when expanded.
|
||||
*/
|
||||
PrintingSelectorViewOptionsToolbarWidget::PrintingSelectorViewOptionsToolbarWidget(QWidget *_parent,
|
||||
PrintingSelector *_printingSelector)
|
||||
: QWidget(_parent), printingSelector(_printingSelector)
|
||||
{
|
||||
// Set up layout for the widget
|
||||
layout = new QVBoxLayout();
|
||||
layout->setContentsMargins(9, 0, 9, 0);
|
||||
layout->setSpacing(0);
|
||||
setLayout(layout);
|
||||
|
||||
// Set up the expanded widget with its layout
|
||||
expandedWidget = new QWidget(this);
|
||||
auto *expandedLayout = new QVBoxLayout(expandedWidget);
|
||||
expandedLayout->setContentsMargins(0, 0, 0, 0);
|
||||
expandedLayout->setSpacing(0);
|
||||
|
||||
// Collapse button to toggle between expanded and collapsed states
|
||||
collapseButton = new QPushButton("▼", this);
|
||||
collapseButton->setFixedSize(20, 20);
|
||||
collapseButton->setToolTip("Collapse");
|
||||
collapseButton->setStyleSheet("border: none;");
|
||||
connect(collapseButton, &QPushButton::clicked, this, &PrintingSelectorViewOptionsToolbarWidget::collapse);
|
||||
expandedLayout->addWidget(collapseButton, 0, Qt::AlignLeft);
|
||||
|
||||
// View options widget
|
||||
viewOptions = new PrintingSelectorViewOptionsWidget(expandedWidget, printingSelector);
|
||||
expandedLayout->addWidget(viewOptions);
|
||||
|
||||
expandedWidget->setLayout(expandedLayout);
|
||||
|
||||
// Set up the collapsed widget with its layout
|
||||
collapsedWidget = new QWidget(this);
|
||||
auto *collapsedLayout = new QHBoxLayout(collapsedWidget);
|
||||
collapsedLayout->setContentsMargins(5, 0, 5, 0);
|
||||
collapsedLayout->setSpacing(0);
|
||||
|
||||
// Expand button to show full options
|
||||
expandButton = new QPushButton("▲", this);
|
||||
expandButton->setFixedSize(20, 20);
|
||||
expandButton->setToolTip("Expand");
|
||||
expandButton->setStyleSheet("border: none;");
|
||||
connect(expandButton, &QPushButton::clicked, this, &PrintingSelectorViewOptionsToolbarWidget::expand);
|
||||
collapsedLayout->addWidget(expandButton);
|
||||
|
||||
// Label for collapsed state
|
||||
auto *collapsedLabel = new QLabel(tr("Display Options"), this);
|
||||
collapsedLayout->addWidget(collapsedLabel);
|
||||
|
||||
collapsedWidget->setLayout(collapsedLayout);
|
||||
|
||||
// Stack widget to switch between expanded and collapsed states
|
||||
stackedWidget = new QStackedWidget(this);
|
||||
stackedWidget->addWidget(expandedWidget);
|
||||
stackedWidget->addWidget(collapsedWidget);
|
||||
|
||||
layout->addWidget(stackedWidget);
|
||||
|
||||
setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
|
||||
|
||||
// Default to the expanded widget
|
||||
stackedWidget->setCurrentWidget(expandedWidget);
|
||||
|
||||
// Connect the stacked widget to update the layout when it changes
|
||||
connect(stackedWidget, &QStackedWidget::currentChanged, this,
|
||||
&PrintingSelectorViewOptionsToolbarWidget::onWidgetChanged);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Toggles the widget to the collapsed state.
|
||||
*/
|
||||
void PrintingSelectorViewOptionsToolbarWidget::collapse()
|
||||
{
|
||||
stackedWidget->setCurrentWidget(collapsedWidget);
|
||||
updateGeometry();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Toggles the widget to the expanded state.
|
||||
*/
|
||||
void PrintingSelectorViewOptionsToolbarWidget::expand()
|
||||
{
|
||||
stackedWidget->setCurrentWidget(expandedWidget);
|
||||
updateGeometry();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Handles the geometry update when the stacked widget changes.
|
||||
*
|
||||
* This ensures that the parent layout is also updated when the widget's display state changes.
|
||||
*/
|
||||
void PrintingSelectorViewOptionsToolbarWidget::onWidgetChanged(int)
|
||||
{
|
||||
updateGeometry();
|
||||
if (parentWidget() && parentWidget()->layout()) {
|
||||
parentWidget()->layout()->invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Provides the recommended size for the widget based on the current view.
|
||||
*
|
||||
* @return QSize The suggested size for the widget.
|
||||
*/
|
||||
QSize PrintingSelectorViewOptionsToolbarWidget::sizeHint() const
|
||||
{
|
||||
return stackedWidget->currentWidget()->sizeHint();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Provides the minimum size required for the widget based on the current view.
|
||||
*
|
||||
* @return QSize The minimum size required for the widget.
|
||||
*/
|
||||
QSize PrintingSelectorViewOptionsToolbarWidget::minimumSizeHint() const
|
||||
{
|
||||
return stackedWidget->currentWidget()->minimumSizeHint();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the view options widget contained within this toolbar.
|
||||
*
|
||||
* @return PrintingSelectorViewOptionsWidget* The view options widget.
|
||||
*/
|
||||
PrintingSelectorViewOptionsWidget *PrintingSelectorViewOptionsToolbarWidget::getViewOptionsWidget() const
|
||||
{
|
||||
return viewOptions;
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
#ifndef PRINTING_SELECTOR_SORT_AND_SEARCH_TOOLBAR_WIDGET_H
|
||||
#define PRINTING_SELECTOR_SORT_AND_SEARCH_TOOLBAR_WIDGET_H
|
||||
|
||||
#include "printing_selector.h"
|
||||
#include "printing_selector_view_options_widget.h"
|
||||
|
||||
#include <QPushButton>
|
||||
#include <QStackedWidget>
|
||||
#include <QVBoxLayout>
|
||||
#include <QWidget>
|
||||
|
||||
class PrintingSelectorViewOptionsToolbarWidget : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit PrintingSelectorViewOptionsToolbarWidget(QWidget *parent, PrintingSelector *printingSelector);
|
||||
void collapse();
|
||||
void expand();
|
||||
void onWidgetChanged(int);
|
||||
QSize sizeHint() const override;
|
||||
QSize minimumSizeHint() const override;
|
||||
PrintingSelectorViewOptionsWidget *getViewOptionsWidget() const;
|
||||
|
||||
private:
|
||||
QVBoxLayout *layout;
|
||||
PrintingSelector *printingSelector;
|
||||
PrintingSelectorViewOptionsWidget *viewOptions;
|
||||
QWidget *expandedWidget;
|
||||
QPushButton *collapseButton;
|
||||
QWidget *collapsedWidget;
|
||||
QPushButton *expandButton;
|
||||
QStackedWidget *stackedWidget;
|
||||
};
|
||||
|
||||
#endif // PRINTING_SELECTOR_SORT_AND_SEARCH_TOOLBAR_WIDGET_H
|
||||