diff --git a/.ci/macos.entitlements b/.ci/macos.entitlements new file mode 100644 index 000000000..2bff1d681 --- /dev/null +++ b/.ci/macos.entitlements @@ -0,0 +1,17 @@ + + + + + com.apple.security.app-sandbox + + + com.apple.security.cs.disable-library-validation + + + com.apple.security.cs.disable-executable-page-protection + + + com.apple.security.cs.allow-unsigned-executable-memory + + + diff --git a/.ci/release_template.md b/.ci/release_template.md index 75671f90e..6e53a3ee1 100644 --- a/.ci/release_template.md +++ b/.ci/release_template.md @@ -4,23 +4,30 @@ git push -d origin --REPLACE-WITH-BETA-LIST-- --> - +
-Pre-compiled binaries we serve:
- - Windows 10+
- - Windows 7+
- - macOS 13+ ("Ventura") / Apple M
- - macOS 13+ ("Ventura") / Intel
- - Ubuntu 24.04 LTS ("Noble Numbat")
- - Ubuntu 22.04 LTS ("Jammy Jellyfish")
- - Ubuntu 20.04 LTS ("Focal Fossa")
- - Debian 12 ("Bookworm")
- - Debian 11 ("Bullseye")
- - Fedora 41
- - Fedora 40
-We are also packaged in Arch Linux's official "extra" repository, courtesy of @FFY00
-General Linux support is available via a flatpak package (Flathub)
+Available pre-compiled binaries for installation:
+
+  Windows
+   • Windows 10+
+   • Windows 7+
+
+  macOS
+   • macOS 15+ Sequoia Apple M
+   • macOS 14+ Sonoma Apple M
+   • macOS 13+ Ventura Intel
+
+  Linux
+   • Ubuntu 24.04 LTS Noble Numbat
+   • Ubuntu 22.04 LTS Jammy Jellyfish
+   • Ubuntu 20.04 LTS Focal Fossa
+   • Debian 12 Bookworm
+   • Debian 11 Bullseye
+   • Fedora 41
+   • Fedora 40
+
+We are also packaged in Arch Linux's official extra repository, courtesy of @FFY00.
+General Linux support is available via a flatpak package at Flathub!
 
@@ -28,22 +35,24 @@ include different targets --> We're pleased to announce the newest official release: --REPLACE-WITH-RELEASE-TITLE-- -We hope you enjoy the changes made and we have listed all changes, with their corresponding tickets, since the last version of Cockatrice was released for your convenience. +We hope you enjoy the changes made! All improvements with their corresponding tickets since the last version of Cockatrice are listed in the changelog below. -If you ever encounter a bug, have a suggestion or idea, or feel a need for a developer to look into something, please feel free to [open a ticket](https://github.com/Cockatrice/Cockatrice/issues). ([How to create a GitHub Ticket for Cockatrice](https://github.com/Cockatrice/Cockatrice/wiki/How-to-Create-a-GitHub-Ticket-Regarding-Cockatrice)) +If you ever encounter a bug, have a suggestion or idea, or feel a need for a developer to look into something, please feel free to [open a ticket](https://github.com/Cockatrice/Cockatrice/issues). ([How to create a Ticket for Cockatrice](https://github.com/Cockatrice/Cockatrice/wiki/How-to-Create-a-GitHub-Ticket-Regarding-Cockatrice)) -For any information relating to Cockatrice, please take a look at our official site: **https://cockatrice.github.io** +For basic information related to the app and getting started, please take a look at our official site: **https://cockatrice.github.io** -If you'd like to help contribute to Cockatrice in any way, check out our [README](https://github.com/Cockatrice/Cockatrice#get-involved-). We're always available to answer questions you may have on how the program works and how you can provide a meaningful contribution. +If you'd like to help and contribute to Cockatrice in any way, check out our [README](https://github.com/Cockatrice/Cockatrice#get-involved-). +We're always available to answer questions you may have on how the program works and how you can provide a meaningful contribution. ## Upgrading Cockatrice -- Run the internal software updater: Help → Check for Client Updates +Run the internal software updater: Help → Check for Client Updates Don't forget to update your card database right after! (Help → Check for Card Updates...) @@ -60,14 +69,14 @@ Remove empty headers when done. --> -### ⚠️ Important: +### 🔖 Highlights: ### ✨ New Features: -### 🐛 Fixed Bugs / Resolved issues: +### 🐛 Fixed Bugs / Resolved Issues:
-📘 Show all changes (--REPLACE-WITH-COMMIT-COUNT-- commits) +Show all changes (--REPLACE-WITH-COMMIT-COUNT-- commits) ### User Interface @@ -88,5 +97,6 @@ Remove empty headers when done. ## Special Thanks -We continue to find it amazing that so many people contribute their time, knowledge, code, testing and more to the project. We'd like to thank the entire Cockatrice community for their efforts. +It's amazing that so many people contribute their time, knowledge, code, testing and more to the project. +We'd like to thank the entire Cockatrice community for their efforts! 🙏 diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 2db045819..10ffa3493 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -7,32 +7,33 @@
# 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. +> +> [![Discord]( +> https://img.shields.io/discord/314987288398659595?label=Discord&logo=discord&logoColor=white)]( +> 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. -[![Discord](https://img.shields.io/discord/314987288398659595?label=Discord&logo=discord&logoColor=white&color=7289da)](https://discord.gg/ZASRzKu) -[![Gitter Chat](https://img.shields.io/gitter/room/Cockatrice/Cockatrice.svg)](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 C++11. +Cockatrice is currently compiled on all platforms using C++20. You'll notice C++03 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.
We have an automated process to update our language source files on a schedule and provide the translators on Transifex with the new contents.
-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.
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**
- 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**
+ With MAJ.MIN.PATCH 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. diff --git a/.github/workflows/desktop-build.yml b/.github/workflows/desktop-build.yml index 99c05deee..fbb882aa2 100644 --- a/.github/workflows/desktop-build.yml +++ b/.github/workflows/desktop-build.yml @@ -217,10 +217,18 @@ jobs: core_count: 3 make_package: 1 - - target: 14 + - target: 15 soc: Apple - os: macos-14 - xcode: "15.4" + os: macos-15 + xcode: "16.2" + type: Release + core_count: 3 + make_package: 1 + + - target: 15 + soc: Apple + os: macos-15 + xcode: "16.2" type: Debug core_count: 3 @@ -246,7 +254,7 @@ jobs: brew update brew install protobuf qt --force-bottle - - name: Build on Xcode ${{matrix.xcode}} + - name: Build & Sign on Xcode ${{matrix.xcode}} shell: bash id: build env: @@ -254,16 +262,75 @@ jobs: MAKE_TEST: 1 MAKE_PACKAGE: '${{matrix.make_package}}' PACKAGE_SUFFIX: '-macOS${{matrix.target}}_${{matrix.soc}}' + MACOS_CERTIFICATE: ${{ secrets.PROD_MACOS_CERTIFICATE }} + MACOS_CERTIFICATE_PWD: ${{ secrets.PROD_MACOS_CERTIFICATE_PWD }} + MACOS_CERTIFICATE_NAME: ${{ secrets.PROD_MACOS_CERTIFICATE_NAME }} + MACOS_CI_KEYCHAIN_PWD: ${{ secrets.PROD_MACOS_CI_KEYCHAIN_PWD }} # macOS runner have 3 cores usually - only the macos-13 image has 4: # https://docs.github.com/en/actions/using-github-hosted-runners/using-github-hosted-runners/about-github-hosted-runners#standard-github-hosted-runners-for-public-repositories # https://github.com/actions/runner-images?tab=readme-ov-file#available-images - run: .ci/compile.sh --server --parallel ${{matrix.core_count}} + run: | + if [[ -n "$MACOS_CERTIFICATE_NAME" ]] + then + echo $MACOS_CERTIFICATE | base64 --decode > certificate.p12 + security create-keychain -p "$MACOS_CI_KEYCHAIN_PWD" build.keychain + security default-keychain -s build.keychain + security set-keychain-settings -t 3600 -l build.keychain + security unlock-keychain -p "$MACOS_CI_KEYCHAIN_PWD" build.keychain + security import certificate.p12 -k build.keychain -P "$MACOS_CERTIFICATE_PWD" -T /usr/bin/codesign + security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$MACOS_CI_KEYCHAIN_PWD" build.keychain + fi + .ci/compile.sh --server --parallel ${{matrix.core_count}} + + - name: Sign app bundle + if: matrix.make_package + env: + MACOS_CERTIFICATE_NAME: ${{ secrets.PROD_MACOS_CERTIFICATE_NAME }} + MACOS_CI_KEYCHAIN_PWD: ${{ secrets.PROD_MACOS_CI_KEYCHAIN_PWD }} + run: | + if [[ -n "$MACOS_CERTIFICATE_NAME" ]] + then + security unlock-keychain -p "$MACOS_CI_KEYCHAIN_PWD" build.keychain + /usr/bin/codesign --sign="$MACOS_CERTIFICATE_NAME" --entitlements=".ci/macos.entitlements" --options=runtime --force --deep --timestamp --verbose ${{steps.build.outputs.path}} + fi + + - name: Notarize app bundle + if: matrix.make_package + env: + MACOS_NOTARIZATION_APPLE_ID: ${{ secrets.PROD_MACOS_NOTARIZATION_APPLE_ID }} + MACOS_NOTARIZATION_TEAM_ID: ${{ secrets.PROD_MACOS_NOTARIZATION_TEAM_ID }} + MACOS_NOTARIZATION_PWD: ${{ secrets.PROD_MACOS_NOTARIZATION_PWD }} + run: | + if [[ -n "$MACOS_NOTARIZATION_APPLE_ID" ]] + then + # Store the notarization credentials so that we can prevent a UI password dialog from blocking the CI + echo "Create keychain profile" + xcrun notarytool store-credentials "notarytool-profile" --apple-id "$MACOS_NOTARIZATION_APPLE_ID" --team-id "$MACOS_NOTARIZATION_TEAM_ID" --password "$MACOS_NOTARIZATION_PWD" + + # We can't notarize an app bundle directly, but we need to compress it as an archive. + # Therefore, we create a zip file containing our app bundle, so that we can send it to the + # notarization service + echo "Creating temp notarization archive" + ditto -c -k --keepParent ${{steps.build.outputs.path}} "notarization.zip" + + # Here we send the notarization request to the Apple's Notarization service, waiting for the result. + # This typically takes a few seconds inside a CI environment, but it might take more depending on the App + # characteristics. Visit the Notarization docs for more information and strategies on how to optimize it if + # you're curious + echo "Notarize app" + xcrun notarytool submit "notarization.zip" --keychain-profile "notarytool-profile" --wait + + # Finally, we need to "attach the staple" to our executable, which will allow our app to be + # validated by macOS even when an internet connection is not available. + echo "Attach staple" + xcrun stapler staple ${{steps.build.outputs.path}} + fi - name: Upload artifact 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 @@ -287,7 +354,7 @@ jobs: qt_arch: msvc2019_64 - target: 10 - qt_version: 6.5.* + qt_version: 6.6.* qt_arch: msvc2019_64 qt_modules: "qtimageformats qtmultimedia qtwebsockets" @@ -347,6 +414,15 @@ jobs: path: ${{steps.build.outputs.path}} if-no-files-found: error + - name: Upload pdb database + uses: actions/upload-artifact@v4 + with: + name: Windows${{matrix.target}}-debug-pdbs + path: | + build/cockatrice/Release/*.pdb + build/servatrice/Release/*.pdb + if-no-files-found: error + - name: Upload to release if: matrix.package != 'skip' && needs.configure.outputs.tag != null shell: bash diff --git a/.gitignore b/.gitignore index eedbba91d..f147c1458 100644 --- a/.gitignore +++ b/.gitignore @@ -6,10 +6,11 @@ mysql.cnf .DS_Store .idea/ *.aps -cmake-build-debug* +cmake-build* preferences compile_commands.json .vs/ .vscode/ .cache .gdb_history +cockatrice/resources/config/qtlogging.ini diff --git a/CMakeLists.txt b/CMakeLists.txt index 6fdf938ed..29ee84798 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -74,16 +74,16 @@ endif() # A project name is needed for CPack # Version can be overriden by git tags, see cmake/getversion.cmake -project("Cockatrice" VERSION 2.10.0) +project("Cockatrice" VERSION 2.11.0) # 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++17 for all targets +# Use c++20 for all targets set(CMAKE_CXX_STANDARD - 17 + 20 CACHE STRING "C++ ISO Standard" ) set(CMAKE_CXX_STANDARD_REQUIRED True) @@ -140,10 +140,17 @@ endif() # Define proper compilation flags if(MSVC) - # Visual Studio: Maximum optimization, disable warning C4251, establish C++17 compatibility - set(CMAKE_CXX_FLAGS_RELEASE "/Ox /MD /wd4251 /Zc:__cplusplus /std:c++17 /permissive- /W4") - # Generate complete debugging information - #set(CMAKE_CXX_FLAGS_DEBUG "/Zi") + # Disable Warning C4251, C++20 compatibility, Multi-threaded Builds, Warn Detection, Unwind Semantics, Debug Symbols + set(CMAKE_CXX_FLAGS "/wd4251 /Zc:__cplusplus /std:c++20 /permissive- /W4 /MP /EHsc /Zi") + # Visual Studio: Maximum Optimization, Multi-threaded DLL + set(CMAKE_CXX_FLAGS_RELEASE "/Ox /MD") + # Visual Studio: No Optimization, Multi-threaded Debug DLL + set(CMAKE_CXX_FLAGS_DEBUG "/Od /MDd") + + # Generate PDB, even when in release (So developers can better analyze crash logs) + set(CMAKE_EXE_LINKER_FLAGS_RELEASE "${CMAKE_EXE_LINKER_FLAGS_RELEASE} /DEBUG /OPT:REF /OPT:ICF") + + add_compile_definitions(_SILENCE_STDEXT_ARR_ITERS_DEPRECATION_WARNING) elseif(CMAKE_COMPILER_IS_GNUCXX) # linux/gcc, bsd/gcc, windows/mingw include(CheckCXXCompilerFlag) @@ -156,7 +163,7 @@ elseif(CMAKE_COMPILER_IS_GNUCXX) endif() if(APPLE) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=gnu++17") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=gnu++20") endif() set(ADDITIONAL_DEBUG_FLAGS @@ -264,6 +271,7 @@ if(UNIX) set(CPACK_PACKAGE_ICON "${CMAKE_CURRENT_SOURCE_DIR}/cockatrice/resources/appicon.icns") set(CPACK_DMG_DS_STORE_SETUP_SCRIPT "${CMAKE_CURRENT_SOURCE_DIR}/cmake/CMakeDMGSetup.script") set(CPACK_DMG_BACKGROUND_IMAGE "${CMAKE_CURRENT_SOURCE_DIR}/cmake/dmgBackground.tif") + set(CPACK_PRE_BUILD_SCRIPTS "${CMAKE_CURRENT_SOURCE_DIR}/cmake/SignMacApplications.cmake") else() # linux if(CPACK_GENERATOR STREQUAL "RPM") diff --git a/Dockerfile b/Dockerfile index 047c9ecf6..330c51b4a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,19 +1,20 @@ -FROM ubuntu:bionic -MAINTAINER Zach Halpern +FROM ubuntu:24.04 + +ARG DEBIAN_FRONTEND=noninteractive RUN apt-get update && apt-get install -y\ - build-essential\ - cmake\ - git\ - libprotobuf-dev\ - libqt5sql5-mysql\ - libmysqlclient-dev\ - libqt5websockets5-dev\ - protobuf-compiler\ - qt5-default\ - qtbase5-dev\ - qttools5-dev-tools\ - qttools5-dev + build-essential \ + cmake \ + file \ + g++ \ + git \ + libmariadb-dev-compat \ + libprotobuf-dev \ + libqt6sql6-mysql \ + qt6-websockets-dev \ + protobuf-compiler \ + qt6-tools-dev \ + qt6-tools-dev-tools COPY . /home/servatrice/code/ WORKDIR /home/servatrice/code @@ -25,7 +26,6 @@ RUN cmake .. -DWITH_SERVER=1 -DWITH_CLIENT=0 -DWITH_ORACLE=0 -DWITH_DBCONVERTER= WORKDIR /home/servatrice -EXPOSE 4747 +EXPOSE 4748 ENTRYPOINT [ "servatrice", "--log-to-console" ] - diff --git a/README.md b/README.md index 53b79f356..789134996 100644 --- a/README.md +++ b/README.md @@ -133,6 +133,7 @@ The following flags (with their non-default values) can be passed to `cmake`: # Run + Cockatrice is the game client
Oracle fetches card data
Servatrice is the server
diff --git a/cmake/FindQtRuntime.cmake b/cmake/FindQtRuntime.cmake index 6db821dd7..3025934f4 100644 --- a/cmake/FindQtRuntime.cmake +++ b/cmake/FindQtRuntime.cmake @@ -23,6 +23,7 @@ if(WITH_CLIENT) Svg WebSockets Widgets + Xml ) endif() if(WITH_ORACLE) diff --git a/cmake/NSIS.template.in b/cmake/NSIS.template.in index dc4412ff3..fbdde0b86 100644 --- a/cmake/NSIS.template.in +++ b/cmake/NSIS.template.in @@ -5,7 +5,7 @@ OutFile "@CPACK_TOPLEVEL_DIRECTORY@/@CPACK_OUTPUT_FILE_NAME@" !define INST_DIR "@CPACK_TEMPORARY_DIRECTORY@" -RequestExecutionlevel highest +RequestExecutionlevel admin SetCompressor LZMA Var NormalDestDir @@ -235,6 +235,13 @@ ${If} $PortableMode = 0 WriteUninstaller "$INSTDIR\uninstall.exe" ${GetSize} "$INSTDIR" "/S=0K" $0 $1 $2 IntFmt $0 "0x%08X" $0 + + ; Enable Windows User-Mode Dumps + ; https://learn.microsoft.com/en-us/windows/win32/wer/collecting-user-mode-dumps + WriteRegExpandStr HKLM "Software\Microsoft\Windows\Windows Error Reporting\LocalDumps\cockatrice.exe" "DumpFolder" "%LOCALAPPDATA%\CrashDumps\Cockatrice" + WriteRegDWORD HKLM "Software\Microsoft\Windows\Windows Error Reporting\LocalDumps\cockatrice.exe" "DumpCount" "5" + WriteRegDWORD HKLM "Software\Microsoft\Windows\Windows Error Reporting\LocalDumps\cockatrice.exe" "DumpType" "2" + WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Cockatrice" "DisplayIcon" "$INSTDIR\cockatrice.exe" WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Cockatrice" "DisplayName" "Cockatrice" WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Cockatrice" "DisplayVersion" "@CPACK_PACKAGE_VERSION_MAJOR@.@CPACK_PACKAGE_VERSION_MINOR@.@CPACK_PACKAGE_VERSION_PATCH@" diff --git a/cmake/SignMacApplications.cmake b/cmake/SignMacApplications.cmake new file mode 100644 index 000000000..b91e53725 --- /dev/null +++ b/cmake/SignMacApplications.cmake @@ -0,0 +1,27 @@ +# This script re-signs all apps after CPack packages them. This is necessary because CPack modifies +# the library references used by Cockatrice to App relative paths, invalidating the code signature. +string(LENGTH "$ENV{MACOS_CERTIFICATE_NAME}" MACOS_CERTIFICATE_NAME_LEN) + +if(APPLE AND MACOS_CERTIFICATE_NAME_LEN GREATER 0) + set(APPLICATIONS "cockatrice" "servatrice" "oracle" "dbconverter") + foreach(app_name IN LISTS APPLICATIONS) + set(FULL_APP_PATH "${CPACK_TEMPORARY_INSTALL_DIRECTORY}/${app_name}.app") + + message(STATUS "Signing Interior Dynamically Loaded Libraries for ${app_name}.app") + execute_process(COMMAND "find" "${FULL_APP_PATH}" "-name" "*.dylib" OUTPUT_VARIABLE INTERIOR_DLLS) + string(REPLACE "\n" ";" INTERIOR_DLLS_LIST ${INTERIOR_DLLS}) + + foreach(INTERIOR_DLL IN LISTS INTERIOR_DLLS_LIST) + execute_process( + COMMAND "codesign" "--sign" "$ENV{MACOS_CERTIFICATE_NAME}" "--entitlements" "../.ci/macos.entitlements" + "--options" "runtime" "--force" "--deep" "--timestamp" "--verbose" "${INTERIOR_DLL}" + ) + endforeach() + + message(STATUS "Signing Exterior Applications ${app_name}.app") + execute_process( + COMMAND "codesign" "--sign" "$ENV{MACOS_CERTIFICATE_NAME}" "--entitlements" "../.ci/macos.entitlements" + "--options" "runtime" "--force" "--deep" "--timestamp" "--verbose" "${FULL_APP_PATH}" + ) + endforeach() +endif() diff --git a/cockatrice/CMakeLists.txt b/cockatrice/CMakeLists.txt index e2fc703e9..9b50c9469 100644 --- a/cockatrice/CMakeLists.txt +++ b/cockatrice/CMakeLists.txt @@ -4,145 +4,15 @@ 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/game/cards/card_item.cpp - src/game/cards/card_list.cpp - src/game/zones/card_zone.cpp - src/server/chat_view/chat_view.cpp - src/game/board/counter_general.cpp - src/deck/custom_line_edit.cpp - src/deck/deck_loader.cpp - src/deck/deck_list_model.cpp - src/deck/deck_stats_interface.cpp - src/deck/deck_view.cpp - src/dialogs/dlg_connect.cpp - src/dialogs/dlg_create_token.cpp - src/dialogs/dlg_create_game.cpp - src/dialogs/dlg_edit_avatar.cpp - src/dialogs/dlg_edit_password.cpp - src/dialogs/dlg_edit_tokens.cpp - src/dialogs/dlg_edit_user.cpp - src/dialogs/dlg_filter_games.cpp - src/dialogs/dlg_forgot_password_challenge.cpp - src/dialogs/dlg_forgot_password_request.cpp - src/dialogs/dlg_forgot_password_reset.cpp - src/dialogs/dlg_load_deck_from_clipboard.cpp - src/dialogs/dlg_load_remote_deck.cpp - src/dialogs/dlg_manage_sets.cpp - src/dialogs/dlg_register.cpp - src/dialogs/dlg_roll_dice.cpp - src/dialogs/dlg_settings.cpp - src/dialogs/dlg_tip_of_the_day.cpp - src/dialogs/dlg_update.cpp - src/dialogs/dlg_view_log.cpp - src/game/filters/filter_string.cpp - src/game/filters/filter_builder.cpp - src/game/filters/filter_tree.cpp - src/game/filters/filter_tree_model.cpp - src/client/ui/layouts/flow_layout.cpp - src/client/ui/layouts/horizontal_flow_layout.cpp - src/client/ui/layouts/vertical_flow_layout.cpp - src/client/ui/widgets/general/layout_containers/flow_widget.cpp - src/game/game_scene.cpp - src/game/game_selector.cpp - src/game/games_model.cpp - src/game/game_view.cpp - src/client/get_text_with_max.cpp - src/game/hand_counter.cpp - src/server/handle_public_servers.cpp - src/game/zones/hand_zone.cpp - src/client/game_logic/key_signals.cpp - src/client/ui/line_edit_completer.cpp - src/server/local_client.cpp - src/server/local_server.cpp - src/server/local_server_interface.cpp - src/utility/logger.cpp - src/client/ui/widgets/cards/card_info_picture_enlarged_widget.cpp - src/client/ui/widgets/cards/card_info_picture_with_text_overlay_widget.cpp - src/client/ui/widgets/general/display/labeled_input.cpp - src/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.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/network/release_channel.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/servers_settings.cpp - src/settings/settings_manager.cpp - src/settings/cache_settings.cpp - src/settings/shortcuts_settings.cpp - src/client/sound_engine.cpp - src/client/network/spoiler_background_updater.cpp - src/game/zones/stack_zone.cpp - src/client/tabs/tab.cpp - src/client/tabs/tab_account.cpp - src/client/tabs/tab_admin.cpp - src/client/tabs/tab_deck_editor.cpp - src/client/tabs/tab_deck_storage.cpp - src/client/tabs/tab_game.cpp - src/client/tabs/tab_logs.cpp - src/client/tabs/tab_message.cpp - src/client/tabs/tab_replays.cpp - src/client/tabs/tab_room.cpp - src/client/tabs/tab_server.cpp - src/client/tabs/tab_supervisor.cpp - src/game/zones/table_zone.cpp - src/client/tapped_out_interface.cpp - src/client/ui/theme_manager.cpp - src/client/ui/tip_of_the_day.cpp - src/client/translate_counter_name.cpp - src/client/update_downloader.cpp - src/server/user/user_context_menu.cpp - src/server/user/user_info_connection.cpp - src/server/user/user_info_box.cpp - src/server/user/user_list.cpp - src/client/ui/window_main.cpp - src/game/zones/view_zone_widget.cpp - src/game/zones/view_zone.cpp - ${VERSION_STRING_CPP} -) +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) +configure_file( + ${CMAKE_SOURCE_DIR}/cockatrice/resources/config/qtlogging.ini ${CMAKE_BINARY_DIR}/cockatrice/qtlogging.ini COPYONLY +) set(cockatrice_RESOURCES cockatrice.qrc) @@ -263,7 +133,7 @@ if(APPLE) set(plugin_dest_dir cockatrice.app/Contents/Plugins) set(qtconf_dest_dir cockatrice.app/Contents/Resources) - # Qt plugins: audio (Qt5), iconengines, imageformats, platforms, printsupport (Qt5), styles, tls (Qt6) + # Qt plugins: audio (Qt5), iconengines, imageformats, multimedia (Qt6), platforms, printsupport (Qt5), styles, tls (Qt6) install( DIRECTORY "${QT_PLUGINS_DIR}/" DESTINATION ${plugin_dest_dir} @@ -274,12 +144,20 @@ if(APPLE) PATTERN "audio/*.dylib" PATTERN "iconengines/*.dylib" PATTERN "imageformats/*.dylib" + PATTERN "multimedia/*.dylib" PATTERN "platforms/*.dylib" PATTERN "printsupport/*.dylib" PATTERN "styles/*.dylib" 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] @@ -314,7 +192,14 @@ if(WIN32) PATTERN "*.dll" ) - # Qt plugins: audio (Qt5), iconengines, imageformats, platforms, printsupport (Qt5), styles, tls (Qt6) + 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}/" DESTINATION ${plugin_dest_dir} @@ -326,6 +211,7 @@ if(WIN32) PATTERN "imageformats/*.dll" PATTERN "mediaservice/dsengine.dll" PATTERN "mediaservice/wmfengine.dll" + PATTERN "multimedia/*.dll" PATTERN "platforms/qdirect2d.dll" PATTERN "platforms/qminimal.dll" PATTERN "platforms/qoffscreen.dll" diff --git a/cockatrice/cockatrice.qrc b/cockatrice/cockatrice.qrc index 2d147bcf6..38c40561f 100644 --- a/cockatrice/cockatrice.qrc +++ b/cockatrice/cockatrice.qrc @@ -1,5 +1,5 @@ - + resources/cardback.svg resources/cockatrice.svg resources/hand.svg @@ -13,9 +13,12 @@ resources/icons/arrow_top_green.svg resources/icons/arrow_up_green.svg resources/icons/clearsearch.svg + resources/icons/cogwheel.svg resources/icons/conceded.svg resources/icons/decrement.svg resources/icons/delete.svg + resources/icons/dropdown_collapsed.svg + resources/icons/dropdown_expanded.svg resources/icons/forgot_password.svg resources/icons/increment.svg resources/icons/info.svg @@ -24,16 +27,25 @@ resources/icons/pencil.svg resources/icons/player.svg resources/icons/ready_start.svg + resources/icons/reload.svg resources/icons/remove_row.svg + resources/icons/rename.svg resources/icons/scales.svg resources/icons/search.svg resources/icons/settings.svg resources/icons/spectator.svg + resources/icons/swap.svg resources/icons/sync.svg resources/icons/tab_changed.svg resources/icons/update.png resources/icons/view.svg + resources/icons/mana/B.svg + resources/icons/mana/G.svg + resources/icons/mana/R.svg + resources/icons/mana/U.svg + resources/icons/mana/W.svg + resources/config/general.svg resources/config/appearance.svg resources/config/interface.svg @@ -41,6 +53,8 @@ resources/config/deckeditor.svg resources/config/shorcuts.svg resources/config/sound.svg + resources/config/debug.ini + resources/config/qtlogging.ini resources/counters/w.svg resources/counters/w_highlight.svg @@ -326,39 +340,42 @@ resources/replay/fastforward.svg resources/replay/pause.svg - resources/userlevels/normal.svg - resources/userlevels/registered.svg - resources/userlevels/registered_buddy.svg - resources/userlevels/registered_vip.svg - resources/userlevels/registered_vip_buddy.svg - resources/userlevels/registered_donator.svg - resources/userlevels/registered_donator_buddy.svg - - resources/userlevels/moderator.svg - resources/userlevels/moderator_buddy.svg - resources/userlevels/moderator_vip.svg - resources/userlevels/moderator_vip_buddy.svg - - resources/userlevels/admin.svg - resources/userlevels/admin_buddy.svg - resources/userlevels/admin_vip.svg - resources/userlevels/admin_vip_buddy.svg + resources/usericons/pawn_single.svg + resources/usericons/pawn_double.svg + resources/usericons/pawn_donator_single.svg + resources/usericons/pawn_donator_double.svg + resources/usericons/pawn_judge_single.svg + resources/usericons/pawn_judge_double.svg + resources/usericons/pawn_vip_single.svg + resources/usericons/pawn_vip_double.svg + resources/usericons/star_single.svg + resources/usericons/star_double.svg resources/tips/images/accounts_tab.png + resources/tips/images/add_card.png resources/tips/images/arrows.png + resources/tips/images/card_select.png resources/tips/images/cockatrice_register.png resources/tips/images/cockatrice_wiki.png resources/tips/images/coin_flip.png resources/tips/images/counter_expression.png resources/tips/images/discord.png + resources/tips/images/edhrec.png + resources/tips/images/expand_card_view.png resources/tips/images/face_down.png resources/tips/images/filter_games.png resources/tips/images/github_logo.png + resources/tips/images/highlight_cards.png + resources/tips/images/pawns.png resources/tips/images/setpt.png resources/tips/images/shortcuts.png + resources/tips/images/syntax_help.png resources/tips/images/themes.png resources/tips/images/tip_of_the_day.png + resources/tips/images/token.png + resources/tips/images/updates.png + resources/tips/images/visual_deck_tags.png resources/tips/tips_of_the_day.xml resources/help/search.md diff --git a/cockatrice/cockatrice_en@source.ts b/cockatrice/cockatrice_en@source.ts index 7418f9dcf..9574f33a4 100644 --- a/cockatrice/cockatrice_en@source.ts +++ b/cockatrice/cockatrice_en@source.ts @@ -4,7 +4,7 @@ AbstractCounter - + &Set counter... @@ -12,95 +12,247 @@ AbstractCounterDialog - + Set counter - + New value for counter '%1': + + AbstractDlgDeckTextEdit + + + &Refresh + + + + + Parse Set Name and Number (if available) + + + + + AbstractTabDeckEditor + + + Open in new tab + + + + + Are you sure? + + + + + The decklist has been modified. +Do you want to save the changes? + + + + + + + + + + + Error + + + + + Could not open deck at %1 + + + + + Could not save remote deck + + + + + + The deck could not be saved. +Please check that the directory is writable and try again. + + + + + Save deck + + + + + The deck could not be saved. + + + + + There are no cards in your deck to be exported + + + + + No deck was selected to be exported. + + + + + AdminNotesDialog + + + Update Notes + + + + + Admin Notes for %1 + + + + + AllZonesCardAmountWidget + + + Mainboard + + + + + Sideboard + + + AppearanceSettingsPage - + Error - + Could not create themes directory at '%1'. - + Theme settings - + Current theme: - + Open themes folder - + + Menu settings + + + + + Show keyboard shortcuts in right-click menus + + + + Card rendering - + Display card names on cards having a picture - + + Auto-Rotate cards with sideways layout + + + + + Override all card art with personal set preference (Pre-ProviderID change behavior) [Requires Client restart] + + + + + Bump sets that the deck contains cards from to the top in the printing selector + + + + Scale cards on mouse over - + + Use rounded card corners + + + + Minimum overlap percentage of cards on the stack and in vertical hand - + + Maximum initial height for card view window: + + + + + + rows + + + + + Maximum expanded height for card view window: + + + + Hand layout - + Display hand horizontally (wastes space) - + Enable left justification - + Table grid layout - + Invert vertical coordinate - + Minimum player count for multi-column layout: - + Maximum font size for information displayed on cards: @@ -108,111 +260,111 @@ BanDialog - + ban &user name - + ban &IP address - + ban client I&D - + Ban type - + &permanent ban - + &temporary ban - + &Days: - + &Hours: - + &Minutes: - + Duration of the ban - + Please enter the reason for the ban. This is only saved for moderators and cannot be seen by the banned person. - + Please enter the reason for the ban that will be visible to the banned person. - + Redact all messages from this user in all rooms - + &OK - + &Cancel - + Ban user from server - - - - + + + + Error - + You have to select a name-based, IP-based, clientId based, or some combination of the three to place a ban. - + You must have a value in the name ban when selecting the name ban checkbox. - + You must have a value in the ip ban when selecting the ip ban checkbox. - + You must have a value in the clientid ban when selecting the clientid ban checkbox. @@ -220,22 +372,22 @@ This is only saved for moderators and cannot be seen by the banned person. BetaReleaseChannel - - Beta Releases + + Beta - + No reply received from the release update server. - + Invalid reply received from the release update server. - + No reply received from the file update server. @@ -243,32 +395,32 @@ This is only saved for moderators and cannot be seen by the banned person. CardDatabaseModel - + Name - + Sets - + Mana cost - + Card type - + P/T - + Color(s) @@ -361,37 +513,65 @@ This is only saved for moderators and cannot be seen by the banned person. - CardFrame + CardInfoFrameWidget - + Image - + Description - + Both + + + View transformation + + - CardInfoText + CardInfoPictureWidget - + + View related cards + + + + + Add card to deck + + + + + Mainboard + + + + + Sideboard + + + + + CardInfoTextWidget + + Name: - + Related cards: - + Unknown card: @@ -399,287 +579,555 @@ This is only saved for moderators and cannot be seen by the banned person. CardItem - + &Move to - + &Power / toughness + + CardSizeWidget + + + Card Size + + + CardZone - + their hand nominative - + %1's hand nominative - + their library look at zone - + %1's library look at zone - + of their library top cards of zone, - + of %1's library top cards of zone - + their library reveal zone - + %1's library reveal zone - + their library shuffle - + %1's library shuffle - + their library nominative - + %1's library nominative - + their graveyard nominative - + %1's graveyard nominative - + their exile nominative - + %1's exile nominative - + their sideboard look at zone - + %1's sideboard look at zone - + their sideboard nominative - + %1's sideboard nominative + + DeckEditorCardInfoDockWidget + + + Card Info + + + + + DeckEditorDatabaseDisplayWidget + + + Search by card name (or search expressions) + + + + + Add to Deck + + + + + Add to Sideboard + + + + + Select Printing + + + + + Show on EDHREC (Commander) + + + + + Show on EDHREC (Card) + + + + + Show Related cards + + + + + Add card to &maindeck + + + + + Add card to &sideboard + + + + + DeckEditorDeckDockWidget + + + Banner Card + + + + + Select Printing + + + + + Deck + + + + + Deck &name: + + + + + &Comments: + + + + + Hash: + + + + + &Increment number + + + + + &Decrement number + + + + + &Remove row + + + + + Swap card to/from sideboard + + + + + DeckEditorFilterDockWidget + + + Filters + + + + + &Clear all filters + + + + + Delete selected + + + + + DeckEditorMenu + + + &Deck Editor + + + + + &New deck + + + + + &Load deck... + + + + + Load recent deck... + + + + + Clear + + + + + &Save deck + + + + + Save deck &as... + + + + + Load deck from cl&ipboard... + + + + + Edit deck in clipboard + + + + + + Annotated + + + + + + Not Annotated + + + + + Save deck to clipboard + + + + + Annotated (No set info) + + + + + Not Annotated (No set info) + + + + + &Print deck... + + + + + &Send deck to online service + + + + + Create decklist (decklist.org) + + + + + Create decklist (decklist.xyz) + + + + + Analyze deck (deckstats.net) + + + + + Analyze deck (tappedout.net) + + + + + &Close + + + + + DeckEditorPrintingSelectorDockWidget + + + Printing Selector + + + DeckEditorSettingsPage - - + + Update Spoilers - - + + Success - + Download URLs have been reset. - + Downloaded card pictures have been reset. - + Error - + One or more downloaded card pictures could not be cleared. - + Add URL - - + + URL: - - + + Edit URL - + + Network Cache Size: + + + + + Redirect Cache TTL: + + + + + How long cached redirects for urls are valid for. + + + + + Picture Cache Size: + + + + Add New URL - + Remove URL - + + Day(s) + + + + Updating... - + Choose path - + URL Download Priority - + Spoilers - + Download Spoilers Automatically - + Spoiler Location: - + Last Change - + Spoilers download automatically on launch - + Press the button to manually update without relaunching - + Do not close settings until manual update is complete - + Download card pictures on the fly - + How to add a custom URL - + Delete Downloaded Images - + Reset Download URLs - - Downloaded images directory size: - - - - + On-disk cache for downloaded pictures - - Picture cache size: - - - - + In-memory cache for pictures not currently on screen @@ -687,16 +1135,218 @@ This is only saved for moderators and cannot be seen by the banned person. DeckListModel - + + Count + + + + + Set + + + + Number - + + Provider ID + + + + Card + + DeckLoader + + + Common deck formats (%1) + + + + + All files (*.*) + + + + + DeckPreviewColorIdentityFilterWidget + + + Mode: Exact Match + + + + + Mode: Includes + + + + + DeckPreviewDeckTagsDisplayWidget + + + Edit tags ... + + + + + DeckPreviewTagDialog + + + Deck Tags Manager + + + + + Manage your deck tags. Check or uncheck tags as needed, or add new ones: + + + + + Add a new tag (e.g., Aggro️) + + + + + Add Tag + + + + + Filter tags... + + + + + OK + + + + + Cancel + + + + + Invalid Input + + + + + Tag name cannot be empty! + + + + + Duplicate Tag + + + + + This tag already exists. + + + + + DeckPreviewWidget + + + Banner Card + + + + + Open in deck editor + + + + + Edit Tags + + + + + Rename Deck + + + + + Save Deck to Clipboard + + + + + Annotated + + + + + Annotated (No set info) + + + + + Not Annotated + + + + + Not Annotated (No set info) + + + + + Rename File + + + + + Delete File + + + + + Set Banner Card + + + + + + New name: + + + + + + Error + + + + + Rename failed + + + + + Delete file + + + + + Are you sure you want to delete the selected file? + + + + + Delete failed + + + DeckStatsInterface @@ -714,181 +1364,217 @@ This is only saved for moderators and cannot be seen by the banned person. DeckViewContainer - + Load deck... - + Load remote deck... - + + Unload deck + Unload deck... + + + + Ready to start - + + Force start + + + + Sideboard unlocked - + Sideboard locked - - Load deck - - - - + Error - + The selected file could not be loaded. + + DialogConvertDeckToCodFormat + + + Deck Format Conversion + + + + + You tried to add a tag to a .txt format deck. + Tags can only be added to .cod format decks. + Do you want to convert the deck to the .cod format? + + + + + Remember and automatically apply choice in the future + + + DlgConnect - + Downloading... - + Known Hosts - + + Delete the currently selected saved server + + + + Refresh the server list with known public servers - + New Host - + Name: - + &Host: - + &Port: - + Player &name: - + P&assword: - + &Save password - + A&uto connect - + Automatically connect to the most recent login when Cockatrice opens - + If you have any trouble connecting or registering then contact the server staff for help! - - + + Webpage - + Reset Password - + + Forgot password? + + + + &Connect - + Server - + Login - + Server Contact - + Connect to Server - + Server URL - + Communication Port - + Unique Server Name - + Connection Warning - + You need to name your new connection profile. - + Connect Warning - + The player name can't be empty. @@ -971,27 +1657,37 @@ This is only saved for moderators and cannot be seen by the banned person. - + + Starting life total: + + + + + Game setup options + + + + &Clear - + Create game - + Game information - + Error - + Server error. @@ -999,92 +1695,92 @@ This is only saved for moderators and cannot be seen by the banned person. DlgCreateToken - + &Name: - + Token - + C&olor: - + white - + blue - + black - + red - + green - + multicolor - + colorless - + &P/T: - + &Annotation: - + &Destroy token when it leaves the table - + Token data - + Show &all tokens - + Show tokens from this &deck - + Choose token from list - + Create token @@ -1129,6 +1825,24 @@ To remove your current avatar, confirm without choosing a new image. + + DlgEditDeckInClipboard + + + Edit deck in clipboard + + + + + Error + + + + + Invalid deck list. + + + DlgEditPassword @@ -1171,93 +1885,93 @@ To remove your current avatar, confirm without choosing a new image. DlgEditTokens - + &Name: - + C&olor: - + white - + blue - + black - + red - + green - + multicolor - + colorless - + &P/T: - + &Annotation: - + Token data - - + + Add token - + Remove token - + Edit custom tokens - + Please enter the name of the token: - + Error - + The chosen name conflicts with an existing card or token. Make sure to enable the 'Token' set in the "Manage sets" dialog to display them correctly. @@ -1294,132 +2008,132 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgFilterGames - + no limit - + 5 minutes - + 10 minutes - + 30 minutes - + 1 hour - + 2 hours - - Show '&buddies only' games + + Hide 'buddies only' games - - Show &full games + + Hide full games - - Show games &that have started + + Hide games that have started - - Show &password protected games + + Hide password protected games - - Hide '&ignored user' games + + Hide 'ignored user' games - + &Newer than: - + Game &description: - + &Creator name: - + General - + &Game types - + at &least: - + at &most: - + Maximum player count - + Restrictions - + Show games only if &spectators can watch - + Show spectator password p&rotected games - + Show only if spectators can ch&at - + Show only if spectators can see &hands - + Spectators - + Filter games @@ -1603,26 +2317,27 @@ Make sure to enable the 'Token' set in the "Manage sets" dia - DlgLoadDeckFromClipboard + DlgLoadDeck - - &Refresh + + Load Deck + + + DlgLoadDeckFromClipboard - + Load deck from clipboard - - + Error - - + Invalid deck list. @@ -1630,11 +2345,49 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgLoadRemoteDeck - + Load deck + + DlgMoveTopCardsUntil + + + Card name (or search expressions): + + + + + Number of hits: + + + + + Auto play hits + + + + + Put top cards on stack until... + + + + + No cards matching the search expression exists in the card database. Proceed anyways? + + + + + Cockatrice + + + + + Invalid filter + + + DlgRegister @@ -1748,12 +2501,12 @@ Your email will be used to verify your account. DlgSettings - + Unknown Error loading card database - + Your card database is invalid. Cockatrice may not function correctly with an invalid database @@ -1764,7 +2517,7 @@ Would you like to change your database location setting? - + Your card database version is too old. This can cause problems loading card information or images @@ -1775,7 +2528,7 @@ Would you like to change your database location setting? - + Your card database did not finish loading Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues with your cards.xml attached @@ -1784,21 +2537,21 @@ Would you like to change your database location setting? - + File Error loading your card database. Would you like to change your database location setting? - + Your card database was loaded but contains no cards. Would you like to change your database location setting? - + Unknown card database load status Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues @@ -1807,59 +2560,59 @@ Would you like to change your database location setting? - - - + + + Error - + The path to your deck directory is invalid. Would you like to go back and set the correct path? - + The path to your card pictures directory is invalid. Would you like to go back and set the correct path? - + Settings - + General - + Appearance - + User Interface - + Card Sources - + Chat - + Sound - + Shortcuts @@ -1885,164 +2638,168 @@ Would you like to change your database location setting? DlgUpdate - + Current release channel - + Reinstall - + Cancel Download - + Open Download Page - + Check for Client Updates - - - - + + + + Error - + Cockatrice was not built with SSL support, therefore you cannot download updates automatically! Please visit the download page to update manually. - - Downloading update... + + Downloading update: %1 + Downloading update... - + Checking for updates... - + Finished checking for updates - + No Update Available - + Cockatrice is up to date! - + You are already running the latest version available in the chosen release channel. - + Current version - + Selected release channel - - + + Update Available - - - - A new version of Cockatrice is available! - - - - - - New version - - - Released + A new version of Cockatrice is available! + + + + + + New version - Changelog + Released + + Changelog + + + + Do you want to update now? - - Unfortunately there are no download packages available for your operating system. -You may have to build from source yourself. + + Unfortunately, the automatic updater failed to find a compatible download. +You may have to manually download the new version. + Unfortunately there are no download packages available for your operating system. +You may have to build from source yourself. - - Please check the download page manually and visit the wiki for instructions on compiling. + + Please check the <a href="%1">releases page</a> on our Github and download the build for your system. + Please check the download page manually and visit the wiki for instructions on compiling. - - - + + + Update Error - + An error occurred while checking for updates: - + An error occurred while downloading an update: - + Installing... - + Cockatrice is unable to open the installer. - + Try to update manually by closing Cockatrice and running the installer. - + Download location @@ -2060,6 +2817,14 @@ You may have to build from source yourself. + + EdhrecCommanderResponseCommanderDetailsDisplayWidget + + + Salt: + + + FilterBuilder @@ -2071,105 +2836,120 @@ You may have to build from source yourself. GameSelector - - - - - - - - - + + + + + + + + + Error - + Please join the appropriate room first. - + Wrong password. - + Spectators are not allowed in this game. - + The game is already full. - + The game does not exist any more. - + This game is only open to registered users. - + This game is only open to its creator's buddies. - + You are being ignored by the creator of this game. - + + Join Game + + + + + Spectate Game + + + + + Game Information + + + + Join game - + Password: - + Please join the respective room first. - + &Filter games - + C&lear filter - + C&reate - + &Join - + J&oin as spectator - + Games shown: %1 / %2 - + Games @@ -2177,12 +2957,12 @@ You may have to build from source yourself. GamesModel - + >1 day - + %1%2 hr short age in hours @@ -2191,12 +2971,12 @@ You may have to build from source yourself. - + new - + %1%2 min short age in minutes @@ -2205,78 +2985,78 @@ You may have to build from source yourself. - + password - + buddies only - + reg. users only - - + + can chat - + see hands - + can see hands - + not allowed - + Room - + Age - + Description - + Creator - + Type - + Restrictions - + Players - + Spectators @@ -2284,92 +3064,102 @@ You may have to build from source yourself. GeneralSettingsPage - + Reset all paths - + All paths have been reset - - + + Choose path - + Personal settings - + Language: - + Paths (editing disabled in portable mode) - + Paths - + + How to help with translations + + + + Decks directory: - + Replays directory: - + Pictures directory: - + Card database: - + Custom database directory: - + Token database: - + Update channel - + + Check for client updates on startup + + + + Notify if a feature supported by the server is missing in my client - + Automatically run Oracle when running a new version of Cockatrice - + Show tips on startup @@ -2377,60 +3167,60 @@ You may have to build from source yourself. MainWindow - - + + The server has reached its maximum user capacity, please check back later. - + There are too many concurrent connections from your address. - + Banned by moderator - + Expected end time: %1 - + This ban lasts indefinitely. - + Scheduled server shutdown. - - + + Invalid username. - + You have been logged out due to logging in at another location. - + Connection closed - + The server has terminated your connection. Reason: %1 - + The server is going to be restarted in %n minute(s). All running games will be lost. Reason for shutdown: %1 @@ -2440,576 +3230,586 @@ Reason for shutdown: %1 - + Scheduled server shutdown - - + + Success - + Registration accepted. Will now login. - + Account activation accepted. Will now login. - + Number of players - + Please enter the number of players. - - + + Player %1 - + Load replay - + About Cockatrice - + Version - + Cockatrice Webpage - + Project Manager: - + Past Project Managers: - + Developers: - + Our Developers - + Help Develop! - + Translators: - + Our Translators - + Help Translate! - + Support: - + Report an Issue - + Troubleshooting - + F.A.Q. - - - - - - - - - - - - - - - + + + + + + + + + + + + + - - - + + + + + Error - + Server timeout - + Failed Login - + Your client seems to be missing features this server requires for connection. - + To update your client, go to 'Help -> Check for Client Updates'. - + Incorrect username or password. Please check your authentication information and try again. - + There is already an active session using this user name. Please close that session first and re-login. - - + + You are banned until %1. - - + + You are banned indefinitely. - + This server requires user registration. Do you want to register now? - + This server requires client IDs. Your client is either failing to generate an ID or you are running a modified client. Please close and reopen your client to try again. - + An internal error has occurred, please close and reopen Cockatrice before trying again. If the error persists, ensure you are running the latest version of the software and if needed contact the software developers. - + Account activation - + Your account has not been activated yet. You need to provide the activation token received in the activation email. - + Server Full - + Unknown login error: %1 - - + + This usually means that your client version is out of date, and the server sent a reply your client doesn't understand. - + Your username must respect these rules: - + is %1 - %2 characters long - + can %1 contain lowercase characters - - - - + + + + NOT - + can %1 contain uppercase characters - + can %1 contain numeric characters - + can contain the following punctuation: %1 - + first character can %1 be a punctuation mark - + no unacceptable language as specified by these server rules: note that the following lines will not be translated - + can not contain any of the following words: %1 - + can not match any of the following expressions: %1 - + You may only use A-Z, a-z, 0-9, _, ., and - in your username. - - - - - - + + + + + + Registration denied - + Registration is currently disabled on this server - + There is already an existing account with the same user name. - + It's mandatory to specify a valid email address when registering. - + It appears you are attempting to register a new account on this server yet you already have an account registered with the email provided. This server restricts the number of accounts a user can register per address. Please contact the server operator for further assistance or to obtain your credential information. - + Password too short. - + Registration failed for a technical problem on the server. - + Unknown registration error: %1 - + Account activation failed - + Socket error: %1 - + You are trying to connect to an obsolete server. Please downgrade your Cockatrice version or connect to a suitable server. Local version is %1, remote version is %2. - + Your Cockatrice client is obsolete. Please update your Cockatrice version. Local version is %1, remote version is %2. - + Connecting to %1... - + Registering to %1 as %2... - + Disconnected - + Connected, logging in at %1 - - - + + + Requesting forgotten password to %1 as %2... - + &Connect... - + &Disconnect - + Start &local game... - + &Watch replay... - - &Deck editor - - - - + &Full screen - + &Register to server... - + &Restore password... - + &Settings... - + &Exit - + A&ctions - + &Cockatrice - + C&ard Database - + &Manage sets... - + Edit custom &tokens... - + Open custom image folder - + Open custom sets folder - + Add custom sets/cards - + + Reload card database + + + + + Tabs + + + + &Help - + &About Cockatrice - + &Tip of the Day - + Check for Client Updates - + Check for Card Updates... - + View &Debug Log - + + Open Settings Folder + + + + Show/Hide - + New Version - + Congratulations on updating to Cockatrice %1! Oracle will now launch to update your card database. - + Cockatrice installed - + Congratulations on installing Cockatrice %1! Oracle will now launch to install the initial card database. - + Card database - + Cockatrice is unable to load the card database. Do you want to update your card database now? If unsure or first time user, choose "Yes" - - + + Yes - - + + No - + Open settings - + New sets found - + %n new set(s) found in the card database Set code(s): %1 Do you want to enable it/them? @@ -3019,75 +3819,76 @@ Do you want to enable it/them? - + View sets - + Welcome - + Hi! It seems like you're running this version of Cockatrice for the first time. All the sets in the card database have been enabled. Read more about changing the set order or disabling specific sets and consequent effects in the "Manage Sets" dialog. - - + + Information - + A card database update is already running. - + Unable to run the card database updater: - - failed to start. + + Failed to start. The file might be missing, or permissions might be incorrect. - - crashed. + + The process crashed some time after starting successfully. - - timed out. + + Timed out. The process took too long to respond. The last waitFor...() function timed out. - - write error. + + An error occurred when attempting to write to the process. For example, the process may not be running, or it may have closed its input channel. - - read error. + + An error occurred when attempting to read from the process. For example, the process may not be running. - - unknown error. + + Unknown error occurred. - - The card database updater exited with an error: %1 + + The card database updater exited with an error: +%1 - + This server supports additional features that your client doesn't have. This is most likely not a problem, but this message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -3095,54 +3896,54 @@ To update your client, go to Help -> Check for Updates. - - - - - + + + + + Load sets/cards - + Selected file cannot be found. - + You can only import XML databases at this time. - + The new sets/cards have been added successfully. Cockatrice will now reload the card database. - + Sets/cards failed to import. - - - + + + Reset Password - + Your password has been reset successfully, you can now log in using the new credentials. - + Failed to reset user account password, please contact the server operator to reset your password. - + Activation request received, please check your email for an activation token. @@ -3419,7 +4220,8 @@ Cockatrice will now reload the card database. - %1 is looking at the top %3 card(s) %2. + %1 is looking at the %4 %3 card(s) %2. + %1 is looking at the top %3 card(s) %2. top card for singular, top %3 cards for plural @@ -3427,57 +4229,67 @@ Cockatrice will now reload the card database. - + + bottom + + + + + top + + + + %1 turns %2 face-down. - + %1 turns %2 face-up. - + The game has been closed. - + The game has started. - + %1 has joined the game. - + %1 is now watching the game. - + You have been kicked out of the game. - + %1 has left the game (%2). - + %1 is not watching the game any more (%2). - + %1 is not ready to start the game any more. - + %1 shuffles their deck and draws a new hand of %2 card(s). @@ -3485,28 +4297,28 @@ Cockatrice will now reload the card database. - + %1 shuffles their deck and draws a new hand. - + You are watching a replay of game #%1. - + %1 is ready to start the game. - + cards an unknown amount of cards - + %1 card(s) a card for singular, %1 cards for plural @@ -3515,92 +4327,92 @@ Cockatrice will now reload the card database. - + %1 lends %2 to %3. - + %1 reveals %2 to %3. - + %1 reveals %2. - + %1 randomly reveals %2%3 to %4. - + %1 randomly reveals %2%3. - + %1 peeks at face down card #%2. - + %1 peeks at face down card #%2: %3. - + %1 reveals %2%3 to %4. - + %1 reveals %2%3. - + %1 reversed turn order, now it's %2. - + reversed - + normal - + Heads - + Tails - + %1 flipped a coin. It landed as %2. - + %1 rolls a %2 with a %3-sided die. - + %1 flips %2 coins. There are %3 heads and %4 tails. - + %1 rolls a %2-sided dice %3 times: %4. @@ -3729,17 +4541,17 @@ Cockatrice will now reload the card database. - + %1 unattaches %2. - + %1 undoes their last draw. - + %1 undoes their last draw (%2). @@ -3747,110 +4559,110 @@ Cockatrice will now reload the card database. MessagesSettingsPage - + Word1 Word2 Word3 - + Add New Message - + Edit Message - + Remove Message - + Add message - - + + Message: - + Edit message - + Chat settings - + Custom alert words - + Enable chat mentions - + Enable mention completer - + In-game message macros - + How to use in-game message macros - + Ignore chat room messages sent by unregistered users - + Ignore private messages sent by unregistered users - - + + Invert text color - + Enable desktop notifications for private messages - + Enable desktop notification for mentions - + Enable room message history on join - - + + (Color is hexadecimal) - + Separate words with a space, alphanumeric characters only @@ -3908,6 +4720,24 @@ Cockatrice will now reload the card database. + + OverlapControlWidget + + + Cards to overlap: + + + + + Overlap percentage: + + + + + Overlap direction: + + + Phase @@ -3974,57 +4804,57 @@ Cockatrice will now reload the card database. PhasesToolbar - + Untap step - + Upkeep step - + Draw step - + First main phase - + Beginning of combat step - + Declare attackers step - + Declare blockers step - + Combat damage step - + End of combat step - + Second main phase - + End of turn step @@ -4032,7 +4862,7 @@ Cockatrice will now reload the card database. PictureLoader - + en code for scryfall's language property, not available for all languages @@ -4041,647 +4871,781 @@ Cockatrice will now reload the card database. Player - + Reveal top cards of library - - - - - - - - - + + + + + + + + + + + + Number of cards: (max. %1) - + &View graveyard - + &View exile - + Player "%1" - - - - + + + + &Graveyard - - - - + + + + &Exile - + &Move hand to... - - - + + + &Top of library - - - + + + &Bottom of library - + &Move graveyard to... - - - - + + + + &Hand - + &Move exile to... - + &View library - + &View hand - + View &top cards of library... - + Reveal &library to... - + &Always reveal top card - + &View sideboard - + &Draw card - + D&raw cards... - + &Undo last draw - + Take &mulligan - - &Shuffle - - - - + Play top card &face down - + Move top card to grave&yard - + Move top card to e&xile - + Move top cards to &graveyard... - + Move top cards to &exile... - + Put top card on &bottom - + + View bottom cards of library... + + + + Lend library to... - + + Shuffle + + + + Put top cards on stack &until... Take top cards &until... - + + Shuffle top cards... + + + + + Shuffle bottom cards... + + + + &Reveal hand to... - + Reveal r&andom card to... - + Reveal random card to... - + &Sideboard - + &Library - + &Counters - + &Untap all permanents - + R&oll die... - + &Create token... - + C&reate another token - + Cr&eate predefined token - - + + &All players - + S&ay - + + &Select All + + + + + S&elect Row + + + + + S&elect Column + + + + &Play - + &Hide - + Play &Face Down - + &Tap / Untap Turn sideways or back again - + Toggle &normal untapping - + T&urn Over Turn face up/face down - + &Peek at card face - + &Clone - + Attac&h to card... - + Unattac&h - + &Draw arrow... - + &Increase power - + &Decrease power - + I&ncrease toughness - + D&ecrease toughness - + In&crease power and toughness - + Dec&rease power and toughness - + Increase power and decrease toughness - + Decrease power and increase toughness - + Set &power and toughness... - + Reset p&ower and toughness - + &Set annotation... - + Red - + Yellow - + Green - + &Add counter (%1) - + &Remove counter (%1) - + &Set counters (%1)... - + &Top of library in random order - + X cards from the top of library... - + &Bottom of library in random order - + View top cards of library - + + View bottom cards of library + + + + + Shuffle top cards of library + + + + + Shuffle bottom cards of library + + + + Which position should this card be placed: - + (max. %1) - + Draw hand - + 0 and lower are in comparison to current hand size - + Draw cards - - + + Number: - + Move top cards to grave - + Reveal &top cards to... - + &Top of library... - + &Bottom of library... - + &Always look at top card - + &Open deck in deck editor - + &Play top card - + &Draw bottom card - + D&raw bottom cards... - + &Play bottom card - + Play bottom card &face down - + Move bottom card to grave&yard - + Move bottom card to e&xile - + Move bottom cards to &graveyard... - + Move bottom cards to &exile... - + Put bottom card on &top - + Selec&ted cards - + Move top cards to exile - + Move bottom cards to grave - + Move bottom cards to exile - + Draw bottom cards - - + + C&reate another %1 token - + Create tokens - + Place card X cards from top of library - + Change power/toughness - + Change stats to: - + Set annotation - + Please enter the new annotation: - + Set counters - + Re&veal to... - + View related cards - + Token: - + All tokens + + PrintingSelector + + + Display Navigation Buttons + + + + + <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. + + + + + PrintingSelectorCardOverlayWidget + + + Preference + + + + + Pin Printing + + + + + Unpin Printing + + + + + Show Related cards + + + + + PrintingSelectorCardSearchWidget + + + Search by set name or set code + + + + + PrintingSelectorCardSelectionWidget + + + Previous Card in Deck + + + + + Next Card in Deck + + + + + PrintingSelectorCardSortingWidget + + + Alphabetical + + + + + Preference + + + + + Release Date + + + + + + Descending + + + + + Ascending + + + QMenuBar - + Services - + Hide %1 - + Hide Others - + Show All - + Preferences... - + Quit %1 - + About %1 @@ -4689,38 +5653,32 @@ Cockatrice will now reload the card database. QObject - + Cockatrice card database (*.xml) - - + All files (*.*) - + Cockatrice replays (*.cor) - - Common deck formats (*.cod *.dec *.dek *.txt *.mwDeck) - - - - + Maindeck - + Sideboard - + Tokens @@ -4728,92 +5686,92 @@ Cockatrice will now reload the card database. QPlatformTheme - + OK - + Save - + Save All - + Open - + &Yes - + Yes to &All - + &No - + N&o to All - + Abort - + Retry - + Ignore - + Close - + Cancel - + Discard - + Help - + Apply - + Reset - + Restore Defaults @@ -4821,17 +5779,17 @@ Cockatrice will now reload the card database. RemoteDeckList_TreeModel - + Name - + ID - + Upload time @@ -4839,32 +5797,32 @@ Cockatrice will now reload the card database. RemoteReplayList_TreeModel - + ID - + Name - + Players - + Keep - + Time started - + Duration (sec) @@ -4910,32 +5868,32 @@ Cockatrice will now reload the card database. SequenceEdit - + Choose an action from the table - + Hit the key/combination of keys you want to set for this action - - Shortcut already in use - - - - + Invalid key - + + Shortcut already in use by: + + + + Clear - + Restore default @@ -4971,72 +5929,80 @@ Cockatrice will now reload the card database. ShortcutSettingsPage - - + + Restore all default shortcuts - + Do you really want to restore all default shortcuts? - + Clear all default shortcuts - + Do you really want to clear all shortcuts? - - Action - - - - - Shortcut - - - - + Section: - + Action: - + Shortcut: - + How to set custom shortcuts - + Clear all shortcuts + + + Search by shortcut name + + + + + ShortcutTreeView + + + Action + + + + + Shortcut + + ShortcutsSettings - + Your configuration file contained invalid shortcuts. Please check your shortcut settings! - + The following shortcuts have been set to default: @@ -5045,17 +6011,17 @@ Please check your shortcut settings! ShutdownDialog - + &Reason for shutdown: - + &Time until shutdown (minutes): - + Shut down server @@ -5063,27 +6029,27 @@ Please check your shortcut settings! SoundSettingsPage - + Enable &sounds - + Current sounds theme: - + Test system sound engine - + Sound settings - + Master volume @@ -5091,48 +6057,48 @@ Please check your shortcut settings! SpoilerBackgroundUpdater - + Spoilers season has ended - + Deleting spoiler.xml. Please run Oracle - - + + Spoilers download failed - + No internet connection - + Error - + Spoilers already up to date - + No new spoilers added - + Spoilers have been updated! - + Last change: @@ -5140,635 +6106,593 @@ Please check your shortcut settings! StableReleaseChannel - - Stable Releases + + Default - + No reply received from the release update server. - + Invalid reply received from the release update server. - + No reply received from the tag update server. - + Invalid reply received from the tag update server. + + TabAccount + + + Add to Buddy List + + + + + Add to Ignore List + + + + + Account + + + TabAdmin - + Administration - + Update server &message - + &Shut down server - + &Reload configuration - + Server administration functions - + + Server moderator functions + + + + + Replay ID + + + + + Grant Replay Access + + + + + Username to Activate + + + + + Force Activate User + + + + &Unlock functions - + &Lock functions + + + + Success + + + + + Replay access granted + + + + + + + + + Error + + + + + Unable to grant replay access. Replay ID invalid + + + + + Unable to grant replay access. Internal error + + + + + User successfully activated + + + + + Unable to activate user. Username invalid + + + + + Unable to activate user. User already active + + + + + Unable to activate user. Internal error + + TabDeckEditor - - Search by card name (or search expressions) - Search by card name - - - - - Add to Deck - - - - - Add to Sideboard - - - - - Show Related cards - - - - - &Clear all filters - - - - - Delete selected - - - - - Deck &name: - - - - - &Comments: - - - - - Hash: - - - - - &New deck - - - - - &Load deck... - - - - - &Save deck - - - - - Save deck &as... - - - - - Load deck from cl&ipboard... - - - - - Save deck to clipboard - - - - - Annotated - - - - - Not Annotated - - - - - &Print deck... - - - - - &Send deck to online service - - - - - Create decklist (decklist.org) - - - - - Analyze deck (deckstats.net) - - - - - Analyze deck (tappedout.net) - - - - - &Close - - - - - Add card to &maindeck - - - - - Add card to &sideboard - - - - - &Remove row - - - - - &Increment number - - - - - &Decrement number - - - - - &Deck Editor - - - - - + Card Info - - + Deck - - + Filters - + &View - - - + + Printing + + + + + + + Visible - - - + + + + Floating - + Reset layout - + Deck: %1 - - - Are you sure? - - - - - The decklist has been modified. -Do you want to save the changes? - - - - - Load deck - - - - - - - - - - Error - - - - - The deck could not be saved. - - - - - Could not save remote deck - - - - - - The deck could not be saved. -Please check that the directory is writable and try again. - - - - - Save deck - - - - - There are no cards in your deck to be exported - - - - - No deck was selected to be saved. - - TabDeckStorage - - Deck storage - - - - + Local file system - + Server deck storage - - + + Open in deck editor - + + Rename deck or folder + + + + Upload deck - + Download deck - - + + + + New folder - - + + Delete - - + + Open decks folder + + + + + Rename local folder + + + + + Rename local file + + + + + New name: + + + + + + + Error - + + Rename failed + + + + + Invalid deck file - + Enter deck name - + This decklist does not have a name. Please enter a name: - + Unnamed deck - + Failed to upload deck to server - + Delete local file - - - - Are you sure you want to delete "%1"? + + Are you sure you want to delete the selected files? - + + Delete remote decks + + + + + Are you sure you want to delete the selected decks? + + + + + Name of new folder: - - Delete remote folder + + Deck Storage + + + + + TabDeckStorageVisual + + + Visual Deck Storage - - Delete remote deck + + Error + + + + + Could not open deck at %1 + + + + + TabEdhRec + + + EDHREC: TabGame - - - + + + Replay - - + + Game - - + + Player List - - + + Card Info - - + + Messages - - + + Replay Timeline - + &Phases - + &Game - + Next &phase - + Next phase with &action - + Next &turn - + Reverse turn order - + &Remove all local arrows - + Rotate View Cl&ockwise - + Rotate View Co&unterclockwise - + Game &information - + + Un&concede + + + + &Concede - + &Leave game - + C&lose replay - + &Focus Chat - + &Say: - + &View - - - - + + + + Visible - - - - + + + + Floating - + Reset layout - + Concede - + Are you sure you want to concede this game? - + Unconcede - + You have already conceded. Do you want to return to this game? - + Leave game - + Are you sure you want to leave this game? - + You are flooding the game. Please wait a couple of seconds. - + A player has joined game #%1 - + %1 has joined the game - + kicked by game host or moderator - + player left the game - + player disconnected from server - + reason unknown - + You have been kicked out of the game. @@ -5781,152 +6705,152 @@ Please enter a name: - - - + + + Time;SenderName;SenderIP;Message;TargetID;TargetName - + Room Logs - + Game Logs - + Chat Logs - - + + Error - + You must select at least one filter. - + You have to select a valid number of days to locate. - + Username: - + IP Address: - + Game Name: - + GameID: - + Message: - + Main Room - + Game Room - + Private Chat - + Past X Days: - + Today - + Last Hour - + Maximum Results: - + At least one filter is required. The more information you put in, the more specific your results will be. - + Get User Logs - + Clear Filters - + Filters - + Log Locations - + Date Range - + Maximum Results - - + + Message History - + Failed to collect message history information. - + There are no messages for the selected filters. @@ -5949,22 +6873,22 @@ The more information you put in, the more specific your results will be. - + This user is ignoring you, they cannot see your messages in main chat and you cannot join their games. - + Private message from - + %1 has left the server. - + %1 has joined the server. @@ -5972,117 +6896,153 @@ The more information you put in, the more specific your results will be. TabReplays - - Game replays - - - - + Local file system - + Server replay storage - - + + Watch replay - - + + Rename + + + + + + New folder + + + + + Delete - + + Open replays folder + + + + Download replay - + Toggle expiration lock - + + Rename local folder + + + + + Rename local file + + + + + New name: + + + + + Error + + + + + Rename failed + + + + + Name of new folder: + + + + Delete local file - - Are you sure you want to delete "%1"? + + Are you sure you want to delete the selected files? - - Downloading Replays + + Are you sure you want to delete the selected replays? - - Folder download is not yet supported. Please download replays individually. - - - - + Delete remote replay - - Are you sure you want to delete the replay of game %1? + + Game Replays TabRoom - + &Say: - + Chat - + &Room - + &Leave room - + &Clear chat - + Chat Settings... - + mentioned you. - + Click to view - + You are flooding the chat. Please wait a couple of seconds. @@ -6095,30 +7055,30 @@ The more information you put in, the more specific your results will be. - - - - + + + + Error - + Failed to join the server room: it doesn't exist on the server. - + The server thinks you are in the server room but your client is unable to display it. Try restarting your client. - + You do not have the required permission to join this server room. - + Failed to join the server room due to an unknown error: %1. @@ -6126,32 +7086,72 @@ The more information you put in, the more specific your results will be. TabSupervisor - + + Deck Editor + + + + + &Visual Deck Storage + + + + + Server + + + + + Account + + + + + Deck Storage + + + + + Game Replays + + + + + Administration + + + + + Logs + + + + Are you sure? - + There are still open games. Are you sure you want to quit? - + Click to view - + Your buddy %1 has signed on! - + Unknown Event - + The server has sent you a message that your client does not understand. This message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -6159,61 +7159,43 @@ To update your client, go to Help -> Check for Updates. - + Idle Timeout - + You are about to be logged out due to inactivity. - + Promotion - + You have been promoted. Please log out and back in for changes to take effect. - + Warned - + You have received a warning due to %1. Please refrain from engaging in this activity or further actions may be taken against you. If you have any questions, please private message a moderator. - + You have received the following message from the server. (custom messages like these could be untranslated) - - TabUserLists - - - Account - - - - - Add to Buddy List - - - - - Add to Ignore List - - - TappedOutInterface @@ -6297,173 +7279,184 @@ Please refrain from engaging in this activity or further actions may be taken ag UserContextMenu - + User &details - + Private &chat - + Show this user's &games - + Add to &buddy list - + Remove from &buddy list - + Add to &ignore list - + Remove from &ignore list - + Kick from &game - + Warn user - + View user's war&n history - + Ban from &server - + View user's &ban history - + &Promote user to moderator - + Dem&ote user from moderator - + Promote user to &judge - + Demote user from judge - + + View admin notes + + + + %1's games - - - + + + Ban History - + Ban Time;Moderator;Ban Length;Ban Reason;Visible Reason - + User has never been banned. - + Failed to collect ban information. - - - + + + Warning History - + Warning Time;Moderator;User Name;Reason - + User has never been warned. - + Failed to collect warning information. - - + + Failed to get admin notes. + + + + + Success - + Successfully promoted user. - + Successfully demoted user. - - + + + Failed - + Failed to promote user. - + Failed to demote user. - + Copy hash to clipboard - + Remove this user's messages @@ -6471,109 +7464,109 @@ Please refrain from engaging in this activity or further actions may be taken ag UserInfoBox - + Location: - + Account Age: - + Edit - + Change password - + Change avatar - + Administrator - + Moderator - + Registered user - - + + Unregistered user - + Judge - + Unknown - + The entered password does not match your account. - - - + + + Information - + User information updated. - - - - - - - - - - + + + + + + + + + + Error - + User Information - + Real Name: - + User Level: - + %n Year(s), amount of years (only shown if more than 0) @@ -6582,7 +7575,7 @@ Please refrain from engaging in this activity or further actions may be taken ag - + %10%n Day(s) %20 amount of years (if more than 0), amount of days, date in local short format @@ -6591,59 +7584,59 @@ Please refrain from engaging in this activity or further actions may be taken ag - + Enter Password - + Password verification is required in order to change your email address - - - + + + An error occurred while trying to update your user information. - + This server does not permit you to update your user informations. - + Password changed. - + This server does not permit you to change your password. - + The new password is too short. - + The old password is incorrect. - + Avatar updated. - + This server does not permit you to update your avatar. - + An error occured while trying to updater your avatar. @@ -6651,124 +7644,251 @@ Please refrain from engaging in this activity or further actions may be taken ag UserInterfaceSettingsPage - + General interface settings - + &Double-click cards to play them (instead of single-click) - + + &Clicking plays all selected cards (instead of just the clicked card) + + + + &Play all nonlands onto the stack (not the battlefield) by default - + + Close card view window when last card is removed + + + + Annotate card text on tokens - + Use tear-off menus, allowing right click menus to persist on screen - + Notifications settings - + Enable notifications in taskbar - + Notify in the taskbar for game events while you are spectating - + Notify in the taskbar when users in your buddy list connect - + Animation settings - + &Tap/untap animation + + + Deck editor/storage settings + + + + + Open deck in new tab by default + + + + + Prompt before converting .txt decks to .cod format + + + + + Always convert if not prompted + + + + + Use visual deck storage in game lobby + + + + + Replay settings + + + + + Buffer time for backwards skip via shortcut: + + - UserList + UserListWidget - + Users connected to server: %1 - + Users in this room: %1 - + Buddies online: %1 / %2 - + Ignored users online: %1 / %2 + + VisualDeckStorageFolderDisplayWidget + + + Deck Storage + + + + + VisualDeckStorageSearchWidget + + + Search by filename + + + + + VisualDeckStorageSortWidget + + + Sort Alphabetically (Deck Name) + + + + + Sort Alphabetically (Filename) + + + + + Sort by Last Modified + + + + + Sort by Last Loaded + + + + + VisualDeckStorageWidget + + + Loading database ... + + + + + Show Folders + + + + + Show Tag Filter + + + + + Show Tags On Deck Previews + + + + + Show Banner Card Selection Option + + + + + Include Folder Names in Search + + + + + Draw unused Color Identities + + + + + Unused Color Identities Opacity + + + WarningDialog - + Which warning would you like to send? - + Redact all messages from this user in all rooms - + &OK - + &Cancel - + Warn user for misconduct - - + + Error - + User name to send a warning to can not be blank, please specify a user to warn. - + Warning to use can not be blank, please select a valid warning to send. @@ -6776,128 +7896,133 @@ Please refrain from engaging in this activity or further actions may be taken ag WndSets - + Move selected set to the top - + Move selected set up - + Move selected set down - + Move selected set to the bottom - + Search by set name, code, or type - + Default order - + Restore original art priority order - + Enable all sets - + Disable all sets - + Enable selected set(s) - + Disable selected set(s) - + Deck Editor - + Use CTRL+A to select all sets in the view. - + Only cards in enabled sets will appear in the card list of the deck editor. - + Image priority is decided in the following order: - + first the CUSTOM Folder (%1), then the Enabled Sets in this dialog (Top to Bottom) %1 is a link to the wiki - + Card Art - + How to use custom card art - + Hints - + Note - + Sorting by column allows you to find a set while not changing set priority. - + To enable ordering again, click the column header until this message disappears. - + Use the current sorting as the set priority instead - + Sorts the set priority using the same column - + + Include online-only (Arena) cards [requires restart] + + + + Manage sets @@ -6905,22 +8030,67 @@ Please refrain from engaging in this activity or further actions may be taken ag ZoneViewWidget - - sort by name + + Ungrouped - - sort by type + + Group by Type - + + Group by Mana Value + + + + + Group by Color + + + + + Unsorted + + + + + Sort by Name + + + + + Sort by Type + + + + + Sort by Mana Cost + + + + + Sort by Colors + + + + + Sort by P/T + + + + + Sort by Set + + + + shuffle when closing - + pile view @@ -6928,7 +8098,7 @@ Please refrain from engaging in this activity or further actions may be taken ag i18n - + English @@ -6936,12 +8106,12 @@ Please refrain from engaging in this activity or further actions may be taken ag main - + Connect on startup - + Debug to file @@ -6949,786 +8119,908 @@ Please refrain from engaging in this activity or further actions may be taken ag shortcutsTab - + Main Window - - + + Deck Editor - + Game Lobby - + Card Counters - + Player Counters - + Power and Toughness - + Game Phases - + Playing Area - + Move Selected Card - + View - + Move Top Card - + Move Bottom Card - + Gameplay - + Drawing - + Chat Room - + Game Window - + Load Deck from Clipboard - + + Replays - + + Tabs + + + + Check for Card Updates... - + Connect... - + Disconnect - + Exit - + Full screen - + Register... - + Settings... - + Start a Local Game... - + Watch Replay... - - Analyze Deck + + Analyze Deck (deckstats.net) + Analyze Deck - - Clear All Filters - - - - - Clear Selected Filter - - - - - Close + + Analyze Deck (tappedout.net) - Remove Card + Clear All Filters + Clear Selected Filter + + + + + Close + + + + + Remove Card + + + + Manage Sets... - + Edit Custom Tokens... - - Export Deck - - - - - Add Card - - - - - Load Deck... - - - - - Load Deck from Clipboard... + + Export Deck (decklist.org) - New Deck + Export Deck (decklist.xyz) - - Open Custom Pictures Folder + + Add Card - - Print Deck... + + Load Deck... + Load Deck from Clipboard... + + + + + Edit Deck in Clipboard, Annotated + + + + + Edit Deck in Clipboard + + + + + New Deck + + + + + Open Custom Pictures Folder + + + + + Print Deck... + + + + Delete Card - - + + Reset Layout - + Save Deck - + Save Deck as... - + Save Deck to Clipboard, Annotated - - Save Deck to Clipboard - - - - - Load Local Deck... - - - - - Load Remote Deck... - - - - - Set Ready to Start - - - - - Toggle Sideboard Lock - - - - - - Add Green Counter - - - - - - Remove Green Counter - - - - - - Set Green Counters... + + Save Deck to Clipboard, Annotated (No Set Info) + Save Deck to Clipboard + + + + + Save Deck to Clipboard (No Set Info) + + + + + Load Local Deck... + + + + + Load Remote Deck... + + + + + Set Ready to Start + + + + + Toggle Sideboard Lock + + + + + + Add Green Counter + + + + + + Remove Green Counter + + + + + + Set Green Counters... + + + + Add Yellow Counter - + Remove Yellow Counter - + Set Yellow Counters... - - + + Add Red Counter - - + + Remove Red Counter - - + + Set Red Counters... - + Add Life Counter - + Remove Life Counter - + Set Life Counters... - + Add White Counter - + Remove White Counter - + Set White Counters... - + Add Blue Counter - + Remove Blue Counter - + Set Blue Counters... - + Add Black Counter - + Remove Black Counter - + Set Black Counters... - + Add Colorless Counter - + Remove Colorless Counter - + Set Colorless Counters... - + Add Other Counter - + Remove Other Counter - + Set Other Counters... - + Add Power (+1/+0) - + Remove Power (-1/-0) - + Move Toughness to Power (+1/-1) - + Add Toughness (+0/+1) - + Remove Toughness (-0/-1) - + Move Power to Toughness (-1/+1) - + Add Power and Toughness (+1/+1) - + Remove Power and Toughness (-1/-1) - + Set Power and Toughness... - + Reset Power and Toughness - + Untap - + Upkeep - + Draw - + First Main Phase - + Start Combat - + Attack - + Block - + Damage - + End Combat - + Second Main Phase - + End - + Next Phase - + Next Phase Action - + Next Turn - + + Hide Card in Reveal Window + + + + Tap / Untap Card - + Untap All - + Toggle Untap - + Turn Card Over - + Peek Card - + Play Card - + Attach Card... - + Unattach Card - + Clone Card - + Create Token... - + Create All Related Tokens - + Create Another Token - + Set Annotation... - - + + Select All Cards in Zone + + + + + Select All Cards in Row + + + + + Select All Cards in Column + + + + + Bottom of Library - - - - + + + + Exile - - - - + + + + Graveyard - - + + Hand - - + + Top of Library - - - + + + Battlefield, Face Down - + Battlefield - + Library - + Sideboard - + Top Cards of Library - + + Bottom Cards of Library + + + + Close Recent View - - + + Stack - - + + Graveyard (Multiple) - - + + Exile (Multiple) - + Stack Until Found - + Draw Bottom Card - + Draw Multiple Cards from Bottom... - + Draw Arrow... - + Remove Local Arrows - + Leave Game - + Concede - + Roll Dice... - + Shuffle Library - + + Shuffle Top Cards of Library + + + + + Shuffle Bottom Cards of Library + + + + Mulligan - + Draw a Card - + Draw Multiple Cards... - + Undo Draw - + Always Reveal Top Card - + Always Look At Top Card - + Rotate View Clockwise - + Rotate View Counterclockwise - + Unfocus Text Box - + Focus Chat - + Clear Chat - + Refresh - + + Skip Forward + + + + + Skip Backward + + + + + Skip Forward by a lot + + + + + Skip Backward by a lot + + + + Play/Pause - + Toggle Fast Forward + + + Visual Deck Storage + + + + + Deck Storage + + + + + Server + + + + + Account + + + + + Administration + + + + + Logs + + diff --git a/cockatrice/resources/config/debug.ini b/cockatrice/resources/config/debug.ini new file mode 100644 index 000000000..a1d203c7a --- /dev/null +++ b/cockatrice/resources/config/debug.ini @@ -0,0 +1,11 @@ +[debug] +showCardId=false + +[localgame] +onStartup=false +playerCount=1 +;deck\Player 1=path/to/deck +;deck\Player 2=path/to/deck + +; Fun Fact: You can assign a deck to your username and it will auto load and ready when you join a server game +;deck\Your Username Here=path/to/deck diff --git a/cockatrice/resources/config/qtlogging.ini b/cockatrice/resources/config/qtlogging.ini new file mode 100644 index 000000000..5404c96fb --- /dev/null +++ b/cockatrice/resources/config/qtlogging.ini @@ -0,0 +1,62 @@ +[Rules] +# 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 +# window_main.* = false +# release_channel = false +# spoiler_background_updater = false +# theme_manager = false +# sound_engine = false +# tapped_out_interface = false + +# tab_game = false +# tab_message = false +# tab_supervisor = false + +# dlg_edit_avatar = false +# dlg_settings = false +# dlg_tip_of_the_day = false +# dlg_update = false + +# settings_cache = false +# servers_settings = false +# shortcuts_settings = false + +# remote_client = false + +# player = false +# game_scene = false +# game_scene.player_addition_removal = false +# card_zone = false +# view_zone = false + +# user_info_connection = false + +# picture_loader = false +# picture_loader.worker = false +# picture_loader.card_back_cache_fail = false +# picture_loader.picture_to_load = false +# deck_loader = false +# card_database = false +# card_database.loading = false +# card_database.loading.success_or_failure = false +# cockatrice_xml.* = false +# cockatrice_xml.xml_3_parser = false +# cockatrice_xml.xml_4_parser = false +# card_info = false +# card_list = false + +#flow_layout = false +#flow_widget = false +#flow_widget.size = false + +# card_info_picture_widget = false + +# pixel_map_generator = false + +# filter_string = false diff --git a/cockatrice/resources/icons/cogwheel.svg b/cockatrice/resources/icons/cogwheel.svg new file mode 100644 index 000000000..8c7aad617 --- /dev/null +++ b/cockatrice/resources/icons/cogwheel.svg @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/cockatrice/resources/icons/dropdown_collapsed.svg b/cockatrice/resources/icons/dropdown_collapsed.svg new file mode 100644 index 000000000..52e833e65 --- /dev/null +++ b/cockatrice/resources/icons/dropdown_collapsed.svg @@ -0,0 +1,10 @@ + + + + + ]> + + + diff --git a/cockatrice/resources/icons/dropdown_expanded.svg b/cockatrice/resources/icons/dropdown_expanded.svg new file mode 100644 index 000000000..fa3ee205c --- /dev/null +++ b/cockatrice/resources/icons/dropdown_expanded.svg @@ -0,0 +1,10 @@ + + + + + ]> + + + diff --git a/cockatrice/resources/icons/mana/B.svg b/cockatrice/resources/icons/mana/B.svg new file mode 100644 index 000000000..d7e360054 --- /dev/null +++ b/cockatrice/resources/icons/mana/B.svg @@ -0,0 +1,47 @@ + + + + + + + + + diff --git a/cockatrice/resources/icons/mana/G.svg b/cockatrice/resources/icons/mana/G.svg new file mode 100644 index 000000000..3521f9d5a --- /dev/null +++ b/cockatrice/resources/icons/mana/G.svg @@ -0,0 +1,47 @@ + + + + + + + + + diff --git a/cockatrice/resources/icons/mana/R.svg b/cockatrice/resources/icons/mana/R.svg new file mode 100644 index 000000000..1f3f606eb --- /dev/null +++ b/cockatrice/resources/icons/mana/R.svg @@ -0,0 +1,47 @@ + + + + + + + + + diff --git a/cockatrice/resources/icons/mana/U.svg b/cockatrice/resources/icons/mana/U.svg new file mode 100644 index 000000000..b9b9f6069 --- /dev/null +++ b/cockatrice/resources/icons/mana/U.svg @@ -0,0 +1,48 @@ + + + + + + + + + diff --git a/cockatrice/resources/icons/mana/W.svg b/cockatrice/resources/icons/mana/W.svg new file mode 100644 index 000000000..3b8a6e06b --- /dev/null +++ b/cockatrice/resources/icons/mana/W.svg @@ -0,0 +1,47 @@ + + + + + + + + + diff --git a/cockatrice/resources/icons/reload.svg b/cockatrice/resources/icons/reload.svg new file mode 100644 index 000000000..102995876 --- /dev/null +++ b/cockatrice/resources/icons/reload.svg @@ -0,0 +1,7 @@ + + + + Svg Vector Icons : http://www.onlinewebfonts.com/icon + + \ No newline at end of file diff --git a/cockatrice/resources/icons/rename.svg b/cockatrice/resources/icons/rename.svg new file mode 100644 index 000000000..c68a00246 --- /dev/null +++ b/cockatrice/resources/icons/rename.svg @@ -0,0 +1,10 @@ + + + + + + + + diff --git a/cockatrice/resources/icons/swap.svg b/cockatrice/resources/icons/swap.svg new file mode 100644 index 000000000..a18ca2f06 --- /dev/null +++ b/cockatrice/resources/icons/swap.svg @@ -0,0 +1,12 @@ + + + + + + + + \ No newline at end of file diff --git a/cockatrice/resources/tips/images/add_card.png b/cockatrice/resources/tips/images/add_card.png new file mode 100644 index 000000000..731dc305a Binary files /dev/null and b/cockatrice/resources/tips/images/add_card.png differ diff --git a/cockatrice/resources/tips/images/card_select.png b/cockatrice/resources/tips/images/card_select.png new file mode 100644 index 000000000..1d2cc8f91 Binary files /dev/null and b/cockatrice/resources/tips/images/card_select.png differ diff --git a/cockatrice/resources/tips/images/edhrec.png b/cockatrice/resources/tips/images/edhrec.png new file mode 100644 index 000000000..8f17e6d49 Binary files /dev/null and b/cockatrice/resources/tips/images/edhrec.png differ diff --git a/cockatrice/resources/tips/images/expand_card_view.png b/cockatrice/resources/tips/images/expand_card_view.png new file mode 100644 index 000000000..0f348cf7a Binary files /dev/null and b/cockatrice/resources/tips/images/expand_card_view.png differ diff --git a/cockatrice/resources/tips/images/filter_games.png b/cockatrice/resources/tips/images/filter_games.png index de4eb7fdb..28cc7eacc 100644 Binary files a/cockatrice/resources/tips/images/filter_games.png and b/cockatrice/resources/tips/images/filter_games.png differ diff --git a/cockatrice/resources/tips/images/highlight_cards.png b/cockatrice/resources/tips/images/highlight_cards.png new file mode 100644 index 000000000..8ffeeb3b0 Binary files /dev/null and b/cockatrice/resources/tips/images/highlight_cards.png differ diff --git a/cockatrice/resources/tips/images/pawns.png b/cockatrice/resources/tips/images/pawns.png new file mode 100644 index 000000000..300a388bc Binary files /dev/null and b/cockatrice/resources/tips/images/pawns.png differ diff --git a/cockatrice/resources/tips/images/setpt.png b/cockatrice/resources/tips/images/setpt.png index 974c0224a..ad3ce3831 100644 Binary files a/cockatrice/resources/tips/images/setpt.png and b/cockatrice/resources/tips/images/setpt.png differ diff --git a/cockatrice/resources/tips/images/shortcuts.png b/cockatrice/resources/tips/images/shortcuts.png index 2034fdfd8..f64f9ad43 100644 Binary files a/cockatrice/resources/tips/images/shortcuts.png and b/cockatrice/resources/tips/images/shortcuts.png differ diff --git a/cockatrice/resources/tips/images/syntax_help.png b/cockatrice/resources/tips/images/syntax_help.png new file mode 100644 index 000000000..460b6d29a Binary files /dev/null and b/cockatrice/resources/tips/images/syntax_help.png differ diff --git a/cockatrice/resources/tips/images/token.png b/cockatrice/resources/tips/images/token.png new file mode 100644 index 000000000..3f4fb14c1 Binary files /dev/null and b/cockatrice/resources/tips/images/token.png differ diff --git a/cockatrice/resources/tips/images/updates.png b/cockatrice/resources/tips/images/updates.png new file mode 100644 index 000000000..b79e80f35 Binary files /dev/null and b/cockatrice/resources/tips/images/updates.png differ diff --git a/cockatrice/resources/tips/images/visual_deck_tags.png b/cockatrice/resources/tips/images/visual_deck_tags.png new file mode 100644 index 000000000..903fa2012 Binary files /dev/null and b/cockatrice/resources/tips/images/visual_deck_tags.png differ diff --git a/cockatrice/resources/tips/tips_of_the_day.xml b/cockatrice/resources/tips/tips_of_the_day.xml index 32afea374..cf1c419c7 100644 --- a/cockatrice/resources/tips/tips_of_the_day.xml +++ b/cockatrice/resources/tips/tips_of_the_day.xml @@ -1,99 +1,222 @@ - - Tip of the Day - 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! - tip_of_the_day.png - 2018-03-01 - - - Suggesting New Tips - You can suggest new Tips of the Day by reaching out to the development team on <a href="https://discord.gg/3Z9yzmA">Discord</a>! - discord.png - 2023-10-18 - - - Reporting Bugs - 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> - github_logo.png - 2018-03-01 - - - FAQ/Troubleshooting Wiki - 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> - cockatrice_wiki.png - 2018-03-01 - - - Register for a Server - Click on either Cockatrice (Windows) or Actions (Mac) and then Register to server... When the dialogue appears, fill out the desired server information. - cockatrice_register.png - 2018-03-01 - - - Drawing Arrows - 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 - - arrows.png - 2018-03-01 - - - Filtering Games - Don't see all the active games? Want to see a smaller selection? Use the Game Filters to change your horizon - filter_games.png - 2018-03-01 - - - Upload Custom Avatar - Want to show off your hippo avatar? Need to update your password? Check out the Accounts Tab for more info! - accounts_tab.png - 2018-03-01 - - - Common Shortcuts - 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! - - shortcuts.png - 2018-03-01 - - - Changing Themes - 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! - themes.png - 2018-03-01 - - - Flip of the Coin - You can flip a coin instead of rolling a die by rolling a 2 sided die instead! - coin_flip.png - 2018-03-01 - - - Face Down Cards - You can hold Shift while dragging or clicking on a card to have it enter play face down - face_down.png - 2018-03-01 - - - Counter expressions - 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. - counter_expression.png - 2019-02-02 - - - Power and Toughness - 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 ). - setpt.png - 2019-03-02 - + + Tip of the Day + 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! + + tip_of_the_day.png + 2025-02-10 + + + Join the Community + 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>! + discord.png + 2025-02-09 + + + Reporting Bugs and Requesting Features + 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> + github_logo.png + 2025-02-09 + + + FAQ/Troubleshooting Wiki + 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> + cockatrice_wiki.png + 2018-03-01 + + + Register for a Server + Click on either Cockatrice (Windows) or Actions (Mac) and then Register to server... When the dialogue appears, fill out the desired server information. + cockatrice_register.png + 2018-03-01 + + + Filtering Games + Don't see an open game or want to see a smaller selection? Use the Game Filters to change your horizon! + filter_games.png + 2025-02-09 + + + Common Shortcuts + 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! + + shortcuts.png + 2025-02-09 + + + Power and Toughness + 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! + + setpt.png + 2025-02-09 + + + Drawing Arrows + 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> + + arrows.png + 2018-03-01 + + + General Etiquette + 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> + + token.png + 2025-02-10 + + + Flip of the Coin + You can flip a coin instead of rolling a die by rolling a 2 sided die instead! + coin_flip.png + 2018-03-01 + + + Upload Custom Avatar + Want to show off your hippo avatar? Need to update your password? Check out the Accounts Tab for more info! + accounts_tab.png + 2018-03-01 + + + Player Icon Key + 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> + + pawns.png + 2025-02-09 + + + Changing Themes + 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! + themes.png + 2018-03-01 + + + Face Down Cards + 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! + + face_down.png + 2025-02-09 + + + Counter expressions + 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. + counter_expression.png + 2019-02-02 + + + Select Multiple Cards + 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> + + highlight_cards.png + 2025-02-09 + + + Auto Cascade and Search Parameters + 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! + + syntax_help.png + 2025-02-09 + + + Visual Deck Storage and Tags + 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! + + visual_deck_tags.png + 2025-02-09 + + + Card Selector And Card Preferences + 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" + + card_select.png + 2025-02-10 + + + EDHREC Integration + 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. + edhrec.png + 2025-02-10 + + + Add Cards to Deck + 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. + add_card.png + 2025-02-10 + + + Update Client and Card Database + 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 + + updates.png + 2025-02-10 + + + Expand Card View Window + 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". + + expand_card_view.png + 2025-02-27 + diff --git a/cockatrice/resources/userlevels/registered.svg b/cockatrice/resources/usericons/pawn_donator_double.svg similarity index 74% rename from cockatrice/resources/userlevels/registered.svg rename to cockatrice/resources/usericons/pawn_donator_double.svg index a7f8a72cf..f0dba08b2 100644 --- a/cockatrice/resources/userlevels/registered.svg +++ b/cockatrice/resources/usericons/pawn_donator_double.svg @@ -2,20 +2,20 @@ + inkscape:version="1.4 (86a8ad7, 2024-10-11)" + sodipodi:docname="pawn_donator_double.svg" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:xlink="http://www.w3.org/1999/xlink" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:dc="http://purl.org/dc/elements/1.1/"> - + + + + + + inkscape:window-width="1147" + inkscape:window-height="1211" + inkscape:window-x="2678" + inkscape:window-y="120" + inkscape:window-maximized="0" + inkscape:showpageshadow="2" + inkscape:pagecheckerboard="0" + inkscape:deskcolor="#d1d1d1" + showguides="true"> + + @@ -325,9 +347,20 @@ id="layer1" transform="translate(0,-952.36218)"> + id="right" /> + + diff --git a/cockatrice/resources/usericons/pawn_donator_single.svg b/cockatrice/resources/usericons/pawn_donator_single.svg new file mode 100644 index 000000000..9e7d63240 --- /dev/null +++ b/cockatrice/resources/usericons/pawn_donator_single.svg @@ -0,0 +1,229 @@ + + + +image/svg+xml diff --git a/cockatrice/resources/userlevels/normal.svg b/cockatrice/resources/usericons/pawn_double.svg similarity index 72% rename from cockatrice/resources/userlevels/normal.svg rename to cockatrice/resources/usericons/pawn_double.svg index 172d8620c..b9f72d385 100644 --- a/cockatrice/resources/userlevels/normal.svg +++ b/cockatrice/resources/usericons/pawn_double.svg @@ -2,20 +2,20 @@ + inkscape:version="1.4 (86a8ad7, 2024-10-11)" + sodipodi:docname="pawn_double.svg" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:xlink="http://www.w3.org/1999/xlink" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:dc="http://purl.org/dc/elements/1.1/"> - - - - - - - - + inkscape:window-width="1147" + inkscape:window-height="1211" + inkscape:window-x="2842" + inkscape:window-y="58" + inkscape:window-maximized="0" + inkscape:showpageshadow="2" + inkscape:pagecheckerboard="0" + inkscape:deskcolor="#d1d1d1" /> @@ -291,10 +244,25 @@ inkscape:groupmode="layer" id="layer1" transform="translate(0,-952.36218)"> - + + + + + diff --git a/cockatrice/resources/userlevels/registered_vip.svg b/cockatrice/resources/usericons/pawn_judge_double.svg similarity index 81% rename from cockatrice/resources/userlevels/registered_vip.svg rename to cockatrice/resources/usericons/pawn_judge_double.svg index 48982fa2f..e929f6490 100644 --- a/cockatrice/resources/userlevels/registered_vip.svg +++ b/cockatrice/resources/usericons/pawn_judge_double.svg @@ -2,33 +2,22 @@ + inkscape:version="1.4 (86a8ad7, 2024-10-11)" + sodipodi:docname="pawn_judge_double.svg" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:xlink="http://www.w3.org/1999/xlink" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:dc="http://purl.org/dc/elements/1.1/"> - - - - - + inkscape:window-width="1440" + inkscape:window-height="792" + inkscape:window-x="2921" + inkscape:window-y="661" + inkscape:window-maximized="0" + inkscape:showpageshadow="2" + inkscape:pagecheckerboard="0" + inkscape:deskcolor="#d1d1d1" /> @@ -326,7 +308,7 @@ image/svg+xml - + @@ -336,9 +318,20 @@ id="layer1" transform="translate(0,-952.36218)"> + id="right" /> + + diff --git a/cockatrice/resources/usericons/pawn_judge_single.svg b/cockatrice/resources/usericons/pawn_judge_single.svg new file mode 100644 index 000000000..10246d42d --- /dev/null +++ b/cockatrice/resources/usericons/pawn_judge_single.svg @@ -0,0 +1,212 @@ + + + +image/svg+xml + + diff --git a/cockatrice/resources/userlevels/registered_donator.svg b/cockatrice/resources/usericons/pawn_single.svg similarity index 78% rename from cockatrice/resources/userlevels/registered_donator.svg rename to cockatrice/resources/usericons/pawn_single.svg index 1ec430e68..2f0c94ae8 100644 --- a/cockatrice/resources/userlevels/registered_donator.svg +++ b/cockatrice/resources/usericons/pawn_single.svg @@ -2,20 +2,20 @@ + inkscape:version="1.4 (86a8ad7, 2024-10-11)" + sodipodi:docname="pawn_single.svg" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:xlink="http://www.w3.org/1999/xlink" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:dc="http://purl.org/dc/elements/1.1/"> - - - - - - - - - - - + inkscape:window-width="1147" + inkscape:window-height="1211" + inkscape:window-x="3185" + inkscape:window-y="44" + inkscape:window-maximized="0" + inkscape:showpageshadow="2" + inkscape:pagecheckerboard="0" + inkscape:deskcolor="#d1d1d1" /> @@ -283,9 +245,9 @@ id="layer1" transform="translate(0,-952.36218)"> diff --git a/cockatrice/resources/userlevels/moderator_vip.svg b/cockatrice/resources/usericons/pawn_vip_double.svg similarity index 96% rename from cockatrice/resources/userlevels/moderator_vip.svg rename to cockatrice/resources/usericons/pawn_vip_double.svg index 6d93983e3..0d002a791 100644 --- a/cockatrice/resources/userlevels/moderator_vip.svg +++ b/cockatrice/resources/usericons/pawn_vip_double.svg @@ -337,19 +337,19 @@ id="layer1" transform="translate(0,-952.36218)"> + id="right" /> + id="left" /> diff --git a/cockatrice/resources/userlevels/registered_buddy.svg b/cockatrice/resources/usericons/star_single.svg similarity index 97% rename from cockatrice/resources/userlevels/registered_buddy.svg rename to cockatrice/resources/usericons/star_single.svg index 2d2ca600b..acc5e4da6 100644 --- a/cockatrice/resources/userlevels/registered_buddy.svg +++ b/cockatrice/resources/usericons/star_single.svg @@ -109,7 +109,7 @@ inkscape:connector-curvature="0" /> diff --git a/cockatrice/resources/userlevels/admin.svg b/cockatrice/resources/userlevels/admin.svg deleted file mode 100644 index 5675cc0f8..000000000 --- a/cockatrice/resources/userlevels/admin.svg +++ /dev/null @@ -1,301 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - image/svg+xml - - - - - - - - - diff --git a/cockatrice/resources/userlevels/admin_buddy.svg b/cockatrice/resources/userlevels/admin_buddy.svg deleted file mode 100644 index 5ff5c87aa..000000000 --- a/cockatrice/resources/userlevels/admin_buddy.svg +++ /dev/null @@ -1,136 +0,0 @@ - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/cockatrice/resources/userlevels/admin_vip_buddy.svg b/cockatrice/resources/userlevels/admin_vip_buddy.svg deleted file mode 100644 index 5ff5c87aa..000000000 --- a/cockatrice/resources/userlevels/admin_vip_buddy.svg +++ /dev/null @@ -1,136 +0,0 @@ - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/cockatrice/resources/userlevels/moderator.svg b/cockatrice/resources/userlevels/moderator.svg deleted file mode 100644 index 37ac156b9..000000000 --- a/cockatrice/resources/userlevels/moderator.svg +++ /dev/null @@ -1,294 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - diff --git a/cockatrice/resources/userlevels/moderator_vip_buddy.svg b/cockatrice/resources/userlevels/moderator_vip_buddy.svg deleted file mode 100644 index 73a3efb04..000000000 --- a/cockatrice/resources/userlevels/moderator_vip_buddy.svg +++ /dev/null @@ -1,131 +0,0 @@ - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/cockatrice/resources/userlevels/registered_donator_buddy.svg b/cockatrice/resources/userlevels/registered_donator_buddy.svg deleted file mode 100644 index 476d55088..000000000 --- a/cockatrice/resources/userlevels/registered_donator_buddy.svg +++ /dev/null @@ -1,137 +0,0 @@ - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/cockatrice/resources/userlevels/registered_vip_buddy.svg b/cockatrice/resources/userlevels/registered_vip_buddy.svg deleted file mode 100644 index 27757b934..000000000 --- a/cockatrice/resources/userlevels/registered_vip_buddy.svg +++ /dev/null @@ -1,127 +0,0 @@ - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/cockatrice/src/client/game_logic/abstract_client.cpp b/cockatrice/src/client/game_logic/abstract_client.cpp index c186ae323..bc227bf68 100644 --- a/cockatrice/src/client/game_logic/abstract_client.cpp +++ b/cockatrice/src/client/game_logic/abstract_client.cpp @@ -52,7 +52,7 @@ AbstractClient::AbstractClient(QObject *parent) FeatureSet features; features.initalizeFeatureList(clientFeatures); - connect(this, SIGNAL(sigQueuePendingCommand(PendingCommand *)), this, SLOT(queuePendingCommand(PendingCommand *))); + connect(this, &AbstractClient::sigQueuePendingCommand, this, &AbstractClient::queuePendingCommand); } AbstractClient::~AbstractClient() diff --git a/cockatrice/src/client/game_logic/abstract_client.h b/cockatrice/src/client/game_logic/abstract_client.h index 97b9c0f2d..5e4948f5e 100644 --- a/cockatrice/src/client/game_logic/abstract_client.h +++ b/cockatrice/src/client/game_logic/abstract_client.h @@ -48,6 +48,7 @@ class AbstractClient : public QObject Q_OBJECT signals: void statusChanged(ClientStatus _status); + void maxPingTime(int seconds, int maxSeconds); // Room events void roomEventReceived(const RoomEvent &event); @@ -97,8 +98,8 @@ protected: virtual void sendCommandContainer(const CommandContainer &cont) = 0; public: - AbstractClient(QObject *parent = nullptr); - ~AbstractClient(); + explicit AbstractClient(QObject *parent = nullptr); + ~AbstractClient() override; ClientStatus getStatus() const { diff --git a/cockatrice/src/client/game_logic/key_signals.h b/cockatrice/src/client/game_logic/key_signals.h index e0808fb0c..32e656c76 100644 --- a/cockatrice/src/client/game_logic/key_signals.h +++ b/cockatrice/src/client/game_logic/key_signals.h @@ -23,7 +23,7 @@ signals: void onCtrlC(); protected: - virtual bool eventFilter(QObject *, QEvent *event); + bool eventFilter(QObject *, QEvent *event) override; }; #endif diff --git a/cockatrice/src/client/menus/deck_editor/deck_editor_menu.cpp b/cockatrice/src/client/menus/deck_editor/deck_editor_menu.cpp new file mode 100644 index 000000000..2d8c613a5 --- /dev/null +++ b/cockatrice/src/client/menus/deck_editor/deck_editor_menu.cpp @@ -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")); +} diff --git a/cockatrice/src/client/menus/deck_editor/deck_editor_menu.h b/cockatrice/src/client/menus/deck_editor/deck_editor_menu.h new file mode 100644 index 000000000..a713b009f --- /dev/null +++ b/cockatrice/src/client/menus/deck_editor/deck_editor_menu.h @@ -0,0 +1,32 @@ +#ifndef DECK_EDITOR_MENU_H +#define DECK_EDITOR_MENU_H + +#include "../../tabs/abstract_tab_deck_editor.h" + +#include + +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 diff --git a/cockatrice/src/client/network/client_update_checker.cpp b/cockatrice/src/client/network/client_update_checker.cpp new file mode 100644 index 000000000..23c52d08d --- /dev/null +++ b/cockatrice/src/client/network/client_update_checker.cpp @@ -0,0 +1,35 @@ +#include "client_update_checker.h" + +#include "../../settings/cache_settings.h" +#include "release_channel.h" + +ClientUpdateChecker::ClientUpdateChecker(QObject *parent) : QObject(parent) +{ +} + +void ClientUpdateChecker::check() +{ + auto releaseChannel = SettingsCache::instance().getUpdateReleaseChannel(); + + finishedCheckConnection = + connect(releaseChannel, &ReleaseChannel::finishedCheck, this, &ClientUpdateChecker::actFinishedCheck); + errorConnection = connect(releaseChannel, &ReleaseChannel::error, this, &ClientUpdateChecker::actError); + + releaseChannel->checkForUpdates(); +} + +void ClientUpdateChecker::actFinishedCheck(bool needToUpdate, bool isCompatible, Release *release) +{ + disconnect(finishedCheckConnection); + disconnect(errorConnection); + + emit finishedCheck(needToUpdate, isCompatible, release); +} + +void ClientUpdateChecker::actError(const QString &errorString) +{ + disconnect(finishedCheckConnection); + disconnect(errorConnection); + + emit error(errorString); +} \ No newline at end of file diff --git a/cockatrice/src/client/network/client_update_checker.h b/cockatrice/src/client/network/client_update_checker.h new file mode 100644 index 000000000..96a7213b1 --- /dev/null +++ b/cockatrice/src/client/network/client_update_checker.h @@ -0,0 +1,45 @@ +#ifndef CLIENT_UPDATE_CHECKER_H +#define CLIENT_UPDATE_CHECKER_H +#include + +class Release; + +/** + * We use a singleton instance of UpdateChannel, which can cause interference and feedback loops when multiple objects + * connect to it. + * + * This class encapsulates the usage of that UpdateChannel to ensure that the check only happens once per connection and + * the connection is destroyed after it's been used. + */ +class ClientUpdateChecker : public QObject +{ + Q_OBJECT + + QMetaObject::Connection finishedCheckConnection; + QMetaObject::Connection errorConnection; + + void actFinishedCheck(bool needToUpdate, bool isCompatible, Release *release); + void actError(const QString &errorString); + +public: + explicit ClientUpdateChecker(QObject *parent = nullptr); + /** + * Actually performs the check, using the currently selected update channel in the settings. + * Any resulting signals will only be sent once. + * This method should only be called ONCE per instance. + */ + void check(); + +signals: + /** + * Forwarded from UpdateChannel::finishedCheck + */ + void finishedCheck(bool needToUpdate, bool isCompatible, Release *release); + + /** + * Forwarded from UpdateChannel::error + */ + void error(const QString &errorString); +}; + +#endif // CLIENT_UPDATE_CHECKER_H diff --git a/cockatrice/src/client/network/release_channel.cpp b/cockatrice/src/client/network/release_channel.cpp index b467aa4a4..9edfe7caf 100644 --- a/cockatrice/src/client/network/release_channel.cpp +++ b/cockatrice/src/client/network/release_channel.cpp @@ -11,6 +11,11 @@ #include #include +#if defined(Q_OS_MACOS) +#include +#include +#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/" @@ -21,11 +26,8 @@ #define GIT_SHORT_HASH_LEN 7 -int ReleaseChannel::sharedIndex = 0; - ReleaseChannel::ReleaseChannel() : netMan(new QNetworkAccessManager(this)), response(nullptr), lastRelease(nullptr) { - index = sharedIndex++; } ReleaseChannel::~ReleaseChannel() @@ -36,31 +38,42 @@ ReleaseChannel::~ReleaseChannel() void ReleaseChannel::checkForUpdates() { QString releaseChannelUrl = getReleaseChannelUrl(); - qDebug() << "Searching for updates on the channel: " << releaseChannelUrl; + qCInfo(ReleaseChannelLog) << "Searching for updates on the channel: " << releaseChannelUrl; response = netMan->get(QNetworkRequest(releaseChannelUrl)); - connect(response, SIGNAL(finished()), this, SLOT(releaseListFinished())); + connect(response, &QNetworkReply::finished, this, &ReleaseChannel::releaseListFinished); } // Different release channel checking functions for different operating systems -#if defined(Q_OS_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 @@ -71,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 { @@ -89,7 +101,7 @@ QString StableReleaseChannel::getManualDownloadUrl() const QString StableReleaseChannel::getName() const { - return tr("Stable Releases"); + return tr("Default"); } QString StableReleaseChannel::getReleaseChannelUrl() const @@ -104,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; } @@ -112,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."; + qCWarning(ReleaseChannelLog) << "Invalid received from the release update server:" << resultMap; emit error(tr("Invalid reply received from the release update server.")); return; } @@ -126,39 +138,31 @@ void StableReleaseChannel::releaseListFinished() if (resultMap.contains("assets")) { auto rawAssets = resultMap["assets"].toList(); - // [(name, url)] - QVector> 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 nameAndUrl) { - return downloadMatchesCurrentOS(nameAndUrl.first); - }); - - if (_releaseAsset != assets.end()) { - std::pair 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); - qDebug() << "Current hash=" << myHash << "update hash=" << shortHash; + qCInfo(ReleaseChannelLog) << "Current hash=" << myHash << "update hash=" << shortHash; - qDebug() << "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; - qDebug() << "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, SIGNAL(finished()), this, SLOT(tagListFinished())); + connect(response, &QNetworkReply::finished, this, &StableReleaseChannel::tagListFinished); } void StableReleaseChannel::tagListFinished() @@ -168,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()); - qDebug() << "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); - qDebug() << "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); @@ -203,7 +207,7 @@ QString BetaReleaseChannel::getManualDownloadUrl() const QString BetaReleaseChannel::getName() const { - return tr("Beta Releases"); + return tr("Beta"); } QString BetaReleaseChannel::getReleaseChannelUrl() const @@ -228,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; } @@ -237,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; } @@ -252,15 +256,15 @@ void BetaReleaseChannel::releaseListFinished() lastRelease->setName(QString("%1 (%2)").arg(resultMap["tag_name"].toString()).arg(shortHash)); lastRelease->setDescriptionUrl(QString(BETARELEASE_CHANGESURL).arg(VERSION_COMMIT, shortHash)); - qDebug() << "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(); - qDebug() << "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, SIGNAL(finished()), this, SLOT(fileListFinished())); + connect(response, &QNetworkReply::finished, this, &BetaReleaseChannel::fileListFinished); } void BetaReleaseChannel::fileListFinished() @@ -270,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; } @@ -278,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); - qDebug() << "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; @@ -295,7 +299,7 @@ void BetaReleaseChannel::fileListFinished() if (downloadMatchesCurrentOS(*url)) { compatibleVersion = true; lastRelease->setDownloadUrl(*url); - qDebug() << "Found compatible version url=" << *url; + qCInfo(ReleaseChannelLog) << "Found compatible version url=" << *url; break; } } diff --git a/cockatrice/src/client/network/release_channel.h b/cockatrice/src/client/network/release_channel.h index f9507b858..0fb459969 100644 --- a/cockatrice/src/client/network/release_channel.h +++ b/cockatrice/src/client/network/release_channel.h @@ -2,11 +2,14 @@ #define RELEASECHANNEL_H #include +#include #include #include #include #include +inline Q_LOGGING_CATEGORY(ReleaseChannelLog, "release_channel"); + class QNetworkReply; class QNetworkAccessManager; @@ -82,9 +85,6 @@ public: ~ReleaseChannel() override; protected: - // shared by all instances - static int sharedIndex; - int index; QNetworkAccessManager *netMan; QNetworkReply *response; Release *lastRelease; @@ -94,10 +94,6 @@ protected: virtual QString getReleaseChannelUrl() const = 0; public: - int getIndex() const - { - return index; - } Release *getLastRelease() { return lastRelease; diff --git a/cockatrice/src/client/network/replay_timeline_widget.cpp b/cockatrice/src/client/network/replay_timeline_widget.cpp index 83b1abda0..919ee3d97 100644 --- a/cockatrice/src/client/network/replay_timeline_widget.cpp +++ b/cockatrice/src/client/network/replay_timeline_widget.cpp @@ -6,10 +6,11 @@ #include ReplayTimelineWidget::ReplayTimelineWidget(QWidget *parent) - : QWidget(parent), maxBinValue(1), maxTime(1), timeScaleFactor(1.0), currentTime(0), currentEvent(0) + : QWidget(parent), maxBinValue(1), maxTime(1), timeScaleFactor(1.0), currentVisualTime(0), currentProcessedTime(0), + currentEvent(0) { replayTimer = new QTimer(this); - connect(replayTimer, SIGNAL(timeout()), this, SLOT(replayTimerTimeout())); + connect(replayTimer, &QTimer::timeout, this, &ReplayTimelineWidget::replayTimerTimeout); rewindBufferingTimer = new QTimer(this); rewindBufferingTimer->setSingleShot(true); @@ -58,7 +59,7 @@ void ReplayTimelineWidget::paintEvent(QPaintEvent * /* event */) painter.fillPath(path, Qt::black); const QColor barColor = QColor::fromHsv(120, 255, 255, 100); - quint64 w = (quint64)(width() - 1) * (quint64)currentTime / maxTime; + quint64 w = (quint64)(width() - 1) * (quint64)currentVisualTime / maxTime; painter.fillRect(0, 0, static_cast(w), height() - 1, barColor); } @@ -85,8 +86,8 @@ void ReplayTimelineWidget::skipToTime(int newTime, bool doRewindBuffering) newTime -= newTime % TIMER_INTERVAL_MS; // Time should always be a multiple of the interval - const bool isBackwardsSkip = newTime < currentTime; - currentTime = newTime; + const bool isBackwardsSkip = newTime < currentProcessedTime; + currentVisualTime = newTime; if (isBackwardsSkip) { handleBackwardsSkip(doRewindBuffering); @@ -106,20 +107,13 @@ void ReplayTimelineWidget::handleBackwardsSkip(bool doRewindBuffering) // The rewind only happens once the timer runs out. // If another backwards skip happens, the timer will just get reset instead of rewinding. rewindBufferingTimer->stop(); - rewindBufferingTimer->start(calcRewindBufferingTimeout()); + rewindBufferingTimer->start(SettingsCache::instance().getRewindBufferingMs()); } else { // otherwise, process the rewind immediately processRewind(); } } -/// The timeout scales based on the current event number, up to a limit -int ReplayTimelineWidget::calcRewindBufferingTimeout() const -{ - int extraTime = currentEvent / 100; - return std::min(BASE_REWIND_BUFFERING_TIMEOUT_MS + extraTime, MAX_REWIND_BUFFERING_TIMEOUT_MS); -} - void ReplayTimelineWidget::processRewind() { // stop any queued-up rewinds @@ -143,25 +137,31 @@ QSize ReplayTimelineWidget::minimumSizeHint() const void ReplayTimelineWidget::replayTimerTimeout() { - currentTime += TIMER_INTERVAL_MS; + currentVisualTime += TIMER_INTERVAL_MS; processNewEvents(NORMAL_PLAYBACK); - if (!(currentTime % 1000)) + if (!(currentVisualTime % 1000)) update(); } /// Processes all unprocessed events up to the current time. void ReplayTimelineWidget::processNewEvents(PlaybackMode playbackMode) { - while ((currentEvent < replayTimeline.size()) && (replayTimeline[currentEvent] < currentTime)) { + currentProcessedTime = currentVisualTime; + + while ((currentEvent < replayTimeline.size()) && (replayTimeline[currentEvent] < currentProcessedTime)) { Player::EventProcessingOptions options; // backwards skip => always skip reveal windows // forwards skip => skip reveal windows that don't happen within a big skip of the target - if (playbackMode == BACKWARD_SKIP || currentTime - replayTimeline[currentEvent] > BIG_SKIP_MS) + if (playbackMode == BACKWARD_SKIP || currentProcessedTime - replayTimeline[currentEvent] > BIG_SKIP_MS) options |= Player::EventProcessingOption::SKIP_REVEAL_WINDOW; + // backwards skip => always skip tap animation + if (playbackMode == BACKWARD_SKIP) + options |= Player::EventProcessingOption::SKIP_TAP_ANIMATION; + emit processNextEvent(options); ++currentEvent; } @@ -189,5 +189,5 @@ void ReplayTimelineWidget::stopReplay() void ReplayTimelineWidget::skipByAmount(int amount) { - skipToTime(currentTime + amount, true); + skipToTime(currentVisualTime + amount, amount < 0); } \ No newline at end of file diff --git a/cockatrice/src/client/network/replay_timeline_widget.h b/cockatrice/src/client/network/replay_timeline_widget.h index b4a0559a4..9676e7b47 100644 --- a/cockatrice/src/client/network/replay_timeline_widget.h +++ b/cockatrice/src/client/network/replay_timeline_widget.h @@ -28,8 +28,6 @@ private: static constexpr int TIMER_INTERVAL_MS = 200; static constexpr int BIN_LENGTH = 5000; - static constexpr int BASE_REWIND_BUFFERING_TIMEOUT_MS = 180; - static constexpr int MAX_REWIND_BUFFERING_TIMEOUT_MS = 280; QTimer *replayTimer; QTimer *rewindBufferingTimer; @@ -37,12 +35,13 @@ private: QList histogram; int maxBinValue, maxTime; qreal timeScaleFactor; - int currentTime; + int currentVisualTime; // time currently displayed by the timeline + int currentProcessedTime; // time that events are currently processed up to. Could differ from visual time due to + // rewind buffering int currentEvent; void skipToTime(int newTime, bool doRewindBuffering); void handleBackwardsSkip(bool doRewindBuffering); - int calcRewindBufferingTimeout() const; void processRewind(); void processNewEvents(PlaybackMode playbackMode); private slots: diff --git a/cockatrice/src/client/network/sets_model.cpp b/cockatrice/src/client/network/sets_model.cpp index de3521772..a86b710b5 100644 --- a/cockatrice/src/client/network/sets_model.cpp +++ b/cockatrice/src/client/network/sets_model.cpp @@ -195,6 +195,13 @@ void SetsModel::swapRows(int oldRow, int newRow) emit dataChanged(index(0, 0), index(rowCount() - 1, columnCount() - 1)); } +void SetsModel::restoreOriginalOrder() +{ + int numRows = rowCount(); + sets.defaultSort(); + emit dataChanged(index(0, 0), index(numRows - 1, columnCount() - 1)); +} + void SetsModel::sort(int column, Qt::SortOrder order) { QMultiMap setMap; diff --git a/cockatrice/src/client/network/sets_model.h b/cockatrice/src/client/network/sets_model.h index 20b5adbcb..0bfbe57e0 100644 --- a/cockatrice/src/client/network/sets_model.h +++ b/cockatrice/src/client/network/sets_model.h @@ -49,44 +49,47 @@ public: LongNameCol, ShortNameCol, SetTypeCol, - ReleaseDateCol + ReleaseDateCol, + PriorityCol }; enum Role { SortRole = Qt::UserRole }; - SetsModel(CardDatabase *_db, QObject *parent = nullptr); - ~SetsModel(); - int rowCount(const QModelIndex &parent = QModelIndex()) const; - int columnCount(const QModelIndex &parent = QModelIndex()) const + explicit SetsModel(CardDatabase *_db, QObject *parent = nullptr); + ~SetsModel() override; + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + int columnCount(const QModelIndex &parent = QModelIndex()) const override { Q_UNUSED(parent); return NUM_COLS; } - QVariant data(const QModelIndex &index, int role) const; - bool setData(const QModelIndex &index, const QVariant &value, int role); - QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; - Qt::ItemFlags flags(const QModelIndex &index) const; - Qt::DropActions supportedDropActions() const; + QVariant data(const QModelIndex &index, int role) const override; + bool setData(const QModelIndex &index, const QVariant &value, int role) override; + QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; + Qt::ItemFlags flags(const QModelIndex &index) const override; + Qt::DropActions supportedDropActions() const override; - QMimeData *mimeData(const QModelIndexList &indexes) const; - bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent); - QStringList mimeTypes() const; + QMimeData *mimeData(const QModelIndexList &indexes) const override; + bool + dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) override; + QStringList mimeTypes() const override; void swapRows(int oldRow, int newRow); void toggleRow(int row, bool enable); void toggleRow(int row); void toggleAll(bool); - void sort(int column, Qt::SortOrder order = Qt::AscendingOrder); + void sort(int column, Qt::SortOrder order = Qt::AscendingOrder) override; void save(CardDatabase *db); void restore(CardDatabase *db); + void restoreOriginalOrder(); }; class SetsDisplayModel : public QSortFilterProxyModel { Q_OBJECT public: - SetsDisplayModel(QObject *parent = NULL); + explicit SetsDisplayModel(QObject *parent = nullptr); protected: bool lessThan(const QModelIndex &left, const QModelIndex &right) const override; diff --git a/cockatrice/src/client/network/spoiler_background_updater.cpp b/cockatrice/src/client/network/spoiler_background_updater.cpp index e60eeb96a..a8a3a4b81 100644 --- a/cockatrice/src/client/network/spoiler_background_updater.cpp +++ b/cockatrice/src/client/network/spoiler_background_updater.cpp @@ -28,7 +28,7 @@ SpoilerBackgroundUpdater::SpoilerBackgroundUpdater(QObject *apParent) : QObject( // File exists means we're in spoiler season startSpoilerDownloadProcess(SPOILERS_STATUS_URL, false); } else { - qDebug() << "Spoilers Disabled"; + qCInfo(SpoilerBackgroundUpdaterLog) << "Spoilers Disabled"; } } @@ -45,10 +45,10 @@ void SpoilerBackgroundUpdater::downloadFromURL(QUrl url, bool saveResults) if (saveResults) { // This will write out to the file (used for spoiler.xml) - connect(reply, SIGNAL(finished()), this, SLOT(actDownloadFinishedSpoilersFile())); + connect(reply, &QNetworkReply::finished, this, &SpoilerBackgroundUpdater::actDownloadFinishedSpoilersFile); } else { // This will check the status (used to see if we're in spoiler season or not) - connect(reply, SIGNAL(finished()), this, SLOT(actCheckIfSpoilerSeasonEnabled())); + connect(reply, &QNetworkReply::finished, this, &SpoilerBackgroundUpdater::actCheckIfSpoilerSeasonEnabled); } } @@ -67,7 +67,7 @@ void SpoilerBackgroundUpdater::actDownloadFinishedSpoilersFile() reply->deleteLater(); emit spoilerCheckerDone(); } else { - qDebug() << "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()) { - qDebug() << "Deleting spoiler.xml"; + qCInfo(SpoilerBackgroundUpdaterLog) << "Deleting spoiler.xml"; return true; } - qDebug() << "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")); } - qDebug() << "Spoiler Season Offline"; + qCInfo(SpoilerBackgroundUpdaterLog) << "Spoiler Season Offline"; emit spoilerCheckerDone(); } else if (errorCode == QNetworkReply::NoError) { - qDebug() << "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")); } - qDebug() << "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); } - qDebug() << "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")); } - qDebug() << "Spoilers Up to Date"; + qCInfo(SpoilerBackgroundUpdaterLog) << "Spoilers Up to Date"; return false; } QFile file(fileName); if (!file.open(QIODevice::WriteOnly)) { - qDebug() << "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) { - qDebug() << "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 - qDebug() << "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 @@ -167,7 +167,7 @@ bool SpoilerBackgroundUpdater::saveDownloadedFile(QByteArray data) if (trayIcon) { QList lines = data.split('\n'); - foreach (QByteArray line, lines) { + for (const QByteArray &line : lines) { if (line.contains("Created At:")) { QString timeStamp = QString(line).replace("Created At:", "").trimmed(); timeStamp.chop(6); // Remove " (UTC)" @@ -202,12 +202,12 @@ QByteArray SpoilerBackgroundUpdater::getHash(const QString fileName) QCryptographicHash hash(QCryptographicHash::Algorithm::Md5); hash.addData(bytes); - qDebug() << "File Hash =" << hash.result(); + qCInfo(SpoilerBackgroundUpdaterLog) << "File Hash =" << hash.result(); file.close(); return hash.result(); } else { - qDebug() << "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); - qDebug() << "Data Hash =" << hash.result(); + qCInfo(SpoilerBackgroundUpdaterLog) << "Data Hash =" << hash.result(); return hash.result(); } diff --git a/cockatrice/src/client/network/spoiler_background_updater.h b/cockatrice/src/client/network/spoiler_background_updater.h index 7d7a2892c..a29488bf2 100644 --- a/cockatrice/src/client/network/spoiler_background_updater.h +++ b/cockatrice/src/client/network/spoiler_background_updater.h @@ -2,9 +2,12 @@ #define COCKATRICE_SPOILER_DOWNLOADER_H #include +#include #include #include +inline Q_LOGGING_CATEGORY(SpoilerBackgroundUpdaterLog, "spoiler_background_updater"); + class SpoilerBackgroundUpdater : public QObject { Q_OBJECT diff --git a/cockatrice/src/client/sound_engine.cpp b/cockatrice/src/client/sound_engine.cpp index 35cc6d5c7..fef2df093 100644 --- a/cockatrice/src/client/sound_engine.cpp +++ b/cockatrice/src/client/sound_engine.cpp @@ -12,11 +12,11 @@ #define DEFAULT_THEME_NAME "Default" #define TEST_SOUND_FILENAME "player_join" -SoundEngine::SoundEngine(QObject *parent) : QObject(parent), player(nullptr) +SoundEngine::SoundEngine(QObject *parent) : QObject(parent), audioOutput(nullptr), player(nullptr) { ensureThemeDirectoryExists(); - connect(&SettingsCache::instance(), SIGNAL(soundThemeChanged()), this, SLOT(themeChangedSlot())); - connect(&SettingsCache::instance(), SIGNAL(soundEnabledChanged()), this, SLOT(soundEnabledChanged())); + connect(&SettingsCache::instance(), &SettingsCache::soundThemeChanged, this, &SoundEngine::themeChangedSlot); + connect(&SettingsCache::instance(), &SettingsCache::soundEnabledChanged, this, &SoundEngine::soundEnabledChanged); soundEnabledChanged(); themeChangedSlot(); @@ -28,26 +28,34 @@ SoundEngine::~SoundEngine() player->deleteLater(); player = nullptr; } + if (audioOutput) { + audioOutput->deleteLater(); + audioOutput = nullptr; + } } void SoundEngine::soundEnabledChanged() { if (SettingsCache::instance().getSoundEnabled()) { - qDebug() << "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)) - auto qAudioOutput = new QAudioOutput; - player->setAudioOutput(qAudioOutput); + audioOutput = new QAudioOutput(player); + player->setAudioOutput(audioOutput); #endif } } else { - qDebug() << "SoundEngine: disabling sound"; + qCInfo(SoundEngineLog) << "SoundEngine: disabling sound"; if (player) { player->stop(); player->deleteLater(); player = nullptr; } + if (audioOutput) { + audioOutput->deleteLater(); + audioOutput = nullptr; + } } } @@ -82,7 +90,7 @@ void SoundEngine::ensureThemeDirectoryExists() { if (SettingsCache::instance().getSoundThemeName().isEmpty() || !getAvailableThemes().contains(SettingsCache::instance().getSoundThemeName())) { - qDebug() << "Sounds theme name not set, setting default value"; + qCInfo(SoundEngineLog) << "Sounds theme name not set, setting default value"; SettingsCache::instance().setSoundThemeName(DEFAULT_THEME_NAME); } } @@ -123,7 +131,7 @@ QStringMap &SoundEngine::getAvailableThemes() void SoundEngine::themeChangedSlot() { QString themeName = SettingsCache::instance().getSoundThemeName(); - qDebug() << "Sound theme changed:" << themeName; + qCInfo(SoundEngineLog) << "Sound theme changed:" << themeName; QDir dir = getAvailableThemes().value(themeName); diff --git a/cockatrice/src/client/sound_engine.h b/cockatrice/src/client/sound_engine.h index 11304874a..851ca3928 100644 --- a/cockatrice/src/client/sound_engine.h +++ b/cockatrice/src/client/sound_engine.h @@ -1,12 +1,15 @@ #ifndef SOUNDENGINE_H #define SOUNDENGINE_H +#include +#include #include #include #include #include -class QAudioOutput; +inline Q_LOGGING_CATEGORY(SoundEngineLog, "sound_engine"); + class QBuffer; typedef QMap QStringMap; @@ -23,6 +26,7 @@ public: private: QStringMap availableThemes; QMap audioData; + QAudioOutput *audioOutput; QMediaPlayer *player; protected: diff --git a/cockatrice/src/client/tabs/abstract_tab_deck_editor.cpp b/cockatrice/src/client/tabs/abstract_tab_deck_editor.cpp new file mode 100644 index 000000000..fe2148293 --- /dev/null +++ b/cockatrice/src/client/tabs/abstract_tab_deck_editor.cpp @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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(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(); +} \ No newline at end of file diff --git a/cockatrice/src/client/tabs/abstract_tab_deck_editor.h b/cockatrice/src/client/tabs/abstract_tab_deck_editor.h new file mode 100644 index 000000000..0a5f97f56 --- /dev/null +++ b/cockatrice/src/client/tabs/abstract_tab_deck_editor.h @@ -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 diff --git a/cockatrice/src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response.cpp b/cockatrice/src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response.cpp new file mode 100644 index 000000000..da2a9c052 --- /dev/null +++ b/cockatrice/src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response.cpp @@ -0,0 +1,45 @@ +#include "edhrec_commander_api_response.h" + +#include +#include + +void EdhrecCommanderApiResponse::fromJson(const QJsonObject &json) +{ + // Parse the collapsed DeckStatistics + deckStats.fromJson(json); + + // Parse Archidekt section + QJsonArray archidektJson = json.value("archidekt").toArray(); + archidekt.fromJson(archidektJson); + + // Parse other fields + similar = json.value("similar").toObject(); + header = json.value("header").toString(); + panels = json.value("panels").toObject(); + description = json.value("description").toString(); + QJsonObject containerJson = json.value("container").toObject(); + container.fromJson(containerJson); +} + +void EdhrecCommanderApiResponse::debugPrint() const +{ + qDebug() << "Deck Statistics:"; + qDebug() << " Creature:" << deckStats.creature; + qDebug() << " Instant:" << deckStats.instant; + qDebug() << " Sorcery:" << deckStats.sorcery; + qDebug() << " Artifact:" << deckStats.artifact; + qDebug() << " Enchantment:" << deckStats.enchantment; + qDebug() << " Battle:" << deckStats.battle; + qDebug() << " Planeswalker:" << deckStats.planeswalker; + qDebug() << " Land:" << deckStats.land; + qDebug() << " Basic:" << deckStats.basic; + qDebug() << " Nonbasic:" << deckStats.nonbasic; + + archidekt.debugPrint(); + + qDebug() << "Similar:" << similar; + qDebug() << "Header:" << header; + qDebug() << "Panels:" << panels; + qDebug() << "Description:" << description; + container.debugPrint(); +} diff --git a/cockatrice/src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response.h b/cockatrice/src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response.h new file mode 100644 index 000000000..91cf3afbc --- /dev/null +++ b/cockatrice/src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response.h @@ -0,0 +1,28 @@ +#ifndef DECKDATA_H +#define DECKDATA_H + +#include "edhrec_commander_api_response_archidekt_links.h" +#include "edhrec_commander_api_response_average_deck_statistics.h" +#include "edhrec_commander_api_response_card_container.h" + +#include +#include +#include + +// Represents the main structure of the JSON +class EdhrecCommanderApiResponse +{ +public: + EdhrecCommanderApiResponseAverageDeckStatistics deckStats; + EdhrecCommanderApiResponseArchidektLinks archidekt; + QJsonObject similar; + QString header; + QJsonObject panels; + QString description; + EdhrecCommanderApiResponseCardContainer container; + + void fromJson(const QJsonObject &json); + void debugPrint() const; +}; + +#endif // DECKDATA_H diff --git a/cockatrice/src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_archidekt_links.cpp b/cockatrice/src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_archidekt_links.cpp new file mode 100644 index 000000000..aac540f90 --- /dev/null +++ b/cockatrice/src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_archidekt_links.cpp @@ -0,0 +1,43 @@ +#include "edhrec_commander_api_response_archidekt_links.h" + +#include +#include +#include +#include + +void EdhrecCommanderApiResponseArchidektLink::fromJson(const QJsonObject &json) +{ + c = json.value("c").toString(); + f = json.value("f").toInt(0); + q = json.value("q").toInt(0); + u = json.value("u").toString(); +} + +void EdhrecCommanderApiResponseArchidektLink::debugPrint() const +{ + qDebug() << " C:" << c; + qDebug() << " F:" << f; + qDebug() << " Q:" << q; + qDebug() << " U:" << u; +} + +void EdhrecCommanderApiResponseArchidektLinks::fromJson(const QJsonArray &json) +{ + entries.clear(); + for (const QJsonValue &value : json) { + if (value.isObject()) { + QJsonObject entryJson = value.toObject(); + EdhrecCommanderApiResponseArchidektLink entry; + entry.fromJson(entryJson); + entries.append(entry); + } + } +} + +void EdhrecCommanderApiResponseArchidektLinks::debugPrint() const +{ + qDebug() << "Archidekt Entries:"; + for (const auto &entry : entries) { + entry.debugPrint(); + } +} diff --git a/cockatrice/src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_archidekt_links.h b/cockatrice/src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_archidekt_links.h new file mode 100644 index 000000000..537dbb1fc --- /dev/null +++ b/cockatrice/src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_archidekt_links.h @@ -0,0 +1,32 @@ +#ifndef ARCHIDEKTENTRY_H +#define ARCHIDEKTENTRY_H + +#include +#include +#include +#include + +// Represents a single Archidekt entry +class EdhrecCommanderApiResponseArchidektLink +{ +public: + QString c; + int f = 0; + int q = 0; + QString u; + + void fromJson(const QJsonObject &json); + void debugPrint() const; +}; + +// Represents the Archidekt section as a list of entries +class EdhrecCommanderApiResponseArchidektLinks +{ +public: + QVector entries; + + void fromJson(const QJsonArray &json); + void debugPrint() const; +}; + +#endif // ARCHIDEKTENTRY_H diff --git a/cockatrice/src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_average_deck_statistics.cpp b/cockatrice/src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_average_deck_statistics.cpp new file mode 100644 index 000000000..74cce5322 --- /dev/null +++ b/cockatrice/src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_average_deck_statistics.cpp @@ -0,0 +1,15 @@ +#include "edhrec_commander_api_response_average_deck_statistics.h" + +void EdhrecCommanderApiResponseAverageDeckStatistics::fromJson(const QJsonObject &json) +{ + creature = json.value("creature").toInt(0); + instant = json.value("instant").toInt(0); + sorcery = json.value("sorcery").toInt(0); + artifact = json.value("artifact").toInt(0); + enchantment = json.value("enchantment").toInt(0); + battle = json.value("battle").toInt(0); + planeswalker = json.value("planeswalker").toInt(0); + land = json.value("land").toInt(0); + basic = json.value("basic").toInt(0); + nonbasic = json.value("nonbasic").toInt(0); +} \ No newline at end of file diff --git a/cockatrice/src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_average_deck_statistics.h b/cockatrice/src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_average_deck_statistics.h new file mode 100644 index 000000000..1dd01abe2 --- /dev/null +++ b/cockatrice/src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_average_deck_statistics.h @@ -0,0 +1,22 @@ +#ifndef AVERAGE_DECK_STATISTICS_H +#define AVERAGE_DECK_STATISTICS_H + +#include + +// Represents the typical deck statistics (collapsed section) +struct EdhrecCommanderApiResponseAverageDeckStatistics +{ + int creature = 0; + int instant = 0; + int sorcery = 0; + int artifact = 0; + int enchantment = 0; + int battle = 0; + int planeswalker = 0; + int land = 0; + int basic = 0; + int nonbasic = 0; + + void fromJson(const QJsonObject &json); +}; +#endif // AVERAGE_DECK_STATISTICS_H diff --git a/cockatrice/src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_card_container.cpp b/cockatrice/src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_card_container.cpp new file mode 100644 index 000000000..f8ffab787 --- /dev/null +++ b/cockatrice/src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_card_container.cpp @@ -0,0 +1,49 @@ +#include "edhrec_commander_api_response_card_container.h" + +#include +#include +#include + +void EdhrecCommanderApiResponseCardContainer::fromJson(const QJsonObject &json) +{ + // Parse breadcrumb + QJsonArray breadcrumbArray = json.value("breadcrumb").toArray(); + for (const QJsonValue &breadcrumbValue : breadcrumbArray) { + breadcrumb.push_back(breadcrumbValue.toObject()); + } + + description = json.value("description").toString(); + QJsonObject jsonDict = json.value("json_dict").toObject(); + card.fromJson(jsonDict.value("card").toObject()); + QJsonArray cardlistsArray = jsonDict.value("cardlists").toArray(); + + for (const QJsonValue &cardlistValue : cardlistsArray) { + QJsonObject cardlistObj = cardlistValue.toObject(); + QJsonArray cardviewsArray = cardlistObj.value("cardviews").toArray(); + EdhrecCommanderApiResponseCardList cardView; + cardView.fromJson(cardlistValue.toObject()); + cardlists.push_back(cardView); + } + + keywords = json.value("keywords").toString(); + title = json.value("title").toString(); +} + +void EdhrecCommanderApiResponseCardContainer::debugPrint() const +{ + qDebug() << "Breadcrumb:"; + for (const auto &breadcrumbEntry : breadcrumb) { + qDebug() << breadcrumbEntry; + } + + qDebug() << "Description:" << description; + card.debugPrint(); + + qDebug() << "Cardlists:"; + for (const auto &cardlist : cardlists) { + cardlist.debugPrint(); + } + + qDebug() << "Keywords:" << keywords; + qDebug() << "Title:" << title; +} diff --git a/cockatrice/src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_card_container.h b/cockatrice/src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_card_container.h new file mode 100644 index 000000000..d2ea05874 --- /dev/null +++ b/cockatrice/src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_card_container.h @@ -0,0 +1,60 @@ +#ifndef CONTAINER_ENTRY_H +#define CONTAINER_ENTRY_H + +#include "edhrec_commander_api_response_card_list.h" +#include "edhrec_commander_api_response_commander_details.h" + +#include +#include +#include +#include +#include + +class EdhrecCommanderApiResponseCardContainer +{ +public: + // Constructor + EdhrecCommanderApiResponseCardContainer() = default; + + // Parse deck-related data from JSON + void fromJson(const QJsonObject &json); + + // Debug method for logging + void debugPrint() const; + + // Getter methods for deck container + const QString &getDescription() const + { + return description; + } + const QVector &getBreadcrumb() const + { + return breadcrumb; + } + const EdhrecCommanderApiResponseCommanderDetails &getCommanderDetails() const + { + return card; + } + const QVector &getCardlists() const + { + return cardlists; + } + const QString &getKeywords() const + { + return keywords; + } + const QString &getTitle() const + { + return title; + } + +private: + QString description; + QVector breadcrumb; + EdhrecCommanderApiResponseCommanderDetails card; + QVector cardlists; + QString keywords; + QString title; +}; + +#endif // CONTAINER_ENTRY_H diff --git a/cockatrice/src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_card_details.cpp b/cockatrice/src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_card_details.cpp new file mode 100644 index 000000000..2f2791de1 --- /dev/null +++ b/cockatrice/src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_card_details.cpp @@ -0,0 +1,36 @@ +#include "edhrec_commander_api_response_card_details.h" + +#include + +EdhrecCommanderApiResponseCardDetails::EdhrecCommanderApiResponseCardDetails() + : synergy(0.0), inclusion(0), numDecks(0), potentialDecks(0) +{ +} + +void EdhrecCommanderApiResponseCardDetails::fromJson(const QJsonObject &json) +{ + // Parse the fields from the JSON object + name = json.value("name").toString(); + sanitized = json.value("sanitized").toString(); + sanitizedWo = json.value("sanitized_wo").toString(); + url = json.value("url").toString(); + synergy = json.value("synergy").toDouble(0.0); + inclusion = json.value("inclusion").toInt(0); + label = json.value("label").toString(); + numDecks = json.value("num_decks").toInt(0); + potentialDecks = json.value("potential_decks").toInt(0); +} + +void EdhrecCommanderApiResponseCardDetails::debugPrint() const +{ + // Print out all the fields for debugging + qDebug() << "Name:" << name; + qDebug() << "Sanitized:" << sanitized; + qDebug() << "Sanitized Wo:" << sanitizedWo; + qDebug() << "URL:" << url; + qDebug() << "Synergy:" << synergy; + qDebug() << "Inclusion:" << inclusion; + qDebug() << "Label:" << label; + qDebug() << "Num Decks:" << numDecks; + qDebug() << "Potential Decks:" << potentialDecks; +} diff --git a/cockatrice/src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_card_details.h b/cockatrice/src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_card_details.h new file mode 100644 index 000000000..82d85824b --- /dev/null +++ b/cockatrice/src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_card_details.h @@ -0,0 +1,29 @@ +#ifndef CARD_VIEW_H +#define CARD_VIEW_H + +#include +#include + +class EdhrecCommanderApiResponseCardDetails +{ +public: + QString name; + QString sanitized; + QString sanitizedWo; + QString url; + double synergy; + int inclusion; + QString label; + int numDecks; + int potentialDecks; + + EdhrecCommanderApiResponseCardDetails(); + + // Method to populate the object from a JSON object + void fromJson(const QJsonObject &json); + + // Debug method to print out the data + void debugPrint() const; +}; + +#endif // CARD_VIEW_H diff --git a/cockatrice/src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_card_list.cpp b/cockatrice/src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_card_list.cpp new file mode 100644 index 000000000..5d3eebedd --- /dev/null +++ b/cockatrice/src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_card_list.cpp @@ -0,0 +1,33 @@ +#include "edhrec_commander_api_response_card_list.h" + +#include + +EdhrecCommanderApiResponseCardList::EdhrecCommanderApiResponseCardList() +{ +} + +void EdhrecCommanderApiResponseCardList::fromJson(const QJsonObject &json) +{ + // Parse the header from the JSON object + header = json.value("header").toString(); + + // Parse the cardviews array and populate cardViews + QJsonArray cardviewsArray = json.value("cardviews").toArray(); + for (const QJsonValue &value : cardviewsArray) { + QJsonObject cardviewObj = value.toObject(); + EdhrecCommanderApiResponseCardDetails cardView; + cardView.fromJson(cardviewObj); + cardViews.append(cardView); + } +} + +void EdhrecCommanderApiResponseCardList::debugPrint() const +{ + // Print out the header + qDebug() << "Header:" << header; + + // Print out all the CardView objects + for (const EdhrecCommanderApiResponseCardDetails &cardView : cardViews) { + cardView.debugPrint(); + } +} diff --git a/cockatrice/src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_card_list.h b/cockatrice/src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_card_list.h new file mode 100644 index 000000000..e0ddc3466 --- /dev/null +++ b/cockatrice/src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_card_list.h @@ -0,0 +1,27 @@ +#ifndef CARD_LIST_H +#define CARD_LIST_H + +#include "edhrec_commander_api_response_card_details.h" + +#include +#include +#include +#include + +class EdhrecCommanderApiResponseCardList +{ +public: + QString header; + QList cardViews; + + // Default constructor + EdhrecCommanderApiResponseCardList(); + + // Method to populate the object from a JSON object + void fromJson(const QJsonObject &json); + + // Debug method to print out the data + void debugPrint() const; +}; + +#endif // CARD_LIST_H diff --git a/cockatrice/src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_card_prices.cpp b/cockatrice/src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_card_prices.cpp new file mode 100644 index 000000000..6454e5500 --- /dev/null +++ b/cockatrice/src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_card_prices.cpp @@ -0,0 +1,31 @@ +#include "edhrec_commander_api_response_card_prices.h" + +#include + +void CardPrices::fromJson(const QJsonObject &json) +{ + // Parse prices from various sources + cardhoarder = json.value("cardhoarder").toObject(); + cardkingdom = json.value("cardkingdom").toObject(); + cardmarket = json.value("cardmarket").toObject(); + face2face = json.value("face2face").toObject(); + manapool = json.value("manapool").toObject(); + mtgstocks = json.value("mtgstocks").toObject(); + scg = json.value("scg").toObject(); + tcgl = json.value("tcgl").toObject(); + tcgplayer = json.value("tcgplayer").toObject(); +} + +void CardPrices::debugPrint() const +{ + qDebug() << "Card Prices:"; + qDebug() << "Cardhoarder:" << cardhoarder; + qDebug() << "Cardkingdom:" << cardkingdom; + qDebug() << "Cardmarket:" << cardmarket; + qDebug() << "Face2Face:" << face2face; + qDebug() << "Manapool:" << manapool; + qDebug() << "Mtgstocks:" << mtgstocks; + qDebug() << "SCG:" << scg; + qDebug() << "TCGL:" << tcgl; + qDebug() << "Tcgplayer:" << tcgplayer; +} diff --git a/cockatrice/src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_card_prices.h b/cockatrice/src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_card_prices.h new file mode 100644 index 000000000..32e017a92 --- /dev/null +++ b/cockatrice/src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_card_prices.h @@ -0,0 +1,66 @@ +#ifndef EDHREC_COMMANDER_API_RESPONSE_CARD_PRICES_H +#define EDHREC_COMMANDER_API_RESPONSE_CARD_PRICES_H + +#include + +class CardPrices +{ +public: + // Constructor + CardPrices() = default; + + // Parse prices from JSON + void fromJson(const QJsonObject &json); + void debugPrint() const; + + // Getter methods for card prices + const QJsonObject &getCardhoarder() const + { + return cardhoarder; + } + const QJsonObject &getCardkingdom() const + { + return cardkingdom; + } + const QJsonObject &getCardmarket() const + { + return cardmarket; + } + const QJsonObject &getFace2face() const + { + return face2face; + } + const QJsonObject &getManapool() const + { + return manapool; + } + const QJsonObject &getMtgstocks() const + { + return mtgstocks; + } + const QJsonObject &getScg() const + { + return scg; + } + const QJsonObject &getTcgl() const + { + return tcgl; + } + const QJsonObject &getTcgplayer() const + { + return tcgplayer; + } + +private: + QJsonObject cardhoarder; + QJsonObject cardkingdom; + QJsonObject cardmarket; + QJsonObject face2face; + QJsonObject manapool; + QJsonObject mtgstocks; + QJsonObject scg; + QJsonObject tcgl; + QJsonObject tcgplayer; +}; + +#endif // EDHREC_COMMANDER_API_RESPONSE_CARD_PRICES_H diff --git a/cockatrice/src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_commander_details.cpp b/cockatrice/src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_commander_details.cpp new file mode 100644 index 000000000..7b220603e --- /dev/null +++ b/cockatrice/src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_commander_details.cpp @@ -0,0 +1,90 @@ +#include "edhrec_commander_api_response_commander_details.h" + +#include + +void EdhrecCommanderApiResponseCommanderDetails::fromJson(const QJsonObject &json) +{ + // Parse card-related data + aetherhubUri = json.value("aetherhub_uri").toString(); + archidektUri = json.value("archidekt_uri").toString(); + cmc = json.value("cmc").toInt(0); + colorIdentity = json.value("color_identity").toArray(); + combos = json.value("combos").toBool(false); + deckstatsUri = json.value("deckstats_uri").toString(); + + // Parse image URIs + QJsonArray imageUrisArray = json.value("image_uris").toArray(); + for (const QJsonValue &imageValue : imageUrisArray) { + QJsonObject imageObject = imageValue.toObject(); + imageUris.push_back(imageObject.value("normal").toString()); + imageUris.push_back(imageObject.value("art_crop").toString()); + } + + inclusion = json.value("inclusion").toInt(0); + isCommander = json.value("is_commander").toBool(false); + label = json.value("label").toString(); + layout = json.value("layout").toString(); + legalCommander = json.value("legal_commander").toBool(false); + moxfieldUri = json.value("moxfield_uri").toString(); + mtggoldfishUri = json.value("mtggoldfish_uri").toString(); + name = json.value("name").toString(); + names = json.value("names").toArray(); + numDecks = json.value("num_decks").toInt(0); + potentialDecks = json.value("potential_decks").toInt(0); + precon = json.value("precon").toString(); + + // Parse prices + prices.fromJson(json.value("prices").toObject()); + + primaryType = json.value("primary_type").toString(); + rarity = json.value("rarity").toString(); + salt = json.value("salt").toDouble(0.0); + sanitized = json.value("sanitized").toString(); + sanitizedWo = json.value("sanitized_wo").toString(); + scryfallUri = json.value("scryfall_uri").toString(); + spellbookUri = json.value("spellbook_uri").toString(); + type = json.value("type").toString(); + url = json.value("url").toString(); +} + +void EdhrecCommanderApiResponseCommanderDetails::debugPrint() const +{ + qDebug() << "Card Data:"; + qDebug() << "Aetherhub URI:" << aetherhubUri; + qDebug() << "Archidekt URI:" << archidektUri; + qDebug() << "CMC:" << cmc; + qDebug() << "Color Identity:" << colorIdentity; + qDebug() << "Combos:" << combos; + qDebug() << "Deckstats URI:" << deckstatsUri; + + qDebug() << "Image URIs:"; + for (const auto &uri : imageUris) { + qDebug() << uri; + } + + qDebug() << "Inclusion:" << inclusion; + qDebug() << "Is Commander:" << isCommander; + qDebug() << "Label:" << label; + qDebug() << "Layout:" << layout; + qDebug() << "Legal Commander:" << legalCommander; + qDebug() << "Moxfield URI:" << moxfieldUri; + qDebug() << "MTGGoldfish URI:" << mtggoldfishUri; + qDebug() << "Name:" << name; + qDebug() << "Names:" << names; + qDebug() << "Number of Decks:" << numDecks; + qDebug() << "Potential Decks:" << potentialDecks; + qDebug() << "Precon:" << precon; + + // Print the prices using the debugPrint method from CardPrices + prices.debugPrint(); + + qDebug() << "Primary Type:" << primaryType; + qDebug() << "Rarity:" << rarity; + qDebug() << "Salt:" << salt; + qDebug() << "Sanitized:" << sanitized; + qDebug() << "Sanitized WO:" << sanitizedWo; + qDebug() << "Scryfall URI:" << scryfallUri; + qDebug() << "Spellbook URI:" << spellbookUri; + qDebug() << "Type:" << type; + qDebug() << "URL:" << url; +} \ No newline at end of file diff --git a/cockatrice/src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_commander_details.h b/cockatrice/src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_commander_details.h new file mode 100644 index 000000000..78ccfc23a --- /dev/null +++ b/cockatrice/src/client/tabs/api/edhrec/api_response/edhrec_commander_api_response_commander_details.h @@ -0,0 +1,173 @@ +#ifndef EDHREC_COMMANDER_API_RESPONSE_COMMANDER_DETAILS_H +#define EDHREC_COMMANDER_API_RESPONSE_COMMANDER_DETAILS_H + +#include "edhrec_commander_api_response_card_prices.h" + +#include +#include +#include +#include + +class EdhrecCommanderApiResponseCommanderDetails +{ +public: + // Constructor + EdhrecCommanderApiResponseCommanderDetails() = default; + + // Parse card-related data from JSON + void fromJson(const QJsonObject &json); + + // Debug method for logging + void debugPrint() const; + + // Getters for the card data + const QString &getAetherhubUri() const + { + return aetherhubUri; + } + const QString &getArchidektUri() const + { + return archidektUri; + } + int getCmc() const + { + return cmc; + } + const QJsonArray &getColorIdentity() const + { + return colorIdentity; + } + bool isCombos() const + { + return combos; + } + const QString &getDeckstatsUri() const + { + return deckstatsUri; + } + const QVector &getImageUris() const + { + return imageUris; + } + int getInclusion() const + { + return inclusion; + } + bool getIsCommander() const + { + return isCommander; + } + const QString &getLabel() const + { + return label; + } + const QString &getLayout() const + { + return layout; + } + bool getLegalCommander() const + { + return legalCommander; + } + const QString &getMoxfieldUri() const + { + return moxfieldUri; + } + const QString &getMtggoldfishUri() const + { + return mtggoldfishUri; + } + const QString &getName() const + { + return name; + } + const QJsonArray &getNames() const + { + return names; + } + int getNumDecks() const + { + return numDecks; + } + int getPotentialDecks() const + { + return potentialDecks; + } + const QString &getPrecon() const + { + return precon; + } + const CardPrices &getPrices() const + { + return prices; + } + const QString &getPrimaryType() const + { + return primaryType; + } + const QString &getRarity() const + { + return rarity; + } + double getSalt() const + { + return salt; + } + const QString &getSanitized() const + { + return sanitized; + } + const QString &getSanitizedWo() const + { + return sanitizedWo; + } + const QString &getScryfallUri() const + { + return scryfallUri; + } + const QString &getSpellbookUri() const + { + return spellbookUri; + } + const QString &getType() const + { + return type; + } + const QString &getUrl() const + { + return url; + } + +private: + QString aetherhubUri; + QString archidektUri; + int cmc = 0; + QJsonArray colorIdentity; + bool combos = false; + QString deckstatsUri; + QVector imageUris; + int inclusion = 0; + bool isCommander = false; + QString label; + QString layout; + bool legalCommander = false; + QString moxfieldUri; + QString mtggoldfishUri; + QString name; + QJsonArray names; + int numDecks = 0; + int potentialDecks = 0; + QString precon; + CardPrices prices; + QString primaryType; + QString rarity; + double salt = 0.0; + QString sanitized; + QString sanitizedWo; + QString scryfallUri; + QString spellbookUri; + QString type; + QString url; +}; + +#endif // EDHREC_COMMANDER_API_RESPONSE_COMMANDER_DETAILS_H diff --git a/cockatrice/src/client/tabs/api/edhrec/edhrec_commander_api_response_card_details_display_widget.cpp b/cockatrice/src/client/tabs/api/edhrec/edhrec_commander_api_response_card_details_display_widget.cpp new file mode 100644 index 000000000..87853b9aa --- /dev/null +++ b/cockatrice/src/client/tabs/api/edhrec/edhrec_commander_api_response_card_details_display_widget.cpp @@ -0,0 +1,41 @@ +#include "edhrec_commander_api_response_card_details_display_widget.h" + +#include "../../../../game/cards/card_database_manager.h" + +EdhrecCommanderApiResponseCardDetailsDisplayWidget::EdhrecCommanderApiResponseCardDetailsDisplayWidget( + QWidget *parent, + const EdhrecCommanderApiResponseCardDetails &_toDisplay) + : QWidget(parent), toDisplay(_toDisplay) +{ + layout = new QVBoxLayout(this); + setLayout(layout); + + cardPictureWidget = new CardInfoPictureWidget(this); + cardPictureWidget->setCard(CardDatabaseManager::getInstance()->getCard(toDisplay.name)); + + label = new QLabel(this); + label->setText(toDisplay.name + "\n" + toDisplay.label); + label->setAlignment(Qt::AlignHCenter); + + // Set label color based on inclusion rate + int inclusionRate = (toDisplay.numDecks * 100) / toDisplay.potentialDecks; + + QColor labelColor; + if (inclusionRate <= 30) { + labelColor = QColor(255, 0, 0); // Red + } else if (inclusionRate <= 60) { + int red = 255 - ((inclusionRate - 30) * 2); + int green = (inclusionRate - 30) * 4; // Adjust green to make the transition smoother + labelColor = QColor(red, green, 0); // purple-ish + } else if (inclusionRate <= 90) { + int green = (inclusionRate - 60) * 5; // Increase green + labelColor = QColor(100, green, 100); // Green shades + } else { + labelColor = QColor(100, 200, 100); // Dark Green + } + + label->setStyleSheet(QString("color: %1").arg(labelColor.name())); + + layout->addWidget(cardPictureWidget); + layout->addWidget(label); +} diff --git a/cockatrice/src/client/tabs/api/edhrec/edhrec_commander_api_response_card_details_display_widget.h b/cockatrice/src/client/tabs/api/edhrec/edhrec_commander_api_response_card_details_display_widget.h new file mode 100644 index 000000000..1cd76989c --- /dev/null +++ b/cockatrice/src/client/tabs/api/edhrec/edhrec_commander_api_response_card_details_display_widget.h @@ -0,0 +1,26 @@ +#ifndef EDHREC_COMMANDER_API_RESPONSE_CARD_DETAILS_DISPLAY_WIDGET_H +#define EDHREC_COMMANDER_API_RESPONSE_CARD_DETAILS_DISPLAY_WIDGET_H + +#include "../../../ui/widgets/cards/card_info_picture_widget.h" +#include "api_response/edhrec_commander_api_response_card_details.h" + +#include +#include +#include + +class EdhrecCommanderApiResponseCardDetailsDisplayWidget : public QWidget +{ + Q_OBJECT +public: + explicit EdhrecCommanderApiResponseCardDetailsDisplayWidget( + QWidget *parent, + const EdhrecCommanderApiResponseCardDetails &_toDisplay); + +private: + EdhrecCommanderApiResponseCardDetails toDisplay; + QVBoxLayout *layout; + CardInfoPictureWidget *cardPictureWidget; + QLabel *label; +}; + +#endif // EDHREC_COMMANDER_API_RESPONSE_CARD_DETAILS_DISPLAY_WIDGET_H diff --git a/cockatrice/src/client/tabs/api/edhrec/edhrec_commander_api_response_card_list_display_widget.cpp b/cockatrice/src/client/tabs/api/edhrec/edhrec_commander_api_response_card_list_display_widget.cpp new file mode 100644 index 000000000..0d1f1e8f3 --- /dev/null +++ b/cockatrice/src/client/tabs/api/edhrec/edhrec_commander_api_response_card_list_display_widget.cpp @@ -0,0 +1,34 @@ +#include "edhrec_commander_api_response_card_list_display_widget.h" + +#include "../../../ui/widgets/general/display/banner_widget.h" +#include "edhrec_commander_api_response_card_details_display_widget.h" + +#include + +EdhrecCommanderApiResponseCardListDisplayWidget::EdhrecCommanderApiResponseCardListDisplayWidget( + QWidget *parent, + EdhrecCommanderApiResponseCardList toDisplay) + : QWidget(parent) +{ + layout = new QVBoxLayout(this); + setLayout(layout); + + header = new BannerWidget(this, toDisplay.header); + + flowWidget = new FlowWidget(this, Qt::Horizontal, Qt::ScrollBarAlwaysOff, Qt::ScrollBarAlwaysOff); + header->setBuddy(flowWidget); + + foreach (EdhrecCommanderApiResponseCardDetails card_detail, toDisplay.cardViews) { + auto widget = new EdhrecCommanderApiResponseCardDetailsDisplayWidget(flowWidget, card_detail); + flowWidget->addWidget(widget); + } + + layout->addWidget(header); + layout->addWidget(flowWidget); +} + +void EdhrecCommanderApiResponseCardListDisplayWidget::resizeEvent(QResizeEvent *event) +{ + QWidget::resizeEvent(event); + qDebug() << event->size(); +} diff --git a/cockatrice/src/client/tabs/api/edhrec/edhrec_commander_api_response_card_list_display_widget.h b/cockatrice/src/client/tabs/api/edhrec/edhrec_commander_api_response_card_list_display_widget.h new file mode 100644 index 000000000..9df667929 --- /dev/null +++ b/cockatrice/src/client/tabs/api/edhrec/edhrec_commander_api_response_card_list_display_widget.h @@ -0,0 +1,30 @@ +#ifndef EDHREC_COMMANDER_API_RESPONSE_CARD_LIST_DISPLAY_WIDGET_H +#define EDHREC_COMMANDER_API_RESPONSE_CARD_LIST_DISPLAY_WIDGET_H + +#include "../../../ui/widgets/general/display/banner_widget.h" +#include "../../../ui/widgets/general/layout_containers/flow_widget.h" +#include "api_response/edhrec_commander_api_response_card_list.h" + +#include +#include +#include + +class EdhrecCommanderApiResponseCardListDisplayWidget : public QWidget +{ + Q_OBJECT +public: + explicit EdhrecCommanderApiResponseCardListDisplayWidget(QWidget *parent, + EdhrecCommanderApiResponseCardList toDisplay); + void resizeEvent(QResizeEvent *event) override; + [[nodiscard]] QString getBannerText() const + { + return header->getText(); + }; + +private: + QVBoxLayout *layout; + BannerWidget *header; + FlowWidget *flowWidget; +}; + +#endif // EDHREC_COMMANDER_API_RESPONSE_CARD_LIST_DISPLAY_WIDGET_H diff --git a/cockatrice/src/client/tabs/api/edhrec/edhrec_commander_api_response_commander_details_display_widget.cpp b/cockatrice/src/client/tabs/api/edhrec/edhrec_commander_api_response_commander_details_display_widget.cpp new file mode 100644 index 000000000..58b77df73 --- /dev/null +++ b/cockatrice/src/client/tabs/api/edhrec/edhrec_commander_api_response_commander_details_display_widget.cpp @@ -0,0 +1,36 @@ +#include "edhrec_commander_api_response_commander_details_display_widget.h" + +#include "../../../../game/cards/card_database_manager.h" +#include "../../../ui/widgets/cards/card_info_picture_widget.h" + +#include + +EdhrecCommanderResponseCommanderDetailsDisplayWidget::EdhrecCommanderResponseCommanderDetailsDisplayWidget( + QWidget *parent, + const EdhrecCommanderApiResponseCommanderDetails &_commanderDetails) + : QWidget(parent), commanderDetails(_commanderDetails) +{ + layout = new QVBoxLayout(this); + setLayout(layout); + + commanderPicture = new CardInfoPictureWidget(this); + commanderPicture->setCard(CardDatabaseManager::getInstance()->getCard(commanderDetails.getName())); + + commanderDetails.debugPrint(); + + label = new QLabel(this); + label->setAlignment(Qt::AlignCenter); + salt = new QLabel(this); + salt->setAlignment(Qt::AlignCenter); + + layout->addWidget(commanderPicture); + layout->addWidget(label); + layout->addWidget(salt); + retranslateUi(); +} + +void EdhrecCommanderResponseCommanderDetailsDisplayWidget::retranslateUi() +{ + label->setText(commanderDetails.getLabel()); + salt->setText(tr("Salt: ") + QString::number(commanderDetails.getSalt())); +} \ No newline at end of file diff --git a/cockatrice/src/client/tabs/api/edhrec/edhrec_commander_api_response_commander_details_display_widget.h b/cockatrice/src/client/tabs/api/edhrec/edhrec_commander_api_response_commander_details_display_widget.h new file mode 100644 index 000000000..85970d503 --- /dev/null +++ b/cockatrice/src/client/tabs/api/edhrec/edhrec_commander_api_response_commander_details_display_widget.h @@ -0,0 +1,28 @@ +#ifndef EDHREC_COMMANDER_API_RESPONSE_COMMANDER_DETAILS_DISPLAY_WIDGET_H +#define EDHREC_COMMANDER_API_RESPONSE_COMMANDER_DETAILS_DISPLAY_WIDGET_H + +#include "../../../ui/widgets/cards/card_info_picture_widget.h" +#include "api_response/edhrec_commander_api_response_commander_details.h" + +#include +#include +#include + +class EdhrecCommanderResponseCommanderDetailsDisplayWidget : public QWidget +{ + Q_OBJECT +public: + explicit EdhrecCommanderResponseCommanderDetailsDisplayWidget( + QWidget *parent, + const EdhrecCommanderApiResponseCommanderDetails &_commanderDetails); + void retranslateUi(); + +private: + QLabel *label; + QLabel *salt; + QVBoxLayout *layout; + CardInfoPictureWidget *commanderPicture; + EdhrecCommanderApiResponseCommanderDetails commanderDetails; +}; + +#endif // EDHREC_COMMANDER_API_RESPONSE_COMMANDER_DETAILS_DISPLAY_WIDGET_H diff --git a/cockatrice/src/client/tabs/api/edhrec/edhrec_commander_api_response_display_widget.cpp b/cockatrice/src/client/tabs/api/edhrec/edhrec_commander_api_response_display_widget.cpp new file mode 100644 index 000000000..3cab65bf8 --- /dev/null +++ b/cockatrice/src/client/tabs/api/edhrec/edhrec_commander_api_response_display_widget.cpp @@ -0,0 +1,102 @@ +#include "edhrec_commander_api_response_display_widget.h" + +#include "../../../ui/widgets/cards/card_info_picture_widget.h" +#include "api_response/edhrec_commander_api_response.h" +#include "edhrec_commander_api_response_card_list_display_widget.h" +#include "edhrec_commander_api_response_commander_details_display_widget.h" + +#include +#include +#include +#include +#include + +EdhrecCommanderApiResponseDisplayWidget::EdhrecCommanderApiResponseDisplayWidget(QWidget *parent, + EdhrecCommanderApiResponse response) + : QWidget(parent) +{ + layout = new QHBoxLayout(this); + setLayout(layout); + + cardDisplayLayout = new QVBoxLayout(this); + + // Create a QSplitter to hold the ListView and ScrollArea holding CardListdisplayWidgets side by side + auto splitter = new QSplitter(this); + splitter->setOrientation(Qt::Horizontal); + + auto listView = new QListView(splitter); + listView->setMinimumWidth(50); + listView->setMaximumWidth(150); + auto listModel = new QStringListModel(this); + QStringList widgetNames; + + // Add commander details + auto commanderPicture = + new EdhrecCommanderResponseCommanderDetailsDisplayWidget(this, response.container.getCommanderDetails()); + cardDisplayLayout->addWidget(commanderPicture); + widgetNames.append("Commander Details"); + + // Add card list widgets + auto edhrec_commander_api_response_card_lists = response.container.getCardlists(); + for (const EdhrecCommanderApiResponseCardList &card_list : edhrec_commander_api_response_card_lists) { + auto cardListDisplayWidget = new EdhrecCommanderApiResponseCardListDisplayWidget(this, card_list); + cardDisplayLayout->addWidget(cardListDisplayWidget); + widgetNames.append(cardListDisplayWidget->getBannerText()); + } + + // Create a QScrollArea to hold the card display widgets + scrollArea = new QScrollArea(splitter); + scrollArea->setWidgetResizable(true); + scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); + scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + + // Set the cardDisplayLayout inside the scroll area + auto scrollWidget = new QWidget(scrollArea); + scrollWidget->setLayout(cardDisplayLayout); + connect(splitter, &QSplitter::splitterMoved, this, &EdhrecCommanderApiResponseDisplayWidget::onSplitterChange); + scrollArea->setWidget(scrollWidget); + + // Configure the list view + listModel->setStringList(widgetNames); + listView->setModel(listModel); + listView->setEditTriggers(QAbstractItemView::NoEditTriggers); + + // Connect the list view to ensure the corresponding widget is visible + connect(listView, &QListView::clicked, this, [this](const QModelIndex &index) { + int widgetIndex = index.row(); + qDebug() << "clicked: " << widgetIndex; + auto targetWidget = cardDisplayLayout->itemAt(widgetIndex)->widget(); + if (targetWidget) { + qDebug() << "Found targetWidget" << targetWidget; + // Attempt to cast the parent to QScrollArea + auto scrollArea = qobject_cast(this->scrollArea); // Use the scroll area instance + if (scrollArea) { + qDebug() << "ScrollArea" << scrollArea; + scrollArea->ensureWidgetVisible(targetWidget); + } + } + }); + + // Add splitter to the main layout + splitter->addWidget(listView); + splitter->addWidget(scrollArea); + + layout->addWidget(splitter); +} + +void EdhrecCommanderApiResponseDisplayWidget::onSplitterChange() +{ + scrollArea->widget()->resize(scrollArea->size()); +} + +void EdhrecCommanderApiResponseDisplayWidget::resizeEvent(QResizeEvent *event) +{ + QWidget::resizeEvent(event); + qDebug() << event->size(); + layout->invalidate(); + layout->activate(); + layout->update(); + if (scrollArea && scrollArea->widget()) { + scrollArea->widget()->resize(event->size()); + } +} diff --git a/cockatrice/src/client/tabs/api/edhrec/edhrec_commander_api_response_display_widget.h b/cockatrice/src/client/tabs/api/edhrec/edhrec_commander_api_response_display_widget.h new file mode 100644 index 000000000..d1e95e3d6 --- /dev/null +++ b/cockatrice/src/client/tabs/api/edhrec/edhrec_commander_api_response_display_widget.h @@ -0,0 +1,27 @@ +#ifndef EDHREC_COMMANDER_API_RESPONSE_DISPLAY_WIDGET_H +#define EDHREC_COMMANDER_API_RESPONSE_DISPLAY_WIDGET_H + +#include "api_response/edhrec_commander_api_response.h" + +#include +#include +#include + +class EdhrecCommanderApiResponseDisplayWidget : public QWidget +{ + Q_OBJECT + +public: + explicit EdhrecCommanderApiResponseDisplayWidget(QWidget *parent, EdhrecCommanderApiResponse response); + void resizeEvent(QResizeEvent *event) override; + +public slots: + void onSplitterChange(); + +private: + QHBoxLayout *layout; + QVBoxLayout *cardDisplayLayout; + QScrollArea *scrollArea; +}; + +#endif // EDHREC_COMMANDER_API_RESPONSE_DISPLAY_WIDGET_H diff --git a/cockatrice/src/client/tabs/api/edhrec/tab_edhrec.cpp b/cockatrice/src/client/tabs/api/edhrec/tab_edhrec.cpp new file mode 100644 index 000000000..18662aaab --- /dev/null +++ b/cockatrice/src/client/tabs/api/edhrec/tab_edhrec.cpp @@ -0,0 +1,112 @@ +#include "tab_edhrec.h" + +#include "api_response/edhrec_commander_api_response.h" +#include "edhrec_commander_api_response_display_widget.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +TabEdhRec::TabEdhRec(TabSupervisor *_tabSupervisor) : Tab(_tabSupervisor) +{ + networkManager = new QNetworkAccessManager(this); +#if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)) + networkManager->setTransferTimeout(); // Use Qt's default timeout +#endif + + networkManager->setRedirectPolicy(QNetworkRequest::ManualRedirectPolicy); + connect(networkManager, SIGNAL(finished(QNetworkReply *)), this, SLOT(processApiJson(QNetworkReply *))); +} + +void TabEdhRec::retranslateUi() +{ +} + +void TabEdhRec::setCard(CardInfoPtr _cardToQuery, bool isCommander) +{ + cardToQuery = _cardToQuery; + + if (!cardToQuery) { + qDebug() << "Invalid card information provided."; + return; + } + + QString cardName = cardToQuery->getName(); + QString formattedName = cardName.toLower().replace(" ", "-").remove(QRegularExpression("[^a-z0-9\\-]")); + + QString url; + if (isCommander) { + url = QString("https://json.edhrec.com/pages/commanders/%1.json").arg(formattedName); + } else { + url = QString("https://json.edhrec.com/pages/cards/%1.json").arg(formattedName); + } + + QNetworkRequest request{QUrl(url)}; + + networkManager->get(request); +} + +void TabEdhRec::processApiJson(QNetworkReply *reply) +{ + if (reply->error() != QNetworkReply::NoError) { + qDebug() << "Network error occurred:" << reply->errorString(); + reply->deleteLater(); + return; + } + + QByteArray responseData = reply->readAll(); + QJsonDocument jsonDoc = QJsonDocument::fromJson(responseData); + + if (!jsonDoc.isObject()) { + qDebug() << "Invalid JSON response received."; + reply->deleteLater(); + return; + } + + QJsonObject jsonObj = jsonDoc.object(); + + // qDebug() << jsonObj; + + EdhrecCommanderApiResponse deckData; + deckData.fromJson(jsonObj); + + displayWidget = new EdhrecCommanderApiResponseDisplayWidget(this, deckData); + // flowWidget->addWidget(displayWidget); + setCentralWidget(displayWidget); + + reply->deleteLater(); + update(); +} + +void TabEdhRec::prettyPrintJson(const QJsonValue &value, int indentLevel) +{ + const QString indent(indentLevel * 2, ' '); // Adjust spacing as needed for pretty printing + + if (value.isObject()) { + QJsonObject obj = value.toObject(); + for (auto it = obj.begin(); it != obj.end(); ++it) { + qDebug().noquote() << indent + it.key() + ":"; + prettyPrintJson(it.value(), indentLevel + 1); + } + } else if (value.isArray()) { + QJsonArray array = value.toArray(); + for (int i = 0; i < array.size(); ++i) { + qDebug().noquote() << indent + QString("[%1]:").arg(i); + prettyPrintJson(array[i], indentLevel + 1); + } + } else if (value.isString()) { + qDebug().noquote() << indent + "\"" + value.toString() + "\""; + } else if (value.isDouble()) { + qDebug().noquote() << indent + QString::number(value.toDouble()); + } else if (value.isBool()) { + qDebug().noquote() << indent + (value.toBool() ? "true" : "false"); + } else if (value.isNull()) { + qDebug().noquote() << indent + "null"; + } +} diff --git a/cockatrice/src/client/tabs/api/edhrec/tab_edhrec.h b/cockatrice/src/client/tabs/api/edhrec/tab_edhrec.h new file mode 100644 index 000000000..c15f22b26 --- /dev/null +++ b/cockatrice/src/client/tabs/api/edhrec/tab_edhrec.h @@ -0,0 +1,37 @@ +#ifndef TAB_EDHREC_H +#define TAB_EDHREC_H + +#include "../../../../game/cards/card_database.h" +#include "../../../ui/widgets/general/layout_containers/flow_widget.h" +#include "../../tab.h" +#include "edhrec_commander_api_response_display_widget.h" + +#include +#include + +class TabEdhRec : public Tab +{ + Q_OBJECT +public: + explicit TabEdhRec(TabSupervisor *_tabSupervisor); + + void retranslateUi() override; + QString getTabText() const override + { + auto cardName = cardToQuery.isNull() ? QString() : cardToQuery->getName(); + return tr("EDHREC: ") + cardName; + } + + QNetworkAccessManager *networkManager; + +public slots: + void processApiJson(QNetworkReply *reply); + void prettyPrintJson(const QJsonValue &value, int indentLevel); + void setCard(CardInfoPtr _cardToQuery, bool isCommander = false); + +private: + CardInfoPtr cardToQuery; + EdhrecCommanderApiResponseDisplayWidget *displayWidget; +}; + +#endif // TAB_EDHREC_H diff --git a/cockatrice/src/client/tabs/tab.cpp b/cockatrice/src/client/tabs/tab.cpp index 0ffc5ecdf..d31b86bc9 100644 --- a/cockatrice/src/client/tabs/tab.cpp +++ b/cockatrice/src/client/tabs/tab.cpp @@ -1,25 +1,29 @@ #include "tab.h" #include "../ui/widgets/cards/card_info_display_widget.h" +#include "./tab_supervisor.h" #include +#include #include #include -Tab::Tab(TabSupervisor *_tabSupervisor, QWidget *parent) - : QMainWindow(parent), tabSupervisor(_tabSupervisor), contentsChanged(false), infoPopup(0) +Tab::Tab(TabSupervisor *_tabSupervisor) + : QMainWindow(_tabSupervisor), tabSupervisor(_tabSupervisor), contentsChanged(false), infoPopup(0) { setAttribute(Qt::WA_DeleteOnClose); } -void Tab::showCardInfoPopup(const QPoint &pos, const QString &cardName) +void Tab::showCardInfoPopup(const QPoint &pos, const QString &cardName, const QString &providerId) { if (infoPopup) { infoPopup->deleteLater(); } currentCardName = cardName; - infoPopup = new CardInfoDisplayWidget( - cardName, 0, Qt::Widget | Qt::FramelessWindowHint | Qt::X11BypassWindowManagerHint | Qt::WindowStaysOnTopHint); + currentProviderId = providerId; + infoPopup = new CardInfoDisplayWidget(cardName, providerId, nullptr, + Qt::Widget | Qt::FramelessWindowHint | Qt::X11BypassWindowManagerHint | + Qt::WindowStaysOnTopHint); infoPopup->setAttribute(Qt::WA_TransparentForMouseEvents); auto screenRect = qApp->primaryScreen()->geometry(); @@ -39,3 +43,17 @@ void Tab::deleteCardInfoPopup(const QString &cardName) } } } + +/** + * Overrides the closeEvent in order to emit a close signal + */ +void Tab::closeEvent(QCloseEvent *event) +{ + emit closed(); + event->accept(); +} + +void Tab::closeRequest(bool /*forced*/) +{ + close(); +} \ No newline at end of file diff --git a/cockatrice/src/client/tabs/tab.h b/cockatrice/src/client/tabs/tab.h index 295c1e28f..f82f9a7b2 100644 --- a/cockatrice/src/client/tabs/tab.h +++ b/cockatrice/src/client/tabs/tab.h @@ -13,6 +13,12 @@ class Tab : public QMainWindow signals: void userEvent(bool globalEvent = true); void tabTextChanged(Tab *tab, const QString &newTabText); + /** + * Emitted when the tab is closed (because Qt doesn't provide a built-in close signal) + * This signal is emitted from this class's overridden Tab::closeEvent method. + * Make sure any subclasses that override closeEvent still emit this signal from there. + */ + void closed(); protected: TabSupervisor *tabSupervisor; @@ -21,17 +27,18 @@ protected: tabMenus.append(menu); } protected slots: - void showCardInfoPopup(const QPoint &pos, const QString &cardName); + void showCardInfoPopup(const QPoint &pos, const QString &cardName, const QString &providerId); void deleteCardInfoPopup(const QString &cardName); + void closeEvent(QCloseEvent *event) override; private: - QString currentCardName; + QString currentCardName, currentProviderId; bool contentsChanged; CardInfoDisplayWidget *infoPopup; QList tabMenus; public: - Tab(TabSupervisor *_tabSupervisor, QWidget *parent = nullptr); + explicit Tab(TabSupervisor *_tabSupervisor); const QList &getTabMenus() const { return tabMenus; @@ -50,9 +57,13 @@ public: } virtual QString getTabText() const = 0; virtual void retranslateUi() = 0; - virtual void closeRequest() - { - } + /** + * Sends a request to close the tab. + * Signals for cleanup should be emitted from this method instead of the destructor. + * + * @param forced whether this close request was initiated by the user or forced by the server. + */ + virtual void closeRequest(bool forced = false); virtual void tabActivated() { } diff --git a/cockatrice/src/client/tabs/tab_account.cpp b/cockatrice/src/client/tabs/tab_account.cpp index fab79bea1..78517a82d 100644 --- a/cockatrice/src/client/tabs/tab_account.cpp +++ b/cockatrice/src/client/tabs/tab_account.cpp @@ -3,7 +3,8 @@ #include "../../deck/custom_line_edit.h" #include "../../server/pending_command.h" #include "../../server/user/user_info_box.h" -#include "../../server/user/user_list.h" +#include "../../server/user/user_list_manager.h" +#include "../../server/user/user_list_widget.h" #include "../game_logic/abstract_client.h" #include "../sound_engine.h" #include "pb/event_add_to_list.pb.h" @@ -12,125 +13,118 @@ #include "pb/event_user_left.pb.h" #include "pb/response_list_users.pb.h" #include "pb/session_commands.pb.h" +#include "tab_supervisor.h" #include "trice_limits.h" -#include #include #include -TabUserLists::TabUserLists(TabSupervisor *_tabSupervisor, - AbstractClient *_client, - const ServerInfo_User &userInfo, - QWidget *parent) - : Tab(_tabSupervisor, parent), client(_client) +TabAccount::TabAccount(TabSupervisor *_tabSupervisor, AbstractClient *_client, const ServerInfo_User &userInfo) + : Tab(_tabSupervisor), client(_client) { - allUsersList = new UserList(_tabSupervisor, client, UserList::AllUsersList); - buddyList = new UserList(_tabSupervisor, client, UserList::BuddyList); - ignoreList = new UserList(_tabSupervisor, client, UserList::IgnoreList); + allUsersList = new UserListWidget(_tabSupervisor, client, UserListWidget::AllUsersList); + buddyList = new UserListWidget(_tabSupervisor, client, UserListWidget::BuddyList); + ignoreList = new UserListWidget(_tabSupervisor, client, UserListWidget::IgnoreList); userInfoBox = new UserInfoBox(client, true); userInfoBox->updateInfo(userInfo); - connect(allUsersList, SIGNAL(openMessageDialog(const QString &, bool)), this, - SIGNAL(openMessageDialog(const QString &, bool))); - connect(buddyList, SIGNAL(openMessageDialog(const QString &, bool)), this, - SIGNAL(openMessageDialog(const QString &, bool))); - connect(ignoreList, SIGNAL(openMessageDialog(const QString &, bool)), this, - SIGNAL(openMessageDialog(const QString &, bool))); + connect(allUsersList, &UserListWidget::openMessageDialog, this, &TabAccount::openMessageDialog); + connect(buddyList, &UserListWidget::openMessageDialog, this, &TabAccount::openMessageDialog); + connect(ignoreList, &UserListWidget::openMessageDialog, this, &TabAccount::openMessageDialog); - connect(client, SIGNAL(userJoinedEventReceived(const Event_UserJoined &)), this, - SLOT(processUserJoinedEvent(const Event_UserJoined &))); - connect(client, SIGNAL(userLeftEventReceived(const Event_UserLeft &)), this, - SLOT(processUserLeftEvent(const Event_UserLeft &))); - connect(client, SIGNAL(buddyListReceived(const QList &)), this, - SLOT(buddyListReceived(const QList &))); - connect(client, SIGNAL(ignoreListReceived(const QList &)), this, - SLOT(ignoreListReceived(const QList &))); - connect(client, SIGNAL(addToListEventReceived(const Event_AddToList &)), this, - SLOT(processAddToListEvent(const Event_AddToList &))); - connect(client, SIGNAL(removeFromListEventReceived(const Event_RemoveFromList &)), this, - SLOT(processRemoveFromListEvent(const Event_RemoveFromList &))); + connect(client, &AbstractClient::userJoinedEventReceived, this, &TabAccount::processUserJoinedEvent); + connect(client, &AbstractClient::userLeftEventReceived, this, &TabAccount::processUserLeftEvent); + connect(client, &AbstractClient::buddyListReceived, this, &TabAccount::buddyListReceived); + connect(client, &AbstractClient::ignoreListReceived, this, &TabAccount::ignoreListReceived); + connect(client, &AbstractClient::addToListEventReceived, this, &TabAccount::processAddToListEvent); + connect(client, &AbstractClient::removeFromListEventReceived, this, &TabAccount::processRemoveFromListEvent); - PendingCommand *pend = client->prepareSessionCommand(Command_ListUsers()); - connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this, - SLOT(processListUsersResponse(const Response &))); + // Attempt to populate the tab with the cache + buddyListReceived(tabSupervisor->getUserListManager()->getBuddyList().values()); + ignoreListReceived(tabSupervisor->getUserListManager()->getIgnoreList().values()); + + PendingCommand *pend = AbstractClient::prepareSessionCommand(Command_ListUsers()); + connect(pend, &PendingCommand::finished, this, &TabAccount::processListUsersResponse); client->sendCommand(pend); - QVBoxLayout *vbox = new QVBoxLayout; + auto *vbox = new QVBoxLayout; vbox->addWidget(userInfoBox); vbox->addWidget(allUsersList); - QHBoxLayout *addToBuddyList = new QHBoxLayout; + auto *addToBuddyList = new QHBoxLayout; addBuddyEdit = new LineEditUnfocusable; addBuddyEdit->setMaxLength(MAX_NAME_LENGTH); addBuddyEdit->setPlaceholderText(tr("Add to Buddy List")); - connect(addBuddyEdit, SIGNAL(returnPressed()), this, SLOT(addToBuddyList())); - QPushButton *addBuddyButton = new QPushButton("Add"); - connect(addBuddyButton, SIGNAL(clicked()), this, SLOT(addToBuddyList())); + connect(addBuddyEdit, &LineEditUnfocusable::returnPressed, this, &TabAccount::addToBuddyList); + auto *addBuddyButton = new QPushButton("Add"); + connect(addBuddyButton, &QPushButton::clicked, this, &TabAccount::addToBuddyList); addToBuddyList->addWidget(addBuddyEdit); addToBuddyList->addWidget(addBuddyButton); - QHBoxLayout *addToIgnoreList = new QHBoxLayout; + auto *addToIgnoreList = new QHBoxLayout; addIgnoreEdit = new LineEditUnfocusable; addIgnoreEdit->setMaxLength(MAX_NAME_LENGTH); addIgnoreEdit->setPlaceholderText(tr("Add to Ignore List")); - connect(addIgnoreEdit, SIGNAL(returnPressed()), this, SLOT(addToIgnoreList())); - QPushButton *addIgnoreButton = new QPushButton("Add"); - connect(addIgnoreButton, SIGNAL(clicked()), this, SLOT(addToIgnoreList())); + connect(addIgnoreEdit, &LineEditUnfocusable::returnPressed, this, &TabAccount::addToIgnoreList); + auto *addIgnoreButton = new QPushButton("Add"); + connect(addIgnoreButton, &QPushButton::clicked, this, &TabAccount::addToIgnoreList); addToIgnoreList->addWidget(addIgnoreEdit); addToIgnoreList->addWidget(addIgnoreButton); - QVBoxLayout *buddyPanel = new QVBoxLayout; + auto *buddyPanel = new QVBoxLayout; buddyPanel->addWidget(buddyList); buddyPanel->addLayout(addToBuddyList); - QVBoxLayout *ignorePanel = new QVBoxLayout; + auto *ignorePanel = new QVBoxLayout; ignorePanel->addWidget(ignoreList); ignorePanel->addLayout(addToIgnoreList); - QHBoxLayout *mainLayout = new QHBoxLayout; + auto *mainLayout = new QHBoxLayout; mainLayout->addLayout(buddyPanel); mainLayout->addLayout(ignorePanel); mainLayout->addLayout(vbox); retranslateUi(); - QWidget *mainWidget = new QWidget(this); + auto *mainWidget = new QWidget(this); mainWidget->setLayout(mainLayout); setCentralWidget(mainWidget); } -void TabUserLists::addToBuddyList() +void TabAccount::addToBuddyList() { - QString userName = addBuddyEdit->text(); - if (userName.length() < 1) + const QString &userName = addBuddyEdit->text(); + if (userName.isEmpty()) { return; + } - std::string listName = "buddy"; + const std::string listName = "buddy"; addToList(listName, userName); addBuddyEdit->clear(); } -void TabUserLists::addToIgnoreList() +void TabAccount::addToIgnoreList() { - QString userName = addIgnoreEdit->text(); - if (userName.length() < 1) + const QString &userName = addIgnoreEdit->text(); + if (userName.isEmpty()) { return; + } - std::string listName = "ignore"; + const std::string listName = "ignore"; addToList(listName, userName); addIgnoreEdit->clear(); } -void TabUserLists::addToList(const std::string &listName, const QString &userName) +void TabAccount::addToList(const std::string &listName, const QString &userName) { Command_AddToList cmd; cmd.set_list(listName); cmd.set_user_name(userName.toStdString()); - client->sendCommand(client->prepareSessionCommand(cmd)); + client->sendCommand(AbstractClient::prepareSessionCommand(cmd)); } -void TabUserLists::retranslateUi() +void TabAccount::retranslateUi() { allUsersList->retranslateUi(); buddyList->retranslateUi(); @@ -138,14 +132,12 @@ void TabUserLists::retranslateUi() userInfoBox->retranslateUi(); } -void TabUserLists::processListUsersResponse(const Response &response) +void TabAccount::processListUsersResponse(const Response &response) { const Response_ListUsers &resp = response.GetExtension(Response_ListUsers::ext); - - const int userListSize = resp.user_list_size(); - for (int i = 0; i < userListSize; ++i) { + for (int i = 0; i < resp.user_list_size(); ++i) { const ServerInfo_User &info = resp.user_list(i); - const QString userName = QString::fromStdString(info.name()); + const QString &userName = QString::fromStdString(info.name()); allUsersList->processUserInfo(info, true); ignoreList->setUserOnline(userName, true); buddyList->setUserOnline(userName, true); @@ -156,10 +148,10 @@ void TabUserLists::processListUsersResponse(const Response &response) buddyList->sortItems(); } -void TabUserLists::processUserJoinedEvent(const Event_UserJoined &event) +void TabAccount::processUserJoinedEvent(const Event_UserJoined &event) { const ServerInfo_User &info = event.user_info(); - const QString userName = QString::fromStdString(info.name()); + const QString &userName = QString::fromStdString(info.name()); allUsersList->processUserInfo(info, true); ignoreList->setUserOnline(userName, true); @@ -169,18 +161,20 @@ void TabUserLists::processUserJoinedEvent(const Event_UserJoined &event) ignoreList->sortItems(); buddyList->sortItems(); - if (buddyList->getUsers().keys().contains(userName)) + if (buddyList->getUsers().keys().contains(userName)) { soundEngine->playSound("buddy_join"); + } emit userJoined(info); } -void TabUserLists::processUserLeftEvent(const Event_UserLeft &event) +void TabAccount::processUserLeftEvent(const Event_UserLeft &event) { - QString userName = QString::fromStdString(event.name()); + const QString &userName = QString::fromStdString(event.name()); - if (buddyList->getUsers().keys().contains(userName)) + if (buddyList->getUsers().keys().contains(userName)) { soundEngine->playSound("buddy_leave"); + } if (allUsersList->deleteUser(userName)) { ignoreList->setUserOnline(userName, false); @@ -192,47 +186,54 @@ void TabUserLists::processUserLeftEvent(const Event_UserLeft &event) } } -void TabUserLists::buddyListReceived(const QList &_buddyList) +void TabAccount::buddyListReceived(const QList &_buddyList) { - for (int i = 0; i < _buddyList.size(); ++i) - buddyList->processUserInfo(_buddyList[i], false); + for (const auto &user : _buddyList) { + buddyList->processUserInfo(user, false); + } buddyList->sortItems(); } -void TabUserLists::ignoreListReceived(const QList &_ignoreList) +void TabAccount::ignoreListReceived(const QList &_ignoreList) { - for (int i = 0; i < _ignoreList.size(); ++i) - ignoreList->processUserInfo(_ignoreList[i], false); + for (const auto &user : _ignoreList) { + ignoreList->processUserInfo(user, false); + } ignoreList->sortItems(); } -void TabUserLists::processAddToListEvent(const Event_AddToList &event) +void TabAccount::processAddToListEvent(const Event_AddToList &event) { const ServerInfo_User &info = event.user_info(); - bool online = allUsersList->getUsers().contains(QString::fromStdString(info.name())); - QString list = QString::fromStdString(event.list_name()); - UserList *userList = 0; - if (list == "buddy") + const bool online = allUsersList->getUsers().contains(QString::fromStdString(info.name())); + const QString &list = QString::fromStdString(event.list_name()); + + UserListWidget *userList; + if (list == "buddy") { userList = buddyList; - else if (list == "ignore") + } else if (list == "ignore") { userList = ignoreList; - if (!userList) + } else { return; + } userList->processUserInfo(info, online); userList->sortItems(); } -void TabUserLists::processRemoveFromListEvent(const Event_RemoveFromList &event) +void TabAccount::processRemoveFromListEvent(const Event_RemoveFromList &event) { - QString list = QString::fromStdString(event.list_name()); - QString user = QString::fromStdString(event.user_name()); - UserList *userList = 0; - if (list == "buddy") + const auto &list = QString::fromStdString(event.list_name()); + const auto &user = QString::fromStdString(event.user_name()); + + UserListWidget *userList; + if (list == "buddy") { userList = buddyList; - else if (list == "ignore") + } else if (list == "ignore") { userList = ignoreList; - if (!userList) + } else { return; + } + userList->deleteUser(user); } diff --git a/cockatrice/src/client/tabs/tab_account.h b/cockatrice/src/client/tabs/tab_account.h index ba6eb78ad..5824a7527 100644 --- a/cockatrice/src/client/tabs/tab_account.h +++ b/cockatrice/src/client/tabs/tab_account.h @@ -5,25 +5,25 @@ #include "tab.h" class AbstractClient; -class UserList; -class UserInfoBox; -class LineEditUnfocusable; - +class Event_AddToList; class Event_ListRooms; +class Event_RemoveFromList; class Event_UserJoined; class Event_UserLeft; +class LineEditUnfocusable; class Response; class ServerInfo_User; -class Event_AddToList; -class Event_RemoveFromList; +class UserInfoBox; +class UserListWidget; -class TabUserLists : public Tab +class TabAccount : public Tab { Q_OBJECT signals: void openMessageDialog(const QString &userName, bool focus); void userLeft(const QString &userName); void userJoined(const ServerInfo_User &userInfo); + private slots: void processListUsersResponse(const Response &response); void processUserJoinedEvent(const Event_UserJoined &event); @@ -37,36 +37,21 @@ private slots: private: AbstractClient *client; - UserList *allUsersList; - UserList *buddyList; - UserList *ignoreList; + UserListWidget *allUsersList; + UserListWidget *buddyList; + UserListWidget *ignoreList; UserInfoBox *userInfoBox; LineEditUnfocusable *addBuddyEdit; LineEditUnfocusable *addIgnoreEdit; void addToList(const std::string &listName, const QString &userName); public: - TabUserLists(TabSupervisor *_tabSupervisor, - AbstractClient *_client, - const ServerInfo_User &userInfo, - QWidget *parent = nullptr); - void retranslateUi(); - QString getTabText() const + explicit TabAccount(TabSupervisor *_tabSupervisor, AbstractClient *_client, const ServerInfo_User &userInfo); + void retranslateUi() override; + [[nodiscard]] QString getTabText() const override { return tr("Account"); } - const UserList *getAllUsersList() const - { - return allUsersList; - } - const UserList *getBuddyList() const - { - return buddyList; - } - const UserList *getIgnoreList() const - { - return ignoreList; - } }; #endif diff --git a/cockatrice/src/client/tabs/tab_admin.cpp b/cockatrice/src/client/tabs/tab_admin.cpp index 07031c1cc..5a8ace0c6 100644 --- a/cockatrice/src/client/tabs/tab_admin.cpp +++ b/cockatrice/src/client/tabs/tab_admin.cpp @@ -1,19 +1,20 @@ #include "tab_admin.h" +#include "../../server/pending_command.h" #include "../game_logic/abstract_client.h" #include "pb/admin_commands.pb.h" +#include "pb/event_replay_added.pb.h" +#include "pb/moderator_commands.pb.h" #include "trice_limits.h" #include #include #include -#include #include #include #include #include #include -#include ShutdownDialog::ShutdownDialog(QWidget *parent) : QDialog(parent) { @@ -29,8 +30,8 @@ ShutdownDialog::ShutdownDialog(QWidget *parent) : QDialog(parent) minutesEdit->setMaximum(999); QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); - connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept())); - connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject())); + connect(buttonBox, &QDialogButtonBox::accepted, this, &ShutdownDialog::accept); + connect(buttonBox, &QDialogButtonBox::rejected, this, &ShutdownDialog::reject); QGridLayout *mainLayout = new QGridLayout; mainLayout->addWidget(reasonLabel, 0, 0); @@ -53,34 +54,66 @@ int ShutdownDialog::getMinutes() const return minutesEdit->value(); } -TabAdmin::TabAdmin(TabSupervisor *_tabSupervisor, AbstractClient *_client, bool _fullAdmin, QWidget *parent) - : Tab(_tabSupervisor, parent), locked(true), client(_client), fullAdmin(_fullAdmin) +TabAdmin::TabAdmin(TabSupervisor *_tabSupervisor, AbstractClient *_client, bool _fullAdmin) + : Tab(_tabSupervisor), locked(true), client(_client), fullAdmin(_fullAdmin) { updateServerMessageButton = new QPushButton; - connect(updateServerMessageButton, SIGNAL(clicked()), this, SLOT(actUpdateServerMessage())); + connect(updateServerMessageButton, &QPushButton::clicked, this, &TabAdmin::actUpdateServerMessage); shutdownServerButton = new QPushButton; - connect(shutdownServerButton, SIGNAL(clicked()), this, SLOT(actShutdownServer())); + connect(shutdownServerButton, &QPushButton::clicked, this, &TabAdmin::actShutdownServer); reloadConfigButton = new QPushButton; - connect(reloadConfigButton, SIGNAL(clicked()), this, SLOT(actReloadConfig())); + connect(reloadConfigButton, &QPushButton::clicked, this, &TabAdmin::actReloadConfig); - QVBoxLayout *vbox = new QVBoxLayout; - vbox->addWidget(updateServerMessageButton); - vbox->addWidget(shutdownServerButton); - vbox->addWidget(reloadConfigButton); - vbox->addStretch(); + grantReplayAccessButton = new QPushButton; + grantReplayAccessButton->setEnabled(false); + connect(grantReplayAccessButton, &QPushButton::clicked, this, &TabAdmin::actGrantReplayAccess); + replayIdToGrant = new QLineEdit; + replayIdToGrant->setMaximumWidth(500); + replayIdToGrant->setValidator(new QIntValidator(0, INT_MAX, this)); + connect(replayIdToGrant, &QLineEdit::textChanged, this, + [=, this]() { grantReplayAccessButton->setEnabled(!replayIdToGrant->text().isEmpty()); }); + auto *grandReplayAccessLayout = new QGridLayout(this); + grandReplayAccessLayout->addWidget(replayIdToGrant, 0, 0); + grandReplayAccessLayout->addWidget(grantReplayAccessButton, 0, 1); + + activateUserButton = new QPushButton; + activateUserButton->setEnabled(false); + connect(activateUserButton, &QPushButton::clicked, this, &TabAdmin::actForceActivateUser); + userToActivate = new QLineEdit; + userToActivate->setMaximumWidth(500); + connect(userToActivate, &QLineEdit::textChanged, this, + [=, this]() { activateUserButton->setEnabled(!userToActivate->text().isEmpty()); }); + auto *activateUserLayout = new QGridLayout(this); + activateUserLayout->addWidget(userToActivate, 0, 0); + activateUserLayout->addWidget(activateUserButton, 0, 1); + + auto *adminVBox = new QVBoxLayout; + adminVBox->addWidget(updateServerMessageButton); + adminVBox->addWidget(shutdownServerButton); + adminVBox->addWidget(reloadConfigButton); adminGroupBox = new QGroupBox; - adminGroupBox->setLayout(vbox); + adminGroupBox->setLayout(adminVBox); adminGroupBox->setEnabled(false); + auto *moderatorVBox = new QVBoxLayout; + moderatorVBox->addLayout(grandReplayAccessLayout); + moderatorVBox->addLayout(activateUserLayout); + + moderatorGroupBox = new QGroupBox; + moderatorGroupBox->setLayout(moderatorVBox); + moderatorGroupBox->setEnabled(false); + unlockButton = new QPushButton; - connect(unlockButton, SIGNAL(clicked()), this, SLOT(actUnlock())); + connect(unlockButton, &QPushButton::clicked, this, &TabAdmin::actUnlock); lockButton = new QPushButton; lockButton->setEnabled(false); - connect(lockButton, SIGNAL(clicked()), this, SLOT(actLock())); + connect(lockButton, &QPushButton::clicked, this, &TabAdmin::actLock); QVBoxLayout *mainLayout = new QVBoxLayout; mainLayout->addWidget(adminGroupBox); + mainLayout->addWidget(moderatorGroupBox); + mainLayout->addStretch(); mainLayout->addWidget(unlockButton); mainLayout->addWidget(lockButton); @@ -99,6 +132,13 @@ void TabAdmin::retranslateUi() shutdownServerButton->setText(tr("&Shut down server")); reloadConfigButton->setText(tr("&Reload configuration")); adminGroupBox->setTitle(tr("Server administration functions")); + moderatorGroupBox->setTitle(tr("Server moderator functions")); + + replayIdToGrant->setPlaceholderText(tr("Replay ID")); + grantReplayAccessButton->setText(tr("Grant Replay Access")); + + userToActivate->setPlaceholderText(tr("Username to Activate")); + activateUserButton->setText(tr("Force Activate User")); unlockButton->setText(tr("&Unlock functions")); lockButton->setText(tr("&Lock functions")); @@ -117,7 +157,7 @@ void TabAdmin::actShutdownServer() cmd.set_reason(dlg.getReason().toStdString()); cmd.set_minutes(dlg.getMinutes()); - client->sendCommand(client->prepareAdminCommand(cmd)); + client->sendCommand(AbstractClient::prepareAdminCommand(cmd)); } } @@ -127,22 +167,98 @@ void TabAdmin::actReloadConfig() client->sendCommand(client->prepareAdminCommand(cmd)); } +void TabAdmin::actGrantReplayAccess() +{ + if (!replayIdToGrant) { + return; + } + + Command_GrantReplayAccess cmd; + cmd.set_replay_id(replayIdToGrant->text().toUInt()); + cmd.set_moderator_name(client->getUserName().toStdString()); + + auto *pend = client->prepareModeratorCommand(cmd); + connect(pend, &PendingCommand::finished, this, &TabAdmin::grantReplayAccessProcessResponse); + client->sendCommand(pend); +} + +void TabAdmin::actForceActivateUser() +{ + if (!userToActivate) { + return; + } + + Command_ForceActivateUser cmd; + cmd.set_username_to_activate(userToActivate->text().trimmed().toStdString()); + cmd.set_moderator_name(client->getUserName().toStdString()); + + auto *pend = client->prepareModeratorCommand(cmd); + connect(pend, + QOverload::of(&PendingCommand::finished), + this, &TabAdmin::activateUserProcessResponse); + client->sendCommand(pend); +} + +void TabAdmin::grantReplayAccessProcessResponse(const Response &response) +{ + auto *event = new Event_ReplayAdded(); + + switch (response.response_code()) { + case Response::RespOk: + client->replayAddedEventReceived(*event); + QMessageBox::information(this, tr("Success"), tr("Replay access granted")); + break; + case Response::RespContextError: + QMessageBox::critical(this, tr("Error"), tr("Unable to grant replay access. Replay ID invalid")); + break; + default: + QMessageBox::critical(this, tr("Error"), tr("Unable to grant replay access. Internal error")); + break; + } +} + +void TabAdmin::activateUserProcessResponse(const Response &response) +{ + switch (response.response_code()) { + case Response::RespActivationAccepted: + QMessageBox::information(this, tr("Success"), tr("User successfully activated")); + break; + case Response::RespNameNotFound: + QMessageBox::critical(this, tr("Error"), tr("Unable to activate user. Username invalid")); + break; + case Response::RespActivationFailed: + QMessageBox::critical(this, tr("Error"), tr("Unable to activate user. User already active")); + break; + default: + QMessageBox::critical(this, tr("Error"), tr("Unable to activate user. Internal error")); + break; + } +} + void TabAdmin::actUnlock() { - if (fullAdmin) + if (fullAdmin) { adminGroupBox->setEnabled(true); + } + + moderatorGroupBox->setEnabled(true); lockButton->setEnabled(true); unlockButton->setEnabled(false); locked = false; + emit adminLockChanged(false); } void TabAdmin::actLock() { - if (fullAdmin) + if (fullAdmin) { adminGroupBox->setEnabled(false); + } + + moderatorGroupBox->setEnabled(false); lockButton->setEnabled(false); unlockButton->setEnabled(true); locked = true; + emit adminLockChanged(true); } diff --git a/cockatrice/src/client/tabs/tab_admin.h b/cockatrice/src/client/tabs/tab_admin.h index 650e881a9..e219aa627 100644 --- a/cockatrice/src/client/tabs/tab_admin.h +++ b/cockatrice/src/client/tabs/tab_admin.h @@ -1,6 +1,8 @@ #ifndef TAB_ADMIN_H #define TAB_ADMIN_H +#include "pb/commands.pb.h" +#include "pb/response.pb.h" #include "tab.h" #include @@ -20,7 +22,7 @@ private: QSpinBox *minutesEdit; public: - ShutdownDialog(QWidget *parent = nullptr); + explicit ShutdownDialog(QWidget *parent = nullptr); QString getReason() const; int getMinutes() const; }; @@ -32,23 +34,29 @@ private: bool locked; AbstractClient *client; bool fullAdmin; - QPushButton *updateServerMessageButton, *shutdownServerButton, *reloadConfigButton; - QGroupBox *adminGroupBox; + QPushButton *updateServerMessageButton, *shutdownServerButton, *reloadConfigButton, *grantReplayAccessButton, + *activateUserButton; + QGroupBox *adminGroupBox, *moderatorGroupBox; QPushButton *unlockButton, *lockButton; + QLineEdit *replayIdToGrant, *userToActivate; signals: void adminLockChanged(bool lock); private slots: void actUpdateServerMessage(); void actShutdownServer(); void actReloadConfig(); + void actGrantReplayAccess(); + void actForceActivateUser(); + void grantReplayAccessProcessResponse(const Response &response); + void activateUserProcessResponse(const Response &response); void actUnlock(); void actLock(); public: - TabAdmin(TabSupervisor *_tabSupervisor, AbstractClient *_client, bool _fullAdmin, QWidget *parent = nullptr); - void retranslateUi(); - QString getTabText() const + TabAdmin(TabSupervisor *_tabSupervisor, AbstractClient *_client, bool _fullAdmin); + void retranslateUi() override; + QString getTabText() const override { return tr("Administration"); } diff --git a/cockatrice/src/client/tabs/tab_deck_editor.cpp b/cockatrice/src/client/tabs/tab_deck_editor.cpp index 1e2284186..dbe38a215 100644 --- a/cockatrice/src/client/tabs/tab_deck_editor.cpp +++ b/cockatrice/src/client/tabs/tab_deck_editor.cpp @@ -3,345 +3,59 @@ #include "../../client/game_logic/abstract_client.h" #include "../../client/tapped_out_interface.h" #include "../../client/ui/widgets/cards/card_info_frame_widget.h" -#include "../../deck/deck_list_model.h" -#include "../../deck/deck_stats_interface.h" +#include "../../dialogs/dlg_load_deck.h" #include "../../dialogs/dlg_load_deck_from_clipboard.h" #include "../../game/cards/card_database_manager.h" #include "../../game/cards/card_database_model.h" #include "../../game/filters/filter_builder.h" #include "../../game/filters/filter_tree_model.h" -#include "../../main.h" #include "../../server/pending_command.h" #include "../../settings/cache_settings.h" -#include "../ui/picture_loader.h" +#include "../menus/deck_editor/deck_editor_menu.h" #include "../ui/pixel_map_generator.h" -#include "pb/command_deck_upload.pb.h" -#include "pb/response.pb.h" +#include "../ui/widgets/deck_editor/deck_editor_filter_dock_widget.h" #include "tab_supervisor.h" #include "trice_limits.h" #include #include -#include #include -#include +#include +#include #include #include #include #include #include #include -#include #include #include #include #include -#include -#include #include -#include #include -#include #include -#include #include #include -#include #include -void SearchLineEdit::keyPressEvent(QKeyEvent *event) +TabDeckEditor::TabDeckEditor(TabSupervisor *_tabSupervisor) : AbstractTabDeckEditor(_tabSupervisor) { - // List of key events that must be handled by the card list instead of the search box - static const QVector forwardToTreeView = {Qt::Key_Up, Qt::Key_Down, Qt::Key_PageDown, Qt::Key_PageUp}; - // forward only if the search text is empty - static const QVector forwardWhenEmpty = {Qt::Key_Home, Qt::Key_End}; - Qt::Key key = static_cast(event->key()); - if (treeView) { - if (forwardToTreeView.contains(key)) - QCoreApplication::sendEvent(treeView, event); - if (text().isEmpty() && forwardWhenEmpty.contains(key)) - QCoreApplication::sendEvent(treeView, event); - } - LineEditUnfocusable::keyPressEvent(event); -} + setObjectName("TabDeckEditor"); -void TabDeckEditor::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); - connect(deckView->selectionModel(), SIGNAL(currentRowChanged(const QModelIndex &, const QModelIndex &)), this, - SLOT(updateCardInfoRight(const QModelIndex &, const QModelIndex &))); - connect(deckView, SIGNAL(doubleClicked(const QModelIndex &)), this, SLOT(actSwapCard())); - 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(actDecrement())); - connect(&deckViewKeySignals, SIGNAL(onShiftRight()), this, SLOT(actIncrement())); - connect(&deckViewKeySignals, SIGNAL(onShiftLeft()), this, SLOT(actDecrement())); - connect(&deckViewKeySignals, SIGNAL(onDelete()), this, SLOT(actRemoveCard())); + TabDeckEditor::createMenus(); - 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())); + installEventFilter(this); - 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); + TabDeckEditor::retranslateUi(); + TabDeckEditor::refreshShortcuts(); - aDecrement = new QAction(QString(), this); - aDecrement->setIcon(QPixmap("theme:icons/decrement")); - connect(aDecrement, SIGNAL(triggered()), this, SLOT(actDecrement())); - 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); - - auto *upperLayout = new QGridLayout; - upperLayout->setObjectName("upperLayout"); - upperLayout->addWidget(nameLabel, 0, 0); - upperLayout->addWidget(nameEdit, 0, 1); - - upperLayout->addWidget(commentsLabel, 1, 0); - upperLayout->addWidget(commentsEdit, 1, 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(deckView, 1, 0, 1, 5); - - // 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); - - deckDock = new QDockWidget(this); - deckDock->setObjectName("deckDock"); - - deckDock->setMinimumSize(QSize(200, 41)); - deckDock->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea); - deckDock->setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetFloatable | - QDockWidget::DockWidgetMovable); - auto *deckDockContents = new QWidget(); - deckDockContents->setObjectName("deckDockContents"); - deckDockContents->setLayout(rightFrame); - deckDock->setWidget(deckDockContents); - - deckDock->installEventFilter(this); - connect(deckDock, SIGNAL(topLevelChanged(bool)), this, SLOT(dockTopLevelChanged(bool))); -} - -void TabDeckEditor::createCardInfoDock() -{ - cardInfo = new CardInfoFrameWidget(); - cardInfo->setObjectName("cardInfo"); - auto *cardInfoFrame = new QVBoxLayout; - cardInfoFrame->setObjectName("cardInfoFrame"); - cardInfoFrame->addWidget(cardInfo); - - cardInfoDock = new QDockWidget(this); - cardInfoDock->setObjectName("cardInfoDock"); - - cardInfoDock->setMinimumSize(QSize(200, 41)); - cardInfoDock->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea); - cardInfoDock->setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetFloatable | - QDockWidget::DockWidgetMovable); - auto *cardInfoDockContents = new QWidget(); - cardInfoDockContents->setObjectName("cardInfoDockContents"); - cardInfoDockContents->setLayout(cardInfoFrame); - cardInfoDock->setWidget(cardInfoDockContents); - - cardInfoDock->installEventFilter(this); - connect(cardInfoDock, SIGNAL(topLevelChanged(bool)), this, SLOT(dockTopLevelChanged(bool))); -} - -void TabDeckEditor::createFiltersDock() -{ - filterModel = new FilterTreeModel(); - filterModel->setObjectName("filterModel"); - databaseDisplayModel->setFilterTree(filterModel->filterTree()); - databaseDisplayModel->setObjectName("databaseDisplayModel"); - 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 *))); - - 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); - - filterDock = new QDockWidget(this); - filterDock->setObjectName("filterDock"); - - filterDock->setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetFloatable | - QDockWidget::DockWidgetMovable); - auto *filterDockContents = new QWidget(this); - filterDockContents->setObjectName("filterDockContents"); - filterDockContents->setLayout(filterFrame); - filterDock->setWidget(filterDockContents); - - filterDock->installEventFilter(this); - connect(filterDock, SIGNAL(topLevelChanged(bool)), this, SLOT(dockTopLevelChanged(bool))); + TabDeckEditor::loadLayout(); } void TabDeckEditor::createMenus() { - aNewDeck = new QAction(QString(), this); - connect(aNewDeck, SIGNAL(triggered()), this, SLOT(actNewDeck())); - - aLoadDeck = new QAction(QString(), this); - connect(aLoadDeck, SIGNAL(triggered()), this, SLOT(actLoadDeck())); - - aSaveDeck = new QAction(QString(), this); - connect(aSaveDeck, SIGNAL(triggered()), this, SLOT(actSaveDeck())); - - aSaveDeckAs = new QAction(QString(), this); - connect(aSaveDeckAs, SIGNAL(triggered()), this, SLOT(actSaveDeckAs())); - - aLoadDeckFromClipboard = new QAction(QString(), this); - connect(aLoadDeckFromClipboard, SIGNAL(triggered()), this, SLOT(actLoadDeckFromClipboard())); - - aSaveDeckToClipboard = new QAction(QString(), this); - connect(aSaveDeckToClipboard, SIGNAL(triggered()), this, SLOT(actSaveDeckToClipboard())); - - aSaveDeckToClipboardRaw = new QAction(QString(), this); - connect(aSaveDeckToClipboardRaw, SIGNAL(triggered()), this, SLOT(actSaveDeckToClipboardRaw())); - - aPrintDeck = new QAction(QString(), this); - connect(aPrintDeck, SIGNAL(triggered()), this, SLOT(actPrintDeck())); - - aExportDeckDecklist = new QAction(QString(), this); - connect(aExportDeckDecklist, SIGNAL(triggered()), this, SLOT(actExportDeckDecklist())); - - aAnalyzeDeckDeckstats = new QAction(QString(), this); - connect(aAnalyzeDeckDeckstats, SIGNAL(triggered()), this, SLOT(actAnalyzeDeckDeckstats())); - - aAnalyzeDeckTappedout = new QAction(QString(), this); - connect(aAnalyzeDeckTappedout, SIGNAL(triggered()), this, SLOT(actAnalyzeDeckTappedout())); - - analyzeDeckMenu = new QMenu(this); - analyzeDeckMenu->addAction(aExportDeckDecklist); - analyzeDeckMenu->addAction(aAnalyzeDeckDeckstats); - analyzeDeckMenu->addAction(aAnalyzeDeckTappedout); - - aClose = new QAction(QString(), this); - connect(aClose, SIGNAL(triggered()), this, SLOT(closeRequest())); - - aClearFilterAll = new QAction(QString(), this); - aClearFilterAll->setIcon(QPixmap("theme:icons/clearsearch")); - connect(aClearFilterAll, SIGNAL(triggered()), this, SLOT(actClearFilterAll())); - - aClearFilterOne = new QAction(QString(), this); - aClearFilterOne->setIcon(QPixmap("theme:icons/decrement")); - connect(aClearFilterOne, SIGNAL(triggered()), this, SLOT(actClearFilterOne())); - - saveDeckToClipboardMenu = new QMenu(this); - saveDeckToClipboardMenu->addAction(aSaveDeckToClipboard); - saveDeckToClipboardMenu->addAction(aSaveDeckToClipboardRaw); - - deckMenu = new QMenu(this); - deckMenu->addAction(aNewDeck); - deckMenu->addAction(aLoadDeck); - deckMenu->addAction(aSaveDeck); - deckMenu->addAction(aSaveDeckAs); - deckMenu->addSeparator(); - deckMenu->addAction(aLoadDeckFromClipboard); - deckMenu->addMenu(saveDeckToClipboardMenu); - deckMenu->addSeparator(); - deckMenu->addAction(aPrintDeck); - deckMenu->addMenu(analyzeDeckMenu); - deckMenu->addSeparator(); - deckMenu->addAction(aClearFilterOne); - deckMenu->addAction(aClearFilterAll); - deckMenu->addSeparator(); - deckMenu->addAction(aClose); + deckMenu = new DeckEditorMenu(this, this); addTabMenu(deckMenu); viewMenu = new QMenu(this); @@ -349,6 +63,7 @@ void TabDeckEditor::createMenus() cardInfoDockMenu = viewMenu->addMenu(QString()); deckDockMenu = viewMenu->addMenu(QString()); filterDockMenu = viewMenu->addMenu(QString()); + printingSelectorDockMenu = viewMenu->addMenu(QString()); aCardInfoDockVisible = cardInfoDockMenu->addAction(QString()); aCardInfoDockVisible->setCheckable(true); @@ -371,315 +86,46 @@ void TabDeckEditor::createMenus() aFilterDockFloating->setCheckable(true); connect(aFilterDockFloating, SIGNAL(triggered()), this, SLOT(dockFloatingTriggered())); + aPrintingSelectorDockVisible = printingSelectorDockMenu->addAction(QString()); + aPrintingSelectorDockVisible->setCheckable(true); + connect(aPrintingSelectorDockVisible, SIGNAL(triggered()), this, SLOT(dockVisibleTriggered())); + aPrintingSelectorDockFloating = printingSelectorDockMenu->addAction(QString()); + aPrintingSelectorDockFloating->setCheckable(true); + connect(aPrintingSelectorDockFloating, SIGNAL(triggered()), this, SLOT(dockFloatingTriggered())); + viewMenu->addSeparator(); aResetLayout = viewMenu->addAction(QString()); connect(aResetLayout, SIGNAL(triggered()), this, SLOT(restartLayout())); viewMenu->addAction(aResetLayout); - setSaveStatus(false); + deckMenu->setSaveStatus(false); addTabMenu(viewMenu); } -void TabDeckEditor::createCentralFrame() +QString TabDeckEditor::getTabText() const { - 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, SIGNAL(onEnter()), this, SLOT(actAddCard())); - connect(&searchKeySignals, SIGNAL(onCtrlAltEqual()), this, SLOT(actAddCard())); - connect(&searchKeySignals, SIGNAL(onCtrlAltRBracket()), this, SLOT(actAddCardToSideboard())); - connect(&searchKeySignals, SIGNAL(onCtrlAltMinus()), this, SLOT(actDecrementCard())); - connect(&searchKeySignals, SIGNAL(onCtrlAltLBracket()), this, SLOT(actDecrementCardFromSideboard())); - connect(&searchKeySignals, SIGNAL(onCtrlAltEnter()), this, SLOT(actAddCardToSideboard())); - connect(&searchKeySignals, SIGNAL(onCtrlEnter()), this, SLOT(actAddCardToSideboard())); - connect(&searchKeySignals, SIGNAL(onCtrlC()), this, SLOT(copyDatabaseCellContents())); - connect(help, &QAction::triggered, this, &TabDeckEditor::showSearchSyntaxHelp); - - databaseModel = new CardDatabaseModel(CardDatabaseManager::getInstance(), true, this); - databaseModel->setObjectName("databaseModel"); - databaseDisplayModel = new CardDatabaseDisplayModel(this); - databaseDisplayModel->setSourceModel(databaseModel); - databaseDisplayModel->setFilterKeyColumn(0); - - databaseView = new QTreeView(); - 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(), SIGNAL(currentRowChanged(const QModelIndex &, const QModelIndex &)), this, - SLOT(updateCardInfoLeft(const QModelIndex &, const QModelIndex &))); - connect(databaseView, SIGNAL(doubleClicked(const QModelIndex &)), this, SLOT(actAddCard())); - - 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, SIGNAL(triggered()), this, SLOT(actAddCard())); - auto *tbAddCard = new QToolButton(this); - tbAddCard->setDefaultAction(aAddCard); - - aAddCardToSideboard = new QAction(QString(), this); - aAddCardToSideboard->setIcon(QPixmap("theme:icons/arrow_right_blue")); - connect(aAddCardToSideboard, SIGNAL(triggered()), this, SLOT(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 = new QVBoxLayout; - centralFrame->setObjectName("centralFrame"); - centralFrame->addLayout(searchLayout); - centralFrame->addWidget(databaseView); - - centralWidget = new QWidget(this); - centralWidget->setObjectName("centralWidget"); - centralWidget->setLayout(centralFrame); - setCentralWidget(centralWidget); - setDockOptions(QMainWindow::AnimatedDocks | QMainWindow::AllowNestedDocks | QMainWindow::AllowTabbedDocks); -} - -void TabDeckEditor::databaseCustomMenu(QPoint point) -{ - QMenu menu; - const CardInfoPtr info = currentCardInfo(); - - // add to deck and sideboard options - QAction *addToDeck, *addToSideboard; - addToDeck = menu.addAction(tr("Add to Deck")); - addToSideboard = menu.addAction(tr("Add to Sideboard")); - connect(addToDeck, SIGNAL(triggered()), this, SLOT(actAddCard())); - connect(addToSideboard, SIGNAL(triggered()), this, SLOT(actAddCardToSideboard())); - - // 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, cardInfo, - [this, relatedCardName] { cardInfo->setCard(relatedCardName); }); - } - } - menu.exec(databaseView->mapToGlobal(point)); -} - -void TabDeckEditor::restartLayout() -{ - deckDock->setVisible(true); - cardInfoDock->setVisible(true); - filterDock->setVisible(true); - - deckDock->setFloating(false); - cardInfoDock->setFloating(false); - filterDock->setFloating(false); - - aCardInfoDockVisible->setChecked(true); - aDeckDockVisible->setChecked(true); - aFilterDockVisible->setChecked(true); - - aCardInfoDockFloating->setChecked(false); - aDeckDockFloating->setChecked(false); - aFilterDockFloating->setChecked(false); - - addDockWidget(static_cast(2), deckDock); - addDockWidget(static_cast(2), cardInfoDock); - addDockWidget(static_cast(2), filterDock); - - splitDockWidget(cardInfoDock, deckDock, Qt::Horizontal); - splitDockWidget(cardInfoDock, filterDock, Qt::Vertical); - - deckDock->setMinimumWidth(360); - deckDock->setMaximumWidth(360); - - cardInfoDock->setMinimumSize(250, 360); - cardInfoDock->setMaximumSize(250, 360); - QTimer::singleShot(100, this, SLOT(freeDocksSize())); -} - -void TabDeckEditor::freeDocksSize() -{ - deckDock->setMinimumSize(100, 100); - deckDock->setMaximumSize(5000, 5000); - - cardInfoDock->setMinimumSize(100, 100); - cardInfoDock->setMaximumSize(5000, 5000); - - filterDock->setMinimumSize(100, 100); - filterDock->setMaximumSize(5000, 5000); -} - -void TabDeckEditor::refreshShortcuts() -{ - ShortcutsSettings &shortcuts = SettingsCache::instance().shortcuts(); - aNewDeck->setShortcuts(shortcuts.getShortcut("TabDeckEditor/aNewDeck")); - aLoadDeck->setShortcuts(shortcuts.getShortcut("TabDeckEditor/aLoadDeck")); - aSaveDeck->setShortcuts(shortcuts.getShortcut("TabDeckEditor/aSaveDeck")); - aExportDeckDecklist->setShortcuts(shortcuts.getShortcut("TabDeckEditor/aExportDeckDecklist")); - aSaveDeckAs->setShortcuts(shortcuts.getShortcut("TabDeckEditor/aSaveDeckAs")); - aLoadDeckFromClipboard->setShortcuts(shortcuts.getShortcut("TabDeckEditor/aLoadDeckFromClipboard")); - aPrintDeck->setShortcuts(shortcuts.getShortcut("TabDeckEditor/aPrintDeck")); - aAnalyzeDeckDeckstats->setShortcuts(shortcuts.getShortcut("TabDeckEditor/aAnalyzeDeck")); - aClose->setShortcuts(shortcuts.getShortcut("TabDeckEditor/aClose")); - aResetLayout->setShortcuts(shortcuts.getShortcut("TabDeckEditor/aResetLayout")); - aClearFilterAll->setShortcuts(shortcuts.getShortcut("TabDeckEditor/aClearFilterAll")); - aClearFilterOne->setShortcuts(shortcuts.getShortcut("TabDeckEditor/aClearFilterOne")); - - aSaveDeckToClipboard->setShortcuts(shortcuts.getShortcut("TabDeckEditor/aSaveDeckToClipboard")); - aSaveDeckToClipboardRaw->setShortcuts(shortcuts.getShortcut("TabDeckEditor/aSaveDeckToClipboardRaw")); - - aClearFilterOne->setShortcuts(shortcuts.getShortcut("TabDeckEditor/aClearFilterOne")); - aClose->setShortcuts(shortcuts.getShortcut("TabDeckEditor/aClose")); - aRemoveCard->setShortcuts(shortcuts.getShortcut("TabDeckEditor/aRemoveCard")); - aIncrement->setShortcuts(shortcuts.getShortcut("TabDeckEditor/aIncrement")); - aDecrement->setShortcuts(shortcuts.getShortcut("TabDeckEditor/aDecrement")); -} - -void TabDeckEditor::loadLayout() -{ - LayoutsSettings &layouts = SettingsCache::instance().layouts(); - auto &layoutState = layouts.getDeckEditorLayoutState(); - if (layoutState.isNull()) { - restartLayout(); - } else { - restoreState(layoutState); - restoreGeometry(layouts.getDeckEditorGeometry()); - } - - aCardInfoDockVisible->setChecked(cardInfoDock->isVisible()); - aFilterDockVisible->setChecked(filterDock->isVisible()); - aDeckDockVisible->setChecked(deckDock->isVisible()); - - aCardInfoDockFloating->setEnabled(aCardInfoDockVisible->isChecked()); - aDeckDockFloating->setEnabled(aDeckDockVisible->isChecked()); - aFilterDockFloating->setEnabled(aFilterDockVisible->isChecked()); - - aCardInfoDockFloating->setChecked(cardInfoDock->isFloating()); - aFilterDockFloating->setChecked(filterDock->isFloating()); - aDeckDockFloating->setChecked(deckDock->isFloating()); - - cardInfoDock->setMinimumSize(layouts.getDeckEditorCardSize()); - cardInfoDock->setMaximumSize(layouts.getDeckEditorCardSize()); - - filterDock->setMinimumSize(layouts.getDeckEditorFilterSize()); - filterDock->setMaximumSize(layouts.getDeckEditorFilterSize()); - - deckDock->setMinimumSize(layouts.getDeckEditorDeckSize()); - deckDock->setMaximumSize(layouts.getDeckEditorDeckSize()); - - QTimer::singleShot(100, this, SLOT(freeDocksSize())); -} - -TabDeckEditor::TabDeckEditor(TabSupervisor *_tabSupervisor, QWidget *parent) - : Tab(_tabSupervisor, parent), modified(false) -{ - setObjectName("TabDeckEditor"); - - createMenus(); - - createCentralFrame(); - - createDeckDock(); - createCardInfoDock(); - createFiltersDock(); - - this->installEventFilter(this); - - retranslateUi(); - connect(&SettingsCache::instance().shortcuts(), SIGNAL(shortCutChanged()), this, SLOT(refreshShortcuts())); - refreshShortcuts(); - - loadLayout(); -} - -TabDeckEditor::~TabDeckEditor() -{ - emit deckEditorClosing(this); + QString result = tr("Deck: %1").arg(deckDockWidget->getSimpleDeckName()); + if (modified) + result.prepend("* "); + return result; } void TabDeckEditor::retranslateUi() { - cardInfo->retranslateUi(); + deckMenu->retranslateUi(); - aClearFilterAll->setText(tr("&Clear all filters")); - aClearFilterOne->setText(tr("Delete selected")); - - nameLabel->setText(tr("Deck &name:")); - commentsLabel->setText(tr("&Comments:")); - hashLabel1->setText(tr("Hash:")); - - aNewDeck->setText(tr("&New deck")); - aLoadDeck->setText(tr("&Load deck...")); - aSaveDeck->setText(tr("&Save deck")); - aSaveDeckAs->setText(tr("Save deck &as...")); - aLoadDeckFromClipboard->setText(tr("Load deck from cl&ipboard...")); - - saveDeckToClipboardMenu->setTitle(tr("Save deck to clipboard")); - aSaveDeckToClipboard->setText(tr("Annotated")); - aSaveDeckToClipboardRaw->setText(tr("Not Annotated")); - - aPrintDeck->setText(tr("&Print deck...")); - - analyzeDeckMenu->setTitle(tr("&Send deck to online service")); - aExportDeckDecklist->setText(tr("Create decklist (decklist.org)")); - aAnalyzeDeckDeckstats->setText(tr("Analyze deck (deckstats.net)")); - aAnalyzeDeckTappedout->setText(tr("Analyze deck (tappedout.net)")); - - aClose->setText(tr("&Close")); - - aAddCard->setText(tr("Add card to &maindeck")); - aAddCardToSideboard->setText(tr("Add card to &sideboard")); - - aRemoveCard->setText(tr("&Remove row")); - - aIncrement->setText(tr("&Increment number")); - - aDecrement->setText(tr("&Decrement number")); - - deckMenu->setTitle(tr("&Deck Editor")); - - cardInfoDock->setWindowTitle(tr("Card Info")); - deckDock->setWindowTitle(tr("Deck")); - filterDock->setWindowTitle(tr("Filters")); + cardInfoDockWidget->retranslateUi(); + deckDockWidget->retranslateUi(); + filterDockWidget->retranslateUi(); + printingSelectorDockWidget->retranslateUi(); viewMenu->setTitle(tr("&View")); cardInfoDockMenu->setTitle(tr("Card Info")); deckDockMenu->setTitle(tr("Deck")); filterDockMenu->setTitle(tr("Filters")); + printingSelectorDockMenu->setTitle(tr("Printing")); aCardInfoDockVisible->setText(tr("Visible")); aCardInfoDockFloating->setText(tr("Floating")); @@ -690,636 +136,175 @@ void TabDeckEditor::retranslateUi() aFilterDockVisible->setText(tr("Visible")); aFilterDockFloating->setText(tr("Floating")); + aPrintingSelectorDockVisible->setText(tr("Visible")); + aPrintingSelectorDockFloating->setText(tr("Floating")); + aResetLayout->setText(tr("Reset layout")); } -QString TabDeckEditor::getTabText() const +void TabDeckEditor::refreshShortcuts() { - QString result = tr("Deck: %1").arg(nameEdit->text().simplified()); - if (modified) - result.prepend("* "); - return result; + ShortcutsSettings &shortcuts = SettingsCache::instance().shortcuts(); + aResetLayout->setShortcuts(shortcuts.getShortcut("TabDeckEditor/aResetLayout")); } -void TabDeckEditor::updateName(const QString &name) +void TabDeckEditor::showPrintingSelector() { - deckModel->getDeckList()->setName(name); - setModified(true); - setSaveStatus(true); + printingSelectorDockWidget->printingSelector->setCard(cardInfoDockWidget->cardInfo->getInfo(), DECK_ZONE_MAIN); + printingSelectorDockWidget->printingSelector->updateDisplay(); + aPrintingSelectorDockVisible->setChecked(true); + printingSelectorDockWidget->setVisible(true); } -void TabDeckEditor::updateComments() +void TabDeckEditor::loadLayout() { - deckModel->getDeckList()->setComments(commentsEdit->toPlainText()); - setModified(true); - setSaveStatus(true); -} + LayoutsSettings &layouts = SettingsCache::instance().layouts(); -void TabDeckEditor::updateCardInfoLeft(const QModelIndex ¤t, const QModelIndex & /*previous*/) -{ - cardInfo->setCard(current.sibling(current.row(), 0).data().toString()); -} + setCentralWidget(databaseDisplayDockWidget); -void TabDeckEditor::updateCardInfoRight(const QModelIndex ¤t, const QModelIndex & /*previous*/) -{ - if (!current.isValid()) - return; - if (!current.model()->hasChildren(current.sibling(current.row(), 0))) { - cardInfo->setCard(current.sibling(current.row(), 1).data().toString(), - current.sibling(current.row(), 4).data().toString()); - } -} - -void TabDeckEditor::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 TabDeckEditor::updateHash() -{ - hashLabel->setText(deckModel->getDeckList()->getDeckHash()); -} - -bool TabDeckEditor::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 TabDeckEditor::closeRequest() -{ - if (confirmClose()) - deleteLater(); -} - -void TabDeckEditor::actNewDeck() -{ - auto deckOpenLocation = confirmOpen(false); - - if (deckOpenLocation == CANCELLED) { - return; - } - - if (deckOpenLocation == NEW_TAB) { - emit openDeckEditor(nullptr); - return; - } - - deckModel->cleanList(); - nameEdit->setText(QString()); - commentsEdit->setText(QString()); - hashLabel->setText(QString()); - setModified(false); - setSaveStatus(false); -} - -void TabDeckEditor::actLoadDeck() -{ - auto deckOpenLocation = confirmOpen(); - - if (deckOpenLocation == CANCELLED) { - return; - } - - QFileDialog dialog(this, tr("Load deck")); - dialog.setDirectory(SettingsCache::instance().getDeckPath()); - dialog.setNameFilters(DeckLoader::fileNameFilters); - if (!dialog.exec()) - return; - - QString fileName = dialog.selectedFiles().at(0); - DeckLoader::FileFormat fmt = DeckLoader::getFormatFromName(fileName); - - auto *l = new DeckLoader; - if (l->loadFromFile(fileName, fmt)) { - if (deckOpenLocation == NEW_TAB) { - emit openDeckEditor(l); - } else { - setSaveStatus(false); - setDeck(l); - } - } else - delete l; - setSaveStatus(true); -} - -void TabDeckEditor::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); -} - -bool TabDeckEditor::actSaveDeck() -{ - DeckLoader *const deck = deckModel->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(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 TabDeckEditor::actSaveDeckAs() -{ - QFileDialog dialog(this, tr("Save deck")); - dialog.setDirectory(SettingsCache::instance().getDeckPath()); - dialog.setAcceptMode(QFileDialog::AcceptSave); - dialog.setDefaultSuffix("cod"); - dialog.setNameFilters(DeckLoader::fileNameFilters); - dialog.selectFile(deckModel->getDeckList()->getName().trimmed() + ".cod"); - if (!dialog.exec()) - return false; - - QString fileName = dialog.selectedFiles().at(0); - DeckLoader::FileFormat fmt = DeckLoader::getFormatFromName(fileName); - - if (!deckModel->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); - return true; -} - -void TabDeckEditor::actLoadDeckFromClipboard() -{ - auto deckOpenLocation = confirmOpen(); - - if (deckOpenLocation == CANCELLED) { - return; - } - - DlgLoadDeckFromClipboard dlg(this); - if (!dlg.exec()) - return; - - if (deckOpenLocation == NEW_TAB) { - emit openDeckEditor(dlg.getDeckList()); + auto &layoutState = layouts.getDeckEditorLayoutState(); + if (layoutState.isNull()) { + restartLayout(); } else { - setDeck(dlg.getDeckList()); - setModified(true); + restoreState(layoutState); + restoreGeometry(layouts.getDeckEditorGeometry()); } - setSaveStatus(true); + aCardInfoDockVisible->setChecked(!cardInfoDockWidget->isHidden()); + aFilterDockVisible->setChecked(!filterDockWidget->isHidden()); + aDeckDockVisible->setChecked(!deckDockWidget->isHidden()); + aPrintingSelectorDockVisible->setChecked(!printingSelectorDockWidget->isHidden()); + + aCardInfoDockFloating->setEnabled(aCardInfoDockVisible->isChecked()); + aDeckDockFloating->setEnabled(aDeckDockVisible->isChecked()); + aFilterDockFloating->setEnabled(aFilterDockVisible->isChecked()); + aPrintingSelectorDockFloating->setEnabled(aPrintingSelectorDockVisible->isChecked()); + + aCardInfoDockFloating->setChecked(cardInfoDockWidget->isFloating()); + aFilterDockFloating->setChecked(filterDockWidget->isFloating()); + aDeckDockFloating->setChecked(deckDockWidget->isFloating()); + aPrintingSelectorDockFloating->setChecked(printingSelectorDockWidget->isFloating()); + + cardInfoDockWidget->setMinimumSize(layouts.getDeckEditorCardSize()); + cardInfoDockWidget->setMaximumSize(layouts.getDeckEditorCardSize()); + + filterDockWidget->setMinimumSize(layouts.getDeckEditorFilterSize()); + filterDockWidget->setMaximumSize(layouts.getDeckEditorFilterSize()); + + deckDockWidget->setMinimumSize(layouts.getDeckEditorDeckSize()); + deckDockWidget->setMaximumSize(layouts.getDeckEditorDeckSize()); + + printingSelectorDockWidget->setMinimumSize(layouts.getDeckEditorPrintingSelectorSize()); + printingSelectorDockWidget->setMaximumSize(layouts.getDeckEditorPrintingSelectorSize()); + + databaseDisplayDockWidget->setMinimumSize(100, 100); + databaseDisplayDockWidget->setMaximumSize(1400, 5000); + + QTimer::singleShot(100, this, SLOT(freeDocksSize())); } -void TabDeckEditor::actSaveDeckToClipboard() +void TabDeckEditor::restartLayout() { - QString buffer; - QTextStream stream(&buffer); - deckModel->getDeckList()->saveToStream_Plain(stream); - QApplication::clipboard()->setText(buffer, QClipboard::Clipboard); - QApplication::clipboard()->setText(buffer, QClipboard::Selection); -} + deckDockWidget->setVisible(true); + cardInfoDockWidget->setVisible(true); + filterDockWidget->setVisible(true); + printingSelectorDockWidget->setVisible(false); -void TabDeckEditor::actSaveDeckToClipboardRaw() -{ - QString buffer; - QTextStream stream(&buffer); - deckModel->getDeckList()->saveToStream_Plain(stream, false); - QApplication::clipboard()->setText(buffer, QClipboard::Clipboard); - QApplication::clipboard()->setText(buffer, QClipboard::Selection); -} + deckDockWidget->setFloating(false); + cardInfoDockWidget->setFloating(false); + filterDockWidget->setFloating(false); + printingSelectorDockWidget->setFloating(false); -void TabDeckEditor::actPrintDeck() -{ - auto *dlg = new QPrintPreviewDialog(this); - connect(dlg, SIGNAL(paintRequested(QPrinter *)), deckModel, SLOT(printDeckList(QPrinter *))); - dlg->exec(); -} - -// Action called when export deck to decklist menu item is pressed. -void TabDeckEditor::actExportDeckDecklist() -{ - // Get the decklist class for the deck. - DeckLoader *const deck = deckModel->getDeckList(); - // create a string to load the decklist url into. - QString decklistUrlString; - // check if deck is not null - if (deck) { - // Get the decklist url string from the deck loader class. - decklistUrlString = deck->exportDeckToDecklist(); - // 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 saved.")); - } -} - -void TabDeckEditor::actAnalyzeDeckDeckstats() -{ - auto *interface = new DeckStatsInterface(*databaseModel->getDatabase(), - this); // it deletes itself when done - interface->analyzeDeck(deckModel->getDeckList()); -} - -void TabDeckEditor::actAnalyzeDeckTappedout() -{ - auto *interface = new TappedOutInterface(*databaseModel->getDatabase(), - this); // it deletes itself when done - interface->analyzeDeck(deckModel->getDeckList()); -} - -void TabDeckEditor::actClearFilterAll() -{ - databaseDisplayModel->clearFilterAll(); - searchEdit->setText(""); -} - -void TabDeckEditor::actClearFilterOne() -{ - QModelIndexList selIndexes = filterView->selectionModel()->selectedIndexes(); - for (QModelIndex idx : selIndexes) { - filterModel->removeRow(idx.row(), idx.parent()); - } -} - -void TabDeckEditor::recursiveExpand(const QModelIndex &index) -{ - if (index.parent().isValid()) - recursiveExpand(index.parent()); - deckView->expand(index); -} - -/** - * @brief Displays the save confirmation dialogue that is shown before loading a deck, if required. Takes into - * account the `openDeckInNewTab` settting. - * - * @param openInSameTabIfBlank Open the deck in the same tab instead of a new tab if the current tab is completely - * blank. Only relevant when the `openDeckInNewTab` setting is enabled. - * - * @returns An enum that indicates if and where to load the deck - */ -TabDeckEditor::DeckOpenLocation TabDeckEditor::confirmOpen(const bool openInSameTabIfBlank) -{ - // handle `openDeckInNewTab` setting - if (SettingsCache::instance().getOpenDeckInNewTab()) { - if (openInSameTabIfBlank && isBlankNewDeck()) { - return SAME_TAB; - } else { - return NEW_TAB; - } - } - - // early return if deck is unmodified - if (!modified) { - return SAME_TAB; - } - - // do the save confirmation dialogue - tabSupervisor->setCurrentWidget(this); - - QMessageBox *msgBox = createSaveConfirmationWindow(); - QPushButton *newTabButton = msgBox->addButton(tr("Open in new tab"), QMessageBox::ApplyRole); - - int ret = msgBox->exec(); - - // `exec()` returns an opaque value if a non-standard button was clicked. - // Directly check if newTabButton was clicked before switching over the standard buttons. - if (msgBox->clickedButton() == newTabButton) { - return NEW_TAB; - } - - switch (ret) { - case QMessageBox::Save: - return actSaveDeck() ? SAME_TAB : CANCELLED; - case QMessageBox::Discard: - return SAME_TAB; - default: - return CANCELLED; - } -} - -/** - * @brief Creates the base save confirmation dialogue box. - * - * @returns A QMessageBox that can be further modified - */ -QMessageBox *TabDeckEditor::createSaveConfirmationWindow() -{ - QMessageBox *msgBox = new QMessageBox(this); - msgBox->setIcon(QMessageBox::Warning); - msgBox->setWindowTitle(tr("Are you sure?")); - msgBox->setText(tr("The decklist has been modified.\nDo you want to save the changes?")); - msgBox->setStandardButtons(QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel); - return msgBox; -} - -/** - * @brief Returns true if this tab is a blank newly opened tab, as if it was just created with the `New Deck` action. - */ -bool TabDeckEditor::isBlankNewDeck() const -{ - DeckLoader *const deck = deckModel->getDeckList(); - return !modified && deck->getLastFileName().isEmpty() && deck->getLastRemoteDeckId() == -1; -} - -CardInfoPtr TabDeckEditor::currentCardInfo() const -{ - const QModelIndex currentIndex = databaseView->selectionModel()->currentIndex(); - if (!currentIndex.isValid()) { - return {}; - } - - const QString cardName = currentIndex.sibling(currentIndex.row(), 0).data().toString(); - - return CardDatabaseManager::getInstance()->getCard(cardName); -} - -void TabDeckEditor::addCardHelper(QString zoneName) -{ - const CardInfoPtr info = currentCardInfo(); - if (!info) - return; - if (info->getIsToken()) - zoneName = DECK_ZONE_TOKENS; - - QModelIndex newCardIndex = deckModel->addPreferredPrintingCard(info->getName(), zoneName, false); - recursiveExpand(newCardIndex); - deckView->setCurrentIndex(newCardIndex); - setModified(true); - searchEdit->setSelection(0, searchEdit->text().length()); -} - -void TabDeckEditor::actSwapCard() -{ - const QModelIndex currentIndex = deckView->selectionModel()->currentIndex(); - if (!currentIndex.isValid()) - return; - const QString cardName = currentIndex.sibling(currentIndex.row(), 1).data().toString(); - const QString cardProviderID = currentIndex.sibling(currentIndex.row(), 2).data().toString(); - const QModelIndex gparent = currentIndex.parent().parent(); - - if (!gparent.isValid()) - return; - - const QString zoneName = gparent.sibling(gparent.row(), 1).data(Qt::EditRole).toString(); - actDecrement(); - 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); - - setModified(true); - setSaveStatus(true); -} - -void TabDeckEditor::actAddCard() -{ - if (QApplication::keyboardModifiers() & Qt::ControlModifier) - actAddCardToSideboard(); - else - addCardHelper(DECK_ZONE_MAIN); - setSaveStatus(true); -} - -void TabDeckEditor::actAddCardToSideboard() -{ - addCardHelper(DECK_ZONE_SIDE); - setSaveStatus(true); -} - -void TabDeckEditor::actRemoveCard() -{ - const QModelIndex ¤tIndex = deckView->selectionModel()->currentIndex(); - if (!currentIndex.isValid() || deckModel->hasChildren(currentIndex)) - return; - deckModel->removeRow(currentIndex.row(), currentIndex.parent()); - - DeckLoader *const deck = deckModel->getDeckList(); - setSaveStatus(!deck->isEmpty()); - setModified(true); -} - -void TabDeckEditor::offsetCountAtIndex(const QModelIndex &idx, int offset) -{ - if (!idx.isValid() || offset == 0) - return; - - const QModelIndex numberIndex = idx.sibling(idx.row(), 0); - const int count = deckModel->data(numberIndex, Qt::EditRole).toInt(); - const int new_count = count + offset; - deckView->setCurrentIndex(numberIndex); - if (new_count <= 0) - deckModel->removeRow(idx.row(), idx.parent()); - else - deckModel->setData(numberIndex, new_count, Qt::EditRole); - setModified(true); -} - -void TabDeckEditor::decrementCardHelper(QString zoneName) -{ - const CardInfoPtr info = currentCardInfo(); - QModelIndex idx; - - if (!info) - return; - if (info->getIsToken()) - zoneName = DECK_ZONE_TOKENS; - - idx = deckModel->findCard(info->getName(), zoneName); - offsetCountAtIndex(idx, -1); -} - -void TabDeckEditor::actDecrementCard() -{ - decrementCardHelper(DECK_ZONE_MAIN); -} - -void TabDeckEditor::actDecrementCardFromSideboard() -{ - decrementCardHelper(DECK_ZONE_SIDE); -} - -void TabDeckEditor::copyDatabaseCellContents() -{ - auto _data = databaseView->selectionModel()->currentIndex().data(); - QApplication::clipboard()->setText(_data.toString()); -} - -void TabDeckEditor::actIncrement() -{ - const QModelIndex ¤tIndex = deckView->selectionModel()->currentIndex(); - offsetCountAtIndex(currentIndex, 1); -} - -void TabDeckEditor::actDecrement() -{ - const QModelIndex ¤tIndex = deckView->selectionModel()->currentIndex(); - offsetCountAtIndex(currentIndex, -1); -} - -void TabDeckEditor::setDeck(DeckLoader *_deck) -{ - deckModel->setDeckList(_deck); - - nameEdit->setText(deckModel->getDeckList()->getName()); - commentsEdit->setText(deckModel->getDeckList()->getComments()); - updateHash(); - deckModel->sort(deckView->header()->sortIndicatorSection(), deckView->header()->sortIndicatorOrder()); - deckView->expandAll(); - setModified(false); - - PictureLoader::cacheCardPixmaps( - CardDatabaseManager::getInstance()->getCards(deckModel->getDeckList()->getCardList())); - deckView->expandAll(); - setModified(false); - - // If they load a deck, make the deck list appear + aCardInfoDockVisible->setChecked(true); aDeckDockVisible->setChecked(true); - deckDock->setVisible(aDeckDockVisible->isChecked()); + aFilterDockVisible->setChecked(true); + aPrintingSelectorDockVisible->setChecked(false); + + aCardInfoDockFloating->setChecked(false); + aDeckDockFloating->setChecked(false); + aFilterDockFloating->setChecked(false); + aPrintingSelectorDockFloating->setChecked(false); + + setCentralWidget(databaseDisplayDockWidget); + addDockWidget(static_cast(2), deckDockWidget); + addDockWidget(static_cast(2), cardInfoDockWidget); + addDockWidget(static_cast(2), filterDockWidget); + addDockWidget(static_cast(2), printingSelectorDockWidget); + + splitDockWidget(cardInfoDockWidget, printingSelectorDockWidget, Qt::Horizontal); + splitDockWidget(printingSelectorDockWidget, deckDockWidget, Qt::Horizontal); + splitDockWidget(cardInfoDockWidget, printingSelectorDockWidget, Qt::Horizontal); + splitDockWidget(cardInfoDockWidget, filterDockWidget, Qt::Vertical); + + QTimer::singleShot(100, this, SLOT(freeDocksSize())); } -void TabDeckEditor::setModified(bool _modified) +void TabDeckEditor::freeDocksSize() { - modified = _modified; - emit tabTextChanged(this, getTabText()); -} + deckDockWidget->setMinimumSize(100, 100); + deckDockWidget->setMaximumSize(5000, 5000); -void TabDeckEditor::filterViewCustomContextMenu(const QPoint &point) -{ - QMenu menu; - QAction *action; - QModelIndex idx; + cardInfoDockWidget->setMinimumSize(100, 100); + cardInfoDockWidget->setMaximumSize(5000, 5000); - idx = filterView->indexAt(point); - if (!idx.isValid()) - return; + filterDockWidget->setMinimumSize(100, 100); + filterDockWidget->setMaximumSize(5000, 5000); - action = menu.addAction(QString("delete")); - action->setData(point); - connect(&menu, SIGNAL(triggered(QAction *)), this, SLOT(filterRemove(QAction *))); - menu.exec(filterView->mapToGlobal(point)); -} + printingSelectorDockWidget->setMinimumSize(525, 100); + printingSelectorDockWidget->setMaximumSize(5000, 5000); -void TabDeckEditor::filterRemove(QAction *action) -{ - QPoint point; - QModelIndex idx; - - point = action->data().toPoint(); - idx = filterView->indexAt(point); - if (!idx.isValid()) - return; - - filterModel->removeRow(idx.row(), idx.parent()); -} - -// Method uses to sync docks state with menu items state -bool TabDeckEditor::eventFilter(QObject *o, QEvent *e) -{ - if (e->type() == QEvent::Close) { - if (o == cardInfoDock) { - aCardInfoDockVisible->setChecked(false); - aCardInfoDockFloating->setEnabled(false); - } else if (o == deckDock) { - aDeckDockVisible->setChecked(false); - aDeckDockFloating->setEnabled(false); - } else if (o == filterDock) { - aFilterDockVisible->setChecked(false); - aFilterDockFloating->setEnabled(false); - } - } - if (o == this && e->type() == QEvent::Hide) { - LayoutsSettings &layouts = SettingsCache::instance().layouts(); - layouts.setDeckEditorLayoutState(saveState()); - layouts.setDeckEditorGeometry(saveGeometry()); - layouts.setDeckEditorCardSize(cardInfoDock->size()); - layouts.setDeckEditorFilterSize(filterDock->size()); - layouts.setDeckEditorDeckSize(deckDock->size()); - } - return false; + databaseDisplayDockWidget->setMinimumSize(100, 100); + databaseDisplayDockWidget->setMaximumSize(1400, 5000); } void TabDeckEditor::dockVisibleTriggered() { QObject *o = sender(); if (o == aCardInfoDockVisible) { - cardInfoDock->setVisible(aCardInfoDockVisible->isChecked()); + cardInfoDockWidget->setHidden(!aCardInfoDockVisible->isChecked()); aCardInfoDockFloating->setEnabled(aCardInfoDockVisible->isChecked()); return; } if (o == aDeckDockVisible) { - deckDock->setVisible(aDeckDockVisible->isChecked()); + deckDockWidget->setHidden(!aDeckDockVisible->isChecked()); aDeckDockFloating->setEnabled(aDeckDockVisible->isChecked()); return; } if (o == aFilterDockVisible) { - filterDock->setVisible(aFilterDockVisible->isChecked()); + filterDockWidget->setHidden(!aFilterDockVisible->isChecked()); aFilterDockFloating->setEnabled(aFilterDockVisible->isChecked()); return; } + + if (o == aPrintingSelectorDockVisible) { + printingSelectorDockWidget->setHidden(!aPrintingSelectorDockVisible->isChecked()); + aPrintingSelectorDockFloating->setEnabled(aPrintingSelectorDockVisible->isChecked()); + return; + } } void TabDeckEditor::dockFloatingTriggered() { QObject *o = sender(); if (o == aCardInfoDockFloating) { - cardInfoDock->setFloating(aCardInfoDockFloating->isChecked()); + cardInfoDockWidget->setFloating(aCardInfoDockFloating->isChecked()); return; } if (o == aDeckDockFloating) { - deckDock->setFloating(aDeckDockFloating->isChecked()); + deckDockWidget->setFloating(aDeckDockFloating->isChecked()); return; } if (o == aFilterDockFloating) { - filterDock->setFloating(aFilterDockFloating->isChecked()); + filterDockWidget->setFloating(aFilterDockFloating->isChecked()); + return; + } + + if (o == aPrintingSelectorDockFloating) { + printingSelectorDockWidget->setFloating(aPrintingSelectorDockFloating->isChecked()); return; } } @@ -1327,70 +312,53 @@ void TabDeckEditor::dockFloatingTriggered() void TabDeckEditor::dockTopLevelChanged(bool topLevel) { QObject *o = sender(); - if (o == cardInfoDock) { + if (o == cardInfoDockWidget) { aCardInfoDockFloating->setChecked(topLevel); return; } - if (o == deckDock) { + if (o == deckDockWidget) { aDeckDockFloating->setChecked(topLevel); return; } - if (o == filterDock) { + if (o == filterDockWidget) { aFilterDockFloating->setChecked(topLevel); return; } -} -void TabDeckEditor::saveDbHeaderState() -{ - SettingsCache::instance().layouts().setDeckEditorDbHeaderState(databaseView->header()->saveState()); -} - -void TabDeckEditor::setSaveStatus(bool newStatus) -{ - aSaveDeck->setEnabled(newStatus); - aSaveDeckAs->setEnabled(newStatus); - aSaveDeckToClipboard->setEnabled(newStatus); - aSaveDeckToClipboardRaw->setEnabled(newStatus); - saveDeckToClipboardMenu->setEnabled(newStatus); - aPrintDeck->setEnabled(newStatus); - analyzeDeckMenu->setEnabled(newStatus); -} - -void TabDeckEditor::showSearchSyntaxHelp() -{ - - QFile file("theme:help/search.md"); - - if (!file.open(QFile::ReadOnly | QFile::Text)) { + if (o == printingSelectorDockWidget) { + aPrintingSelectorDockFloating->setChecked(topLevel); return; } - - QTextStream in(&file); - QString text = in.readAll(); - file.close(); - - // Poor Markdown Converter - auto opts = QRegularExpression::MultilineOption; - text = text.replace(QRegularExpression("^(###)(.*)", opts), "

\\2

") - .replace(QRegularExpression("^(##)(.*)", opts), "

\\2

") - .replace(QRegularExpression("^(#)(.*)", opts), "

\\2

") - .replace(QRegularExpression("^------*", opts), "
") - .replace(QRegularExpression(R"(\[([^[]+)\]\(([^\)]+)\))", opts), R"(\1)"); - - 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, [=](const QUrl &link) { searchEdit->setText(link.fragment()); }); - browser->show(); } + +// Method uses to sync docks state with menu items state +bool TabDeckEditor::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; +} \ No newline at end of file diff --git a/cockatrice/src/client/tabs/tab_deck_editor.h b/cockatrice/src/client/tabs/tab_deck_editor.h index 6aff1cc31..d9b10de7d 100644 --- a/cockatrice/src/client/tabs/tab_deck_editor.h +++ b/cockatrice/src/client/tabs/tab_deck_editor.h @@ -1,186 +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 "tab.h" - -#include -#include +#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 QGroupBox; -class QMessageBox; -class QHBoxLayout; -class QVBoxLayout; -class QPushButton; -class QDockWidget; -class SearchLineEdit : public LineEditUnfocusable -{ -private: - QTreeView *treeView; - -protected: - void keyPressEvent(QKeyEvent *event) override; - -public: - SearchLineEdit() : LineEditUnfocusable(), treeView(nullptr) - { - } - void setTreeView(QTreeView *_treeView) - { - treeView = _treeView; - } -}; - -class TabDeckEditor : public Tab +class TabDeckEditor : public AbstractTabDeckEditor { Q_OBJECT -private slots: - void updateName(const QString &name); - void updateComments(); - void updateHash(); - void updateCardInfoLeft(const QModelIndex ¤t, const QModelIndex &previous); - void updateCardInfoRight(const QModelIndex ¤t, const QModelIndex &previous); - void updateSearch(const QString &search); - void databaseCustomMenu(QPoint point); - void actNewDeck(); - void actLoadDeck(); - bool actSaveDeck(); - bool actSaveDeckAs(); - void actLoadDeckFromClipboard(); - void actSaveDeckToClipboard(); - void actSaveDeckToClipboardRaw(); - 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 addCardHelper(QString zoneName); - void offsetCountAtIndex(const QModelIndex &idx, int offset); - void decrementCardHelper(QString zoneName); - void recursiveExpand(const QModelIndex &index); - - CardDatabaseModel *databaseModel; - CardDatabaseDisplayModel *databaseDisplayModel; - DeckListModel *deckModel; - QTreeView *databaseView; - - QTreeView *deckView; - KeySignals deckViewKeySignals; - CardInfoFrameWidget *cardInfo; - SearchLineEdit *searchEdit; - KeySignals searchKeySignals; - - QLabel *nameLabel; - LineEditUnfocusable *nameEdit; - QLabel *commentsLabel; - QTextEdit *commentsEdit; - QLabel *hashLabel1; - LineEditUnfocusable *hashLabel; - FilterTreeModel *filterModel; - QTreeView *filterView; - KeySignals filterViewKeySignals; - QWidget *filterBox; - - QMenu *deckMenu, *viewMenu, *cardInfoDockMenu, *deckDockMenu, *filterDockMenu, *analyzeDeckMenu, - *saveDeckToClipboardMenu; - QAction *aNewDeck, *aLoadDeck, *aSaveDeck, *aSaveDeckAs, *aLoadDeckFromClipboard, *aSaveDeckToClipboard, - *aSaveDeckToClipboardRaw, *aPrintDeck, *aExportDeckDecklist, *aAnalyzeDeckDeckstats, *aAnalyzeDeckTappedout, - *aClose; - QAction *aClearFilterAll, *aClearFilterOne; - QAction *aAddCard, *aAddCardToSideboard, *aRemoveCard, *aIncrement, *aDecrement; - QAction *aResetLayout; - QAction *aCardInfoDockVisible, *aCardInfoDockFloating, *aDeckDockVisible, *aDeckDockFloating, *aFilterDockVisible, - *aFilterDockFloating; - - bool modified; - QVBoxLayout *centralFrame; - QHBoxLayout *searchLayout; - QDockWidget *cardInfoDock; - QDockWidget *deckDock; - QDockWidget *filterDock; - QWidget *centralWidget; + void dockVisibleTriggered() override; + void dockFloatingTriggered() override; + void dockTopLevelChanged(bool topLevel) override; public: - explicit TabDeckEditor(TabSupervisor *_tabSupervisor, QWidget *parent = nullptr); - ~TabDeckEditor() override; + 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 createMenus(); - void createCentralFrame(); + void createMenus() override; public slots: - void closeRequest() override; -signals: - void openDeckEditor(const DeckLoader *deckLoader); - void deckEditorClosing(TabDeckEditor *tab); + void showPrintingSelector() override; }; #endif diff --git a/cockatrice/src/client/tabs/tab_deck_storage.cpp b/cockatrice/src/client/tabs/tab_deck_storage.cpp index 8ab91e2be..f696f607c 100644 --- a/cockatrice/src/client/tabs/tab_deck_storage.cpp +++ b/cockatrice/src/client/tabs/tab_deck_storage.cpp @@ -4,7 +4,6 @@ #include "../../server/pending_command.h" #include "../../server/remote/remote_decklist_tree_widget.h" #include "../../settings/cache_settings.h" -#include "../game_logic/abstract_client.h" #include "../get_text_with_max.h" #include "decklist.h" #include "pb/command_deck_del.pb.h" @@ -19,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -27,9 +27,12 @@ #include #include #include +#include #include -TabDeckStorage::TabDeckStorage(TabSupervisor *_tabSupervisor, AbstractClient *_client) +TabDeckStorage::TabDeckStorage(TabSupervisor *_tabSupervisor, + AbstractClient *_client, + const ServerInfo_User *currentUserInfo) : Tab(_tabSupervisor), client(_client) { localDirModel = new QFileSystemModel(this); @@ -41,16 +44,33 @@ TabDeckStorage::TabDeckStorage(TabSupervisor *_tabSupervisor, AbstractClient *_c localDirView->setColumnHidden(1, true); localDirView->setRootIndex(localDirModel->index(localDirModel->rootPath(), 0)); localDirView->setSortingEnabled(true); + localDirView->setSelectionMode(QAbstractItemView::ExtendedSelection); localDirView->header()->setSectionResizeMode(QHeaderView::ResizeToContents); localDirView->header()->setSortIndicator(0, Qt::AscendingOrder); - leftToolBar = new QToolBar; + connect(localDirView, &QTreeView::doubleClicked, this, &TabDeckStorage::actLocalDoubleClick); + + // Left side layout + /* put an invisible dummy QToolBar in the leftmost column so that the main toolbar is centered. + * Really ugly workaround, but I couldn't figure out the proper way to make it centered */ + QToolBar *dummyToolBar = new QToolBar(this); + QSizePolicy sizePolicy = dummyToolBar->sizePolicy(); + sizePolicy.setRetainSizeWhenHidden(true); + dummyToolBar->setSizePolicy(sizePolicy); + dummyToolBar->setVisible(false); + + leftToolBar = new QToolBar(this); leftToolBar->setOrientation(Qt::Horizontal); leftToolBar->setIconSize(QSize(32, 32)); - QHBoxLayout *leftToolBarLayout = new QHBoxLayout; - leftToolBarLayout->addStretch(); - leftToolBarLayout->addWidget(leftToolBar); - leftToolBarLayout->addStretch(); + + QToolBar *leftRightmostToolBar = new QToolBar(this); + leftRightmostToolBar->setOrientation(Qt::Horizontal); + leftRightmostToolBar->setIconSize(QSize(32, 32)); + + QGridLayout *leftToolBarLayout = new QGridLayout; + leftToolBarLayout->addWidget(dummyToolBar, 0, 0, Qt::AlignLeft); + leftToolBarLayout->addWidget(leftToolBar, 0, 1, Qt::AlignHCenter); + leftToolBarLayout->addWidget(leftRightmostToolBar, 0, 2, Qt::AlignRight); QVBoxLayout *leftVbox = new QVBoxLayout; leftVbox->addWidget(localDirView); @@ -58,6 +78,7 @@ TabDeckStorage::TabDeckStorage(TabSupervisor *_tabSupervisor, AbstractClient *_c leftGroupBox = new QGroupBox; leftGroupBox->setLayout(leftVbox); + // Right side layout rightToolBar = new QToolBar; rightToolBar->setOrientation(Qt::Horizontal); rightToolBar->setIconSize(QSize(32, 32)); @@ -68,25 +89,41 @@ TabDeckStorage::TabDeckStorage(TabSupervisor *_tabSupervisor, AbstractClient *_c serverDirView = new RemoteDeckList_TreeWidget(client); + connect(serverDirView, &QTreeView::doubleClicked, this, &TabDeckStorage::actRemoteDoubleClick); + QVBoxLayout *rightVbox = new QVBoxLayout; rightVbox->addWidget(serverDirView); rightVbox->addLayout(rightToolBarLayout); rightGroupBox = new QGroupBox; rightGroupBox->setLayout(rightVbox); + // combine layouts QHBoxLayout *hbox = new QHBoxLayout; hbox->addWidget(leftGroupBox); hbox->addWidget(rightGroupBox); + // Left side actions aOpenLocalDeck = new QAction(this); aOpenLocalDeck->setIcon(QPixmap("theme:icons/pencil")); connect(aOpenLocalDeck, SIGNAL(triggered()), this, SLOT(actOpenLocalDeck())); + 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())); + aNewLocalFolder = new QAction(this); + aNewLocalFolder->setIcon(qApp->style()->standardIcon(QStyle::SP_FileDialogNewFolder)); + connect(aNewLocalFolder, &QAction::triggered, this, &TabDeckStorage::actNewLocalFolder); aDeleteLocalDeck = new QAction(this); aDeleteLocalDeck->setIcon(QPixmap("theme:icons/remove_row")); connect(aDeleteLocalDeck, SIGNAL(triggered()), this, SLOT(actDeleteLocalDeck())); + + aOpenDecksFolder = new QAction(this); + aOpenDecksFolder->setIcon(qApp->style()->standardIcon(QStyle::SP_DirOpenIcon)); + connect(aOpenDecksFolder, &QAction::triggered, this, &TabDeckStorage::actOpenDecksFolder); + + // Right side actions aOpenRemoteDeck = new QAction(this); aOpenRemoteDeck->setIcon(QPixmap("theme:icons/pencil")); connect(aOpenRemoteDeck, SIGNAL(triggered()), this, SLOT(actOpenRemoteDeck())); @@ -100,9 +137,15 @@ TabDeckStorage::TabDeckStorage(TabSupervisor *_tabSupervisor, AbstractClient *_c aDeleteRemoteDeck->setIcon(QPixmap("theme:icons/remove_row")); connect(aDeleteRemoteDeck, SIGNAL(triggered()), this, SLOT(actDeleteRemoteDeck())); + // Add actions to toolbars leftToolBar->addAction(aOpenLocalDeck); + leftToolBar->addAction(aRenameLocal); leftToolBar->addAction(aUpload); + leftToolBar->addAction(aNewLocalFolder); leftToolBar->addAction(aDeleteLocalDeck); + + leftRightmostToolBar->addAction(aOpenDecksFolder); + rightToolBar->addAction(aOpenRemoteDeck); rightToolBar->addAction(aDownload); rightToolBar->addAction(aNewFolder); @@ -113,6 +156,10 @@ TabDeckStorage::TabDeckStorage(TabSupervisor *_tabSupervisor, AbstractClient *_c QWidget *mainWidget = new QWidget(this); mainWidget->setLayout(hbox); setCentralWidget(mainWidget); + + connect(client, &AbstractClient::userInfoChanged, this, &TabDeckStorage::handleConnected); + connect(client, &AbstractClient::statusChanged, this, &TabDeckStorage::handleConnectionChanged); + setRemoteEnabled(currentUserInfo && currentUserInfo->user_level() & ServerInfo_User::IsRegistered); } void TabDeckStorage::retranslateUi() @@ -121,12 +168,15 @@ 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")); + aNewLocalFolder->setText(tr("New folder")); aNewFolder->setText(tr("New folder")); aDeleteLocalDeck->setText(tr("Delete")); aDeleteRemoteDeck->setText(tr("Delete")); + aOpenDecksFolder->setText(tr("Open decks folder")); } QString TabDeckStorage::getTargetPath() const @@ -147,43 +197,119 @@ QString TabDeckStorage::getTargetPath() const } } +void TabDeckStorage::handleConnected(const ServerInfo_User &userInfo) +{ + setRemoteEnabled(userInfo.user_level() & ServerInfo_User::IsRegistered); +} + +/** + * This is only responsible for handling the disconnect. The connect is already handled elsewhere + */ +void TabDeckStorage::handleConnectionChanged(ClientStatus status) +{ + if (status == StatusDisconnected) { + setRemoteEnabled(false); + } +} + +void TabDeckStorage::setRemoteEnabled(bool enabled) +{ + aUpload->setEnabled(enabled); + aOpenRemoteDeck->setEnabled(enabled); + aDownload->setEnabled(enabled); + aNewFolder->setEnabled(enabled); + aDeleteRemoteDeck->setEnabled(enabled); + + if (enabled) { + serverDirView->refreshTree(); + } else { + serverDirView->clearTree(); + } +} + +void TabDeckStorage::actLocalDoubleClick(const QModelIndex &curLeft) +{ + if (!localDirModel->isDir(curLeft)) { + actOpenLocalDeck(); + } +} + void TabDeckStorage::actOpenLocalDeck() { - QModelIndex curLeft = localDirView->selectionModel()->currentIndex(); - if (localDirModel->isDir(curLeft)) - return; - QString filePath = localDirModel->filePath(curLeft); + QModelIndexList curLefts = localDirView->selectionModel()->selectedRows(); + for (const auto &curLeft : curLefts) { + if (localDirModel->isDir(curLeft)) + continue; + QString filePath = localDirModel->filePath(curLeft); - DeckLoader deckLoader; - if (!deckLoader.loadFromFile(filePath, DeckLoader::CockatriceFormat)) - return; + DeckLoader deckLoader; + if (!deckLoader.loadFromFile(filePath, DeckLoader::CockatriceFormat, true)) + continue; - emit openDeckEditor(&deckLoader); + 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() { - QModelIndex curLeft = localDirView->selectionModel()->currentIndex(); - if (localDirModel->isDir(curLeft)) + QModelIndexList curLefts = localDirView->selectionModel()->selectedRows(); + if (curLefts.isEmpty()) { return; + } + QString targetPath = getTargetPath(); if (targetPath.length() > MAX_NAME_LENGTH) { qCritical() << "target path to upload to is too long" << targetPath; return; } - QString filePath = localDirModel->filePath(curLeft); + for (const auto &curLeft : curLefts) { + if (localDirModel->isDir(curLeft)) { + continue; + } + + QString filePath = localDirModel->filePath(curLeft); + uploadDeck(filePath, targetPath); + } +} + +void TabDeckStorage::uploadDeck(const QString &filePath, const QString &targetPath) +{ QFile deckFile(filePath); QFileInfo deckFileInfo(deckFile); - QString deckString; DeckLoader deck; - bool error = !deck.loadFromFile(filePath, DeckLoader::CockatriceFormat); - if (!error) { - deckString = deck.writeToString_Native(); - error = deckString.length() > MAX_FILE_LENGTH; - } - if (error) { + if (!deck.loadFromFile(filePath, DeckLoader::CockatriceFormat)) { QMessageBox::critical(this, tr("Error"), tr("Invalid deck file")); return; } @@ -202,6 +328,12 @@ void TabDeckStorage::actUpload() deck.setName(deck.getName().left(MAX_NAME_LENGTH)); } + QString deckString = deck.writeToString_Native(); + if (deckString.length() > MAX_FILE_LENGTH) { + QMessageBox::critical(this, tr("Error"), tr("Invalid deck file")); + return; + } + Command_DeckUpload cmd; cmd.set_path(targetPath.toStdString()); cmd.set_deck_list(deckString.toStdString()); @@ -226,34 +358,73 @@ void TabDeckStorage::uploadFinished(const Response &r, const CommandContainer &c serverDirView->addFileToTree(resp.new_file(), serverDirView->getNodeByPath(QString::fromStdString(cmd.path()))); } -void TabDeckStorage::actDeleteLocalDeck() +void TabDeckStorage::actNewLocalFolder() { QModelIndex curLeft = localDirView->selectionModel()->currentIndex(); - if (localDirModel->isDir(curLeft)) + + QModelIndex dirIndex; + if (curLeft.isValid() && !localDirModel->isDir(curLeft)) { + dirIndex = curLeft.parent(); + } else { + dirIndex = curLeft; + } + + bool ok; + QString folderName = + QInputDialog::getText(this, tr("New folder"), tr("Name of new folder:"), QLineEdit::Normal, "", &ok); + if (!ok || folderName.isEmpty()) return; - if (QMessageBox::warning(this, tr("Delete local file"), - tr("Are you sure you want to delete \"%1\"?").arg(localDirModel->fileName(curLeft)), + localDirModel->mkdir(dirIndex, folderName); +} + +void TabDeckStorage::actDeleteLocalDeck() +{ + const QModelIndexList curLefts = localDirView->selectionModel()->selectedRows(); + + if (curLefts.isEmpty()) { + return; + } + + if (QMessageBox::warning(this, tr("Delete local file"), tr("Are you sure you want to delete the selected files?"), QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes) return; - localDirModel->remove(curLeft); + for (const auto &curLeft : curLefts) { + if (curLeft.isValid()) { + localDirModel->remove(curLeft); + } + } +} + +void TabDeckStorage::actOpenDecksFolder() +{ + QString dir = localDirModel->rootPath(); + QDesktopServices::openUrl(QUrl::fromLocalFile(dir)); +} + +void TabDeckStorage::actRemoteDoubleClick(const QModelIndex &curRight) +{ + if (dynamic_cast(serverDirView->getNode(curRight))) { + actOpenRemoteDeck(); + } } void TabDeckStorage::actOpenRemoteDeck() { - RemoteDeckList_TreeModel::FileNode *curRight = - dynamic_cast(serverDirView->getCurrentItem()); - if (!curRight) - return; + for (const auto &curRight : serverDirView->getCurrentSelection()) { + RemoteDeckList_TreeModel::FileNode *node = dynamic_cast(curRight); + if (!node) + continue; - Command_DeckDownload cmd; - cmd.set_deck_id(curRight->getId()); + Command_DeckDownload cmd; + cmd.set_deck_id(node->getId()); - PendingCommand *pend = client->prepareSessionCommand(cmd); - connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this, - SLOT(openRemoteDeckFinished(Response, CommandContainer))); - client->sendCommand(pend); + PendingCommand *pend = client->prepareSessionCommand(cmd); + connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this, + SLOT(openRemoteDeckFinished(Response, CommandContainer))); + client->sendCommand(pend); + } } void TabDeckStorage::openRemoteDeckFinished(const Response &r, const CommandContainer &commandContainer) @@ -273,30 +444,46 @@ void TabDeckStorage::openRemoteDeckFinished(const Response &r, const CommandCont void TabDeckStorage::actDownload() { - QString filePath; QModelIndex curLeft = localDirView->selectionModel()->currentIndex(); - if (!curLeft.isValid()) - filePath = localDirModel->rootPath(); - else { - while (!localDirModel->isDir(curLeft)) - curLeft = curLeft.parent(); - filePath = localDirModel->filePath(curLeft); + while (!localDirModel->isDir(curLeft)) { + curLeft = curLeft.parent(); } - RemoteDeckList_TreeModel::FileNode *curRight = - dynamic_cast(serverDirView->getCurrentItem()); - if (!curRight) - return; - filePath += QString("/deck_%1.cod").arg(curRight->getId()); + for (const auto curRight : serverDirView->selectionModel()->selectedRows()) { + downloadNodeAtIndex(curLeft, curRight); + } +} - Command_DeckDownload cmd; - cmd.set_deck_id(curRight->getId()); +void TabDeckStorage::downloadNodeAtIndex(const QModelIndex &curLeft, const QModelIndex &curRight) +{ + auto node = serverDirView->getNode(curRight); + if (const auto dirNode = dynamic_cast(node)) { + // node at index is a folder + const QString name = dirNode->getName(); - PendingCommand *pend = client->prepareSessionCommand(cmd); - pend->setExtraData(filePath); - connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this, - SLOT(downloadFinished(Response, CommandContainer, QVariant))); - client->sendCommand(pend); + const auto dirIndex = curLeft.isValid() ? curLeft : localDirModel->index(localDirModel->rootPath()); + const auto newDirIndex = localDirModel->mkdir(dirIndex, name); + + int rows = serverDirView->model()->rowCount(curRight); + for (int i = 0; i < rows; i++) { + const auto childIndex = serverDirView->model()->index(i, 0, curRight); + downloadNodeAtIndex(newDirIndex, childIndex); + } + } else if (const auto fileNode = dynamic_cast(node)) { + // node at index is a deck + const QString dirPath = curLeft.isValid() ? localDirModel->filePath(curLeft) : localDirModel->rootPath(); + const QString filePath = dirPath + QString("/deck_%1.cod").arg(fileNode->getId()); + + Command_DeckDownload cmd; + cmd.set_deck_id(fileNode->getId()); + + PendingCommand *pend = client->prepareSessionCommand(cmd); + pend->setExtraData(filePath); + connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this, + SLOT(downloadFinished(Response, CommandContainer, QVariant))); + client->sendCommand(pend); + } + // node at index is invalid } void TabDeckStorage::downloadFinished(const Response &r, @@ -352,12 +539,30 @@ void TabDeckStorage::newFolderFinished(const Response &response, const CommandCo void TabDeckStorage::actDeleteRemoteDeck() { - PendingCommand *pend; - RemoteDeckList_TreeModel::Node *curRight = serverDirView->getCurrentItem(); - if (!curRight) + auto curRights = serverDirView->getCurrentSelection(); + + if (curRights.isEmpty()) { return; - RemoteDeckList_TreeModel::DirectoryNode *dir = dynamic_cast(curRight); - if (dir) { + } + + if (QMessageBox::warning(this, tr("Delete remote decks"), tr("Are you sure you want to delete the selected decks?"), + QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes) { + return; + } + + for (const auto &curRight : curRights) { + deleteRemoteDeck(curRight); + } +} + +void TabDeckStorage::deleteRemoteDeck(const RemoteDeckList_TreeModel::Node *curRight) +{ + if (!curRight) { + return; + } + + PendingCommand *pend; + if (const auto *dir = dynamic_cast(curRight)) { QString targetPath = dir->getPath(); if (targetPath.isEmpty()) return; @@ -365,22 +570,13 @@ void TabDeckStorage::actDeleteRemoteDeck() qCritical() << "target path to delete is too long" << targetPath; return; } - if (QMessageBox::warning(this, tr("Delete remote folder"), - tr("Are you sure you want to delete \"%1\"?").arg(targetPath), - QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes) - return; Command_DeckDelDir cmd; cmd.set_path(targetPath.toStdString()); pend = client->prepareSessionCommand(cmd); connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this, SLOT(deleteFolderFinished(Response, CommandContainer))); } else { - RemoteDeckList_TreeModel::FileNode *deckNode = dynamic_cast(curRight); - if (QMessageBox::warning(this, tr("Delete remote deck"), - tr("Are you sure you want to delete \"%1\"?").arg(deckNode->getName()), - QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes) - return; - + const auto *deckNode = dynamic_cast(curRight); Command_DeckDel cmd; cmd.set_deck_id(deckNode->getId()); pend = client->prepareSessionCommand(cmd); diff --git a/cockatrice/src/client/tabs/tab_deck_storage.h b/cockatrice/src/client/tabs/tab_deck_storage.h index 2c7d1e726..e207a1187 100644 --- a/cockatrice/src/client/tabs/tab_deck_storage.h +++ b/cockatrice/src/client/tabs/tab_deck_storage.h @@ -1,8 +1,11 @@ #ifndef TAB_DECK_STORAGE_H #define TAB_DECK_STORAGE_H +#include "../../server/remote/remote_decklist_tree_widget.h" +#include "../game_logic/abstract_client.h" #include "tab.h" +class ServerInfo_User; class AbstractClient; class QTreeView; class QFileSystemModel; @@ -10,7 +13,6 @@ class QToolBar; class QTreeWidget; class QTreeWidgetItem; class QGroupBox; -class RemoteDeckList_TreeWidget; class CommandContainer; class Response; class DeckLoader; @@ -26,16 +28,36 @@ private: RemoteDeckList_TreeWidget *serverDirView; QGroupBox *leftGroupBox, *rightGroupBox; - QAction *aOpenLocalDeck, *aUpload, *aDeleteLocalDeck, *aOpenRemoteDeck, *aDownload, *aNewFolder, *aDeleteRemoteDeck; + QAction *aOpenLocalDeck, *aRenameLocal, *aUpload, *aNewLocalFolder, *aDeleteLocalDeck; + QAction *aOpenDecksFolder; + QAction *aOpenRemoteDeck, *aDownload, *aNewFolder, *aDeleteRemoteDeck; QString getTargetPath() const; + + void setRemoteEnabled(bool enabled); + + void uploadDeck(const QString &filePath, const QString &targetPath); + void deleteRemoteDeck(const RemoteDeckList_TreeModel::Node *node); + + void downloadNodeAtIndex(const QModelIndex &curLeft, const QModelIndex &curRight); + private slots: + void handleConnected(const ServerInfo_User &userInfo); + void handleConnectionChanged(ClientStatus status); + + void actLocalDoubleClick(const QModelIndex &curLeft); void actOpenLocalDeck(); + void actRenameLocal(); + void actUpload(); void uploadFinished(const Response &r, const CommandContainer &commandContainer); + void actNewLocalFolder(); void actDeleteLocalDeck(); + void actOpenDecksFolder(); + + void actRemoteDoubleClick(const QModelIndex &curRight); void actOpenRemoteDeck(); void openRemoteDeckFinished(const Response &r, const CommandContainer &commandContainer); @@ -50,11 +72,11 @@ private slots: void deleteDeckFinished(const Response &response, const CommandContainer &commandContainer); public: - TabDeckStorage(TabSupervisor *_tabSupervisor, AbstractClient *_client); - void retranslateUi(); - QString getTabText() const + TabDeckStorage(TabSupervisor *_tabSupervisor, AbstractClient *_client, const ServerInfo_User *currentUserInfo); + void retranslateUi() override; + QString getTabText() const override { - return tr("Deck storage"); + return tr("Deck Storage"); } signals: void openDeckEditor(const DeckLoader *deckLoader); diff --git a/cockatrice/src/client/tabs/tab_game.cpp b/cockatrice/src/client/tabs/tab_game.cpp index a37e05567..8c018f644 100644 --- a/cockatrice/src/client/tabs/tab_game.cpp +++ b/cockatrice/src/client/tabs/tab_game.cpp @@ -1,43 +1,36 @@ #include "tab_game.h" #include "../../client/ui/widgets/cards/card_info_frame_widget.h" -#include "../../deck/deck_loader.h" -#include "../../deck/deck_view.h" #include "../../dialogs/dlg_create_game.h" -#include "../../dialogs/dlg_load_remote_deck.h" -#include "../../dialogs/dlg_manage_sets.h" #include "../../game/board/arrow_item.h" #include "../../game/cards/card_database.h" #include "../../game/cards/card_database_manager.h" #include "../../game/cards/card_item.h" +#include "../../game/deckview/deck_view_container.h" #include "../../game/game_scene.h" #include "../../game/game_view.h" #include "../../game/player/player.h" #include "../../game/player/player_list_widget.h" -#include "../../game/zones/view_zone.h" -#include "../../game/zones/view_zone_widget.h" +#include "../../game/zones/card_zone.h" #include "../../main.h" #include "../../server/message_log_widget.h" #include "../../server/pending_command.h" +#include "../../server/user/user_list_manager.h" #include "../../settings/cache_settings.h" #include "../game_logic/abstract_client.h" #include "../network/replay_timeline_widget.h" #include "../ui/line_edit_completer.h" #include "../ui/phases_toolbar.h" -#include "../ui/picture_loader.h" +#include "../ui/picture_loader/picture_loader.h" #include "../ui/window_main.h" #include "get_pb_extension.h" #include "pb/command_concede.pb.h" -#include "pb/command_deck_select.pb.h" #include "pb/command_delete_arrow.pb.h" #include "pb/command_game_say.pb.h" #include "pb/command_leave_game.pb.h" #include "pb/command_next_turn.pb.h" -#include "pb/command_ready_start.pb.h" #include "pb/command_reverse_turn.pb.h" #include "pb/command_set_active_phase.pb.h" -#include "pb/command_set_sideboard_lock.pb.h" -#include "pb/command_set_sideboard_plan.pb.h" #include "pb/context_connection_state_changed.pb.h" #include "pb/context_deck_select.pb.h" #include "pb/context_ping_changed.pb.h" @@ -55,7 +48,6 @@ #include "pb/event_set_active_player.pb.h" #include "pb/game_event_container.pb.h" #include "pb/game_replay.pb.h" -#include "pb/response_deck_download.pb.h" #include "tab_supervisor.h" #include "trice_limits.h" @@ -73,110 +65,237 @@ #include #include #include -#include -ToggleButton::ToggleButton(QWidget *parent) : QPushButton(parent), state(false) +TabGame::TabGame(TabSupervisor *_tabSupervisor, GameReplay *_replay) + : Tab(_tabSupervisor), secondsElapsed(0), hostId(-1), localPlayerId(-1), + isLocalGame(_tabSupervisor->getIsLocalGame()), spectator(true), judge(false), gameStateKnown(false), + resuming(false), currentPhase(-1), activeCard(nullptr), gameClosed(false), replay(_replay), currentReplayStep(0), + sayLabel(nullptr), sayEdit(nullptr) { -} + // THIS CTOR IS USED ON REPLAY + gameInfo.CopyFrom(replay->game_info()); + gameInfo.set_spectators_omniscient(true); -void ToggleButton::paintEvent(QPaintEvent *event) -{ - QPushButton::paintEvent(event); + // Create list: event number -> time [ms] + // Distribute simultaneous events evenly across 1 second. + unsigned int lastEventTimestamp = 0; + const int eventCount = replay->event_list_size(); + for (int i = 0; i < eventCount; ++i) { + int j = i + 1; + while ((j < eventCount) && (replay->event_list(j).seconds_elapsed() == lastEventTimestamp)) + ++j; - QPainter painter(this); - QPen pen; - pen.setWidth(3); - pen.setJoinStyle(Qt::MiterJoin); - pen.setColor(state ? Qt::green : Qt::red); - painter.setPen(pen); - painter.drawRect(QRect(1, 1, width() - 3, height() - 3)); -} + const int numberEventsThisSecond = j - i; + for (int k = 0; k < numberEventsThisSecond; ++k) + replayTimeline.append(replay->event_list(i + k).seconds_elapsed() * 1000 + + (int)((qreal)k / (qreal)numberEventsThisSecond * 1000)); -void ToggleButton::setState(bool _state) -{ - state = _state; - emit stateChanged(); - update(); -} - -DeckViewContainer::DeckViewContainer(int _playerId, TabGame *parent) - : QWidget(nullptr), parentGame(parent), playerId(_playerId) -{ - loadLocalButton = new QPushButton; - loadRemoteButton = new QPushButton; - readyStartButton = new ToggleButton; - readyStartButton->setEnabled(false); - sideboardLockButton = new ToggleButton; - sideboardLockButton->setEnabled(false); - - connect(loadLocalButton, SIGNAL(clicked()), this, SLOT(loadLocalDeck())); - connect(readyStartButton, SIGNAL(clicked()), this, SLOT(readyStart())); - connect(sideboardLockButton, SIGNAL(clicked()), this, SLOT(sideboardLockButtonClicked())); - connect(sideboardLockButton, SIGNAL(stateChanged()), this, SLOT(updateSideboardLockButtonText())); - - if (parentGame->getIsLocalGame()) { - loadRemoteButton->setEnabled(false); - } else { - connect(loadRemoteButton, SIGNAL(clicked()), this, SLOT(loadRemoteDeck())); + if (j < eventCount) + lastEventTimestamp = replay->event_list(j).seconds_elapsed(); + i += numberEventsThisSecond - 1; } - auto *buttonHBox = new QHBoxLayout; - buttonHBox->addWidget(loadLocalButton); - buttonHBox->addWidget(loadRemoteButton); - buttonHBox->addWidget(readyStartButton); - buttonHBox->addWidget(sideboardLockButton); - buttonHBox->setContentsMargins(0, 0, 0, 0); - buttonHBox->addStretch(); - deckView = new DeckView; - connect(deckView, SIGNAL(newCardAdded(AbstractCardItem *)), this, SIGNAL(newCardAdded(AbstractCardItem *))); - connect(deckView, SIGNAL(sideboardPlanChanged()), this, SLOT(sideboardPlanChanged())); + createCardInfoDock(true); + createPlayerListDock(true); + createMessageDock(true); + createPlayAreaWidget(true); + createDeckViewContainerWidget(true); + createReplayDock(); - auto *deckViewLayout = new QVBoxLayout; - deckViewLayout->addLayout(buttonHBox); - deckViewLayout->addWidget(deckView); - deckViewLayout->setContentsMargins(0, 0, 0, 0); - setLayout(deckViewLayout); + addDockWidget(Qt::RightDockWidgetArea, cardInfoDock); + addDockWidget(Qt::RightDockWidgetArea, playerListDock); + addDockWidget(Qt::RightDockWidgetArea, messageLayoutDock); + addDockWidget(Qt::BottomDockWidgetArea, replayDock); + mainWidget = new QStackedWidget(this); + mainWidget->addWidget(deckViewContainerWidget); + mainWidget->addWidget(gamePlayAreaWidget); + setCentralWidget(mainWidget); + + createReplayMenuItems(); + createViewMenuItems(); retranslateUi(); connect(&SettingsCache::instance().shortcuts(), SIGNAL(shortCutChanged()), this, SLOT(refreshShortcuts())); refreshShortcuts(); + messageLog->logReplayStarted(gameInfo.game_id()); + + QTimer::singleShot(0, this, SLOT(loadLayout())); } -void DeckViewContainer::retranslateUi() +TabGame::TabGame(TabSupervisor *_tabSupervisor, + QList &_clients, + const Event_GameJoined &event, + const QMap &_roomGameTypes) + : Tab(_tabSupervisor), userListProxy(_tabSupervisor->getUserListManager()), clients(_clients), + gameInfo(event.game_info()), roomGameTypes(_roomGameTypes), hostId(event.host_id()), + localPlayerId(event.player_id()), isLocalGame(_tabSupervisor->getIsLocalGame()), spectator(event.spectator()), + judge(event.judge()), gameStateKnown(false), resuming(event.resuming()), currentPhase(-1), activeCard(nullptr), + gameClosed(false), replay(nullptr), replayPlayButton(nullptr), replayFastForwardButton(nullptr), + aReplaySkipForward(nullptr), aReplaySkipBackward(nullptr), aReplaySkipForwardBig(nullptr), + aReplaySkipBackwardBig(nullptr), replayDock(nullptr) { - loadLocalButton->setText(tr("Load deck...")); - loadRemoteButton->setText(tr("Load remote deck...")); - readyStartButton->setText(tr("Ready to start")); - updateSideboardLockButtonText(); + // THIS CTOR IS USED ON GAMES + gameInfo.set_started(false); + + createCardInfoDock(); + createPlayerListDock(); + createMessageDock(); + createPlayAreaWidget(); + createDeckViewContainerWidget(); + + addDockWidget(Qt::RightDockWidgetArea, cardInfoDock); + addDockWidget(Qt::RightDockWidgetArea, playerListDock); + addDockWidget(Qt::RightDockWidgetArea, messageLayoutDock); + + mainWidget = new QStackedWidget(this); + mainWidget->addWidget(deckViewContainerWidget); + mainWidget->addWidget(gamePlayAreaWidget); + mainWidget->setContentsMargins(0, 0, 0, 0); + setCentralWidget(mainWidget); + + createMenuItems(); + createViewMenuItems(); + retranslateUi(); + connect(&SettingsCache::instance().shortcuts(), SIGNAL(shortCutChanged()), this, SLOT(refreshShortcuts())); + refreshShortcuts(); + + // append game to rooms game list for others to see + for (int i = gameInfo.game_types_size() - 1; i >= 0; i--) + gameTypes.append(roomGameTypes.find(gameInfo.game_types(i)).value()); + + QTimer::singleShot(0, this, SLOT(loadLayout())); } -void DeckViewContainer::setButtonsVisible(bool _visible) +void TabGame::addMentionTag(const QString &value) { - loadLocalButton->setVisible(_visible); - loadRemoteButton->setVisible(_visible); - readyStartButton->setVisible(_visible); - sideboardLockButton->setVisible(_visible); + sayEdit->insert(value + " "); + sayEdit->setFocus(); } -void DeckViewContainer::updateSideboardLockButtonText() +void TabGame::linkCardToChat(const QString &cardName) { - if (sideboardLockButton->getState()) { - sideboardLockButton->setText(tr("Sideboard unlocked")); - } else { - sideboardLockButton->setText(tr("Sideboard locked")); + sayEdit->insert("[[" + cardName + "]] "); + sayEdit->setFocus(); +} + +void TabGame::emitUserEvent() +{ + bool globalEvent = !spectator || SettingsCache::instance().getSpectatorNotificationsEnabled(); + emit userEvent(globalEvent); + updatePlayerListDockTitle(); +} + +TabGame::~TabGame() +{ + delete replay; +} + +void TabGame::updatePlayerListDockTitle() +{ + QString tabText = " | " + (replay ? tr("Replay") : tr("Game")) + " #" + QString::number(gameInfo.game_id()); + QString userCountInfo = QString(" %1/%2").arg(players.size()).arg(gameInfo.max_players()); + playerListDock->setWindowTitle(tr("Player List") + userCountInfo + + (playerListDock->isWindow() ? tabText : QString())); +} + +bool TabGame::isMainPlayerConceded() const +{ + Player *player = players.value(localPlayerId, nullptr); + return player && player->getConceded(); +} + +void TabGame::retranslateUi() +{ + QString tabText = " | " + (replay ? tr("Replay") : tr("Game")) + " #" + QString::number(gameInfo.game_id()); + + updatePlayerListDockTitle(); + cardInfoDock->setWindowTitle(tr("Card Info") + (cardInfoDock->isWindow() ? tabText : QString())); + messageLayoutDock->setWindowTitle(tr("Messages") + (messageLayoutDock->isWindow() ? tabText : QString())); + if (replayDock) + replayDock->setWindowTitle(tr("Replay Timeline") + (replayDock->isWindow() ? tabText : QString())); + + if (phasesMenu) { + for (int i = 0; i < phaseActions.size(); ++i) + phaseActions[i]->setText(phasesToolbar->getLongPhaseName(i)); + phasesMenu->setTitle(tr("&Phases")); } - // setting text on a button removes its shortcut - ShortcutsSettings &shortcuts = SettingsCache::instance().shortcuts(); - sideboardLockButton->setShortcut(shortcuts.getSingleShortcut("DeckViewContainer/sideboardLockButton")); -} -void DeckViewContainer::refreshShortcuts() -{ - ShortcutsSettings &shortcuts = SettingsCache::instance().shortcuts(); - loadLocalButton->setShortcut(shortcuts.getSingleShortcut("DeckViewContainer/loadLocalButton")); - loadRemoteButton->setShortcut(shortcuts.getSingleShortcut("DeckViewContainer/loadRemoteButton")); - readyStartButton->setShortcut(shortcuts.getSingleShortcut("DeckViewContainer/readyStartButton")); - sideboardLockButton->setShortcut(shortcuts.getSingleShortcut("DeckViewContainer/sideboardLockButton")); + gameMenu->setTitle(tr("&Game")); + if (aNextPhase) { + aNextPhase->setText(tr("Next &phase")); + } + if (aNextPhaseAction) { + aNextPhaseAction->setText(tr("Next phase with &action")); + } + if (aNextTurn) { + aNextTurn->setText(tr("Next &turn")); + } + if (aReverseTurn) { + aReverseTurn->setText(tr("Reverse turn order")); + } + if (aRemoveLocalArrows) { + aRemoveLocalArrows->setText(tr("&Remove all local arrows")); + } + if (aRotateViewCW) { + aRotateViewCW->setText(tr("Rotate View Cl&ockwise")); + } + if (aRotateViewCCW) { + aRotateViewCCW->setText(tr("Rotate View Co&unterclockwise")); + } + if (aGameInfo) + aGameInfo->setText(tr("Game &information")); + if (aConcede) { + if (isMainPlayerConceded()) { + aConcede->setText(tr("Un&concede")); + } else { + aConcede->setText(tr("&Concede")); + } + } + if (aLeaveGame) { + aLeaveGame->setText(tr("&Leave game")); + } + if (aCloseReplay) { + aCloseReplay->setText(tr("C&lose replay")); + } + if (aFocusChat) { + aFocusChat->setText(tr("&Focus Chat")); + } + if (sayLabel) { + sayLabel->setText(tr("&Say:")); + } + + viewMenu->setTitle(tr("&View")); + cardInfoDockMenu->setTitle(tr("Card Info")); + messageLayoutDockMenu->setTitle(tr("Messages")); + playerListDockMenu->setTitle(tr("Player List")); + + aCardInfoDockVisible->setText(tr("Visible")); + aCardInfoDockFloating->setText(tr("Floating")); + + aMessageLayoutDockVisible->setText(tr("Visible")); + aMessageLayoutDockFloating->setText(tr("Floating")); + + aPlayerListDockVisible->setText(tr("Visible")); + aPlayerListDockFloating->setText(tr("Floating")); + + if (replayDock) { + replayDockMenu->setTitle(tr("Replay Timeline")); + aReplayDockVisible->setText(tr("Visible")); + aReplayDockFloating->setText(tr("Floating")); + } + + aResetLayout->setText(tr("Reset layout")); + + cardInfoFrameWidget->retranslateUi(); + + QMapIterator i(players); + while (i.hasNext()) + i.next().value()->retranslateUi(); + QMapIterator j(deckViewContainers); + while (j.hasNext()) + j.next().value()->retranslateUi(); + + scene->retranslateUi(); } void TabGame::refreshShortcuts() @@ -278,341 +397,15 @@ void TabGame::refreshShortcuts() } } -void DeckViewContainer::loadLocalDeck() +void TabGame::closeRequest(bool forced) { - QFileDialog dialog(this, tr("Load deck")); - dialog.setDirectory(SettingsCache::instance().getDeckPath()); - dialog.setNameFilters(DeckLoader::fileNameFilters); - if (!dialog.exec()) - return; - - QString fileName = dialog.selectedFiles().at(0); - DeckLoader::FileFormat fmt = DeckLoader::getFormatFromName(fileName); - QString deckString; - DeckLoader deck; - - bool error = !deck.loadFromFile(fileName, fmt); - if (!error) { - deckString = deck.writeToString_Native(); - error = deckString.length() > MAX_FILE_LENGTH; - } - if (error) { - QMessageBox::critical(this, tr("Error"), tr("The selected file could not be loaded.")); + if (!forced && !leaveGame()) { return; } - Command_DeckSelect cmd; - cmd.set_deck(deckString.toStdString()); - PendingCommand *pend = parentGame->prepareGameCommand(cmd); - connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this, - SLOT(deckSelectFinished(const Response &))); - parentGame->sendGameCommand(pend, playerId); -} - -void DeckViewContainer::loadRemoteDeck() -{ - DlgLoadRemoteDeck dlg(parentGame->getClientForPlayer(playerId), this); - if (dlg.exec()) { - Command_DeckSelect cmd; - cmd.set_deck_id(dlg.getDeckId()); - PendingCommand *pend = parentGame->prepareGameCommand(cmd); - connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this, - SLOT(deckSelectFinished(const Response &))); - parentGame->sendGameCommand(pend, playerId); - } -} - -void DeckViewContainer::deckSelectFinished(const Response &r) -{ - const Response_DeckDownload &resp = r.GetExtension(Response_DeckDownload::ext); - DeckLoader newDeck(QString::fromStdString(resp.deck())); - // TODO CHANGE THIS TO BE SELECTED BY UUID - PictureLoader::cacheCardPixmaps(CardDatabaseManager::getInstance()->getCards(newDeck.getCardList())); - setDeck(newDeck); -} - -void DeckViewContainer::readyStart() -{ - Command_ReadyStart cmd; - cmd.set_ready(!readyStartButton->getState()); - parentGame->sendGameCommand(cmd, playerId); -} - -void DeckViewContainer::sideboardLockButtonClicked() -{ - Command_SetSideboardLock cmd; - cmd.set_locked(sideboardLockButton->getState()); - - parentGame->sendGameCommand(cmd, playerId); -} - -void DeckViewContainer::sideboardPlanChanged() -{ - Command_SetSideboardPlan cmd; - const QList &newPlan = deckView->getSideboardPlan(); - for (const auto &i : newPlan) - cmd.add_move_list()->CopyFrom(i); - parentGame->sendGameCommand(cmd, playerId); -} - -void DeckViewContainer::setReadyStart(bool ready) -{ - readyStartButton->setState(ready); - deckView->setLocked(ready || !sideboardLockButton->getState()); - sideboardLockButton->setEnabled(!readyStartButton->getState() && readyStartButton->isEnabled()); -} - -void DeckViewContainer::setSideboardLocked(bool locked) -{ - sideboardLockButton->setState(!locked); - deckView->setLocked(readyStartButton->getState() || !sideboardLockButton->getState()); - if (locked) - deckView->resetSideboardPlan(); -} - -void DeckViewContainer::setDeck(const DeckLoader &deck) -{ - deckView->setDeck(deck); - readyStartButton->setEnabled(true); - sideboardLockButton->setState(false); - sideboardLockButton->setEnabled(true); -} - -TabGame::TabGame(TabSupervisor *_tabSupervisor, GameReplay *_replay) - : Tab(_tabSupervisor), secondsElapsed(0), hostId(-1), localPlayerId(-1), - isLocalGame(_tabSupervisor->getIsLocalGame()), spectator(true), judge(false), gameStateKnown(false), - resuming(false), currentPhase(-1), activeCard(nullptr), gameClosed(false), replay(_replay), currentReplayStep(0), - sayLabel(nullptr), sayEdit(nullptr) -{ - // THIS CTOR IS USED ON REPLAY - gameInfo.CopyFrom(replay->game_info()); - gameInfo.set_spectators_omniscient(true); - - // Create list: event number -> time [ms] - // Distribute simultaneous events evenly across 1 second. - unsigned int lastEventTimestamp = 0; - const int eventCount = replay->event_list_size(); - for (int i = 0; i < eventCount; ++i) { - int j = i + 1; - while ((j < eventCount) && (replay->event_list(j).seconds_elapsed() == lastEventTimestamp)) - ++j; - - const int numberEventsThisSecond = j - i; - for (int k = 0; k < numberEventsThisSecond; ++k) - replayTimeline.append(replay->event_list(i + k).seconds_elapsed() * 1000 + - (int)((qreal)k / (qreal)numberEventsThisSecond * 1000)); - - if (j < eventCount) - lastEventTimestamp = replay->event_list(j).seconds_elapsed(); - i += numberEventsThisSecond - 1; - } - - createCardInfoDock(true); - createPlayerListDock(true); - createMessageDock(true); - createPlayAreaWidget(true); - createDeckViewContainerWidget(true); - createReplayDock(); - - addDockWidget(Qt::RightDockWidgetArea, cardInfoDock); - addDockWidget(Qt::RightDockWidgetArea, playerListDock); - addDockWidget(Qt::RightDockWidgetArea, messageLayoutDock); - addDockWidget(Qt::BottomDockWidgetArea, replayDock); - - mainWidget = new QStackedWidget(this); - mainWidget->addWidget(deckViewContainerWidget); - mainWidget->addWidget(gamePlayAreaWidget); - setCentralWidget(mainWidget); - - createReplayMenuItems(); - createViewMenuItems(); - retranslateUi(); - connect(&SettingsCache::instance().shortcuts(), SIGNAL(shortCutChanged()), this, SLOT(refreshShortcuts())); - refreshShortcuts(); - messageLog->logReplayStarted(gameInfo.game_id()); - - this->installEventFilter(this); - QTimer::singleShot(0, this, SLOT(loadLayout())); -} - -TabGame::TabGame(TabSupervisor *_tabSupervisor, - QList &_clients, - const Event_GameJoined &event, - const QMap &_roomGameTypes) - : Tab(_tabSupervisor), clients(_clients), gameInfo(event.game_info()), roomGameTypes(_roomGameTypes), - hostId(event.host_id()), localPlayerId(event.player_id()), isLocalGame(_tabSupervisor->getIsLocalGame()), - spectator(event.spectator()), judge(event.judge()), gameStateKnown(false), resuming(event.resuming()), - currentPhase(-1), activeCard(nullptr), gameClosed(false), replay(nullptr), replayPlayButton(nullptr), - replayFastForwardButton(nullptr), aReplaySkipForward(nullptr), aReplaySkipBackward(nullptr), - aReplaySkipForwardBig(nullptr), aReplaySkipBackwardBig(nullptr), replayDock(nullptr) -{ - // THIS CTOR IS USED ON GAMES - gameInfo.set_started(false); - - createCardInfoDock(); - createPlayerListDock(); - createMessageDock(); - createPlayAreaWidget(); - createDeckViewContainerWidget(); - - addDockWidget(Qt::RightDockWidgetArea, cardInfoDock); - addDockWidget(Qt::RightDockWidgetArea, playerListDock); - addDockWidget(Qt::RightDockWidgetArea, messageLayoutDock); - - mainWidget = new QStackedWidget(this); - mainWidget->addWidget(deckViewContainerWidget); - mainWidget->addWidget(gamePlayAreaWidget); - mainWidget->setContentsMargins(0, 0, 0, 0); - setCentralWidget(mainWidget); - - createMenuItems(); - createViewMenuItems(); - retranslateUi(); - connect(&SettingsCache::instance().shortcuts(), SIGNAL(shortCutChanged()), this, SLOT(refreshShortcuts())); - refreshShortcuts(); - - // append game to rooms game list for others to see - 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())); -} - -void TabGame::addMentionTag(const QString &value) -{ - sayEdit->insert(value + " "); - sayEdit->setFocus(); -} - -void TabGame::linkCardToChat(const QString &cardName) -{ - sayEdit->insert("[[" + cardName + "]] "); - sayEdit->setFocus(); -} - -void TabGame::emitUserEvent() -{ - bool globalEvent = !spectator || SettingsCache::instance().getSpectatorNotificationsEnabled(); - emit userEvent(globalEvent); - updatePlayerListDockTitle(); -} - -TabGame::~TabGame() -{ - delete replay; - - QMapIterator i(players); - while (i.hasNext()) { - delete i.next().value(); - } - - players.clear(); - emit gameClosing(this); -} -void TabGame::updatePlayerListDockTitle() -{ - QString tabText = " | " + (replay ? tr("Replay") : tr("Game")) + " #" + QString::number(gameInfo.game_id()); - QString userCountInfo = QString(" %1/%2").arg(players.size()).arg(gameInfo.max_players()); - playerListDock->setWindowTitle(tr("Player List") + userCountInfo + - (playerListDock->isWindow() ? tabText : QString())); -} - -void TabGame::retranslateUi() -{ - QString tabText = " | " + (replay ? tr("Replay") : tr("Game")) + " #" + QString::number(gameInfo.game_id()); - - updatePlayerListDockTitle(); - cardInfoDock->setWindowTitle(tr("Card Info") + (cardInfoDock->isWindow() ? tabText : QString())); - messageLayoutDock->setWindowTitle(tr("Messages") + (messageLayoutDock->isWindow() ? tabText : QString())); - if (replayDock) - replayDock->setWindowTitle(tr("Replay Timeline") + (replayDock->isWindow() ? tabText : QString())); - - if (phasesMenu) { - for (int i = 0; i < phaseActions.size(); ++i) - phaseActions[i]->setText(phasesToolbar->getLongPhaseName(i)); - phasesMenu->setTitle(tr("&Phases")); - } - - gameMenu->setTitle(tr("&Game")); - if (aNextPhase) { - aNextPhase->setText(tr("Next &phase")); - } - if (aNextPhaseAction) { - aNextPhaseAction->setText(tr("Next phase with &action")); - } - if (aNextTurn) { - aNextTurn->setText(tr("Next &turn")); - } - if (aReverseTurn) { - aReverseTurn->setText(tr("Reverse turn order")); - } - if (aRemoveLocalArrows) { - aRemoveLocalArrows->setText(tr("&Remove all local arrows")); - } - if (aRotateViewCW) { - aRotateViewCW->setText(tr("Rotate View Cl&ockwise")); - } - if (aRotateViewCCW) { - aRotateViewCCW->setText(tr("Rotate View Co&unterclockwise")); - } - if (aGameInfo) - aGameInfo->setText(tr("Game &information")); - if (aConcede) { - aConcede->setText(tr("&Concede")); - } - if (aLeaveGame) { - aLeaveGame->setText(tr("&Leave game")); - } - if (aCloseReplay) { - aCloseReplay->setText(tr("C&lose replay")); - } - if (aFocusChat) { - aFocusChat->setText(tr("&Focus Chat")); - } - if (sayLabel) { - sayLabel->setText(tr("&Say:")); - } - - viewMenu->setTitle(tr("&View")); - cardInfoDockMenu->setTitle(tr("Card Info")); - messageLayoutDockMenu->setTitle(tr("Messages")); - playerListDockMenu->setTitle(tr("Player List")); - - aCardInfoDockVisible->setText(tr("Visible")); - aCardInfoDockFloating->setText(tr("Floating")); - - aMessageLayoutDockVisible->setText(tr("Visible")); - aMessageLayoutDockFloating->setText(tr("Floating")); - - aPlayerListDockVisible->setText(tr("Visible")); - aPlayerListDockFloating->setText(tr("Floating")); - - if (replayDock) { - replayDockMenu->setTitle(tr("Replay Timeline")); - aReplayDockVisible->setText(tr("Visible")); - aReplayDockFloating->setText(tr("Floating")); - } - - aResetLayout->setText(tr("Reset layout")); - - cardInfoFrameWidget->retranslateUi(); - - QMapIterator i(players); - while (i.hasNext()) - i.next().value()->retranslateUi(); - QMapIterator j(deckViewContainers); - while (j.hasNext()) - j.next().value()->retranslateUi(); - - scene->retranslateUi(); -} - -void TabGame::closeRequest() -{ - actLeaveGame(); + close(); } void TabGame::replayNextEvent(Player::EventProcessingOptions options) @@ -703,19 +496,25 @@ void TabGame::actConcede() } } -void TabGame::actLeaveGame() +/** + * Confirms the leave game and sends the leave game command, if applicable. + * + * @return True if the leave game is confirmed + */ +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; + return false; + } if (!replay) sendGameCommand(Command_LeaveGame()); } - scene->clearViews(); - deleteLater(); + return true; } void TabGame::actSay() @@ -723,6 +522,12 @@ void TabGame::actSay() if (completer->popup()->isVisible()) return; + if (sayEdit->text().startsWith("/card ")) { + cardInfoFrameWidget->setCard(sayEdit->text().mid(6)); + sayEdit->clear(); + return; + } + if (!sayEdit->text().isEmpty()) { Command_GameSay cmd; cmd.set_message(sayEdit->text().toStdString()); @@ -834,6 +639,15 @@ Player *TabGame::addPlayer(int playerId, const ServerInfo_User &info) connect(deckView, SIGNAL(newCardAdded(AbstractCardItem *)), this, SLOT(newCardAdded(AbstractCardItem *))); deckViewContainers.insert(playerId, deckView); deckViewContainerLayout->addWidget(deckView); + + // auto load deck for player if that debug setting is enabled + QString deckPath = SettingsCache::instance().debug().getDeckPathForPlayer(newPlayer->getName()); + if (!deckPath.isEmpty()) { + QTimer::singleShot(0, this, [deckView, deckPath] { + deckView->loadDeckFromFile(deckPath); + deckView->readyAndUpdate(); + }); + } } gameMenu->insertMenu(playersSeparator, newPlayer->getPlayerMenu()); @@ -858,6 +672,9 @@ Player *TabGame::addPlayer(int playerId, const ServerInfo_User &info) } } + // update menu text when player concedes so that "concede" gets updated to "unconcede" + connect(newPlayer, &Player::playerCountChanged, this, &TabGame::retranslateUi); + emit playerAdded(newPlayer); return newPlayer; } @@ -936,7 +753,7 @@ void TabGame::processGameEventContainer(const GameEventContainer &cont, default: { Player *player = players.value(playerId, 0); if (!player) { - qDebug() << "unhandled game event: invalid player id"; + qCWarning(TabGameLog) << "unhandled game event: invalid player id"; break; } player->processGameEvent(eventType, event, context, options); @@ -1019,6 +836,7 @@ void TabGame::startGame(bool _resuming) while (i.hasNext()) { i.next(); i.value()->setReadyStart(false); + i.value()->setVisualDeckStorageExists(false); i.value()->hide(); } @@ -1065,8 +883,7 @@ void TabGame::closeGame() void TabGame::eventSpectatorSay(const Event_GameSay &event, int eventPlayerId, const GameEventContext & /*context*/) { const ServerInfo_User &userInfo = spectators.value(eventPlayerId); - messageLog->logSpectatorSay(QString::fromStdString(userInfo.name()), UserLevelFlags(userInfo.user_level()), - QString::fromStdString(userInfo.privlevel()), QString::fromStdString(event.message())); + messageLog->logSpectatorSay(userInfo, QString::fromStdString(event.message())); } void TabGame::eventSpectatorLeave(const Event_Leave &event, int eventPlayerId, const GameEventContext & /*context*/) @@ -1112,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); } @@ -1273,6 +1090,7 @@ void TabGame::eventLeave(const Event_Leave &event, int eventPlayerId, const Game players.remove(eventPlayerId); emit playerRemoved(player); player->clear(); + scene->removePlayer(player); player->deleteLater(); // Rearrange all remaining zones so that attachment relationship updates take place @@ -1381,7 +1199,7 @@ void TabGame::eventSetActivePhase(const Event_SetActivePhase &event, void TabGame::newCardAdded(AbstractCardItem *card) { connect(card, SIGNAL(hovered(AbstractCardItem *)), cardInfoFrameWidget, SLOT(setCard(AbstractCardItem *))); - connect(card, SIGNAL(showCardInfoPopup(QPoint, QString)), this, SLOT(showCardInfoPopup(QPoint, QString))); + connect(card, &AbstractCardItem::showCardInfoPopup, this, &TabGame::showCardInfoPopup); connect(card, SIGNAL(deleteCardInfoPopup(QString)), this, SLOT(deleteCardInfoPopup(QString))); connect(card, SIGNAL(cardShiftClicked(QString)), this, SLOT(linkCardToChat(QString))); } @@ -1489,7 +1307,7 @@ void TabGame::createMenuItems() aConcede = new QAction(this); connect(aConcede, SIGNAL(triggered()), this, SLOT(actConcede())); aLeaveGame = new QAction(this); - connect(aLeaveGame, SIGNAL(triggered()), this, SLOT(actLeaveGame())); + connect(aLeaveGame, &QAction::triggered, this, [this] { closeRequest(); }); aFocusChat = new QAction(this); connect(aFocusChat, SIGNAL(triggered()), sayEdit, SLOT(setFocus())); aCloseReplay = nullptr; @@ -1539,7 +1357,7 @@ void TabGame::createReplayMenuItems() aFocusChat = nullptr; aLeaveGame = nullptr; aCloseReplay = new QAction(this); - connect(aCloseReplay, SIGNAL(triggered()), this, SLOT(actLeaveGame())); + connect(aCloseReplay, &QAction::triggered, this, [this] { closeRequest(); }); phasesMenu = nullptr; gameMenu = new QMenu(this); @@ -1742,22 +1560,22 @@ void TabGame::createReplayDock() aReplaySkipForward = new QAction(timelineWidget); timelineWidget->addAction(aReplaySkipForward); connect(aReplaySkipForward, &QAction::triggered, this, - [=]() { timelineWidget->skipByAmount(ReplayTimelineWidget::SMALL_SKIP_MS); }); + [this] { timelineWidget->skipByAmount(ReplayTimelineWidget::SMALL_SKIP_MS); }); aReplaySkipBackward = new QAction(timelineWidget); timelineWidget->addAction(aReplaySkipBackward); connect(aReplaySkipBackward, &QAction::triggered, this, - [=]() { timelineWidget->skipByAmount(-ReplayTimelineWidget::SMALL_SKIP_MS); }); + [this] { timelineWidget->skipByAmount(-ReplayTimelineWidget::SMALL_SKIP_MS); }); aReplaySkipForwardBig = new QAction(timelineWidget); timelineWidget->addAction(aReplaySkipForwardBig); connect(aReplaySkipForwardBig, &QAction::triggered, this, - [=]() { timelineWidget->skipByAmount(ReplayTimelineWidget::BIG_SKIP_MS); }); + [this] { timelineWidget->skipByAmount(ReplayTimelineWidget::BIG_SKIP_MS); }); aReplaySkipBackwardBig = new QAction(timelineWidget); timelineWidget->addAction(aReplaySkipBackwardBig); connect(aReplaySkipBackwardBig, &QAction::triggered, this, - [=]() { timelineWidget->skipByAmount(-ReplayTimelineWidget::BIG_SKIP_MS); }); + [this] { timelineWidget->skipByAmount(-ReplayTimelineWidget::BIG_SKIP_MS); }); // buttons replayPlayButton = new QToolButton; @@ -1807,9 +1625,9 @@ void TabGame::createDeckViewContainerWidget(bool bReplay) deckViewContainerWidget->setLayout(deckViewContainerLayout); } -void TabGame::viewCardInfo(const QString &cardName) +void TabGame::viewCardInfo(const QString &cardName, const QString &providerId) const { - cardInfoFrameWidget->setCard(cardName); + cardInfoFrameWidget->setCard(cardName, providerId); } void TabGame::createCardInfoDock(bool bReplay) @@ -1861,9 +1679,9 @@ void TabGame::createPlayerListDock(bool bReplay) void TabGame::createMessageDock(bool bReplay) { - messageLog = new MessageLogWidget(tabSupervisor, tabSupervisor, this); + messageLog = new MessageLogWidget(tabSupervisor, this); connect(messageLog, SIGNAL(cardNameHovered(QString)), cardInfoFrameWidget, SLOT(setCard(QString))); - connect(messageLog, SIGNAL(showCardInfoPopup(QPoint, QString)), this, SLOT(showCardInfoPopup(QPoint, QString))); + connect(messageLog, &MessageLogWidget::showCardInfoPopup, this, &TabGame::showCardInfoPopup); connect(messageLog, SIGNAL(deleteCardInfoPopup(QString)), this, SLOT(deleteCardInfoPopup(QString))); if (!bReplay) { @@ -1933,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) { @@ -1952,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; } diff --git a/cockatrice/src/client/tabs/tab_game.h b/cockatrice/src/client/tabs/tab_game.h index 0d9d1d0db..096998728 100644 --- a/cockatrice/src/client/tabs/tab_game.h +++ b/cockatrice/src/client/tabs/tab_game.h @@ -3,25 +3,28 @@ #include "../../client/tearoff_menu.h" #include "../../game/player/player.h" +#include "../ui/widgets/visual_deck_storage/visual_deck_storage_widget.h" #include "pb/event_leave.pb.h" #include "pb/serverinfo_game.pb.h" #include "tab.h" #include +#include #include -#include +inline Q_LOGGING_CATEGORY(TabGameLog, "tab_game"); + +class UserListProxy; +class DeckViewContainer; class AbstractClient; class CardDatabase; class GameView; -class DeckView; class GameScene; class CardInfoFrameWidget; class MessageLogWidget; class QTimer; class QSplitter; class QLabel; -class QPushButton; class QToolButton; class QMenu; class ZoneViewLayout; @@ -51,7 +54,6 @@ class Event_ReverseTurn; class CardZone; class AbstractCardItem; class CardItem; -class TabGame; class DeckLoader; class QVBoxLayout; class QHBoxLayout; @@ -62,63 +64,13 @@ class LineEditCompleter; class QDockWidget; class QStackedWidget; -class ToggleButton : public QPushButton -{ - Q_OBJECT -private: - bool state; -signals: - void stateChanged(); - -public: - ToggleButton(QWidget *parent = nullptr); - bool getState() const - { - return state; - } - void setState(bool _state); - -protected: - void paintEvent(QPaintEvent *event); -}; - -class DeckViewContainer : public QWidget -{ - Q_OBJECT -private: - QPushButton *loadLocalButton, *loadRemoteButton; - ToggleButton *readyStartButton, *sideboardLockButton; - DeckView *deckView; - TabGame *parentGame; - int playerId; -private slots: - void loadLocalDeck(); - void loadRemoteDeck(); - void readyStart(); - void deckSelectFinished(const Response &r); - void sideboardPlanChanged(); - void sideboardLockButtonClicked(); - void updateSideboardLockButtonText(); - void refreshShortcuts(); -signals: - void newCardAdded(AbstractCardItem *card); - void notIdle(); - -public: - DeckViewContainer(int _playerId, TabGame *parent); - void retranslateUi(); - void setButtonsVisible(bool _visible); - void setReadyStart(bool ready); - void setSideboardLocked(bool locked); - void setDeck(const DeckLoader &deck); -}; - class TabGame : public Tab { Q_OBJECT private: QTimer *gameTimer; int secondsElapsed; + const UserListProxy *userListProxy; QList clients; ServerInfo_Game gameInfo; QMap roomGameTypes; @@ -176,9 +128,12 @@ private: Player *addPlayer(int playerId, const ServerInfo_User &info); + bool isMainPlayerConceded() const; + void startGame(bool resuming); void stopGame(); void closeGame(); + bool leaveGame(); void eventSpectatorSay(const Event_GameSay &event, int eventPlayerId, const GameEventContext &context); void eventSpectatorLeave(const Event_Leave &event, int eventPlayerId, const GameEventContext &context); @@ -232,7 +187,6 @@ private slots: void actGameInfo(); void actConcede(); - void actLeaveGame(); void actRemoveLocalArrows(); void actRotateViewCW(); void actRotateViewCCW(); @@ -254,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(); @@ -268,7 +223,7 @@ public: ~TabGame() override; void retranslateUi() override; void updatePlayerListDockTitle(); - void closeRequest() override; + void closeRequest(bool forced = false) override; const QMap &getPlayers() const { return players; @@ -313,7 +268,7 @@ public: public slots: void sendGameCommand(PendingCommand *pend, int playerId = -1); void sendGameCommand(const ::google::protobuf::Message &command, int playerId = -1); - void viewCardInfo(const QString &cardName); + void viewCardInfo(const QString &cardName, const QString &providerId = "") const; }; #endif diff --git a/cockatrice/src/client/tabs/tab_logs.cpp b/cockatrice/src/client/tabs/tab_logs.cpp index b9d06dee2..e43be2215 100644 --- a/cockatrice/src/client/tabs/tab_logs.cpp +++ b/cockatrice/src/client/tabs/tab_logs.cpp @@ -21,8 +21,7 @@ #include #include -TabLog::TabLog(TabSupervisor *_tabSupervisor, AbstractClient *_client, QWidget *parent) - : Tab(_tabSupervisor, parent), client(_client) +TabLog::TabLog(TabSupervisor *_tabSupervisor, AbstractClient *_client) : Tab(_tabSupervisor), client(_client) { roomTable = new QTableWidget(); roomTable->setColumnCount(6); diff --git a/cockatrice/src/client/tabs/tab_logs.h b/cockatrice/src/client/tabs/tab_logs.h index d31d68407..5694a24c7 100644 --- a/cockatrice/src/client/tabs/tab_logs.h +++ b/cockatrice/src/client/tabs/tab_logs.h @@ -53,10 +53,10 @@ private slots: void restartLayout(); public: - TabLog(TabSupervisor *_tabSupervisor, AbstractClient *_client, QWidget *parent = nullptr); - ~TabLog(); - void retranslateUi(); - QString getTabText() const + TabLog(TabSupervisor *_tabSupervisor, AbstractClient *_client); + ~TabLog() override; + void retranslateUi() override; + QString getTabText() const override { return tr("Logs"); } diff --git a/cockatrice/src/client/tabs/tab_message.cpp b/cockatrice/src/client/tabs/tab_message.cpp index 21fc0e265..78950e5be 100644 --- a/cockatrice/src/client/tabs/tab_message.cpp +++ b/cockatrice/src/client/tabs/tab_message.cpp @@ -4,6 +4,7 @@ #include "../../main.h" #include "../../server/chat_view/chat_view.h" #include "../../server/pending_command.h" +#include "../../server/user/user_list_manager.h" #include "../../settings/cache_settings.h" #include "../game_logic/abstract_client.h" #include "../sound_engine.h" @@ -25,8 +26,8 @@ TabMessage::TabMessage(TabSupervisor *_tabSupervisor, : Tab(_tabSupervisor), client(_client), ownUserInfo(new ServerInfo_User(_ownUserInfo)), otherUserInfo(new ServerInfo_User(_otherUserInfo)), userOnline(true) { - chatView = new ChatView(tabSupervisor, tabSupervisor, 0, true); - connect(chatView, SIGNAL(showCardInfoPopup(QPoint, QString)), this, SLOT(showCardInfoPopup(QPoint, QString))); + chatView = new ChatView(tabSupervisor, 0, true); + connect(chatView, &ChatView::showCardInfoPopup, this, &TabMessage::showCardInfoPopup); connect(chatView, SIGNAL(deleteCardInfoPopup(QString)), this, SLOT(deleteCardInfoPopup(QString))); connect(chatView, SIGNAL(addMentionTag(QString)), this, SLOT(addMentionTag(QString))); sayEdit = new LineEditUnfocusable; @@ -38,7 +39,7 @@ TabMessage::TabMessage(TabSupervisor *_tabSupervisor, vbox->addWidget(sayEdit); aLeave = new QAction(this); - connect(aLeave, SIGNAL(triggered()), this, SLOT(actLeave())); + connect(aLeave, &QAction::triggered, this, [this] { closeRequest(); }); messageMenu = new QMenu(this); messageMenu->addAction(aLeave); @@ -53,7 +54,6 @@ TabMessage::TabMessage(TabSupervisor *_tabSupervisor, TabMessage::~TabMessage() { - emit talkClosing(this); delete ownUserInfo; delete otherUserInfo; } @@ -86,9 +86,10 @@ QString TabMessage::getTabText() const return tr("%1 - Private chat").arg(QString::fromStdString(otherUserInfo->name())); } -void TabMessage::closeRequest() +void TabMessage::closeRequest(bool /*forced*/) { - actLeave(); + emit talkClosing(this); + close(); } void TabMessage::sendMessage() @@ -114,19 +115,11 @@ void TabMessage::messageSent(const Response &response) "This user is ignoring you, they cannot see your messages in main chat and you cannot join their games.")); } -void TabMessage::actLeave() -{ - deleteLater(); -} - void TabMessage::processUserMessageEvent(const Event_UserMessage &event) { auto userInfo = event.sender_name() == otherUserInfo->name() ? otherUserInfo : ownUserInfo; - const UserLevelFlags userLevel(userInfo->user_level()); - const QString userPriv = QString::fromStdString(userInfo->privlevel()); - chatView->appendMessage(QString::fromStdString(event.message()), {}, QString::fromStdString(event.sender_name()), - userLevel, userPriv, true); + chatView->appendMessage(QString::fromStdString(event.message()), {}, *userInfo, true); if (tabSupervisor->currentIndex() != tabSupervisor->indexOf(this)) soundEngine->playSound("private_message"); if (SettingsCache::instance().getShowMessagePopup() && shouldShowSystemPopup(event)) @@ -152,7 +145,7 @@ void TabMessage::showSystemPopup(const Event_UserMessage &event) event.message().c_str()); connect(trayIcon, SIGNAL(messageClicked()), this, SLOT(messageClicked())); } else { - qDebug() << "Error: trayIcon is NULL. TabMessage::showSystemPopup failed"; + qCWarning(TabMessageLog) << "Error: trayIcon is NULL. TabMessage::showSystemPopup failed"; } } diff --git a/cockatrice/src/client/tabs/tab_message.h b/cockatrice/src/client/tabs/tab_message.h index 79653ffcc..dd7424e5d 100644 --- a/cockatrice/src/client/tabs/tab_message.h +++ b/cockatrice/src/client/tabs/tab_message.h @@ -3,6 +3,10 @@ #include "tab.h" +#include + +inline Q_LOGGING_CATEGORY(TabMessageLog, "tab_message"); + class AbstractClient; class ChatView; class LineEditUnfocusable; @@ -29,7 +33,6 @@ signals: void maximizeClient(); private slots: void sendMessage(); - void actLeave(); void messageSent(const Response &response); void addMentionTag(QString mentionTag); void messageClicked(); @@ -39,12 +42,12 @@ public: AbstractClient *_client, const ServerInfo_User &_ownUserInfo, const ServerInfo_User &_otherUserInfo); - ~TabMessage(); - void retranslateUi(); - void closeRequest(); - void tabActivated(); + ~TabMessage() override; + void retranslateUi() override; + void closeRequest(bool forced = false) override; + void tabActivated() override; QString getUserName() const; - QString getTabText() const; + QString getTabText() const override; void processUserMessageEvent(const Event_UserMessage &event); diff --git a/cockatrice/src/client/tabs/tab_replays.cpp b/cockatrice/src/client/tabs/tab_replays.cpp index 3a3655d79..8081883cd 100644 --- a/cockatrice/src/client/tabs/tab_replays.cpp +++ b/cockatrice/src/client/tabs/tab_replays.cpp @@ -15,6 +15,7 @@ #include #include +#include #include #include #include @@ -23,9 +24,11 @@ #include #include #include +#include #include -TabReplays::TabReplays(TabSupervisor *_tabSupervisor, AbstractClient *_client) : Tab(_tabSupervisor), client(_client) +TabReplays::TabReplays(TabSupervisor *_tabSupervisor, AbstractClient *_client, const ServerInfo_User *currentUserInfo) + : Tab(_tabSupervisor), client(_client) { localDirModel = new QFileSystemModel(this); localDirModel->setRootPath(SettingsCache::instance().getReplaysPath()); @@ -36,16 +39,31 @@ TabReplays::TabReplays(TabSupervisor *_tabSupervisor, AbstractClient *_client) : localDirView->setColumnHidden(1, true); localDirView->setRootIndex(localDirModel->index(localDirModel->rootPath(), 0)); localDirView->setSortingEnabled(true); + localDirView->setSelectionMode(QAbstractItemView::ExtendedSelection); localDirView->header()->setSectionResizeMode(QHeaderView::ResizeToContents); localDirView->header()->setSortIndicator(0, Qt::AscendingOrder); - leftToolBar = new QToolBar; + // Left side layout + /* put an invisible dummy QToolBar in the leftmost column so that the main toolbar is centered. + * Really ugly workaround, but I couldn't figure out the proper way to make it centered */ + QToolBar *dummyToolBar = new QToolBar(this); + QSizePolicy sizePolicy = dummyToolBar->sizePolicy(); + sizePolicy.setRetainSizeWhenHidden(true); + dummyToolBar->setSizePolicy(sizePolicy); + dummyToolBar->setVisible(false); + + leftToolBar = new QToolBar(this); leftToolBar->setOrientation(Qt::Horizontal); leftToolBar->setIconSize(QSize(32, 32)); - QHBoxLayout *leftToolBarLayout = new QHBoxLayout; - leftToolBarLayout->addStretch(); - leftToolBarLayout->addWidget(leftToolBar); - leftToolBarLayout->addStretch(); + + QToolBar *leftRightmostToolBar = new QToolBar(this); + leftRightmostToolBar->setOrientation(Qt::Horizontal); + leftRightmostToolBar->setIconSize(QSize(32, 32)); + + QGridLayout *leftToolBarLayout = new QGridLayout; + leftToolBarLayout->addWidget(dummyToolBar, 0, 0, Qt::AlignLeft); + leftToolBarLayout->addWidget(leftToolBar, 0, 1, Qt::AlignHCenter); + leftToolBarLayout->addWidget(leftRightmostToolBar, 0, 2, Qt::AlignRight); QVBoxLayout *leftVbox = new QVBoxLayout; leftVbox->addWidget(localDirView); @@ -53,6 +71,7 @@ TabReplays::TabReplays(TabSupervisor *_tabSupervisor, AbstractClient *_client) : leftGroupBox = new QGroupBox; leftGroupBox->setLayout(leftVbox); + // Right side layout rightToolBar = new QToolBar; rightToolBar->setOrientation(Qt::Horizontal); rightToolBar->setIconSize(QSize(32, 32)); @@ -69,17 +88,31 @@ TabReplays::TabReplays(TabSupervisor *_tabSupervisor, AbstractClient *_client) : rightGroupBox = new QGroupBox; rightGroupBox->setLayout(rightVbox); + // combine layouts QHBoxLayout *hbox = new QHBoxLayout; hbox->addWidget(leftGroupBox); hbox->addWidget(rightGroupBox); + // Left side actions aOpenLocalReplay = new QAction(this); aOpenLocalReplay->setIcon(QPixmap("theme:icons/view")); connect(aOpenLocalReplay, SIGNAL(triggered()), this, SLOT(actOpenLocalReplay())); connect(localDirView, SIGNAL(doubleClicked(const QModelIndex &)), this, SLOT(actOpenLocalReplay())); + aRenameLocal = new QAction(this); + aRenameLocal->setIcon(QPixmap("theme:icons/rename")); + connect(aRenameLocal, &QAction::triggered, this, &TabReplays::actRenameLocal); + aNewLocalFolder = new QAction(this); + aNewLocalFolder->setIcon(qApp->style()->standardIcon(QStyle::SP_FileDialogNewFolder)); + connect(aNewLocalFolder, &QAction::triggered, this, &TabReplays::actNewLocalFolder); aDeleteLocalReplay = new QAction(this); aDeleteLocalReplay->setIcon(QPixmap("theme:icons/remove_row")); connect(aDeleteLocalReplay, SIGNAL(triggered()), this, SLOT(actDeleteLocalReplay())); + + aOpenReplaysFolder = new QAction(this); + aOpenReplaysFolder->setIcon(qApp->style()->standardIcon(QStyle::SP_DirOpenIcon)); + connect(aOpenReplaysFolder, &QAction::triggered, this, &TabReplays::actOpenReplaysFolder); + + // Right side actions aOpenRemoteReplay = new QAction(this); aOpenRemoteReplay->setIcon(QPixmap("theme:icons/view")); connect(aOpenRemoteReplay, SIGNAL(triggered()), this, SLOT(actOpenRemoteReplay())); @@ -94,8 +127,14 @@ TabReplays::TabReplays(TabSupervisor *_tabSupervisor, AbstractClient *_client) : aDeleteRemoteReplay->setIcon(QPixmap("theme:icons/remove_row")); connect(aDeleteRemoteReplay, SIGNAL(triggered()), this, SLOT(actDeleteRemoteReplay())); + // Add actions to toolbars leftToolBar->addAction(aOpenLocalReplay); + leftToolBar->addAction(aRenameLocal); + leftToolBar->addAction(aNewLocalFolder); leftToolBar->addAction(aDeleteLocalReplay); + + leftRightmostToolBar->addAction(aOpenReplaysFolder); + rightToolBar->addAction(aOpenRemoteReplay); rightToolBar->addAction(aDownload); rightToolBar->addAction(aKeep); @@ -109,6 +148,10 @@ TabReplays::TabReplays(TabSupervisor *_tabSupervisor, AbstractClient *_client) : connect(client, SIGNAL(replayAddedEventReceived(const Event_ReplayAdded &)), this, SLOT(replayAddedEventReceived(const Event_ReplayAdded &))); + + connect(client, &AbstractClient::userInfoChanged, this, &TabReplays::handleConnected); + connect(client, &AbstractClient::statusChanged, this, &TabReplays::handleConnectionChanged); + setRemoteEnabled(currentUserInfo && currentUserInfo->user_level() & ServerInfo_User::IsRegistered); } void TabReplays::retranslateUi() @@ -117,59 +160,173 @@ void TabReplays::retranslateUi() rightGroupBox->setTitle(tr("Server replay storage")); aOpenLocalReplay->setText(tr("Watch replay")); + aRenameLocal->setText(tr("Rename")); + aNewLocalFolder->setText(tr("New folder")); aDeleteLocalReplay->setText(tr("Delete")); + aOpenReplaysFolder->setText(tr("Open replays folder")); aOpenRemoteReplay->setText(tr("Watch replay")); aDownload->setText(tr("Download replay")); aKeep->setText(tr("Toggle expiration lock")); aDeleteRemoteReplay->setText(tr("Delete")); } +void TabReplays::handleConnected(const ServerInfo_User &userInfo) +{ + setRemoteEnabled(userInfo.user_level() & ServerInfo_User::IsRegistered); +} + +/** + * This is only responsible for handling the disconnect. The connect is already handled elsewhere + */ +void TabReplays::handleConnectionChanged(ClientStatus status) +{ + if (status == StatusDisconnected) { + setRemoteEnabled(false); + } +} + +void TabReplays::setRemoteEnabled(bool enabled) +{ + aOpenRemoteReplay->setEnabled(enabled); + aDownload->setEnabled(enabled); + aKeep->setEnabled(enabled); + aDeleteRemoteReplay->setEnabled(enabled); + + if (enabled) { + serverDirView->refreshTree(); + } else { + serverDirView->clearTree(); + } +} + +void TabReplays::actLocalDoubleClick(const QModelIndex &curLeft) +{ + if (!localDirModel->isDir(curLeft)) { + actOpenLocalReplay(); + } +} + void TabReplays::actOpenLocalReplay() +{ + QModelIndexList curLefts = localDirView->selectionModel()->selectedRows(); + for (const auto &curLeft : curLefts) { + if (localDirModel->isDir(curLeft)) + continue; + QString filePath = localDirModel->filePath(curLeft); + + QFile f(filePath); + if (!f.open(QIODevice::ReadOnly)) + continue; + QByteArray _data = f.readAll(); + f.close(); + + GameReplay *replay = new GameReplay; + replay->ParseFromArray(_data.data(), _data.size()); + + emit openReplay(replay); + } +} + +void TabReplays::actRenameLocal() +{ + QModelIndexList curLefts = localDirView->selectionModel()->selectedRows(); + for (const auto &curLeft : curLefts) { + const QFileInfo info = localDirModel->fileInfo(curLeft); + + const QString oldName = info.baseName(); + const QString title = info.isDir() ? tr("Rename local folder") : tr("Rename local file"); + + bool ok; + QString newName = QInputDialog::getText(this, title, tr("New name:"), QLineEdit::Normal, oldName, &ok); + if (!ok) { // terminate all remaining selections if user cancels + return; + } + if (newName.isEmpty() || oldName == newName) { + continue; + } + + QString newFileName = newName; + if (!info.suffix().isEmpty()) { + newFileName += "." + info.suffix(); + } + const QString newFilePath = QFileInfo(info.dir(), newFileName).filePath(); + + if (!QFile::rename(info.filePath(), newFilePath)) { + QMessageBox::critical(this, tr("Error"), tr("Rename failed")); + } + } +} + +void TabReplays::actNewLocalFolder() { QModelIndex curLeft = localDirView->selectionModel()->currentIndex(); - if (localDirModel->isDir(curLeft)) + + QModelIndex dirIndex; + if (curLeft.isValid() && !localDirModel->isDir(curLeft)) { + dirIndex = curLeft.parent(); + } else { + dirIndex = curLeft; + } + + bool ok; + QString folderName = + QInputDialog::getText(this, tr("New folder"), tr("Name of new folder:"), QLineEdit::Normal, "", &ok); + if (!ok || folderName.isEmpty()) return; - QString filePath = localDirModel->filePath(curLeft); - QFile f(filePath); - if (!f.open(QIODevice::ReadOnly)) - return; - QByteArray _data = f.readAll(); - f.close(); - - GameReplay *replay = new GameReplay; - replay->ParseFromArray(_data.data(), _data.size()); - - emit openReplay(replay); + localDirModel->mkdir(dirIndex, folderName); } void TabReplays::actDeleteLocalReplay() { - QModelIndex curLeft = localDirView->selectionModel()->currentIndex(); - if (!curLeft.isValid()) - return; + QModelIndexList curLefts = localDirView->selectionModel()->selectedRows(); - if (QMessageBox::warning(this, tr("Delete local file"), - tr("Are you sure you want to delete \"%1\"?").arg(localDirModel->fileName(curLeft)), - QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes) + if (curLefts.isEmpty()) { return; + } - localDirModel->remove(curLeft); + if (QMessageBox::warning(this, tr("Delete local file"), tr("Are you sure you want to delete the selected files?"), + QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes) { + return; + } + + for (const auto &curLeft : curLefts) { + if (curLeft.isValid()) { + localDirModel->remove(curLeft); + } + } +} + +void TabReplays::actOpenReplaysFolder() +{ + QString dir = localDirModel->rootPath(); + QDesktopServices::openUrl(QUrl::fromLocalFile(dir)); +} + +void TabReplays::actRemoteDoubleClick(const QModelIndex &curRight) +{ + if (serverDirView->getReplay(curRight)) { + actOpenRemoteReplay(); + } } void TabReplays::actOpenRemoteReplay() { - ServerInfo_Replay const *curRight = serverDirView->getCurrentReplay(); - if (!curRight) - return; + auto const curRights = serverDirView->getSelectedReplays(); - Command_ReplayDownload cmd; - cmd.set_replay_id(curRight->replay_id()); + for (const auto curRight : curRights) { + if (!curRight) { + continue; + } - PendingCommand *pend = client->prepareSessionCommand(cmd); - connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this, - SLOT(openRemoteReplayFinished(const Response &))); - client->sendCommand(pend); + Command_ReplayDownload cmd; + cmd.set_replay_id(curRight->replay_id()); + + PendingCommand *pend = client->prepareSessionCommand(cmd); + connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this, + SLOT(openRemoteReplayFinished(const Response &))); + client->sendCommand(pend); + } } void TabReplays::openRemoteReplayFinished(const Response &r) @@ -186,34 +343,46 @@ void TabReplays::openRemoteReplayFinished(const Response &r) void TabReplays::actDownload() { - QString filePath; QModelIndex curLeft = localDirView->selectionModel()->currentIndex(); - if (!curLeft.isValid()) - filePath = localDirModel->rootPath(); - else { - while (!localDirModel->isDir(curLeft)) - curLeft = curLeft.parent(); - filePath = localDirModel->filePath(curLeft); + while (!localDirModel->isDir(curLeft)) { + curLeft = curLeft.parent(); } - ServerInfo_Replay const *curRight = serverDirView->getCurrentReplay(); - - if (!curRight) { - QMessageBox::information(this, tr("Downloading Replays"), - tr("Folder download is not yet supported. Please download replays individually.")); - return; + for (const auto curRight : serverDirView->selectionModel()->selectedRows()) { + downloadNodeAtIndex(curLeft, curRight); } +} - filePath += QString("/replay_%1.cor").arg(curRight->replay_id()); +void TabReplays::downloadNodeAtIndex(const QModelIndex &curLeft, const QModelIndex &curRight) +{ + if (const auto replayMatch = serverDirView->getReplayMatch(curRight)) { + // node at index is a folder + const QString name = + QString::number(replayMatch->game_id()) + "_" + QString::fromStdString(replayMatch->game_name()); - Command_ReplayDownload cmd; - cmd.set_replay_id(curRight->replay_id()); + const auto dirIndex = curLeft.isValid() ? curLeft : localDirModel->index(localDirModel->rootPath()); + const auto newDirIndex = localDirModel->mkdir(dirIndex, name); - PendingCommand *pend = client->prepareSessionCommand(cmd); - pend->setExtraData(filePath); - connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this, - SLOT(downloadFinished(Response, CommandContainer, QVariant))); - client->sendCommand(pend); + int rows = serverDirView->model()->rowCount(curRight); + for (int i = 0; i < rows; i++) { + const auto childIndex = serverDirView->model()->index(i, 0, curRight); + downloadNodeAtIndex(newDirIndex, childIndex); + } + } else if (const auto replay = serverDirView->getReplay(curRight)) { + // node at index is a replay + const QString dirPath = curLeft.isValid() ? localDirModel->filePath(curLeft) : localDirModel->rootPath(); + const QString filePath = dirPath + QString("/replay_%1.cor").arg(replay->replay_id()); + + Command_ReplayDownload cmd; + cmd.set_replay_id(replay->replay_id()); + + PendingCommand *pend = client->prepareSessionCommand(cmd); + pend->setExtraData(filePath); + connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this, + SLOT(downloadFinished(Response, CommandContainer, QVariant))); + client->sendCommand(pend); + } + // node at index was invalid } void TabReplays::downloadFinished(const Response &r, @@ -235,18 +404,22 @@ void TabReplays::downloadFinished(const Response &r, void TabReplays::actKeepRemoteReplay() { - ServerInfo_ReplayMatch const *curRight = serverDirView->getCurrentReplayMatch(); - if (!curRight) + const auto curRights = serverDirView->getSelectedReplayMatches(); + + if (curRights.isEmpty()) { return; + } - Command_ReplayModifyMatch cmd; - cmd.set_game_id(curRight->game_id()); - cmd.set_do_not_hide(!curRight->do_not_hide()); + for (const auto curRight : curRights) { + Command_ReplayModifyMatch cmd; + cmd.set_game_id(curRight->game_id()); + cmd.set_do_not_hide(!curRight->do_not_hide()); - PendingCommand *pend = client->prepareSessionCommand(cmd); - connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this, - SLOT(keepRemoteReplayFinished(Response, CommandContainer))); - client->sendCommand(pend); + PendingCommand *pend = client->prepareSessionCommand(cmd); + connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this, + SLOT(keepRemoteReplayFinished(Response, CommandContainer))); + client->sendCommand(pend); + } } void TabReplays::keepRemoteReplayFinished(const Response &r, const CommandContainer &commandContainer) @@ -265,21 +438,27 @@ void TabReplays::keepRemoteReplayFinished(const Response &r, const CommandContai void TabReplays::actDeleteRemoteReplay() { - ServerInfo_ReplayMatch const *curRight = serverDirView->getCurrentReplayMatch(); - if (!curRight) + const auto curRights = serverDirView->getSelectedReplayMatches(); + + if (curRights.isEmpty()) { return; + } + if (QMessageBox::warning(this, tr("Delete remote replay"), - tr("Are you sure you want to delete the replay of game %1?").arg(curRight->game_id()), - QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes) + tr("Are you sure you want to delete the selected replays?"), + QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes) { return; + } - Command_ReplayDeleteMatch cmd; - cmd.set_game_id(curRight->game_id()); + for (const auto curRight : curRights) { + Command_ReplayDeleteMatch cmd; + cmd.set_game_id(curRight->game_id()); - PendingCommand *pend = client->prepareSessionCommand(cmd); - connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this, - SLOT(deleteRemoteReplayFinished(Response, CommandContainer))); - client->sendCommand(pend); + PendingCommand *pend = client->prepareSessionCommand(cmd); + connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this, + SLOT(deleteRemoteReplayFinished(Response, CommandContainer))); + client->sendCommand(pend); + } } void TabReplays::deleteRemoteReplayFinished(const Response &r, const CommandContainer &commandContainer) @@ -294,5 +473,11 @@ void TabReplays::deleteRemoteReplayFinished(const Response &r, const CommandCont void TabReplays::replayAddedEventReceived(const Event_ReplayAdded &event) { - serverDirView->addMatchInfo(event.match_info()); + if (event.has_match_info()) { + // 99.9% of events will have match info (Normal Workflow) + serverDirView->addMatchInfo(event.match_info()); + } else { + // When a Moderator force adds a replay, we need to refresh their view + serverDirView->refreshTree(); + } } diff --git a/cockatrice/src/client/tabs/tab_replays.h b/cockatrice/src/client/tabs/tab_replays.h index 4641e69e3..7014a7f6e 100644 --- a/cockatrice/src/client/tabs/tab_replays.h +++ b/cockatrice/src/client/tabs/tab_replays.h @@ -1,8 +1,10 @@ #ifndef TAB_REPLAYS_H #define TAB_REPLAYS_H +#include "../game_logic/abstract_client.h" #include "tab.h" +class ServerInfo_User; class Response; class AbstractClient; class QTreeView; @@ -25,12 +27,27 @@ private: RemoteReplayList_TreeWidget *serverDirView; QGroupBox *leftGroupBox, *rightGroupBox; - QAction *aOpenLocalReplay, *aDeleteLocalReplay, *aOpenRemoteReplay, *aDownload, *aKeep, *aDeleteRemoteReplay; -private slots: - void actOpenLocalReplay(); + QAction *aOpenLocalReplay, *aRenameLocal, *aNewLocalFolder, *aDeleteLocalReplay; + QAction *aOpenReplaysFolder; + QAction *aOpenRemoteReplay, *aDownload, *aKeep, *aDeleteRemoteReplay; + void setRemoteEnabled(bool enabled); + + void downloadNodeAtIndex(const QModelIndex &curLeft, const QModelIndex &curRight); + +private slots: + void handleConnected(const ServerInfo_User &userInfo); + void handleConnectionChanged(ClientStatus status); + + void actLocalDoubleClick(const QModelIndex &curLeft); + void actRenameLocal(); + void actOpenLocalReplay(); + void actNewLocalFolder(); void actDeleteLocalReplay(); + void actOpenReplaysFolder(); + + void actRemoteDoubleClick(const QModelIndex &curLeft); void actOpenRemoteReplay(); void openRemoteReplayFinished(const Response &r); @@ -48,11 +65,11 @@ signals: void openReplay(GameReplay *replay); public: - TabReplays(TabSupervisor *_tabSupervisor, AbstractClient *_client); - void retranslateUi(); - QString getTabText() const + TabReplays(TabSupervisor *_tabSupervisor, AbstractClient *_client, const ServerInfo_User *currentUserInfo); + void retranslateUi() override; + QString getTabText() const override { - return tr("Game replays"); + return tr("Game Replays"); } }; diff --git a/cockatrice/src/client/tabs/tab_room.cpp b/cockatrice/src/client/tabs/tab_room.cpp index 034a236ce..02243cd19 100644 --- a/cockatrice/src/client/tabs/tab_room.cpp +++ b/cockatrice/src/client/tabs/tab_room.cpp @@ -6,7 +6,8 @@ #include "../../main.h" #include "../../server/chat_view/chat_view.h" #include "../../server/pending_command.h" -#include "../../server/user/user_list.h" +#include "../../server/user/user_list_manager.h" +#include "../../server/user/user_list_widget.h" #include "../../settings/cache_settings.h" #include "get_pb_extension.h" #include "pb/event_join_room.pb.h" @@ -37,7 +38,7 @@ TabRoom::TabRoom(TabSupervisor *_tabSupervisor, ServerInfo_User *_ownUser, const ServerInfo_Room &info) : Tab(_tabSupervisor), client(_client), roomId(info.room_id()), roomName(QString::fromStdString(info.name())), - ownUser(_ownUser) + ownUser(_ownUser), userListProxy(_tabSupervisor->getUserListManager()) { const int gameTypeListSize = info.gametype_list_size(); for (int i = 0; i < gameTypeListSize; ++i) @@ -47,15 +48,15 @@ TabRoom::TabRoom(TabSupervisor *_tabSupervisor, QMap tempMap; tempMap.insert(info.room_id(), gameTypes); gameSelector = new GameSelector(client, tabSupervisor, this, QMap(), tempMap, true, true); - userList = new UserList(tabSupervisor, client, UserList::RoomList); + userList = new UserListWidget(tabSupervisor, client, UserListWidget::RoomList); connect(userList, SIGNAL(openMessageDialog(const QString &, bool)), this, SIGNAL(openMessageDialog(const QString &, bool))); - chatView = new ChatView(tabSupervisor, tabSupervisor, nullptr, true, this); + chatView = new ChatView(tabSupervisor, nullptr, true, this); connect(chatView, SIGNAL(showMentionPopup(const QString &)), this, SLOT(actShowMentionPopup(const QString &))); connect(chatView, SIGNAL(messageClickedSignal()), this, SLOT(focusTab())); connect(chatView, SIGNAL(openMessageDialog(QString, bool)), this, SIGNAL(openMessageDialog(QString, bool))); - connect(chatView, SIGNAL(showCardInfoPopup(QPoint, QString)), this, SLOT(showCardInfoPopup(QPoint, QString))); + connect(chatView, &ChatView::showCardInfoPopup, this, &TabRoom::showCardInfoPopup); connect(chatView, SIGNAL(deleteCardInfoPopup(QString)), this, SLOT(deleteCardInfoPopup(QString))); connect(chatView, SIGNAL(addMentionTag(QString)), this, SLOT(addMentionTag(QString))); connect(&SettingsCache::instance(), SIGNAL(chatMentionCompleterChanged()), this, SLOT(actCompleterChanged())); @@ -101,7 +102,7 @@ TabRoom::TabRoom(TabSupervisor *_tabSupervisor, hbox->addWidget(userList, 1); aLeaveRoom = new QAction(this); - connect(aLeaveRoom, SIGNAL(triggered()), this, SLOT(actLeaveRoom())); + connect(aLeaveRoom, &QAction::triggered, this, [this] { closeRequest(); }); roomMenu = new QMenu(this); roomMenu->addAction(aLeaveRoom); @@ -135,11 +136,6 @@ TabRoom::TabRoom(TabSupervisor *_tabSupervisor, setCentralWidget(mainWidget); } -TabRoom::~TabRoom() -{ - emit roomClosing(this); -} - void TabRoom::retranslateUi() { gameSelector->retranslateUi(); @@ -175,9 +171,11 @@ void TabRoom::actShowPopup(const QString &message) } } -void TabRoom::closeRequest() +void TabRoom::closeRequest(bool /*forced*/) { - actLeaveRoom(); + sendRoomCommand(prepareRoomCommand(Command_LeaveRoom())); + emit roomClosing(this); + close(); } void TabRoom::tabActivated() @@ -216,12 +214,6 @@ void TabRoom::sayFinished(const Response &response) chatView->appendMessage(tr("You are flooding the chat. Please wait a couple of seconds.")); } -void TabRoom::actLeaveRoom() -{ - sendRoomCommand(prepareRoomCommand(Command_LeaveRoom())); - deleteLater(); -} - void TabRoom::actClearChat() { chatView->clearChat(); @@ -291,17 +283,15 @@ void TabRoom::processRoomSayEvent(const Event_RoomSay &event) QString senderName = QString::fromStdString(event.name()); QString message = QString::fromStdString(event.message()); - if (tabSupervisor->getUserListsTab()->getIgnoreList()->getUsers().contains(senderName)) + if (userListProxy->isUserIgnored(senderName)) return; UserListTWI *twi = userList->getUsers().value(senderName); - UserLevelFlags userLevel; - QString userPrivLevel; + ServerInfo_User userInfo = {}; if (twi) { - userLevel = UserLevelFlags(twi->getUserInfo().user_level()); - userPrivLevel = QString::fromStdString(twi->getUserInfo().privlevel()); + userInfo = twi->getUserInfo(); if (SettingsCache::instance().getIgnoreUnregisteredUsers() && - !userLevel.testFlag(ServerInfo_User::IsRegistered)) + !UserLevelFlags(userInfo.user_level()).testFlag(ServerInfo_User::IsRegistered)) return; } @@ -314,7 +304,7 @@ void TabRoom::processRoomSayEvent(const Event_RoomSay &event) QString(QDateTime::fromMSecsSinceEpoch(event.time_of()).toLocalTime().toString("d MMM yyyy HH:mm:ss")) + "] " + message; - chatView->appendMessage(message, event.message_type(), senderName, userLevel, userPrivLevel, true); + chatView->appendMessage(message, event.message_type(), userInfo, true); emit userEvent(false); } diff --git a/cockatrice/src/client/tabs/tab_room.h b/cockatrice/src/client/tabs/tab_room.h index a01943742..ca88a256f 100644 --- a/cockatrice/src/client/tabs/tab_room.h +++ b/cockatrice/src/client/tabs/tab_room.h @@ -9,6 +9,8 @@ #include #include +class UserListProxy; +class UserListManager; namespace google { namespace protobuf @@ -17,7 +19,7 @@ class Message; } } // namespace google class AbstractClient; -class UserList; +class UserListWidget; class QLabel; class ChatView; class QPushButton; @@ -48,7 +50,8 @@ private: QMap gameTypes; GameSelector *gameSelector; - UserList *userList; + UserListWidget *userList; + const UserListProxy *userListProxy; ChatView *chatView; QLabel *sayLabel; LineEditCompleter *sayEdit; @@ -70,7 +73,6 @@ signals: private slots: void sendMessage(); void sayFinished(const Response &response); - void actLeaveRoom(); void actClearChat(); void actOpenChatSettings(); void addMentionTag(QString mentionTag); @@ -91,10 +93,9 @@ public: AbstractClient *_client, ServerInfo_User *_ownUser, const ServerInfo_Room &info); - ~TabRoom(); - void retranslateUi(); - void closeRequest(); - void tabActivated(); + void retranslateUi() override; + void closeRequest(bool forced = false) override; + void tabActivated() override; void processRoomEvent(const RoomEvent &event); int getRoomId() const { @@ -108,7 +109,7 @@ public: { return roomName; } - QString getTabText() const + QString getTabText() const override { return roomName; } diff --git a/cockatrice/src/client/tabs/tab_server.cpp b/cockatrice/src/client/tabs/tab_server.cpp index e0906e8d4..40fa401d2 100644 --- a/cockatrice/src/client/tabs/tab_server.cpp +++ b/cockatrice/src/client/tabs/tab_server.cpp @@ -2,7 +2,7 @@ #include "../../client/game_logic/abstract_client.h" #include "../../server/pending_command.h" -#include "../../server/user/user_list.h" +#include "../../server/user/user_list_widget.h" #include "pb/event_list_rooms.pb.h" #include "pb/event_server_message.pb.h" #include "pb/response_join_room.pb.h" @@ -138,8 +138,7 @@ void RoomSelector::joinClicked() emit joinRoomRequest(id, true); } -TabServer::TabServer(TabSupervisor *_tabSupervisor, AbstractClient *_client, QWidget *parent) - : Tab(_tabSupervisor, parent), client(_client) +TabServer::TabServer(TabSupervisor *_tabSupervisor, AbstractClient *_client) : Tab(_tabSupervisor), client(_client) { roomSelector = new RoomSelector(client); serverInfoBox = new QTextBrowser; diff --git a/cockatrice/src/client/tabs/tab_server.h b/cockatrice/src/client/tabs/tab_server.h index aa2a17c1e..51df54397 100644 --- a/cockatrice/src/client/tabs/tab_server.h +++ b/cockatrice/src/client/tabs/tab_server.h @@ -10,7 +10,7 @@ class AbstractClient; class QTextEdit; class QLabel; -class UserList; +class UserListWidget; class QPushButton; class Event_ListRooms; @@ -34,7 +34,7 @@ signals: void joinRoomRequest(int, bool setCurrent); public: - RoomSelector(AbstractClient *_client, QWidget *parent = nullptr); + explicit RoomSelector(AbstractClient *_client, QWidget *parent = nullptr); void retranslateUi(); }; @@ -55,9 +55,9 @@ private: bool shouldEmitUpdate = false; public: - TabServer(TabSupervisor *_tabSupervisor, AbstractClient *_client, QWidget *parent = nullptr); - void retranslateUi(); - QString getTabText() const + TabServer(TabSupervisor *_tabSupervisor, AbstractClient *_client); + void retranslateUi() override; + QString getTabText() const override { return tr("Server"); } diff --git a/cockatrice/src/client/tabs/tab_supervisor.cpp b/cockatrice/src/client/tabs/tab_supervisor.cpp index 9ef7ee385..e7be2a1ff 100644 --- a/cockatrice/src/client/tabs/tab_supervisor.cpp +++ b/cockatrice/src/client/tabs/tab_supervisor.cpp @@ -2,14 +2,15 @@ #include "../../client/game_logic/abstract_client.h" #include "../../main.h" -#include "../../server/user/user_list.h" +#include "../../server/user/user_list_manager.h" +#include "../../server/user/user_list_widget.h" #include "../../settings/cache_settings.h" #include "../ui/pixel_map_generator.h" #include "pb/event_game_joined.pb.h" #include "pb/event_notify_user.pb.h" #include "pb/event_user_message.pb.h" #include "pb/game_event_container.pb.h" -#include "pb/moderator_commands.pb.h" +#include "pb/game_replay.pb.h" #include "pb/room_commands.pb.h" #include "pb/room_event.pb.h" #include "pb/serverinfo_room.pb.h" @@ -24,9 +25,9 @@ #include "tab_replays.h" #include "tab_room.h" #include "tab_server.h" +#include "visual_deck_storage/tab_deck_storage_visual.h" #include -#include #include #include #include @@ -47,15 +48,15 @@ CloseButton::CloseButton(QWidget *parent) : QAbstractButton(parent) { setFocusPolicy(Qt::NoFocus); setCursor(Qt::ArrowCursor); - resize(sizeHint()); + resize(this->sizeHint()); } QSize CloseButton::sizeHint() const { ensurePolished(); - int width = style()->pixelMetric(QStyle::PM_TabCloseIndicatorWidth, 0, this); - int height = style()->pixelMetric(QStyle::PM_TabCloseIndicatorHeight, 0, this); - return QSize(width, height); + int width = style()->pixelMetric(QStyle::PM_TabCloseIndicatorWidth, nullptr, this); + int height = style()->pixelMetric(QStyle::PM_TabCloseIndicatorHeight, nullptr, this); + return {width, height}; } #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) @@ -87,10 +88,9 @@ void CloseButton::paintEvent(QPaintEvent * /*event*/) if (isDown()) opt.state |= QStyle::State_Sunken; - if (const QTabBar *tb = qobject_cast(parent())) { + if (const auto *tb = qobject_cast(parent())) { int index = tb->currentIndex(); - QTabBar::ButtonPosition position = - (QTabBar::ButtonPosition)style()->styleHint(QStyle::SH_TabBar_CloseButtonPosition, 0, tb); + auto position = (QTabBar::ButtonPosition)style()->styleHint(QStyle::SH_TabBar_CloseButtonPosition, nullptr, tb); if (tb->tabButton(index, position) == this) opt.state |= QStyle::State_Selected; } @@ -98,9 +98,10 @@ void CloseButton::paintEvent(QPaintEvent * /*event*/) style()->drawPrimitive(QStyle::PE_IndicatorTabClose, &opt, &p, this); } -TabSupervisor::TabSupervisor(AbstractClient *_client, QWidget *parent) - : QTabWidget(parent), userInfo(0), client(_client), tabServer(0), tabUserLists(0), tabDeckStorage(0), tabReplays(0), - tabAdmin(0), tabLog(0) +TabSupervisor::TabSupervisor(AbstractClient *_client, QMenu *tabsMenu, QWidget *parent) + : QTabWidget(parent), userInfo(nullptr), client(_client), tabsMenu(tabsMenu), tabVisualDeckStorage(nullptr), + tabServer(nullptr), tabAccount(nullptr), tabDeckStorage(nullptr), tabReplays(nullptr), tabAdmin(nullptr), + tabLog(nullptr), isLocalGame(false) { setElideMode(Qt::ElideRight); setMovable(true); @@ -112,18 +113,56 @@ TabSupervisor::TabSupervisor(AbstractClient *_client, QWidget *parent) tabBar()->setStyle(new MacOSTabFixStyle); #endif - connect(this, SIGNAL(currentChanged(int)), this, SLOT(updateCurrent(int))); + userListManager = new UserListManager(client, this); - connect(client, SIGNAL(roomEventReceived(const RoomEvent &)), this, SLOT(processRoomEvent(const RoomEvent &))); - connect(client, SIGNAL(gameEventContainerReceived(const GameEventContainer &)), this, - SLOT(processGameEventContainer(const GameEventContainer &))); - connect(client, SIGNAL(gameJoinedEventReceived(const Event_GameJoined &)), this, - SLOT(gameJoined(const Event_GameJoined &))); - connect(client, SIGNAL(userMessageEventReceived(const Event_UserMessage &)), this, - SLOT(processUserMessageEvent(const Event_UserMessage &))); - connect(client, SIGNAL(maxPingTime(int, int)), this, SLOT(updatePingTime(int, int))); - connect(client, SIGNAL(notifyUserEventReceived(const Event_NotifyUser &)), this, - SLOT(processNotifyUserEvent(const Event_NotifyUser &))); + // connect tab changes + connect(this, &TabSupervisor::currentChanged, this, &TabSupervisor::updateCurrent); + + // connect client + connect(client, &AbstractClient::roomEventReceived, this, &TabSupervisor::processRoomEvent); + connect(client, &AbstractClient::gameEventContainerReceived, this, &TabSupervisor::processGameEventContainer); + connect(client, &AbstractClient::gameJoinedEventReceived, this, &TabSupervisor::gameJoined); + connect(client, &AbstractClient::userMessageEventReceived, this, &TabSupervisor::processUserMessageEvent); + connect(client, &AbstractClient::maxPingTime, this, &TabSupervisor::updatePingTime); + connect(client, &AbstractClient::notifyUserEventReceived, this, &TabSupervisor::processNotifyUserEvent); + + // create tabs menu actions + aTabDeckEditor = new QAction(this); + connect(aTabDeckEditor, &QAction::triggered, this, [this] { addDeckEditorTab(nullptr); }); + + aTabVisualDeckStorage = new QAction(this); + aTabVisualDeckStorage->setCheckable(true); + connect(aTabVisualDeckStorage, &QAction::triggered, this, &TabSupervisor::actTabVisualDeckStorage); + + aTabServer = new QAction(this); + aTabServer->setCheckable(true); + connect(aTabServer, &QAction::triggered, this, &TabSupervisor::actTabServer); + + aTabAccount = new QAction(this); + aTabAccount->setCheckable(true); + connect(aTabAccount, &QAction::triggered, this, &TabSupervisor::actTabAccount); + + aTabDeckStorage = new QAction(this); + aTabDeckStorage->setCheckable(true); + connect(aTabDeckStorage, &QAction::triggered, this, &TabSupervisor::actTabDeckStorage); + + aTabReplays = new QAction(this); + aTabReplays->setCheckable(true); + connect(aTabReplays, &QAction::triggered, this, &TabSupervisor::actTabReplays); + + aTabAdmin = new QAction(this); + aTabAdmin->setCheckable(true); + connect(aTabAdmin, &QAction::triggered, this, &TabSupervisor::actTabAdmin); + + aTabLog = new QAction(this); + aTabLog->setCheckable(true); + connect(aTabLog, &QAction::triggered, this, &TabSupervisor::actTabLog); + + connect(&SettingsCache::instance().shortcuts(), &ShortcutsSettings::shortCutChanged, this, + &TabSupervisor::refreshShortcuts); + refreshShortcuts(); + + resetTabsMenu(); retranslateUi(); } @@ -135,12 +174,23 @@ TabSupervisor::~TabSupervisor() void TabSupervisor::retranslateUi() { + // tab menu actions + aTabDeckEditor->setText(tr("Deck Editor")); + aTabVisualDeckStorage->setText(tr("&Visual Deck Storage")); + aTabServer->setText(tr("Server")); + aTabAccount->setText(tr("Account")); + aTabDeckStorage->setText(tr("Deck Storage")); + aTabReplays->setText(tr("Game Replays")); + aTabAdmin->setText(tr("Administration")); + aTabLog->setText(tr("Logs")); + + // tabs QList tabs; tabs.append(tabServer); tabs.append(tabReplays); tabs.append(tabDeckStorage); tabs.append(tabAdmin); - tabs.append(tabUserLists); + tabs.append(tabAccount); tabs.append(tabLog); QMapIterator roomIterator(roomTabs); while (roomIterator.hasNext()) @@ -151,39 +201,76 @@ void TabSupervisor::retranslateUi() QListIterator replayIterator(replayTabs); while (replayIterator.hasNext()) tabs.append(replayIterator.next()); - QListIterator deckEditorIterator(deckEditorTabs); + QListIterator deckEditorIterator(deckEditorTabs); while (deckEditorIterator.hasNext()) tabs.append(deckEditorIterator.next()); QMapIterator messageIterator(messageTabs); while (messageIterator.hasNext()) tabs.append(messageIterator.next().value()); - for (int i = 0; i < tabs.size(); ++i) - if (tabs[i]) { - int idx = indexOf(tabs[i]); - QString tabText = tabs[i]->getTabText(); + for (auto &tab : tabs) { + if (tab) { + int idx = indexOf(tab); + QString tabText = tab->getTabText(); setTabText(idx, sanitizeTabName(tabText)); setTabToolTip(idx, sanitizeHtml(tabText)); - tabs[i]->retranslateUi(); + tab->retranslateUi(); } + } } -bool TabSupervisor::closeRequest() +void TabSupervisor::refreshShortcuts() { + ShortcutsSettings &shortcuts = SettingsCache::instance().shortcuts(); + aTabDeckEditor->setShortcuts(shortcuts.getShortcut("Tabs/aTabDeckEditor")); + aTabVisualDeckStorage->setShortcuts(shortcuts.getShortcut("Tabs/aTabVisualDeckStorage")); + aTabServer->setShortcuts(shortcuts.getShortcut("Tabs/aTabServer")); + aTabAccount->setShortcuts(shortcuts.getShortcut("Tabs/aTabAccount")); + aTabDeckStorage->setShortcuts(shortcuts.getShortcut("Tabs/aTabDeckStorage")); + aTabReplays->setShortcuts(shortcuts.getShortcut("Tabs/aTabReplays")); + aTabAdmin->setShortcuts(shortcuts.getShortcut("Tabs/aTabAdmin")); + aTabLog->setShortcuts(shortcuts.getShortcut("Tabs/aTabLog")); +} + +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; } } - foreach (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 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 @@ -191,71 +278,137 @@ AbstractClient *TabSupervisor::getClient() const return localClients.isEmpty() ? client : localClients.first(); } -QString TabSupervisor::sanitizeTabName(QString dirty) const +QString TabSupervisor::sanitizeTabName(QString dirty) { return dirty.replace("&", "&&"); } -QString TabSupervisor::sanitizeHtml(QString dirty) const +QString TabSupervisor::sanitizeHtml(QString dirty) { return dirty.replace("&", "&").replace("<", "<").replace(">", ">").replace("\"", """); } -int TabSupervisor::myAddTab(Tab *tab) +/** + * If the action is not in the target checked state, then set it to that state by triggering the action. + * If the action is already in the target checked state, then do nothing. + * + * This allows us to programmatically trigger a QAction::triggered signal for a specific checked state. + */ +static void checkAndTrigger(QAction *checkableAction, bool checked) { - connect(tab, SIGNAL(userEvent(bool)), this, SLOT(tabUserEvent(bool))); - connect(tab, SIGNAL(tabTextChanged(Tab *, QString)), this, SLOT(updateTabText(Tab *, QString))); + if (checkableAction->isChecked() != checked) { + checkableAction->trigger(); + } +} + +/** + * Opens the always-available tabs, depending on settings. + */ +void TabSupervisor::initStartupTabs() +{ + addDeckEditorTab(nullptr); + + if (SettingsCache::instance().getTabVisualDeckStorageOpen()) { + openTabVisualDeckStorage(); + } + if (SettingsCache::instance().getTabDeckStorageOpen()) { + openTabDeckStorage(); + } + if (SettingsCache::instance().getTabReplaysOpen()) { + openTabReplays(); + } +} + +/** + * Adds the tab to the TabSupervisor's tab bar. + * + * @param tab The Tab to add + * @param manager The menu action that corresponds to this tab, if this is a single-instance managed tab. Pass in + * nullptr if this is not a managed tab. + * @return The index of the added tab in the tab widget's tab menu + */ +int TabSupervisor::myAddTab(Tab *tab, QAction *manager) +{ + connect(tab, &TabGame::userEvent, this, &TabSupervisor::tabUserEvent); + connect(tab, &TabGame::tabTextChanged, this, &TabSupervisor::updateTabText); QString tabText = tab->getTabText(); int idx = addTab(tab, sanitizeTabName(tabText)); setTabToolTip(idx, sanitizeHtml(tabText)); + addCloseButtonToTab(tab, idx, manager); + return idx; } +/** + * Adds a usable close button to the tab. + * + * @param tab The Tab + * @param tabIndex The tab bar index of the tab + * @param manager The menu action that corresponds to this tab, if this is a single-instance managed tab. Pass in + * nullptr if this is not a managed tab. + */ +void TabSupervisor::addCloseButtonToTab(Tab *tab, int tabIndex, QAction *manager) +{ + auto closeSide = static_cast( + tabBar()->style()->styleHint(QStyle::SH_TabBar_CloseButtonPosition, nullptr, tabBar())); + auto *closeButton = new CloseButton(tab); + if (manager) { + // If managed, all close requests should go through the menu action + connect(closeButton, &CloseButton::clicked, this, [manager] { checkAndTrigger(manager, false); }); + } else { + connect(closeButton, &CloseButton::clicked, tab, [tab] { tab->closeRequest(); }); + } + tabBar()->setTabButton(tabIndex, closeSide, closeButton); +} + +/** + * Resets the tabs menu to the tabs that are always available + */ +void TabSupervisor::resetTabsMenu() +{ + tabsMenu->clear(); + tabsMenu->addAction(aTabDeckEditor); + tabsMenu->addSeparator(); + tabsMenu->addAction(aTabVisualDeckStorage); + tabsMenu->addAction(aTabDeckStorage); + tabsMenu->addAction(aTabReplays); +} + void TabSupervisor::start(const ServerInfo_User &_userInfo) { isLocalGame = false; userInfo = new ServerInfo_User(_userInfo); - tabServer = new TabServer(this, client); - connect(tabServer, SIGNAL(roomJoined(const ServerInfo_Room &, bool)), this, - SLOT(addRoomTab(const ServerInfo_Room &, bool))); - myAddTab(tabServer); + userListManager->handleConnect(); - tabUserLists = new TabUserLists(this, client, *userInfo); - connect(tabUserLists, SIGNAL(openMessageDialog(const QString &, bool)), this, - SLOT(addMessageTab(const QString &, bool))); - connect(tabUserLists, SIGNAL(userJoined(ServerInfo_User)), this, SLOT(processUserJoined(ServerInfo_User))); - connect(tabUserLists, SIGNAL(userLeft(const QString &)), this, SLOT(processUserLeft(const QString &))); - myAddTab(tabUserLists); + resetTabsMenu(); + + tabsMenu->addSeparator(); + tabsMenu->addAction(aTabServer); + tabsMenu->addAction(aTabAccount); + + if (SettingsCache::instance().getTabServerOpen()) { + openTabServer(); + } + if (SettingsCache::instance().getTabAccountOpen()) { + openTabAccount(); + } updatePingTime(0, -1); - if (userInfo->user_level() & ServerInfo_User::IsRegistered) { - tabDeckStorage = new TabDeckStorage(this, client); - connect(tabDeckStorage, SIGNAL(openDeckEditor(const DeckLoader *)), this, - SLOT(addDeckEditorTab(const DeckLoader *))); - myAddTab(tabDeckStorage); - - tabReplays = new TabReplays(this, client); - connect(tabReplays, SIGNAL(openReplay(GameReplay *)), this, SLOT(openReplay(GameReplay *))); - myAddTab(tabReplays); - } else { - tabDeckStorage = 0; - tabReplays = 0; - } - if (userInfo->user_level() & ServerInfo_User::IsModerator) { - tabAdmin = new TabAdmin(this, client, (userInfo->user_level() & ServerInfo_User::IsAdmin)); - connect(tabAdmin, SIGNAL(adminLockChanged(bool)), this, SIGNAL(adminLockChanged(bool))); - myAddTab(tabAdmin); + tabsMenu->addSeparator(); + tabsMenu->addAction(aTabAdmin); + tabsMenu->addAction(aTabLog); - tabLog = new TabLog(this, client); - myAddTab(tabLog); - } else { - tabAdmin = 0; - tabLog = 0; + if (SettingsCache::instance().getTabAdminOpen()) { + openTabAdmin(); + } + if (SettingsCache::instance().getTabLogOpen()) { + openTabLog(); + } } retranslateUi(); @@ -263,70 +416,235 @@ void TabSupervisor::start(const ServerInfo_User &_userInfo) void TabSupervisor::startLocal(const QList &_clients) { - tabUserLists = 0; - tabDeckStorage = 0; - tabReplays = 0; - tabAdmin = 0; - tabLog = 0; + resetTabsMenu(); + + tabAccount = nullptr; + tabAdmin = nullptr; + tabLog = nullptr; isLocalGame = true; userInfo = new ServerInfo_User; localClients = _clients; for (int i = 0; i < localClients.size(); ++i) - connect(localClients[i], SIGNAL(gameEventContainerReceived(const GameEventContainer &)), this, - SLOT(processGameEventContainer(const GameEventContainer &))); - connect(localClients.first(), SIGNAL(gameJoinedEventReceived(const Event_GameJoined &)), this, - SLOT(localGameJoined(const Event_GameJoined &))); + connect(localClients[i], &AbstractClient::gameEventContainerReceived, this, + &TabSupervisor::processGameEventContainer); + connect(localClients.first(), &AbstractClient::gameJoinedEventReceived, this, &TabSupervisor::localGameJoined); } +/** + * Call this when Cockatrice disconnects from the server in order to clean up. + */ void TabSupervisor::stop() { if ((!client) && localClients.isEmpty()) return; + resetTabsMenu(); + if (!localClients.isEmpty()) { - for (int i = 0; i < localClients.size(); ++i) - localClients[i]->deleteLater(); + for (auto &localClient : localClients) { + localClient->deleteLater(); + } localClients.clear(); emit localGameEnded(); } else { - if (tabUserLists) - tabUserLists->deleteLater(); - if (tabServer) - tabServer->deleteLater(); - if (tabDeckStorage) - tabDeckStorage->deleteLater(); - if (tabReplays) - tabReplays->deleteLater(); - if (tabAdmin) - tabAdmin->deleteLater(); - if (tabLog) - tabLog->deleteLater(); + if (tabAccount) { + tabAccount->closeRequest(true); + } + if (tabServer) { + tabServer->closeRequest(true); + } + if (tabAdmin) { + tabAdmin->closeRequest(true); + } + if (tabLog) { + tabLog->closeRequest(true); + } } - tabUserLists = 0; - tabServer = 0; - tabDeckStorage = 0; - tabReplays = 0; - tabAdmin = 0; - tabLog = 0; - QMapIterator roomIterator(roomTabs); - while (roomIterator.hasNext()) - roomIterator.next().value()->deleteLater(); - roomTabs.clear(); + QList tabsToDelete; - QMapIterator gameIterator(gameTabs); - while (gameIterator.hasNext()) - gameIterator.next().value()->deleteLater(); - gameTabs.clear(); + for (auto i = roomTabs.cbegin(), end = roomTabs.cend(); i != end; ++i) { + tabsToDelete << i.value(); + } - QListIterator replayIterator(replayTabs); - while (replayIterator.hasNext()) - replayIterator.next()->deleteLater(); - replayTabs.clear(); + for (auto i = gameTabs.cbegin(), end = gameTabs.cend(); i != end; ++i) { + tabsToDelete << i.value(); + } + + for (auto i = messageTabs.cbegin(), end = messageTabs.cend(); i != end; ++i) { + tabsToDelete << i.value(); + } + + for (const auto tab : tabsToDelete) { + tab->closeRequest(true); + } + + userListManager->handleDisconnect(); delete userInfo; - userInfo = 0; + userInfo = nullptr; +} + +void TabSupervisor::actTabVisualDeckStorage(bool checked) +{ + SettingsCache::instance().setTabVisualDeckStorageOpen(checked); + if (checked && !tabVisualDeckStorage) { + 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) { + 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) { + 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) { + 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) { + 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) { + 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) { + 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) @@ -339,22 +657,6 @@ void TabSupervisor::updatePingTime(int value, int max) setTabIcon(indexOf(tabServer), QIcon(PingPixmapGenerator::generatePixmap(15, value, max))); } -void TabSupervisor::closeButtonPressed() -{ - Tab *tab = static_cast(static_cast(sender())->property("tab").value()); - tab->closeRequest(); -} - -void TabSupervisor::addCloseButtonToTab(Tab *tab, int tabIndex) -{ - QTabBar::ButtonPosition closeSide = - (QTabBar::ButtonPosition)tabBar()->style()->styleHint(QStyle::SH_TabBar_CloseButtonPosition, 0, tabBar()); - CloseButton *closeButton = new CloseButton; - connect(closeButton, SIGNAL(clicked()), this, SLOT(closeButtonPressed())); - closeButton->setProperty("tab", QVariant::fromValue((QObject *)tab)); - tabBar()->setTabButton(tabIndex, closeSide, closeButton); -} - void TabSupervisor::gameJoined(const Event_GameJoined &event) { QMap roomGameTypes; @@ -366,23 +668,21 @@ void TabSupervisor::gameJoined(const Event_GameJoined &event) roomGameTypes.insert(event.game_types(i).game_type_id(), QString::fromStdString(event.game_types(i).description())); - TabGame *tab = new TabGame(this, QList() << client, event, roomGameTypes); - connect(tab, SIGNAL(gameClosing(TabGame *)), this, SLOT(gameLeft(TabGame *))); - connect(tab, SIGNAL(openMessageDialog(const QString &, bool)), this, SLOT(addMessageTab(const QString &, bool))); - connect(tab, SIGNAL(openDeckEditor(const DeckLoader *)), this, SLOT(addDeckEditorTab(const DeckLoader *))); - int tabIndex = myAddTab(tab); - addCloseButtonToTab(tab, tabIndex); + auto *tab = new TabGame(this, QList() << client, event, roomGameTypes); + connect(tab, &TabGame::gameClosing, this, &TabSupervisor::gameLeft); + connect(tab, &TabGame::openMessageDialog, this, &TabSupervisor::addMessageTab); + connect(tab, &TabGame::openDeckEditor, this, &TabSupervisor::addDeckEditorTab); + myAddTab(tab); gameTabs.insert(event.game_info().game_id(), tab); setCurrentWidget(tab); } void TabSupervisor::localGameJoined(const Event_GameJoined &event) { - TabGame *tab = new TabGame(this, localClients, event, QMap()); - connect(tab, SIGNAL(gameClosing(TabGame *)), this, SLOT(gameLeft(TabGame *))); - connect(tab, SIGNAL(openDeckEditor(const DeckLoader *)), this, SLOT(addDeckEditorTab(const DeckLoader *))); - int tabIndex = myAddTab(tab); - addCloseButtonToTab(tab, tabIndex); + auto *tab = new TabGame(this, localClients, event, QMap()); + connect(tab, &TabGame::gameClosing, this, &TabSupervisor::gameLeft); + connect(tab, &TabGame::openDeckEditor, this, &TabSupervisor::addDeckEditorTab); + myAddTab(tab); gameTabs.insert(event.game_info().game_id(), tab); setCurrentWidget(tab); @@ -407,12 +707,11 @@ void TabSupervisor::gameLeft(TabGame *tab) void TabSupervisor::addRoomTab(const ServerInfo_Room &info, bool setCurrent) { - TabRoom *tab = new TabRoom(this, client, userInfo, info); - connect(tab, SIGNAL(maximizeClient()), this, SLOT(maximizeMainWindow())); - connect(tab, SIGNAL(roomClosing(TabRoom *)), this, SLOT(roomLeft(TabRoom *))); - connect(tab, SIGNAL(openMessageDialog(const QString &, bool)), this, SLOT(addMessageTab(const QString &, bool))); - int tabIndex = myAddTab(tab); - addCloseButtonToTab(tab, tabIndex); + auto *tab = new TabRoom(this, client, userInfo, info); + connect(tab, &TabRoom::maximizeClient, this, &TabSupervisor::maximizeMainWindow); + connect(tab, &TabRoom::roomClosing, this, &TabSupervisor::roomLeft); + connect(tab, &TabRoom::openMessageDialog, this, &TabSupervisor::addMessageTab); + myAddTab(tab); roomTabs.insert(info.room_id(), tab); if (setCurrent) setCurrentWidget(tab); @@ -429,10 +728,9 @@ void TabSupervisor::roomLeft(TabRoom *tab) void TabSupervisor::openReplay(GameReplay *replay) { - TabGame *replayTab = new TabGame(this, replay); - connect(replayTab, SIGNAL(gameClosing(TabGame *)), this, SLOT(replayLeft(TabGame *))); - int tabIndex = myAddTab(replayTab); - addCloseButtonToTab(replayTab, tabIndex); + auto *replayTab = new TabGame(this, replay); + connect(replayTab, &TabGame::gameClosing, this, &TabSupervisor::replayLeft); + myAddTab(replayTab); replayTabs.append(replayTab); setCurrentWidget(replayTab); } @@ -451,11 +749,11 @@ TabMessage *TabSupervisor::addMessageTab(const QString &receiverName, bool focus return nullptr; ServerInfo_User otherUser; - UserListTWI *twi = tabUserLists->getAllUsersList()->getUsers().value(receiverName); - if (twi) - otherUser = twi->getUserInfo(); - else + if (auto user = userListManager->getOnlineUser(receiverName)) { + otherUser = ServerInfo_User(*user); + } else { otherUser.set_name(receiverName.toStdString()); + } TabMessage *tab; tab = messageTabs.value(QString::fromStdString(otherUser.name())); @@ -466,10 +764,9 @@ TabMessage *TabSupervisor::addMessageTab(const QString &receiverName, bool focus } tab = new TabMessage(this, client, *userInfo, otherUser); - connect(tab, SIGNAL(talkClosing(TabMessage *)), this, SLOT(talkLeft(TabMessage *))); - connect(tab, SIGNAL(maximizeClient()), this, SLOT(maximizeMainWindow())); - int tabIndex = myAddTab(tab); - addCloseButtonToTab(tab, tabIndex); + connect(tab, &TabMessage::talkClosing, this, &TabSupervisor::talkLeft); + connect(tab, &TabMessage::maximizeClient, this, &TabSupervisor::maximizeMainWindow); + myAddTab(tab); messageTabs.insert(receiverName, tab); if (focus) setCurrentWidget(tab); @@ -490,21 +787,36 @@ 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) { - TabDeckEditor *tab = new TabDeckEditor(this); + auto *tab = new TabDeckEditor(this); if (deckToOpen) - tab->setDeck(new DeckLoader(*deckToOpen)); - connect(tab, SIGNAL(deckEditorClosing(TabDeckEditor *)), this, SLOT(deckEditorClosed(TabDeckEditor *))); - connect(tab, SIGNAL(openDeckEditor(const DeckLoader *)), this, SLOT(addDeckEditorTab(const DeckLoader *))); - int tabIndex = myAddTab(tab); - addCloseButtonToTab(tab, tabIndex); + 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); return tab; } -void TabSupervisor::deckEditorClosed(TabDeckEditor *tab) +TabEdhRec *TabSupervisor::addEdhrecTab(const CardInfoPtr &cardToQuery, bool isCommander) +{ + auto *tab = new TabEdhRec(this); + if (cardToQuery) { + tab->setCard(cardToQuery, isCommander); + } + + myAddTab(tab); + setCurrentWidget(tab); + return tab; +} + +void TabSupervisor::deckEditorClosed(AbstractTabDeckEditor *tab) { if (tab == currentWidget()) emit setMenu(); @@ -544,7 +856,7 @@ void TabSupervisor::processGameEventContainer(const GameEventContainer &cont) if (tab) tab->processGameEventContainer(cont, qobject_cast(sender()), {}); else - qDebug() << "gameEvent: invalid gameId"; + qCInfo(TabSupervisorLog) << "gameEvent: invalid gameId" << cont.game_id(); } void TabSupervisor::processUserMessageEvent(const Event_UserMessage &event) @@ -554,9 +866,9 @@ void TabSupervisor::processUserMessageEvent(const Event_UserMessage &event) if (!tab) tab = messageTabs.value(QString::fromStdString(event.receiver_name())); if (!tab) { - UserListTWI *twi = tabUserLists->getAllUsersList()->getUsers().value(senderName); - if (twi) { - UserLevelFlags userLevel = UserLevelFlags(twi->getUserInfo().user_level()); + const ServerInfo_User *onlineUserInfo = userListManager->getOnlineUser(senderName); + if (onlineUserInfo) { + auto userLevel = UserLevelFlags(onlineUserInfo->user_level()); if (SettingsCache::instance().getIgnoreUnregisteredUserMessages() && !userLevel.testFlag(ServerInfo_User::IsRegistered)) // Flags are additive, so reg/mod/admin are all IsRegistered @@ -571,9 +883,9 @@ void TabSupervisor::processUserMessageEvent(const Event_UserMessage &event) void TabSupervisor::actShowPopup(const QString &message) { - qDebug() << "ACT SHOW POPUP"; + qCDebug(TabSupervisorLog) << "ACT SHOW POPUP"; if (trayIcon && (QApplication::activeWindow() == nullptr || QApplication::focusWidget() == nullptr)) { - qDebug() << "LAUNCHING POPUP"; + qCDebug(TabSupervisorLog) << "LAUNCHING POPUP"; // disconnect(trayIcon, SIGNAL(messageClicked()), nullptr, nullptr); trayIcon->showMessage(message, tr("Click to view")); // connect(trayIcon, SIGNAL(messageClicked()), chatView, SLOT(actMessageClicked())); @@ -590,15 +902,15 @@ void TabSupervisor::processUserLeft(const QString &userName) void TabSupervisor::processUserJoined(const ServerInfo_User &userInfoJoined) { QString userName = QString::fromStdString(userInfoJoined.name()); - if (isUserBuddy(userName)) { - Tab *tab = static_cast(getUserListsTab()); - - if (tab != currentWidget()) { - tab->setContentsChanged(true); - QPixmap avatarPixmap = - UserLevelPixmapGenerator::generatePixmap(13, (UserLevelFlags)userInfoJoined.user_level(), true, - QString::fromStdString(userInfoJoined.privlevel())); - setTabIcon(indexOf(tab), QPixmap(avatarPixmap)); + if (userListManager->isUserBuddy(userName)) { + if (auto *tab = getTabAccount()) { + if (tab != currentWidget()) { + tab->setContentsChanged(true); + QIcon avatarIcon = UserLevelPixmapGenerator::generateIcon( + 13, (UserLevelFlags)userInfoJoined.user_level(), userInfoJoined.pawn_colors(), true, + QString::fromStdString(userInfoJoined.privlevel())); + setTabIcon(indexOf(tab), avatarIcon); + } } if (SettingsCache::instance().getBuddyConnectNotificationsEnabled()) { @@ -686,57 +998,6 @@ void TabSupervisor::processNotifyUserEvent(const Event_NotifyUser &event) } } -bool TabSupervisor::isOwnUserRegistered() const -{ - return userInfo != nullptr && (userInfo->user_level() & ServerInfo_User::IsRegistered) != 0; -} - -QString TabSupervisor::getOwnUsername() const -{ - return userInfo != nullptr ? QString::fromStdString(userInfo->name()) : QString(); -} - -bool TabSupervisor::isUserBuddy(const QString &userName) const -{ - if (!getUserListsTab()) - return false; - if (!getUserListsTab()->getBuddyList()) - return false; - QMap buddyList = getUserListsTab()->getBuddyList()->getUsers(); - bool senderIsBuddy = buddyList.contains(userName); - return senderIsBuddy; -} - -bool TabSupervisor::isUserIgnored(const QString &userName) const -{ - if (!getUserListsTab()) - return false; - if (!getUserListsTab()->getIgnoreList()) - return false; - QMap buddyList = getUserListsTab()->getIgnoreList()->getUsers(); - bool senderIsBuddy = buddyList.contains(userName); - return senderIsBuddy; -} - -const ServerInfo_User *TabSupervisor::getOnlineUser(const QString &userName) const -{ - if (!getUserListsTab()) - return nullptr; - if (!getUserListsTab()->getAllUsersList()) - return nullptr; - QMap userList = getUserListsTab()->getAllUsersList()->getUsers(); - const QString &userNameToMatchLower = userName.toLower(); - QMap::iterator i; - - for (i = userList.begin(); i != userList.end(); ++i) - if (i.key().toLower() == userNameToMatchLower) { - const ServerInfo_User &_userInfo = i.value()->getUserInfo(); - return &_userInfo; - } - - return nullptr; -}; - bool TabSupervisor::switchToGameTabIfAlreadyExists(const int gameId) { bool isGameTabExists = false; diff --git a/cockatrice/src/client/tabs/tab_supervisor.h b/cockatrice/src/client/tabs/tab_supervisor.h index 7b43881d3..c8ae109d8 100644 --- a/cockatrice/src/client/tabs/tab_supervisor.h +++ b/cockatrice/src/client/tabs/tab_supervisor.h @@ -2,14 +2,21 @@ #define TAB_SUPERVISOR_H #include "../../deck/deck_loader.h" -#include "../../server/chat_view/user_list_proxy.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" #include #include +#include #include #include #include +inline Q_LOGGING_CATEGORY(TabSupervisorLog, "tab_supervisor"); + +class UserListManager; class QMenu; class AbstractClient; class Tab; @@ -20,7 +27,7 @@ class TabDeckStorage; class TabReplays; class TabAdmin; class TabMessage; -class TabUserLists; +class TabAccount; class TabDeckEditor; class TabLog; class RoomEvent; @@ -37,39 +44,42 @@ class MacOSTabFixStyle : public QProxyStyle { Q_OBJECT public: - QRect subElementRect(SubElement, const QStyleOption *, const QWidget *) const; + QRect subElementRect(SubElement, const QStyleOption *, const QWidget *) const override; }; class CloseButton : public QAbstractButton { Q_OBJECT public: - CloseButton(QWidget *parent = nullptr); - QSize sizeHint() const; - inline QSize minimumSizeHint() const + explicit CloseButton(QWidget *parent = nullptr); + QSize sizeHint() const override; + inline QSize minimumSizeHint() const override { return sizeHint(); } protected: #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) - void enterEvent(QEnterEvent *event); + void enterEvent(QEnterEvent *event) override; #else - void enterEvent(QEvent *event); + void enterEvent(QEvent *event) override; #endif - void leaveEvent(QEvent *event); - void paintEvent(QPaintEvent *event); + void leaveEvent(QEvent *event) override; + void paintEvent(QPaintEvent *event) override; }; -class TabSupervisor : public QTabWidget, public UserlistProxy +class TabSupervisor : public QTabWidget { Q_OBJECT private: ServerInfo_User *userInfo; AbstractClient *client; + UserListManager *userListManager; QList localClients; + QMenu *tabsMenu; + TabDeckStorageVisual *tabVisualDeckStorage; TabServer *tabServer; - TabUserLists *tabUserLists; + TabAccount *tabAccount; TabDeckStorage *tabDeckStorage; TabReplays *tabReplays; TabAdmin *tabAdmin; @@ -78,17 +88,23 @@ private: QMap gameTabs; QList replayTabs; QMap messageTabs; - QList deckEditorTabs; - int myAddTab(Tab *tab); - void addCloseButtonToTab(Tab *tab, int tabIndex); - QString sanitizeTabName(QString dirty) const; - QString sanitizeHtml(QString dirty) const; + QList deckEditorTabs; bool isLocalGame; + QAction *aTabDeckEditor, *aTabVisualDeckStorage, *aTabServer, *aTabAccount, *aTabDeckStorage, *aTabReplays, + *aTabAdmin, *aTabLog; + + int myAddTab(Tab *tab, QAction *manager = nullptr); + void addCloseButtonToTab(Tab *tab, int tabIndex, QAction *manager); + static QString sanitizeTabName(QString dirty); + static QString sanitizeHtml(QString dirty); + void resetTabsMenu(); + public: - TabSupervisor(AbstractClient *_client, QWidget *parent = nullptr); - ~TabSupervisor(); + explicit TabSupervisor(AbstractClient *_client, QMenu *tabsMenu, QWidget *parent = nullptr); + ~TabSupervisor() override; void retranslateUi(); + void initStartupTabs(); void start(const ServerInfo_User &userInfo); void startLocal(const QList &_clients); void stop(); @@ -100,28 +116,31 @@ public: { return gameTabs.size(); } - TabUserLists *getUserListsTab() const + TabAccount *getTabAccount() const { - return tabUserLists; + return tabAccount; } ServerInfo_User *getUserInfo() const { return userInfo; } AbstractClient *getClient() const; + const UserListManager *getUserListManager() const + { + return userListManager; + } const QMap &getRoomTabs() const { return roomTabs; } + QList getDeckEditorTabs() const + { + return deckEditorTabs; + } bool getAdminLocked() const; - bool closeRequest(); - bool isOwnUserRegistered() const; - QString getOwnUsername() const; - bool isUserBuddy(const QString &userName) const; - bool isUserIgnored(const QString &userName) const; - const ServerInfo_User *getOnlineUser(const QString &userName) const; + void closeEvent(QCloseEvent *event) override; bool switchToGameTabIfAlreadyExists(const int gameId); - void actShowPopup(const QString &message); + static void actShowPopup(const QString &message); signals: void setMenu(const QList &newMenuList = QList()); void localGameEnded(); @@ -130,10 +149,28 @@ signals: public slots: TabDeckEditor *addDeckEditorTab(const DeckLoader *deckToOpen); + TabEdhRec *addEdhrecTab(const CardInfoPtr &cardToQuery, bool isCommander = false); void openReplay(GameReplay *replay); void maximizeMainWindow(); private slots: - void closeButtonPressed(); + void refreshShortcuts(); + + void actTabVisualDeckStorage(bool checked); + void actTabServer(bool checked); + void actTabAccount(bool checked); + void actTabDeckStorage(bool checked); + void actTabReplays(bool checked); + 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); @@ -146,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); @@ -155,4 +192,4 @@ private slots: void processNotifyUserEvent(const Event_NotifyUser &event); }; -#endif \ No newline at end of file +#endif diff --git a/cockatrice/src/client/tabs/visual_deck_storage/tab_deck_storage_visual.cpp b/cockatrice/src/client/tabs/visual_deck_storage/tab_deck_storage_visual.cpp new file mode 100644 index 000000000..332f5b779 --- /dev/null +++ b/cockatrice/src/client/tabs/visual_deck_storage/tab_deck_storage_visual.cpp @@ -0,0 +1,37 @@ +#include "tab_deck_storage_visual.h" + +#include "../../../game/cards/card_database_model.h" +#include "../../ui/widgets/cards/deck_preview_card_picture_widget.h" +#include "../../ui/widgets/visual_deck_storage/visual_deck_storage_widget.h" +#include "../tab_supervisor.h" +#include "pb/command_deck_del.pb.h" + +#include +#include + +TabDeckStorageVisual::TabDeckStorageVisual(TabSupervisor *_tabSupervisor) + : Tab(_tabSupervisor), visualDeckStorageWidget(new VisualDeckStorageWidget(this)) +{ + connect(this, &TabDeckStorageVisual::openDeckEditor, tabSupervisor, &TabSupervisor::addDeckEditorTab); + connect(visualDeckStorageWidget, &VisualDeckStorageWidget::deckLoadRequested, this, + &TabDeckStorageVisual::actOpenLocalDeck); + connect(visualDeckStorageWidget, &VisualDeckStorageWidget::openDeckEditor, this, + &TabDeckStorageVisual::openDeckEditor); + + auto *widget = new QWidget(this); + auto *layout = new QVBoxLayout(widget); + widget->setLayout(layout); + this->setCentralWidget(widget); + layout->addWidget(visualDeckStorageWidget); +} + +void TabDeckStorageVisual::actOpenLocalDeck(const QString &filePath) +{ + DeckLoader deckLoader; + if (!deckLoader.loadFromFile(filePath, DeckLoader::getFormatFromName(filePath), true)) { + QMessageBox::critical(this, tr("Error"), tr("Could not open deck at %1").arg(filePath)); + return; + } + + emit openDeckEditor(&deckLoader); +} diff --git a/cockatrice/src/client/tabs/visual_deck_storage/tab_deck_storage_visual.h b/cockatrice/src/client/tabs/visual_deck_storage/tab_deck_storage_visual.h new file mode 100644 index 000000000..10cd3b0cb --- /dev/null +++ b/cockatrice/src/client/tabs/visual_deck_storage/tab_deck_storage_visual.h @@ -0,0 +1,40 @@ +#ifndef TAB_DECK_STORAGE_VISUAL_H +#define TAB_DECK_STORAGE_VISUAL_H + +#include "../tab.h" + +class AbstractClient; +class CommandContainer; +class DeckLoader; +class DeckPreviewWidget; +class QFileSystemModel; +class QGroupBox; +class QToolBar; +class QTreeView; +class QTreeWidget; +class QTreeWidgetItem; +class Response; +class VisualDeckStorageWidget; + +class TabDeckStorageVisual final : public Tab +{ + Q_OBJECT +public: + explicit TabDeckStorageVisual(TabSupervisor *_tabSupervisor); + void retranslateUi() override{}; + [[nodiscard]] QString getTabText() const override + { + return tr("Visual Deck Storage"); + } + +public slots: + void actOpenLocalDeck(const QString &filePath); + +signals: + void openDeckEditor(const DeckLoader *deckLoader); + +private: + VisualDeckStorageWidget *visualDeckStorageWidget; +}; + +#endif diff --git a/cockatrice/src/client/tapped_out_interface.cpp b/cockatrice/src/client/tapped_out_interface.cpp index c0cbab095..d6a47b018 100644 --- a/cockatrice/src/client/tapped_out_interface.cpp +++ b/cockatrice/src/client/tapped_out_interface.cpp @@ -14,7 +14,7 @@ TappedOutInterface::TappedOutInterface(CardDatabase &_cardDatabase, QObject *par : QObject(parent), cardDatabase(_cardDatabase) { manager = new QNetworkAccessManager(this); - connect(manager, SIGNAL(finished(QNetworkReply *)), this, SLOT(queryFinished(QNetworkReply *))); + connect(manager, &QNetworkAccessManager::finished, this, &TappedOutInterface::queryFinished); } void TappedOutInterface::queryFinished(QNetworkReply *reply) @@ -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"); - qDebug() << "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"); - qDebug() << "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); } @@ -115,7 +115,7 @@ struct CopyMainOrSide } }; -void TappedOutInterface::copyDeckSplitMainAndSide(const DeckList &source, DeckList &mainboard, DeckList &sideboard) +void TappedOutInterface::copyDeckSplitMainAndSide(DeckList &source, DeckList &mainboard, DeckList &sideboard) { CopyMainOrSide copyMainOrSide(cardDatabase, mainboard, sideboard); source.forEachCard(copyMainOrSide); diff --git a/cockatrice/src/client/tapped_out_interface.h b/cockatrice/src/client/tapped_out_interface.h index 239a3ee18..a6a763d45 100644 --- a/cockatrice/src/client/tapped_out_interface.h +++ b/cockatrice/src/client/tapped_out_interface.h @@ -4,8 +4,11 @@ #include "../game/cards/card_database.h" #include "decklist.h" +#include #include +inline Q_LOGGING_CATEGORY(TappedOutInterfaceLog, "tapped_out_interface"); + class QByteArray; class QNetworkAccessManager; class QNetworkReply; @@ -24,13 +27,13 @@ private: QNetworkAccessManager *manager; CardDatabase &cardDatabase; - void copyDeckSplitMainAndSide(const DeckList &source, DeckList &mainboard, DeckList &sideboard); + void copyDeckSplitMainAndSide(DeckList &source, DeckList &mainboard, DeckList &sideboard); private slots: void queryFinished(QNetworkReply *reply); void getAnalyzeRequestData(DeckList *deck, QByteArray *data); public: - TappedOutInterface(CardDatabase &_cardDatabase, QObject *parent = nullptr); + explicit TappedOutInterface(CardDatabase &_cardDatabase, QObject *parent = nullptr); void analyzeDeck(DeckList *deck); }; diff --git a/cockatrice/src/client/tearoff_menu.h b/cockatrice/src/client/tearoff_menu.h index 20ee30e10..1c1258f5f 100644 --- a/cockatrice/src/client/tearoff_menu.h +++ b/cockatrice/src/client/tearoff_menu.h @@ -7,23 +7,23 @@ class TearOffMenu : public QMenu { public: - TearOffMenu(const QString &title, QWidget *parent = nullptr) : QMenu(title, parent) + explicit TearOffMenu(const QString &title, QWidget *parent = nullptr) : QMenu(title, parent) { connect(&SettingsCache::instance(), &SettingsCache::useTearOffMenusChanged, this, - [=](bool state) { setTearOffEnabled(state); }); + [this](const bool state) { setTearOffEnabled(state); }); setTearOffEnabled(SettingsCache::instance().getUseTearOffMenus()); } - TearOffMenu(QWidget *parent = nullptr) : QMenu(parent) + explicit TearOffMenu(QWidget *parent = nullptr) : QMenu(parent) { connect(&SettingsCache::instance(), &SettingsCache::useTearOffMenusChanged, this, - [=](bool state) { setTearOffEnabled(state); }); + [this](const bool state) { setTearOffEnabled(state); }); setTearOffEnabled(SettingsCache::instance().getUseTearOffMenus()); } TearOffMenu *addTearOffMenu(const QString &title) { - TearOffMenu *menu = new TearOffMenu(title, this); + auto *menu = new TearOffMenu(title, this); addMenu(menu); return menu; } diff --git a/cockatrice/src/client/ui/layouts/flow_layout.cpp b/cockatrice/src/client/ui/layouts/flow_layout.cpp index f1a776c47..6e00fdb68 100644 --- a/cockatrice/src/client/ui/layouts/flow_layout.cpp +++ b/cockatrice/src/client/ui/layouts/flow_layout.cpp @@ -20,8 +20,12 @@ * @param hSpacing The horizontal spacing between items. * @param vSpacing The vertical spacing between items. */ -FlowLayout::FlowLayout(QWidget *parent, const int margin, const int hSpacing, const int vSpacing) - : QLayout(parent), horizontalMargin(hSpacing), verticalMargin(vSpacing) +FlowLayout::FlowLayout(QWidget *parent, + const Qt::Orientation _flowDirection, + const int margin, + const int hSpacing, + const int vSpacing) + : QLayout(parent), flowDirection(_flowDirection), horizontalMargin(hSpacing), verticalMargin(vSpacing) { setContentsMargins(margin, margin, margin, margin); } @@ -62,27 +66,51 @@ bool FlowLayout::hasHeightForWidth() const */ int FlowLayout::heightForWidth(const int width) const { - int height = 0; - int rowWidth = 0; - int rowHeight = 0; + if (flowDirection == Qt::Vertical) { + int height = 0; + int rowWidth = 0; + int rowHeight = 0; - for (const QLayoutItem *item : items) { - if (item == nullptr || item->isEmpty()) { - continue; - } + for (const QLayoutItem *item : items) { + if (item == nullptr || item->isEmpty()) { + continue; + } - int itemWidth = item->sizeHint().width() + horizontalSpacing(); - if (rowWidth + itemWidth > width) { // Start a new row if the row width exceeds available width - height += rowHeight + verticalSpacing(); - rowWidth = itemWidth; - rowHeight = item->sizeHint().height() + verticalSpacing(); - } else { - rowWidth += itemWidth; - rowHeight = qMax(rowHeight, item->sizeHint().height()); + int itemWidth = item->sizeHint().width() + horizontalSpacing(); + if (rowWidth + itemWidth > width) { + height += rowHeight + verticalSpacing(); + rowWidth = itemWidth; + rowHeight = item->sizeHint().height(); + } else { + rowWidth += itemWidth; + rowHeight = qMax(rowHeight, item->sizeHint().height()); + } } + height += rowHeight; // Add height of the last row + return height; + } else { + int width = 0; + int colWidth = 0; + int colHeight = 0; + + for (const QLayoutItem *item : items) { + if (item == nullptr || item->isEmpty()) { + continue; + } + + int itemHeight = item->sizeHint().height(); + if (colHeight + itemHeight > width) { + width += colWidth; + colHeight = itemHeight; + colWidth = item->sizeHint().width(); + } else { + colHeight += itemHeight; + colWidth = qMax(colWidth, item->sizeHint().width()); + } + } + width += colWidth; // Add width of the last column + return width; } - height += rowHeight; // Add the final row's height - return height; } /** @@ -93,132 +121,420 @@ void FlowLayout::setGeometry(const QRect &rect) { QLayout::setGeometry(rect); // Sets the geometry of the layout based on the given rectangle. - int left, top, right, bottom; - getContentsMargins(&left, &top, &right, &bottom); // Retrieves the layout's content margins. + if (flowDirection == Qt::Horizontal) { + // If we have a parent scroll area, we're clamped to that, else we use our own rectangle. + const int availableWidth = getParentScrollAreaWidth() == 0 ? rect.width() : getParentScrollAreaWidth(); - // Adjust the rectangle to exclude margins. - const QRect adjustedRect = rect.adjusted(+left, +top, -right, -bottom); + const int totalHeight = layoutAllRows(rect.x(), rect.y(), availableWidth); - // Calculate the available width for items, considering either the adjusted rectangle's width - // or the parent scroll area width, if applicable. - const int availableWidth = qMax(adjustedRect.width(), getParentScrollAreaWidth()); + if (QWidget *parentWidgetPtr = parentWidget()) { + parentWidgetPtr->setFixedSize(availableWidth, totalHeight); + } + } else { + const int availableHeight = qMax(rect.height(), getParentScrollAreaHeight()); - // Arrange all rows of items within the available width and get the total height used. - const int totalHeight = layoutAllRows(adjustedRect.x(), adjustedRect.y(), availableWidth); + const int totalWidth = layoutAllColumns(rect.x(), rect.y(), availableHeight); - // If the layout's parent is a QWidget, update its minimum size to ensure it can accommodate - // the arranged items' dimensions. - if (QWidget *parentWidgetPtr = parentWidget()) { - parentWidgetPtr->setMinimumSize(availableWidth, totalHeight); + if (QWidget *parentWidgetPtr = parentWidget()) { + parentWidgetPtr->setFixedSize(totalWidth, availableHeight); + } } } /** - * @brief Arranges items in rows based on the available width. - * Items are added to a row until the row's width exceeds `availableWidth`. - * Then, a new row is started. - * @param originX The starting x-coordinate for the row layout. - * @param originY The starting y-coordinate for the row layout. - * @param availableWidth The available width to lay out items. - * @return The y-coordinate of the final row's end position. + * @brief Lays out items into rows according to the available width, starting from a given origin. + * Each row is arranged within `availableWidth`, wrapping to a new row as necessary. + * @param originX The x-coordinate for the layout start position. + * @param originY The y-coordinate for the layout start position. + * @param availableWidth The width within which each row is constrained. + * @return The total height after arranging all rows. */ int FlowLayout::layoutAllRows(const int originX, const int originY, const int availableWidth) { - QVector rowItems; // Temporary storage for items in the current row. - int currentXPosition = originX; // Tracks the x-coordinate for placing items in the current row. - int currentYPosition = originY; // Tracks the y-coordinate, updated after each row. + QVector rowItems; // Holds items for the current row + int currentXPosition = originX; // Tracks the x-coordinate while placing items + int currentYPosition = originY; // Tracks the y-coordinate, moving down after each row - int rowHeight = 0; // Tracks the maximum height of items in the current row. + int rowHeight = 0; // Tracks the maximum height of items in the current row - // Iterate through all layout items to arrange them. for (QLayoutItem *item : items) { if (item == nullptr || item->isEmpty()) { continue; } - QSize itemSize = item->sizeHint(); // The suggested size for the item. - const int itemWidth = itemSize.width() + horizontalSpacing(); + QSize itemSize = item->sizeHint(); // The suggested size for the current item + int itemWidth = itemSize.width() + horizontalSpacing(); // Item width plus spacing - // Check if the item fits in the current row's remaining width. + // Check if the current item fits in the remaining width of the current row if (currentXPosition + itemWidth > availableWidth) { - // If not, layout the current row and start a new row. + // If not, layout the current row and start a new row layoutSingleRow(rowItems, originX, currentYPosition); - rowItems.clear(); // Clear the temporary storage for the new row. - currentXPosition = originX; // Reset x-position to the start of the new row. - currentYPosition += rowHeight + verticalSpacing(); // Move y-position down for the new row. - rowHeight = 0; // Reset row height for the new row. + rowItems.clear(); // Reset the list for the new row + currentXPosition = originX; // Reset x-position to the row's start + currentYPosition += rowHeight + verticalSpacing(); // Move y-position down to the next row + rowHeight = 0; // Reset row height for the new row } - // Add the item to the current row. + // Add the item to the current row rowItems.append(item); - rowHeight = qMax(rowHeight, itemSize.height()); // Update the row height to the tallest item. - currentXPosition += itemSize.width() + horizontalSpacing(); // Move x-position for the next item. + rowHeight = qMax(rowHeight, itemSize.height()); // Update the row's height to the tallest item + currentXPosition += itemWidth + horizontalSpacing(); // Move x-position for the next item } - // Layout the final row if there are remaining items. + // Layout the final row if there are any remaining items layoutSingleRow(rowItems, originX, currentYPosition); - currentYPosition += rowHeight; // Add the final row's height - return currentYPosition; + // Return the total height used, including the last row's height + return currentYPosition + rowHeight; } /** - * @brief Helper function for arranging a single row of items within specified bounds. - * @param rowItems Items to be arranged in the row. - * @param x The x-coordinate for starting the row. - * @param y The y-coordinate for starting the row. + * @brief Arranges a single row of items within specified x and y starting positions. + * @param rowItems A list of items to be arranged in the row. + * @param x The starting x-coordinate for the row. + * @param y The starting y-coordinate for the row. */ void FlowLayout::layoutSingleRow(const QVector &rowItems, int x, const int y) { - // Iterate through each item in the row and position it. for (QLayoutItem *item : rowItems) { if (item == nullptr || item->isEmpty()) { continue; } - QSize itemMaxSize = item->widget()->maximumSize(); // Get the item's maximum allowable size. - // Constrain the item's width and height to its size hint or maximum size. - int itemWidth = qMin(item->sizeHint().width(), itemMaxSize.width()); - int itemHeight = qMin(item->sizeHint().height(), itemMaxSize.height()); - // Set the item's geometry based on the calculated size and position. + // Get the maximum allowed size for the item + QSize itemMaxSize = item->widget()->maximumSize(); + // Constrain the item's width and height to its size hint or maximum size + const int itemWidth = qMin(item->sizeHint().width(), itemMaxSize.width()); + const int itemHeight = qMin(item->sizeHint().height(), itemMaxSize.height()); + // Set the item's geometry based on the computed size and position item->setGeometry(QRect(QPoint(x, y), QSize(itemWidth, itemHeight))); - // Move the x-position for the next item, including horizontal spacing. + // Move the x-position to the right, leaving space for horizontal spacing x += itemWidth + horizontalSpacing(); } } /** - * @brief Returns the preferred size for this layout. - * @return The maximum of all item size hints as a QSize. + * @brief Lays out items into columns according to the available height, starting from a given origin. + * Each column is arranged within `availableHeight`, wrapping to a new column as necessary. + * @param originX The x-coordinate for the layout start position. + * @param originY The y-coordinate for the layout start position. + * @param availableHeight The height within which each column is constrained. + * @return The total width after arranging all columns. */ -QSize FlowLayout::sizeHint() const +int FlowLayout::layoutAllColumns(const int originX, const int originY, const int availableHeight) { - QSize size; - for (const QLayoutItem *item : items) { - if (item != nullptr && !item->isEmpty()) { - size = size.expandedTo(item->sizeHint()); + QVector colItems; // Holds items for the current column + int currentXPosition = originX; // Tracks the x-coordinate while placing items + int currentYPosition = originY; // Tracks the y-coordinate, resetting for each new column + + int colWidth = 0; // Tracks the maximum width of items in the current column + + for (QLayoutItem *item : items) { + if (item == nullptr || item->isEmpty()) { + continue; } + + QSize itemSize = item->sizeHint(); // The suggested size for the current item + + // Check if the current item fits in the remaining height of the current column + if (currentYPosition + itemSize.height() > availableHeight) { + // If not, layout the current column and start a new column + layoutSingleColumn(colItems, currentXPosition, originY); + colItems.clear(); // Reset the list for the new column + currentYPosition = originY; // Reset y-position to the column's start + currentXPosition += colWidth; // Move x-position to the next column + colWidth = 0; // Reset column width for the new column + } + + // Add the item to the current column + colItems.append(item); + colWidth = qMax(colWidth, itemSize.width()); // Update the column's width to the widest item + currentYPosition += itemSize.height(); // Move y-position for the next item } - return size.isValid() ? size : QSize(0, 0); + + // Layout the final column if there are any remaining items + layoutSingleColumn(colItems, currentXPosition, originY); + + // Return the total width used, including the last column's width + return currentXPosition + colWidth; } /** - * @brief Returns the minimum size required to display all layout items. - * @return The minimum QSize needed by the layout. + * @brief Arranges a single column of items within specified x and y starting positions. + * @param colItems A list of items to be arranged in the column. + * @param x The starting x-coordinate for the column. + * @param y The starting y-coordinate for the column. + */ +void FlowLayout::layoutSingleColumn(const QVector &colItems, const int x, int y) +{ + for (QLayoutItem *item : colItems) { + if (item == nullptr) { + qCDebug(FlowLayoutLog) << "Item is null."; + continue; + } + + if (item->isEmpty()) { + qCDebug(FlowLayoutLog) << "Skipping empty item."; + continue; + } + + // Debugging: Print the item's widget class name and size hint + QWidget *widget = item->widget(); + if (widget) { + qCDebug(FlowLayoutLog) << "Widget class:" << widget->metaObject()->className(); + qCDebug(FlowLayoutLog) << "Widget size hint:" << widget->sizeHint(); + qCDebug(FlowLayoutLog) << "Widget maximum size:" << widget->maximumSize(); + qCDebug(FlowLayoutLog) << "Widget minimum size:" << widget->minimumSize(); + + // Debugging: Print child widgets + const QObjectList &children = widget->children(); + qCDebug(FlowLayoutLog) << "Child widgets:"; + for (QObject *child : children) { + if (QWidget *childWidget = qobject_cast(child)) { + qCDebug(FlowLayoutLog) << " - Child widget class:" << childWidget->metaObject()->className(); + qCDebug(FlowLayoutLog) << " Size hint:" << childWidget->sizeHint(); + qCDebug(FlowLayoutLog) << " Maximum size:" << childWidget->maximumSize(); + } + } + } else { + qCDebug(FlowLayoutLog) << "Item does not have a widget."; + } + + // Get the maximum allowed size for the item + QSize itemMaxSize = widget->maximumSize(); + // Constrain the item's width and height to its size hint or maximum size + const int itemWidth = qMin(item->sizeHint().width(), itemMaxSize.width()); + const int itemHeight = qMin(item->sizeHint().height(), itemMaxSize.height()); + // Debugging: Print the computed geometry + qCDebug(FlowLayoutLog) << "Computed geometry: x=" << x << ", y=" << y << ", width=" << itemWidth + << ", height=" << itemHeight; + + // Set the item's geometry based on the computed size and position + item->setGeometry(QRect(QPoint(x, y), QSize(itemWidth, itemHeight))); + + // Move the y-position down by the item's height to place the next item below + y += itemHeight; + } +} + +/** + * @brief Calculates the preferred size of the layout based on the flow direction. + * @return A QSize representing the ideal dimensions of the layout. + */ +QSize FlowLayout::sizeHint() const +{ + if (flowDirection == Qt::Horizontal) { + return calculateSizeHintHorizontal(); + } else { + return calculateSizeHintVertical(); + } +} + +/** + * @brief Calculates the minimum size required by the layout based on the flow direction. + * @return A QSize representing the minimum required dimensions. */ QSize FlowLayout::minimumSize() const { - QSize size; + if (flowDirection == Qt::Horizontal) { + return calculateMinimumSizeHorizontal(); + } else { + return calculateMinimumSizeVertical(); + } +} + +/** + * @brief Calculates the size hint for horizontal flow direction. + * @return A QSize representing the preferred dimensions. + */ +QSize FlowLayout::calculateSizeHintHorizontal() const +{ + int maxWidth = 0; // Tracks the maximum width needed + int totalHeight = 0; // Tracks the total height across all rows + int rowHeight = 0; // Tracks the height of the current row + int currentWidth = 0; // Tracks the current row's width + + const int availableWidth = getParentScrollAreaWidth() == 0 ? parentWidget()->width() : getParentScrollAreaWidth(); + + qCDebug(FlowLayoutLog) << "Calculating horizontal size hint. Available width:" << availableWidth; + for (const QLayoutItem *item : items) { - if (item != nullptr && !item->isEmpty()) { - size = size.expandedTo(item->minimumSize()); + if (!item || item->isEmpty()) { + qCDebug(FlowLayoutLog) << "Skipping empty item."; + continue; } + + QSize itemSize = item->sizeHint(); + int itemWidth = itemSize.width() + horizontalSpacing(); + qCDebug(FlowLayoutLog) << "Processing item. Size:" << itemSize << "Width with spacing:" << itemWidth; + + if (currentWidth + itemWidth > availableWidth) { + qCDebug(FlowLayoutLog) << "Row overflow. Current width:" << currentWidth << "Row height:" << rowHeight; + maxWidth = qMax(maxWidth, currentWidth); + totalHeight += rowHeight + verticalSpacing(); + qCDebug(FlowLayoutLog) << "Updated total height:" << totalHeight << "Max width so far:" << maxWidth; + + currentWidth = 0; + rowHeight = 0; + } + + currentWidth += itemWidth; + rowHeight = qMax(rowHeight, itemSize.height()); + qCDebug(FlowLayoutLog) << "Updated current width:" << currentWidth << "Updated row height:" << rowHeight; } - size.setWidth(qMin(size.width(), getParentScrollAreaWidth())); - size.setHeight(qMin(size.height(), getParentScrollAreaHeight())); + // Account for the final row + maxWidth = qMax(maxWidth, currentWidth); + totalHeight += rowHeight; + qCDebug(FlowLayoutLog) << "Final total height:" << totalHeight << "Final max width:" << maxWidth; - return size.isValid() ? size : QSize(0, 0); + return QSize(maxWidth, totalHeight); +} + +/** + * @brief Calculates the minimum size for horizontal flow direction. + * @return A QSize representing the minimum required dimensions. + */ +QSize FlowLayout::calculateMinimumSizeHorizontal() const +{ + int maxWidth = 0; // Tracks the maximum width of a row + int totalHeight = 0; // Tracks the total height across all rows + int rowHeight = 0; // Tracks the height of the current row + int currentWidth = 0; // Tracks the current row's width + + const int availableWidth = getParentScrollAreaWidth() == 0 ? parentWidget()->width() : getParentScrollAreaWidth(); + + qCDebug(FlowLayoutLog) << "Calculating horizontal minimum size. Available width:" << availableWidth; + + for (const QLayoutItem *item : items) { + if (!item || item->isEmpty()) { + qCDebug(FlowLayoutLog) << "Skipping empty item."; + continue; + } + + QSize itemMinSize = item->minimumSize(); + int itemWidth = itemMinSize.width() + horizontalSpacing(); + qCDebug(FlowLayoutLog) << "Processing item. Minimum size:" << itemMinSize << "Width with spacing:" << itemWidth; + + if (currentWidth + itemWidth > availableWidth) { + qCDebug(FlowLayoutLog) << "Row overflow. Current width:" << currentWidth << "Row height:" << rowHeight; + maxWidth = qMax(maxWidth, currentWidth); + totalHeight += rowHeight + verticalSpacing(); + qCDebug(FlowLayoutLog) << "Updated total height:" << totalHeight << "Max width so far:" << maxWidth; + + currentWidth = 0; + rowHeight = 0; + } + + currentWidth += itemWidth; + rowHeight = qMax(rowHeight, itemMinSize.height()); + qCDebug(FlowLayoutLog) << "Updated current width:" << currentWidth << "Updated row height:" << rowHeight; + } + + // Account for the final row + maxWidth = qMax(maxWidth, currentWidth); + totalHeight += rowHeight; + qCDebug(FlowLayoutLog) << "Final total height:" << totalHeight << "Final max width:" << maxWidth; + + return QSize(maxWidth, totalHeight); +} + +/** + * @brief Calculates the size hint for vertical flow direction. + * @return A QSize representing the preferred dimensions. + */ +QSize FlowLayout::calculateSizeHintVertical() const +{ + int totalWidth = 0; + int maxHeight = 0; + int colWidth = 0; + int currentHeight = 0; + + const int availableHeight = qMax(parentWidget()->height(), getParentScrollAreaHeight()); + + qCDebug(FlowLayoutLog) << "Calculating vertical size hint. Available height:" << availableHeight; + + for (const QLayoutItem *item : items) { + if (!item || item->isEmpty()) { + qCDebug(FlowLayoutLog) << "Skipping empty item."; + continue; + } + + QSize itemSize = item->sizeHint(); + qCDebug(FlowLayoutLog) << "Processing item. Size:" << itemSize; + + if (currentHeight + itemSize.height() > availableHeight) { + qCDebug(FlowLayoutLog) << "Column overflow. Current height:" << currentHeight + << "Column width:" << colWidth; + totalWidth += colWidth + horizontalSpacing(); + maxHeight = qMax(maxHeight, currentHeight); + qCDebug(FlowLayoutLog) << "Updated total width:" << totalWidth << "Max height so far:" << maxHeight; + + currentHeight = 0; + colWidth = 0; + } + + currentHeight += itemSize.height() + verticalSpacing(); + colWidth = qMax(colWidth, itemSize.width()); + qCDebug(FlowLayoutLog) << "Updated current height:" << currentHeight << "Updated column width:" << colWidth; + } + + // Account for the final column + totalWidth += colWidth; + maxHeight = qMax(maxHeight, currentHeight); + qCDebug(FlowLayoutLog) << "Final total width:" << totalWidth << "Final max height:" << maxHeight; + + return QSize(totalWidth, maxHeight); +} + +/** + * @brief Calculates the minimum size for vertical flow direction. + * @return A QSize representing the minimum required dimensions. + */ +QSize FlowLayout::calculateMinimumSizeVertical() const +{ + int totalWidth = 0; // Tracks the total width across all columns + int maxHeight = 0; // Tracks the maximum height of a column + int colWidth = 0; // Tracks the width of the current column + int currentHeight = 0; // Tracks the current column's height + + const int availableHeight = qMax(parentWidget()->height(), getParentScrollAreaHeight()); + + qCDebug(FlowLayoutLog) << "Calculating vertical minimum size. Available height:" << availableHeight; + + for (const QLayoutItem *item : items) { + if (!item || item->isEmpty()) { + qCDebug(FlowLayoutLog) << "Skipping empty item."; + continue; + } + + QSize itemMinSize = item->minimumSize(); + int itemHeight = itemMinSize.height() + verticalSpacing(); + qCDebug(FlowLayoutLog) << "Processing item. Minimum size:" << itemMinSize + << "Height with spacing:" << itemHeight; + + if (currentHeight + itemHeight > availableHeight) { + qCDebug(FlowLayoutLog) << "Column overflow. Current height:" << currentHeight + << "Column width:" << colWidth; + totalWidth += colWidth + horizontalSpacing(); + maxHeight = qMax(maxHeight, currentHeight); + qCDebug(FlowLayoutLog) << "Updated total width:" << totalWidth << "Max height so far:" << maxHeight; + + currentHeight = 0; + colWidth = 0; + } + + currentHeight += itemHeight; + colWidth = qMax(colWidth, itemMinSize.width()); + qCDebug(FlowLayoutLog) << "Updated current height:" << currentHeight << "Updated column width:" << colWidth; + } + + // Account for the final column + totalWidth += colWidth; + maxHeight = qMax(maxHeight, currentHeight); + qCDebug(FlowLayoutLog) << "Final total width:" << totalWidth << "Final max height:" << maxHeight; + + return QSize(totalWidth, maxHeight); } /** diff --git a/cockatrice/src/client/ui/layouts/flow_layout.h b/cockatrice/src/client/ui/layouts/flow_layout.h index 6fde7b802..d8c6d15c8 100644 --- a/cockatrice/src/client/ui/layouts/flow_layout.h +++ b/cockatrice/src/client/ui/layouts/flow_layout.h @@ -3,16 +3,22 @@ #include #include +#include #include #include +inline Q_LOGGING_CATEGORY(FlowLayoutLog, "flow_layout", QtInfoMsg); + class FlowLayout : public QLayout { public: explicit FlowLayout(QWidget *parent = nullptr); - FlowLayout(QWidget *parent, int margin, int hSpacing, int vSpacing); + FlowLayout(QWidget *parent, Qt::Orientation _flowDirection, int margin = 0, int hSpacing = 0, int vSpacing = 0); ~FlowLayout() override; + QSize calculateMinimumSizeHorizontal() const; + QSize calculateSizeHintVertical() const; + QSize calculateMinimumSizeVertical() const; void addItem(QLayoutItem *item) override; [[nodiscard]] int count() const override; [[nodiscard]] QLayoutItem *itemAt(int index) const override; @@ -31,11 +37,15 @@ public: void setGeometry(const QRect &rect) override; virtual int layoutAllRows(int originX, int originY, int availableWidth); virtual void layoutSingleRow(const QVector &rowItems, int x, int y); + int layoutAllColumns(int originX, int originY, int availableHeight); + void layoutSingleColumn(const QVector &colItems, int x, int y); [[nodiscard]] QSize sizeHint() const override; [[nodiscard]] QSize minimumSize() const override; + QSize calculateSizeHintHorizontal() const; protected: QList items; // List to store layout items + Qt::Orientation flowDirection; int horizontalMargin; int verticalMargin; }; diff --git a/cockatrice/src/client/ui/layouts/horizontal_flow_layout.cpp b/cockatrice/src/client/ui/layouts/horizontal_flow_layout.cpp deleted file mode 100644 index 16f409db7..000000000 --- a/cockatrice/src/client/ui/layouts/horizontal_flow_layout.cpp +++ /dev/null @@ -1,142 +0,0 @@ -#include "horizontal_flow_layout.h" - -/** - * @brief Constructs a HorizontalFlowLayout instance with the specified parent widget. - * This layout arranges items in columns within the given height, automatically adjusting its width. - * @param parent The parent widget to which this layout belongs. - * @param margin The layout margin. - * @param hSpacing The horizontal spacing between items. - * @param vSpacing The vertical spacing between items. - */ -HorizontalFlowLayout::HorizontalFlowLayout(QWidget *parent, const int margin, const int hSpacing, const int vSpacing) - : FlowLayout(parent, margin, hSpacing, vSpacing) -{ -} - -/** - * @brief Destructor for HorizontalFlowLayout, responsible for cleaning up layout items. - */ -HorizontalFlowLayout::~HorizontalFlowLayout() -{ - QLayoutItem *item; - while ((item = FlowLayout::takeAt(0))) { - delete item; - } -} - -/** - * @brief Calculates the required width to display all items, given a specified height. - * This method arranges items into columns and determines the total width needed. - * @param height The available height for arranging layout items. - * @return The total width required to fit all items, organized in columns constrained by the given height. - */ -int HorizontalFlowLayout::heightForWidth(const int height) const -{ - int width = 0; - int colWidth = 0; - int colHeight = 0; - - for (const QLayoutItem *item : items) { - if (item == nullptr || item->isEmpty()) { - continue; - } - - int itemHeight = item->sizeHint().height(); - if (colHeight + itemHeight > height) { - width += colWidth; - colHeight = itemHeight; - colWidth = item->sizeHint().width(); - } else { - colHeight += itemHeight; - colWidth = qMax(colWidth, item->sizeHint().width()); - } - } - width += colWidth; // Add width of the last column - return width; -} - -/** - * @brief Sets the geometry of the layout items, arranging them in columns within the given height. - * @param rect The rectangle area defining the layout space. - */ -void HorizontalFlowLayout::setGeometry(const QRect &rect) -{ - const int availableHeight = qMax(rect.height(), getParentScrollAreaHeight()); - - const int totalWidth = layoutAllColumns(rect.x(), rect.y(), availableHeight); - - if (QWidget *parentWidgetPtr = parentWidget()) { - parentWidgetPtr->setMinimumSize(totalWidth, availableHeight); - } -} - -/** - * @brief Lays out items into columns according to the available height, starting from a given origin. - * Each column is arranged within `availableHeight`, wrapping to a new column as necessary. - * @param originX The x-coordinate for the layout start position. - * @param originY The y-coordinate for the layout start position. - * @param availableHeight The height within which each column is constrained. - * @return The total width after arranging all columns. - */ -int HorizontalFlowLayout::layoutAllColumns(const int originX, const int originY, const int availableHeight) -{ - QVector colItems; // Holds items for the current column - int currentXPosition = originX; // Tracks the x-coordinate while placing items - int currentYPosition = originY; // Tracks the y-coordinate, resetting for each new column - - int colWidth = 0; // Tracks the maximum width of items in the current column - - for (QLayoutItem *item : items) { - if (item == nullptr || item->isEmpty()) { - continue; - } - - QSize itemSize = item->sizeHint(); // The suggested size for the current item - - // Check if the current item fits in the remaining height of the current column - if (currentYPosition + itemSize.height() > availableHeight) { - // If not, layout the current column and start a new column - layoutSingleColumn(colItems, currentXPosition, originY); - colItems.clear(); // Reset the list for the new column - currentYPosition = originY; // Reset y-position to the column's start - currentXPosition += colWidth; // Move x-position to the next column - colWidth = 0; // Reset column width for the new column - } - - // Add the item to the current column - colItems.append(item); - colWidth = qMax(colWidth, itemSize.width()); // Update the column's width to the widest item - currentYPosition += itemSize.height(); // Move y-position for the next item - } - - // Layout the final column if there are any remaining items - layoutSingleColumn(colItems, currentXPosition, originY); - - // Return the total width used, including the last column's width - return currentXPosition + colWidth; -} - -/** - * @brief Arranges a single column of items within specified x and y starting positions. - * @param colItems A list of items to be arranged in the column. - * @param x The starting x-coordinate for the column. - * @param y The starting y-coordinate for the column. - */ -void HorizontalFlowLayout::layoutSingleColumn(const QVector &colItems, const int x, int y) -{ - for (QLayoutItem *item : colItems) { - if (item != nullptr && item->isEmpty()) { - continue; - } - - // Get the maximum allowed size for the item - QSize itemMaxSize = item->widget()->maximumSize(); - // Constrain the item's width and height to its size hint or maximum size - const int itemWidth = qMin(item->sizeHint().width(), itemMaxSize.width()); - const int itemHeight = qMin(item->sizeHint().height(), itemMaxSize.height()); - // Set the item's geometry based on the computed size and position - item->setGeometry(QRect(QPoint(x, y), QSize(itemWidth, itemHeight))); - // Move the y-position down by the item's height to place the next item below - y += itemHeight; - } -} diff --git a/cockatrice/src/client/ui/layouts/horizontal_flow_layout.h b/cockatrice/src/client/ui/layouts/horizontal_flow_layout.h deleted file mode 100644 index f6aebb284..000000000 --- a/cockatrice/src/client/ui/layouts/horizontal_flow_layout.h +++ /dev/null @@ -1,19 +0,0 @@ -#ifndef HORIZONTAL_FLOW_LAYOUT_H -#define HORIZONTAL_FLOW_LAYOUT_H - -#include "flow_layout.h" - -class HorizontalFlowLayout : public FlowLayout -{ -public: - explicit HorizontalFlowLayout(QWidget *parent = nullptr, int margin = 0, int hSpacing = 0, int vSpacing = 0); - ~HorizontalFlowLayout() override; - - [[nodiscard]] int heightForWidth(int height) const override; - - void setGeometry(const QRect &rect) override; - int layoutAllColumns(int originX, int originY, int availableHeight); - static void layoutSingleColumn(const QVector &colItems, int x, int y); -}; - -#endif // HORIZONTAL_FLOW_LAYOUT_H \ No newline at end of file diff --git a/cockatrice/src/client/ui/layouts/vertical_flow_layout.cpp b/cockatrice/src/client/ui/layouts/vertical_flow_layout.cpp deleted file mode 100644 index 90e154662..000000000 --- a/cockatrice/src/client/ui/layouts/vertical_flow_layout.cpp +++ /dev/null @@ -1,144 +0,0 @@ -#include "vertical_flow_layout.h" - -/** - * @brief Constructs a VerticalFlowLayout instance with the specified parent widget. - * This layout arranges items in rows within the given width, automatically adjusting its height. - * @param parent The parent widget to which this layout belongs. - * @param margin The layout margin. - * @param hSpacing The horizontal spacing between items. - * @param vSpacing The vertical spacing between items. - */ -VerticalFlowLayout::VerticalFlowLayout(QWidget *parent, const int margin, const int hSpacing, const int vSpacing) - : FlowLayout(parent, margin, hSpacing, vSpacing) -{ -} - -/** - * @brief Destructor for VerticalFlowLayout, responsible for cleaning up layout items. - */ -VerticalFlowLayout::~VerticalFlowLayout() -{ - QLayoutItem *item; - while ((item = FlowLayout::takeAt(0))) { - delete item; - } -} - -/** - * @brief Calculates the required height to display all items, given a specified width. - * This method arranges items into rows and determines the total height needed. - * @param width The available width for arranging layout items. - * @return The total height required to fit all items, organized in rows constrained by the given width. - */ -int VerticalFlowLayout::heightForWidth(const int width) const -{ - int height = 0; - int rowWidth = 0; - int rowHeight = 0; - - for (const QLayoutItem *item : items) { - if (item == nullptr || item->isEmpty()) { - continue; - } - - int itemWidth = item->sizeHint().width() + horizontalSpacing(); - if (rowWidth + itemWidth > width) { - height += rowHeight + verticalSpacing(); - rowWidth = itemWidth; - rowHeight = item->sizeHint().height(); - } else { - rowWidth += itemWidth; - rowHeight = qMax(rowHeight, item->sizeHint().height()); - } - } - height += rowHeight; // Add height of the last row - return height; -} - -/** - * @brief Sets the geometry of the layout items, arranging them in rows within the given width. - * @param rect The rectangle area defining the layout space. - */ -void VerticalFlowLayout::setGeometry(const QRect &rect) -{ - // If we have a parent scroll area, we're clamped to that, else we use our own rectangle. - const int availableWidth = getParentScrollAreaWidth() == 0 ? rect.width() : getParentScrollAreaWidth(); - - const int totalHeight = layoutAllRows(rect.x(), rect.y(), availableWidth); - - if (QWidget *parentWidgetPtr = parentWidget()) { - parentWidgetPtr->setMinimumSize(availableWidth, totalHeight); - } -} - -/** - * @brief Lays out items into rows according to the available width, starting from a given origin. - * Each row is arranged within `availableWidth`, wrapping to a new row as necessary. - * @param originX The x-coordinate for the layout start position. - * @param originY The y-coordinate for the layout start position. - * @param availableWidth The width within which each row is constrained. - * @return The total height after arranging all rows. - */ -int VerticalFlowLayout::layoutAllRows(const int originX, const int originY, const int availableWidth) -{ - QVector rowItems; // Holds items for the current row - int currentXPosition = originX; // Tracks the x-coordinate while placing items - int currentYPosition = originY; // Tracks the y-coordinate, moving down after each row - - int rowHeight = 0; // Tracks the maximum height of items in the current row - - for (QLayoutItem *item : items) { - if (item == nullptr || item->isEmpty()) { - continue; - } - - QSize itemSize = item->sizeHint(); // The suggested size for the current item - int itemWidth = itemSize.width() + horizontalSpacing(); // Item width plus spacing - - // Check if the current item fits in the remaining width of the current row - if (currentXPosition + itemWidth > availableWidth) { - // If not, layout the current row and start a new row - layoutSingleRow(rowItems, originX, currentYPosition); - rowItems.clear(); // Reset the list for the new row - currentXPosition = originX; // Reset x-position to the row's start - currentYPosition += rowHeight + verticalSpacing(); // Move y-position down to the next row - rowHeight = 0; // Reset row height for the new row - } - - // Add the item to the current row - rowItems.append(item); - rowHeight = qMax(rowHeight, itemSize.height()); // Update the row's height to the tallest item - currentXPosition += itemWidth + horizontalSpacing(); // Move x-position for the next item - } - - // Layout the final row if there are any remaining items - layoutSingleRow(rowItems, originX, currentYPosition); - - // Return the total height used, including the last row's height - return currentYPosition + rowHeight; -} - -/** - * @brief Arranges a single row of items within specified x and y starting positions. - * @param rowItems A list of items to be arranged in the row. - * @param x The starting x-coordinate for the row. - * @param y The starting y-coordinate for the row. - */ -void VerticalFlowLayout::layoutSingleRow(const QVector &rowItems, int x, const int y) -{ - for (QLayoutItem *item : rowItems) { - if (item == nullptr || item->isEmpty()) { - continue; - } - - // Get the maximum allowed size for the item - QSize itemMaxSize = item->widget()->maximumSize(); - // Constrain the item's width and height to its size hint or maximum size - const int itemWidth = qMin(item->sizeHint().width(), itemMaxSize.width()); - const int itemHeight = qMin(item->sizeHint().height(), itemMaxSize.height()); - // Set the item's geometry based on the computed size and position - item->setGeometry(QRect(QPoint(x, y), QSize(itemWidth, itemHeight))); - // Move the x-position to the right, leaving space for horizontal spacing - x += itemWidth + horizontalSpacing(); - } -} diff --git a/cockatrice/src/client/ui/layouts/vertical_flow_layout.h b/cockatrice/src/client/ui/layouts/vertical_flow_layout.h deleted file mode 100644 index 5dcd4945c..000000000 --- a/cockatrice/src/client/ui/layouts/vertical_flow_layout.h +++ /dev/null @@ -1,19 +0,0 @@ -#ifndef VERTICAL_FLOW_LAYOUT_H -#define VERTICAL_FLOW_LAYOUT_H - -#include "flow_layout.h" - -class VerticalFlowLayout : public FlowLayout -{ -public: - explicit VerticalFlowLayout(QWidget *parent = nullptr, int margin = 0, int hSpacing = 0, int vSpacing = 0); - ~VerticalFlowLayout() override; - - [[nodiscard]] int heightForWidth(int width) const override; - - void setGeometry(const QRect &rect) override; - int layoutAllRows(int originX, int originY, int availableWidth) override; - void layoutSingleRow(const QVector &rowItems, int x, int y) override; -}; - -#endif // VERTICAL_FLOW_LAYOUT_H \ No newline at end of file diff --git a/cockatrice/src/client/ui/picture_loader.cpp b/cockatrice/src/client/ui/picture_loader.cpp deleted file mode 100644 index a28a98eac..000000000 --- a/cockatrice/src/client/ui/picture_loader.cpp +++ /dev/null @@ -1,807 +0,0 @@ -#include "picture_loader.h" - -#include "../../game/cards/card_database_manager.h" -#include "../../settings/cache_settings.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -// never cache more than 300 cards at once for a single deck -#define CACHED_CARD_PER_DECK_MAX 300 - -PictureToLoad::PictureToLoad(CardInfoPtr _card) - : card(std::move(_card)), urlTemplates(SettingsCache::instance().downloads().getAllURLs()) -{ - if (card) { - for (const auto &set : card->getSets()) { - sortedSets << set.getPtr(); - } - if (sortedSets.empty()) { - sortedSets << CardSet::newInstance("", "", "", QDate()); - } - std::sort(sortedSets.begin(), sortedSets.end(), SetDownloadPriorityComparator()); - // If the pixmapCacheKey corresponds to a specific set, we have to try to load it first. - for (const auto &set : card->getSets()) { - if (QLatin1String("card_") + card->getName() + QString("_") + QString(set.getProperty("uuid")) == - card->getPixmapCacheKey()) { - long long setIndex = sortedSets.indexOf(set.getPtr()); - CardSetPtr setForCardProviderID = sortedSets.takeAt(setIndex); - sortedSets.prepend(setForCardProviderID); - } - } - // The first time called, nextSet will also populate the Urls for the first set. - nextSet(); - } -} - -void PictureToLoad::populateSetUrls() -{ - /* currentSetUrls is a list, populated each time a new set is requested for a particular card - and Urls are removed from it as a download is attempted from each one. Custom Urls for - a set are given higher priority, so should be placed first in the list. */ - currentSetUrls.clear(); - - if (card && currentSet) { - QString setCustomURL = card->getCustomPicURL(currentSet->getShortName()); - - if (!setCustomURL.isEmpty()) { - currentSetUrls.append(setCustomURL); - } - } - - for (const QString &urlTemplate : urlTemplates) { - QString transformedUrl = transformUrl(urlTemplate); - - if (!transformedUrl.isEmpty()) { - currentSetUrls.append(transformedUrl); - } - } - - /* Call nextUrl to make sure currentUrl is up-to-date - but we don't need the result here. */ - (void)nextUrl(); -} - -bool PictureToLoad::nextSet() -{ - if (!sortedSets.isEmpty()) { - currentSet = sortedSets.takeFirst(); - populateSetUrls(); - return true; - } - currentSet = {}; - return false; -} - -bool PictureToLoad::nextUrl() -{ - if (!currentSetUrls.isEmpty()) { - currentUrl = currentSetUrls.takeFirst(); - return true; - } - currentUrl = QString(); - return false; -} - -QString PictureToLoad::getSetName() const -{ - if (currentSet) { - return currentSet->getCorrectedShortName(); - } else { - return QString(); - } -} - -// Card back returned by gatherer when card is not found -QStringList PictureLoaderWorker::md5Blacklist = QStringList() << "db0c48db407a907c16ade38de048a441"; - -PictureLoaderWorker::PictureLoaderWorker() - : QObject(nullptr), picsPath(SettingsCache::instance().getPicsPath()), - customPicsPath(SettingsCache::instance().getCustomPicsPath()), - picDownload(SettingsCache::instance().getPicDownload()), downloadRunning(false), loadQueueRunning(false) -{ - 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())); - - networkManager = new QNetworkAccessManager(this); - // We need a timeout to ensure requests don't hang indefinitely in case of - // cache corruption, see related Qt bug: https://bugreports.qt.io/browse/QTBUG-111397 - // Use Qt's default timeout (30s, as of 2023-02-22) -#if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)) - networkManager->setTransferTimeout(); -#endif - auto cache = new QNetworkDiskCache(this); - cache->setCacheDirectory(SettingsCache::instance().getNetworkCachePath()); - cache->setMaximumCacheSize(1024L * 1024L * - static_cast(SettingsCache::instance().getNetworkCacheSizeInMB())); - // Note: the settings is in MB, but QNetworkDiskCache uses bytes - connect(&SettingsCache::instance(), &SettingsCache::networkCacheSizeChanged, cache, - [cache](int newSizeInMB) { cache->setMaximumCacheSize(1024L * 1024L * static_cast(newSizeInMB)); }); - networkManager->setCache(cache); - // Use a ManualRedirectPolicy since we keep track of redirects in picDownloadFinished - // We can't use NoLessSafeRedirectPolicy because it is not applied with AlwaysCache - networkManager->setRedirectPolicy(QNetworkRequest::ManualRedirectPolicy); - connect(networkManager, SIGNAL(finished(QNetworkReply *)), this, SLOT(picDownloadFinished(QNetworkReply *))); - - cacheFilePath = SettingsCache::instance().getRedirectCachePath() + REDIRECT_CACHE_FILENAME; - loadRedirectCache(); - cleanStaleEntries(); - - connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, this, - &PictureLoaderWorker::saveRedirectCache); - - pictureLoaderThread = new QThread; - pictureLoaderThread->start(QThread::LowPriority); - moveToThread(pictureLoaderThread); -} - -PictureLoaderWorker::~PictureLoaderWorker() -{ - pictureLoaderThread->deleteLater(); -} - -void PictureLoaderWorker::processLoadQueue() -{ - if (loadQueueRunning) { - return; - } - - loadQueueRunning = true; - while (true) { - mutex.lock(); - if (loadQueue.isEmpty()) { - mutex.unlock(); - loadQueueRunning = false; - return; - } - cardBeingLoaded = loadQueue.takeFirst(); - mutex.unlock(); - - QString setName = cardBeingLoaded.getSetName(); - QString cardName = cardBeingLoaded.getCard()->getName(); - QString correctedCardName = cardBeingLoaded.getCard()->getCorrectedName(); - - qDebug().nospace() << "PictureLoader: [card: " << cardName << " set: " << setName - << "]: Trying to load picture"; - - if (CardDatabaseManager::getInstance()->isProviderIdForPreferredPrinting( - cardName, cardBeingLoaded.getCard()->getPixmapCacheKey())) { - if (cardImageExistsOnDisk(setName, correctedCardName)) { - continue; - } - } - - qDebug().nospace() << "PictureLoader: [card: " << cardName << " set: " << setName - << "]: No custom picture, trying to download"; - cardsToDownload.append(cardBeingLoaded); - cardBeingLoaded.clear(); - if (!downloadRunning) { - startNextPicDownload(); - } - } -} - -bool PictureLoaderWorker::cardImageExistsOnDisk(QString &setName, QString &correctedCardname) -{ - QImage image; - QImageReader imgReader; - imgReader.setDecideFormatFromContent(true); - QList picsPaths = QList(); - QDirIterator it(customPicsPath, QDirIterator::Subdirectories | QDirIterator::FollowSymlinks); - - // Recursively check all subdirectories of the CUSTOM folder - while (it.hasNext()) { - QString thisPath(it.next()); - QFileInfo thisFileInfo(thisPath); - - if (thisFileInfo.isFile() && - (thisFileInfo.fileName() == correctedCardname || thisFileInfo.completeBaseName() == correctedCardname || - thisFileInfo.baseName() == correctedCardname)) { - picsPaths << thisPath; // Card found in the CUSTOM directory, somewhere - } - } - - if (!setName.isEmpty()) { - picsPaths << picsPath + "/" + setName + "/" + correctedCardname - // We no longer store downloaded images there, but don't just ignore - // stuff that old versions have put there. - << picsPath + "/downloadedPics/" + setName + "/" + correctedCardname; - } - - // Iterates through the list of paths, searching for images with the desired - // name with any QImageReader-supported - // extension - for (const auto &_picsPath : picsPaths) { - imgReader.setFileName(_picsPath); - if (imgReader.read(&image)) { - qDebug().nospace() << "PictureLoader: [card: " << correctedCardname << " set: " << setName - << "]: Picture found on disk."; - imageLoaded(cardBeingLoaded.getCard(), image); - return true; - } - imgReader.setFileName(_picsPath + ".full"); - if (imgReader.read(&image)) { - qDebug().nospace() << "PictureLoader: [card: " << correctedCardname << " set: " << setName - << "]: Picture.full found on disk."; - imageLoaded(cardBeingLoaded.getCard(), image); - return true; - } - imgReader.setFileName(_picsPath + ".xlhq"); - if (imgReader.read(&image)) { - qDebug().nospace() << "PictureLoader: [card: " << correctedCardname << " set: " << setName - << "]: Picture.xlhq found on disk."; - imageLoaded(cardBeingLoaded.getCard(), image); - return true; - } - } - - return false; -} - -static int parse(const QString &urlTemplate, - const QString &propType, - const QString &cardName, - const QString &setName, - std::function getProperty, - QMap &transformMap) -{ - static const QRegularExpression rxFillWith("^(.+)_fill_with_(.+)$"); - static const QRegularExpression rxSubStr("^(.+)_substr_(\\d+)_(\\d+)$"); - - const QRegularExpression rxCardProp("!" + propType + ":([^!]+)!"); - - auto matches = rxCardProp.globalMatch(urlTemplate); - while (matches.hasNext()) { - auto match = matches.next(); - QString templatePropertyName = match.captured(1); - auto fillMatch = rxFillWith.match(templatePropertyName); - QString cardPropertyName; - QString fillWith; - int subStrPos = 0; - int subStrLen = -1; - if (fillMatch.hasMatch()) { - cardPropertyName = fillMatch.captured(1); - fillWith = fillMatch.captured(2); - } else { - fillWith = QString(); - auto subStrMatch = rxSubStr.match(templatePropertyName); - if (subStrMatch.hasMatch()) { - cardPropertyName = subStrMatch.captured(1); - subStrPos = subStrMatch.captured(2).toInt(); - subStrLen = subStrMatch.captured(3).toInt(); - } else { - cardPropertyName = templatePropertyName; - } - } - QString propertyValue = getProperty(cardPropertyName); - if (propertyValue.isEmpty()) { - qDebug().nospace() << "PictureLoader: [card: " << cardName << " set: " << setName << "]: Requested " - << propType << "property (" << cardPropertyName << ") for Url template (" << urlTemplate - << ") is not available"; - return 1; - } else { - int propLength = propertyValue.length(); - if (subStrLen > 0) { - if (subStrPos + subStrLen > propLength) { - qDebug().nospace() << "PictureLoader: [card: " << cardName << " set: " << setName << "]: Requested " - << propType << " property (" << cardPropertyName << ") for Url template (" - << urlTemplate << ") is smaller than substr specification (" << subStrPos - << " + " << subStrLen << " > " << propLength << ")"; - return 1; - } else { - propertyValue = propertyValue.mid(subStrPos, subStrLen); - propLength = subStrLen; - } - } - - if (!fillWith.isEmpty()) { - int fillLength = fillWith.length(); - if (fillLength < propLength) { - qDebug().nospace() << "PictureLoader: [card: " << cardName << " set: " << setName << "]: Requested " - << propType << " property (" << cardPropertyName << ") for Url template (" - << urlTemplate << ") is longer than fill specification (" << fillWith << ")"; - return 1; - } else { - - propertyValue = fillWith.left(fillLength - propLength) + propertyValue; - } - } - - transformMap["!" + propType + ":" + templatePropertyName + "!"] = propertyValue; - } - } - return 0; -} - -QString PictureToLoad::transformUrl(const QString &urlTemplate) const -{ - /* This function takes Url templates and substitutes actual card details - into the url. This is used for making Urls with follow a predictable format - for downloading images. If information is requested by the template that is - not populated for this specific card/set combination, an empty string is returned.*/ - - CardSetPtr set = getCurrentSet(); - - QMap transformMap = QMap(); - QString setName = getSetName(); - - // name - QString cardName = card->getName(); - transformMap["!name!"] = cardName; - transformMap["!name_lower!"] = card->getName().toLower(); - transformMap["!corrected_name!"] = card->getCorrectedName(); - transformMap["!corrected_name_lower!"] = card->getCorrectedName().toLower(); - - // card properties - if (parse( - urlTemplate, "prop", cardName, setName, [&](const QString &name) { return card->getProperty(name); }, - transformMap)) { - return QString(); - } - - if (set) { - transformMap["!setcode!"] = set->getShortName(); - transformMap["!setcode_lower!"] = set->getShortName().toLower(); - transformMap["!setname!"] = set->getLongName(); - transformMap["!setname_lower!"] = set->getLongName().toLower(); - - if (parse( - urlTemplate, "set", cardName, setName, - [&](const QString &name) { return card->getSetProperty(set->getShortName(), name); }, transformMap)) { - return QString(); - } - } - - // language setting - transformMap["!sflang!"] = QString(QCoreApplication::translate( - "PictureLoader", "en", "code for scryfall's language property, not available for all languages")); - - QString transformedUrl = urlTemplate; - for (const QString &prop : transformMap.keys()) { - if (transformedUrl.contains(prop)) { - if (!transformMap[prop].isEmpty()) { - transformedUrl.replace(prop, QUrl::toPercentEncoding(transformMap[prop])); - } else { - /* This means the template is requesting information that is not - * populated in this card, so it should return an empty string, - * indicating an invalid Url. - */ - qDebug().nospace() << "PictureLoader: [card: " << cardName << " set: " << setName - << "]: Requested information (" << prop << ") for Url template (" << urlTemplate - << ") is not available"; - return QString(); - } - } - } - - return transformedUrl; -} - -void PictureLoaderWorker::startNextPicDownload() -{ - if (cardsToDownload.isEmpty()) { - cardBeingDownloaded.clear(); - downloadRunning = false; - return; - } - - downloadRunning = true; - - cardBeingDownloaded = cardsToDownload.takeFirst(); - - QString picUrl = cardBeingDownloaded.getCurrentUrl(); - - if (picUrl.isEmpty()) { - downloadRunning = false; - picDownloadFailed(); - } else { - QUrl url(picUrl); - qDebug().nospace() << "PictureLoader: [card: " << cardBeingDownloaded.getCard()->getCorrectedName() - << " set: " << cardBeingDownloaded.getSetName() << "]: Trying to fetch picture from url " - << url.toDisplayString(); - makeRequest(url); - } -} - -void PictureLoaderWorker::picDownloadFailed() -{ - /* Take advantage of short circuiting here to call the nextUrl until one - is not available. Only once nextUrl evaluates to false will this move - on to nextSet. If the Urls for a particular card are empty, this will - effectively go through the sets for that card. */ - if (cardBeingDownloaded.nextUrl() || cardBeingDownloaded.nextSet()) { - mutex.lock(); - loadQueue.prepend(cardBeingDownloaded); - mutex.unlock(); - } else { - qDebug().nospace() << "PictureLoader: [card: " << cardBeingDownloaded.getCard()->getCorrectedName() - << " set: " << cardBeingDownloaded.getSetName() << "]: Picture NOT found, " - << (picDownload ? "download failed" : "downloads disabled") - << ", no more url combinations to try: BAILING OUT"; - imageLoaded(cardBeingDownloaded.getCard(), QImage()); - cardBeingDownloaded.clear(); - } - emit startLoadQueue(); -} - -bool PictureLoaderWorker::imageIsBlackListed(const QByteArray &picData) -{ - QString md5sum = QCryptographicHash::hash(picData, QCryptographicHash::Md5).toHex(); - return md5Blacklist.contains(md5sum); -} - -QNetworkReply *PictureLoaderWorker::makeRequest(const QUrl &url) -{ - // Check if the redirect is cached - QUrl cachedRedirect = getCachedRedirect(url); - if (!cachedRedirect.isEmpty()) { - qDebug().nospace() << "PictureLoader: [card: " << cardBeingDownloaded.getCard()->getCorrectedName() - << " set: " << cardBeingDownloaded.getSetName() << "]: Using cached redirect for " - << url.toDisplayString() << " to " << cachedRedirect.toDisplayString(); - return makeRequest(cachedRedirect); // Use the cached redirect - } - - QNetworkRequest req(url); - - if (!picDownload) { - req.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::AlwaysCache); - } - - QNetworkReply *reply = networkManager->get(req); - - connect(reply, &QNetworkReply::finished, this, [this, reply, url]() { - QVariant redirectTarget = reply->attribute(QNetworkRequest::RedirectionTargetAttribute); - - if (redirectTarget.isValid()) { - QUrl redirectUrl = redirectTarget.toUrl(); - if (redirectUrl.isRelative()) { - redirectUrl = url.resolved(redirectUrl); - } - - cacheRedirect(url, redirectUrl); - qDebug().nospace() << "PictureLoader: [card: " << cardBeingDownloaded.getCard()->getCorrectedName() - << " set: " << cardBeingDownloaded.getSetName() << "]: Caching redirect from " - << url.toDisplayString() << " to " << redirectUrl.toDisplayString(); - } - - reply->deleteLater(); - }); - - return reply; -} - -void PictureLoaderWorker::cacheRedirect(const QUrl &originalUrl, const QUrl &redirectUrl) -{ - redirectCache[originalUrl] = qMakePair(redirectUrl, QDateTime::currentDateTimeUtc()); - saveRedirectCache(); -} - -QUrl PictureLoaderWorker::getCachedRedirect(const QUrl &originalUrl) const -{ - if (redirectCache.contains(originalUrl)) { - return redirectCache[originalUrl].first; - } - return {}; -} - -void PictureLoaderWorker::loadRedirectCache() -{ - QSettings settings(cacheFilePath, QSettings::IniFormat); - - redirectCache.clear(); - int size = settings.beginReadArray(REDIRECT_HEADER_NAME); - for (int i = 0; i < size; ++i) { - settings.setArrayIndex(i); - QUrl originalUrl = settings.value(REDIRECT_ORIGINAL_URL).toUrl(); - QUrl redirectUrl = settings.value(REDIRECT_URL).toUrl(); - QDateTime timestamp = settings.value(REDIRECT_TIMESTAMP).toDateTime(); - - if (originalUrl.isValid() && redirectUrl.isValid()) { - redirectCache[originalUrl] = qMakePair(redirectUrl, timestamp); - } - } - settings.endArray(); -} - -void PictureLoaderWorker::saveRedirectCache() const -{ - QSettings settings(cacheFilePath, QSettings::IniFormat); - - settings.beginWriteArray(REDIRECT_HEADER_NAME, static_cast(redirectCache.size())); - int index = 0; - for (auto it = redirectCache.cbegin(); it != redirectCache.cend(); ++it) { - settings.setArrayIndex(index++); - settings.setValue(REDIRECT_ORIGINAL_URL, it.key()); - settings.setValue(REDIRECT_URL, it.value().first); - settings.setValue(REDIRECT_TIMESTAMP, it.value().second); - } - settings.endArray(); -} - -void PictureLoaderWorker::cleanStaleEntries() -{ - QDateTime now = QDateTime::currentDateTimeUtc(); - - auto it = redirectCache.begin(); - while (it != redirectCache.end()) { - if (it.value().second.addDays(SettingsCache::instance().getRedirectCacheTtl()) < now) { - it = redirectCache.erase(it); // Remove stale entry - } else { - ++it; - } - } -} - -void PictureLoaderWorker::picDownloadFinished(QNetworkReply *reply) -{ - bool isFromCache = reply->attribute(QNetworkRequest::SourceIsFromCacheAttribute).toBool(); - - if (reply->error()) { - if (isFromCache) { - qDebug().nospace() << "PictureLoader: [card: " << cardBeingDownloaded.getCard()->getName() - << " set: " << cardBeingDownloaded.getSetName() - << "]: Removing corrupted cache file for url " << reply->url().toDisplayString() - << " and retrying (" << reply->errorString() << ")"; - - networkManager->cache()->remove(reply->url()); - - makeRequest(reply->url()); - } else { - qDebug().nospace() << "PictureLoader: [card: " << cardBeingDownloaded.getCard()->getName() - << " set: " << cardBeingDownloaded.getSetName() - << "]: " << (picDownload ? "Download" : "Cache search") << " failed for url " - << reply->url().toDisplayString() << " (" << reply->errorString() << ")"; - - picDownloadFailed(); - startNextPicDownload(); - } - - reply->deleteLater(); - return; - } - - // List of status codes from https://doc.qt.io/qt-6/qnetworkreply.html#redirected - int statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); - if (statusCode == 301 || statusCode == 302 || statusCode == 303 || statusCode == 305 || statusCode == 307 || - statusCode == 308) { - QUrl redirectUrl = reply->header(QNetworkRequest::LocationHeader).toUrl(); - qDebug().nospace() << "PictureLoader: [card: " << cardBeingDownloaded.getCard()->getName() - << " set: " << cardBeingDownloaded.getSetName() << "]: following " - << (isFromCache ? "cached redirect" : "redirect") << " to " << redirectUrl.toDisplayString(); - makeRequest(redirectUrl); - reply->deleteLater(); - return; - } - - // peek is used to keep the data in the buffer for use by QImageReader - const QByteArray &picData = reply->peek(reply->size()); - - if (imageIsBlackListed(picData)) { - qDebug().nospace() << "PictureLoader: [card: " << cardBeingDownloaded.getCard()->getName() - << " set: " << cardBeingDownloaded.getSetName() - << "]: Picture found, but blacklisted, will consider it as not found"; - - picDownloadFailed(); - reply->deleteLater(); - startNextPicDownload(); - return; - } - - QImage testImage; - - QImageReader imgReader; - imgReader.setDecideFormatFromContent(true); - imgReader.setDevice(reply); - - bool logSuccessMessage = false; - - static const int riffHeaderSize = 12; // RIFF_HEADER_SIZE from webp/format_constants.h - auto replyHeader = reply->peek(riffHeaderSize); - - if (replyHeader.startsWith("RIFF") && replyHeader.endsWith("WEBP")) { - auto imgBuf = QBuffer(this); - imgBuf.setData(reply->readAll()); - - auto movie = QMovie(&imgBuf); - movie.start(); - movie.stop(); - - imageLoaded(cardBeingDownloaded.getCard(), movie.currentImage()); - logSuccessMessage = true; - } else if (imgReader.read(&testImage)) { - imageLoaded(cardBeingDownloaded.getCard(), testImage); - logSuccessMessage = true; - } else { - qDebug().nospace() << "PictureLoader: [card: " << cardBeingDownloaded.getCard()->getName() - << " set: " << cardBeingDownloaded.getSetName() << "]: Possible " - << (isFromCache ? "cached" : "downloaded") << " picture at " - << reply->url().toDisplayString() << " could not be loaded: " << reply->errorString(); - - picDownloadFailed(); - } - - if (logSuccessMessage) { - qDebug().nospace() << "PictureLoader: [card: " << cardBeingDownloaded.getCard()->getName() - << " set: " << cardBeingDownloaded.getSetName() << "]: Image successfully " - << (isFromCache ? "loaded from cached" : "downloaded from") << " url " - << reply->url().toDisplayString(); - } - - reply->deleteLater(); - startNextPicDownload(); -} - -void PictureLoaderWorker::enqueueImageLoad(CardInfoPtr card) -{ - QMutexLocker locker(&mutex); - - // avoid queueing the same card more than once - if (!card || card == cardBeingLoaded.getCard() || card == cardBeingDownloaded.getCard()) { - return; - } - - for (const PictureToLoad &pic : loadQueue) { - if (pic.getCard() == card) - return; - } - - for (const PictureToLoad &pic : cardsToDownload) { - if (pic.getCard() == card) - return; - } - - loadQueue.append(PictureToLoad(card)); - emit startLoadQueue(); -} - -void PictureLoaderWorker::picDownloadChanged() -{ - QMutexLocker locker(&mutex); - picDownload = SettingsCache::instance().getPicDownload(); -} - -void PictureLoaderWorker::picsPathChanged() -{ - QMutexLocker locker(&mutex); - picsPath = SettingsCache::instance().getPicsPath(); - customPicsPath = SettingsCache::instance().getCustomPicsPath(); -} - -void PictureLoaderWorker::clearNetworkCache() -{ - networkManager->cache()->clear(); -} - -PictureLoader::PictureLoader() : QObject(nullptr) -{ - worker = new PictureLoaderWorker; - connect(&SettingsCache::instance(), SIGNAL(picsPathChanged()), this, SLOT(picsPathChanged())); - connect(&SettingsCache::instance(), SIGNAL(picDownloadChanged()), this, SLOT(picDownloadChanged())); - - connect(worker, SIGNAL(imageLoaded(CardInfoPtr, const QImage &)), this, - SLOT(imageLoaded(CardInfoPtr, const QImage &))); -} - -PictureLoader::~PictureLoader() -{ - worker->deleteLater(); -} - -void PictureLoader::getCardBackPixmap(QPixmap &pixmap, QSize size) -{ - QString backCacheKey = "_trice_card_back_" + QString::number(size.width()) + QString::number(size.height()); - if (!QPixmapCache::find(backCacheKey, &pixmap)) { - qDebug() << "PictureLoader: cache fail for" << backCacheKey; - pixmap = QPixmap("theme:cardback").scaled(size, Qt::KeepAspectRatio, Qt::SmoothTransformation); - QPixmapCache::insert(backCacheKey, pixmap); - } -} - -void PictureLoader::getPixmap(QPixmap &pixmap, CardInfoPtr card, QSize size) -{ - if (card == nullptr) { - 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; - - // load the image and create a copy of the correct size - QPixmap bigPixmap; - if (QPixmapCache::find(key, &bigPixmap)) { - QScreen *screen = qApp->primaryScreen(); - qreal dpr = screen->devicePixelRatio(); - pixmap = bigPixmap.scaled(size * dpr, Qt::KeepAspectRatio, Qt::SmoothTransformation); - pixmap.setDevicePixelRatio(dpr); - QPixmapCache::insert(sizeKey, pixmap); - return; - } - - // add the card to the load queue - getInstance().worker->enqueueImageLoad(card); -} - -void PictureLoader::imageLoaded(CardInfoPtr card, const QImage &image) -{ - if (image.isNull()) { - QPixmapCache::insert(card->getPixmapCacheKey(), QPixmap()); - } else { - if (card->getUpsideDownArt()) { - QImage mirrorImage = image.mirrored(true, true); - QPixmapCache::insert(card->getPixmapCacheKey(), QPixmap::fromImage(mirrorImage)); - } else { - QPixmapCache::insert(card->getPixmapCacheKey(), QPixmap::fromImage(image)); - } - } - - card->emitPixmapUpdated(); -} - -void PictureLoader::clearPixmapCache(CardInfoPtr card) -{ - if (card) { - QPixmapCache::remove(card->getPixmapCacheKey()); - } -} - -void PictureLoader::clearPixmapCache() -{ - QPixmapCache::clear(); -} - -void PictureLoader::clearNetworkCache() -{ - getInstance().worker->clearNetworkCache(); -} - -void PictureLoader::cacheCardPixmaps(QList cards) -{ - QPixmap tmp; - int max = qMin(cards.size(), CACHED_CARD_PER_DECK_MAX); - for (int i = 0; i < max; ++i) { - const CardInfoPtr &card = cards.at(i); - if (!card) { - continue; - } - - QString key = card->getPixmapCacheKey(); - if (QPixmapCache::find(key, &tmp)) { - continue; - } - - getInstance().worker->enqueueImageLoad(card); - } -} - -void PictureLoader::picDownloadChanged() -{ - QPixmapCache::clear(); -} - -void PictureLoader::picsPathChanged() -{ - QPixmapCache::clear(); -} diff --git a/cockatrice/src/client/ui/picture_loader.h b/cockatrice/src/client/ui/picture_loader.h deleted file mode 100644 index 245114358..000000000 --- a/cockatrice/src/client/ui/picture_loader.h +++ /dev/null @@ -1,159 +0,0 @@ -#ifndef PICTURELOADER_H -#define PICTURELOADER_H - -#include "../../game/cards/card_database.h" - -#include -#include -#include -#include -class QNetworkAccessManager; -class QNetworkReply; -class QThread; - -#define REDIRECT_HEADER_NAME "redirects" -#define REDIRECT_ORIGINAL_URL "original" -#define REDIRECT_URL "redirect" -#define REDIRECT_TIMESTAMP "timestamp" -#define REDIRECT_CACHE_FILENAME "cache.ini" - -class PictureToLoad -{ -private: - class SetDownloadPriorityComparator - { - public: - /* - * Returns true if a has higher download priority than b - * Enabled sets have priority over disabled sets - * Both groups follows the user-defined order - */ - inline bool operator()(const CardSetPtr &a, const CardSetPtr &b) const - { - if (a->getEnabled()) { - return !b->getEnabled() || a->getSortKey() < b->getSortKey(); - } else { - return !b->getEnabled() && a->getSortKey() < b->getSortKey(); - } - } - }; - - CardInfoPtr card; - QList sortedSets; - QList urlTemplates; - QList currentSetUrls; - QString currentUrl; - CardSetPtr currentSet; - -public: - explicit PictureToLoad(CardInfoPtr _card = CardInfoPtr()); - - CardInfoPtr getCard() const - { - return card; - } - void clear() - { - card.clear(); - } - QString getCurrentUrl() const - { - return currentUrl; - } - CardSetPtr getCurrentSet() const - { - return currentSet; - } - QString getSetName() const; - QString transformUrl(const QString &urlTemplate) const; - bool nextSet(); - bool nextUrl(); - void populateSetUrls(); -}; - -class PictureLoaderWorker : public QObject -{ - Q_OBJECT -public: - explicit PictureLoaderWorker(); - ~PictureLoaderWorker() override; - - void enqueueImageLoad(CardInfoPtr card); - void clearNetworkCache(); - -private: - static QStringList md5Blacklist; - - QThread *pictureLoaderThread; - QString picsPath, customPicsPath; - QList loadQueue; - QMutex mutex; - QNetworkAccessManager *networkManager; - QHash> redirectCache; // Stores redirect and timestamp - QString cacheFilePath; // Path to persistent storage - static constexpr int CacheTTLInDays = 30; // TODO: Make user configurable - QList cardsToDownload; - PictureToLoad cardBeingLoaded; - PictureToLoad cardBeingDownloaded; - bool picDownload, downloadRunning, loadQueueRunning; - void startNextPicDownload(); - bool cardImageExistsOnDisk(QString &setName, QString &correctedCardName); - bool imageIsBlackListed(const QByteArray &); - QNetworkReply *makeRequest(const QUrl &url); - void cacheRedirect(const QUrl &originalUrl, const QUrl &redirectUrl); - QUrl getCachedRedirect(const QUrl &originalUrl) const; - void loadRedirectCache(); - void saveRedirectCache() const; - void cleanStaleEntries(); - -private slots: - void picDownloadFinished(QNetworkReply *reply); - void picDownloadFailed(); - - void picDownloadChanged(); - void picsPathChanged(); -public slots: - void processLoadQueue(); - -signals: - void startLoadQueue(); - void imageLoaded(CardInfoPtr card, const QImage &image); -}; - -class PictureLoader : public QObject -{ - Q_OBJECT -public: - static PictureLoader &getInstance() - { - static PictureLoader instance; - return instance; - } - -private: - explicit PictureLoader(); - ~PictureLoader() override; - // Singleton - Don't implement copy constructor and assign operator - PictureLoader(PictureLoader const &); - void operator=(PictureLoader const &); - - PictureLoaderWorker *worker; - -public: - static void getPixmap(QPixmap &pixmap, CardInfoPtr card, QSize size); - static void getCardBackPixmap(QPixmap &pixmap, QSize size); - static void clearPixmapCache(CardInfoPtr card); - static void clearPixmapCache(); - static void cacheCardPixmaps(QList cards); - -public slots: - static void clearNetworkCache(); - -private slots: - void picDownloadChanged(); - void picsPathChanged(); - -public slots: - void imageLoaded(CardInfoPtr card, const QImage &image); -}; -#endif diff --git a/cockatrice/src/client/ui/picture_loader/picture_loader.cpp b/cockatrice/src/client/ui/picture_loader/picture_loader.cpp new file mode 100644 index 000000000..eafeb9467 --- /dev/null +++ b/cockatrice/src/client/ui/picture_loader/picture_loader.cpp @@ -0,0 +1,229 @@ +#include "picture_loader.h" + +#include "../../../settings/cache_settings.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// never cache more than 300 cards at once for a single deck +#define CACHED_CARD_PER_DECK_MAX 300 + +PictureLoader::PictureLoader() : QObject(nullptr) +{ + worker = new PictureLoaderWorker; + connect(&SettingsCache::instance(), SIGNAL(picsPathChanged()), this, SLOT(picsPathChanged())); + connect(&SettingsCache::instance(), SIGNAL(picDownloadChanged()), this, SLOT(picDownloadChanged())); + + connect(worker, SIGNAL(imageLoaded(CardInfoPtr, const QImage &)), this, + SLOT(imageLoaded(CardInfoPtr, const QImage &))); +} + +PictureLoader::~PictureLoader() +{ + worker->deleteLater(); +} + +void PictureLoader::getCardBackPixmap(QPixmap &pixmap, QSize size) +{ + QString backCacheKey = "_trice_card_back_" + QString::number(size.width()) + "x" + QString::number(size.height()); + if (!QPixmapCache::find(backCacheKey, &pixmap)) { + 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_inprogress_" + QString::number(size.width()) + "x" + QString::number(size.height()); + if (!QPixmapCache::find(backCacheKey, &pixmap)) { + 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_failed_" + QString::number(size.width()) + "x" + QString::number(size.height()); + if (!QPixmapCache::find(backCacheKey, &pixmap)) { + 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) { + qCWarning(PictureLoaderLog) << "getPixmap called with null card!"; + return; + } + + QString key = card->getPixmapCacheKey(); + 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 ? 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); + return; + } + + // add the card to the load queue + qCDebug(PictureLoaderLog) << "Enqueuing " << card->getName() << " for " << card->getPixmapCacheKey(); + getInstance().worker->enqueueImageLoad(card); +} + +void PictureLoader::imageLoaded(CardInfoPtr card, const QImage &image) +{ + if (image.isNull()) { + QPixmapCache::insert(card->getPixmapCacheKey(), QPixmap()); + } else { + if (card->getUpsideDownArt()) { +#if (QT_VERSION >= QT_VERSION_CHECK(6, 9, 0)) + QImage mirrorImage = image.flipped(Qt::Horizontal | Qt::Vertical); +#else + QImage mirrorImage = image.mirrored(true, true); +#endif + QPixmapCache::insert(card->getPixmapCacheKey(), QPixmap::fromImage(mirrorImage)); + } else { + QPixmapCache::insert(card->getPixmapCacheKey(), QPixmap::fromImage(image)); + } + } + + card->emitPixmapUpdated(); +} + +void PictureLoader::clearPixmapCache(CardInfoPtr card) +{ + if (card) { + QPixmapCache::remove(card->getPixmapCacheKey()); + } +} + +void PictureLoader::clearPixmapCache() +{ + QPixmapCache::clear(); +} + +void PictureLoader::clearNetworkCache() +{ + getInstance().worker->clearNetworkCache(); +} + +void PictureLoader::cacheCardPixmaps(QList cards) +{ + QPixmap tmp; + int max = qMin(cards.size(), CACHED_CARD_PER_DECK_MAX); + for (int i = 0; i < max; ++i) { + const CardInfoPtr &card = cards.at(i); + if (!card) { + continue; + } + + QString key = card->getPixmapCacheKey(); + if (QPixmapCache::find(key, &tmp)) { + continue; + } + + getInstance().worker->enqueueImageLoad(card); + } +} + +void PictureLoader::picDownloadChanged() +{ + QPixmapCache::clear(); +} + +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; +} diff --git a/cockatrice/src/client/ui/picture_loader/picture_loader.h b/cockatrice/src/client/ui/picture_loader/picture_loader.h new file mode 100644 index 000000000..68a9424e4 --- /dev/null +++ b/cockatrice/src/client/ui/picture_loader/picture_loader.h @@ -0,0 +1,51 @@ +#ifndef PICTURELOADER_H +#define PICTURELOADER_H + +#include "../../../game/cards/card_database.h" +#include "picture_loader_worker.h" + +#include + +inline Q_LOGGING_CATEGORY(PictureLoaderLog, "picture_loader"); +inline Q_LOGGING_CATEGORY(PictureLoaderCardBackCacheFailLog, "picture_loader.card_back_cache_fail"); + +class PictureLoader : public QObject +{ + Q_OBJECT +public: + static PictureLoader &getInstance() + { + static PictureLoader instance; + return instance; + } + +private: + explicit PictureLoader(); + ~PictureLoader() override; + // Singleton - Don't implement copy constructor and assign operator + PictureLoader(PictureLoader const &); + void operator=(PictureLoader const &); + + PictureLoaderWorker *worker; + +public: + static void getPixmap(QPixmap &pixmap, CardInfoPtr card, QSize size); + static void getCardBackPixmap(QPixmap &pixmap, QSize size); + static void getCardBackLoadingInProgressPixmap(QPixmap &pixmap, QSize size); + static void getCardBackLoadingFailedPixmap(QPixmap &pixmap, QSize size); + static void clearPixmapCache(CardInfoPtr card); + static void clearPixmapCache(); + static void cacheCardPixmaps(QList cards); + static bool hasCustomArt(); + +public slots: + static void clearNetworkCache(); + +private slots: + void picDownloadChanged(); + void picsPathChanged(); + +public slots: + void imageLoaded(CardInfoPtr card, const QImage &image); +}; +#endif diff --git a/cockatrice/src/client/ui/picture_loader/picture_loader_worker.cpp b/cockatrice/src/client/ui/picture_loader/picture_loader_worker.cpp new file mode 100644 index 000000000..a1cfa53be --- /dev/null +++ b/cockatrice/src/client/ui/picture_loader/picture_loader_worker.cpp @@ -0,0 +1,481 @@ +#include "picture_loader_worker.h" + +#include "../../../game/cards/card_database_manager.h" +#include "../../../settings/cache_settings.h" + +#include +#include +#include +#include +#include +#include + +// Card back returned by gatherer when card is not found +QStringList PictureLoaderWorker::md5Blacklist = QStringList() << "db0c48db407a907c16ade38de048a441"; + +PictureLoaderWorker::PictureLoaderWorker() + : QObject(nullptr), picsPath(SettingsCache::instance().getPicsPath()), + customPicsPath(SettingsCache::instance().getCustomPicsPath()), + picDownload(SettingsCache::instance().getPicDownload()), downloadRunning(false), loadQueueRunning(false), + overrideAllCardArtWithPersonalPreference(SettingsCache::instance().getOverrideAllCardArtWithPersonalPreference()) +{ + connect(this, SIGNAL(startLoadQueue()), this, SLOT(processLoadQueue()), Qt::QueuedConnection); + connect(&SettingsCache::instance(), SIGNAL(picsPathChanged()), this, SLOT(picsPathChanged())); + connect(&SettingsCache::instance(), SIGNAL(picDownloadChanged()), this, SLOT(picDownloadChanged())); + connect(&SettingsCache::instance(), &SettingsCache::overrideAllCardArtWithPersonalPreferenceChanged, this, + &PictureLoaderWorker::setOverrideAllCardArtWithPersonalPreference); + + networkManager = new QNetworkAccessManager(this); + // We need a timeout to ensure requests don't hang indefinitely in case of + // cache corruption, see related Qt bug: https://bugreports.qt.io/browse/QTBUG-111397 + // Use Qt's default timeout (30s, as of 2023-02-22) +#if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)) + networkManager->setTransferTimeout(); +#endif + auto cache = new QNetworkDiskCache(this); + cache->setCacheDirectory(SettingsCache::instance().getNetworkCachePath()); + cache->setMaximumCacheSize(1024L * 1024L * + static_cast(SettingsCache::instance().getNetworkCacheSizeInMB())); + // Note: the settings is in MB, but QNetworkDiskCache uses bytes + connect(&SettingsCache::instance(), &SettingsCache::networkCacheSizeChanged, cache, + [cache](int newSizeInMB) { cache->setMaximumCacheSize(1024L * 1024L * static_cast(newSizeInMB)); }); + networkManager->setCache(cache); + // Use a ManualRedirectPolicy since we keep track of redirects in picDownloadFinished + // We can't use NoLessSafeRedirectPolicy because it is not applied with AlwaysCache + networkManager->setRedirectPolicy(QNetworkRequest::ManualRedirectPolicy); + connect(networkManager, SIGNAL(finished(QNetworkReply *)), this, SLOT(picDownloadFinished(QNetworkReply *))); + + cacheFilePath = SettingsCache::instance().getRedirectCachePath() + REDIRECT_CACHE_FILENAME; + loadRedirectCache(); + cleanStaleEntries(); + + connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, this, + &PictureLoaderWorker::saveRedirectCache); + + pictureLoaderThread = new QThread; + pictureLoaderThread->start(QThread::LowPriority); + moveToThread(pictureLoaderThread); +} + +PictureLoaderWorker::~PictureLoaderWorker() +{ + pictureLoaderThread->deleteLater(); +} + +void PictureLoaderWorker::processLoadQueue() +{ + if (loadQueueRunning) { + return; + } + + loadQueueRunning = true; + while (true) { + mutex.lock(); + if (loadQueue.isEmpty()) { + mutex.unlock(); + loadQueueRunning = false; + return; + } + cardBeingLoaded = loadQueue.takeFirst(); + mutex.unlock(); + + QString setName = cardBeingLoaded.getSetName(); + QString cardName = cardBeingLoaded.getCard()->getName(); + QString correctedCardName = cardBeingLoaded.getCard()->getCorrectedName(); + + qCDebug(PictureLoaderWorkerLog).nospace() + << "[card: " << cardName << " set: " << setName << "]: Trying to load picture"; + + // FIXME: This is a hack so that to keep old Cockatrice behavior + // (ignoring provider ID) when the "override all card art with personal + // preference" is set. + // + // Figure out a proper way to integrate the two systems at some point. + // + // Note: need to go through a member for + // overrideAllCardArtWithPersonalPreference as reading from the + // SettingsCache instance from the PictureLoaderWorker thread could + // cause race conditions. + // + // XXX: Reading from the CardDatabaseManager instance from the + // PictureLoaderWorker thread might not be safe either + bool searchCustomPics = overrideAllCardArtWithPersonalPreference || + CardDatabaseManager::getInstance()->isProviderIdForPreferredPrinting( + cardName, cardBeingLoaded.getCard()->getPixmapCacheKey()); + if (searchCustomPics && cardImageExistsOnDisk(setName, correctedCardName, searchCustomPics)) { + continue; + } + + qCDebug(PictureLoaderWorkerLog).nospace() + << "[card: " << cardName << " set: " << setName << "]: No custom picture, trying to download"; + cardsToDownload.append(cardBeingLoaded); + cardBeingLoaded.clear(); + if (!downloadRunning) { + startNextPicDownload(); + } + } +} + +bool PictureLoaderWorker::cardImageExistsOnDisk(QString &setName, QString &correctedCardname, bool searchCustomPics) +{ + QImage image; + QImageReader imgReader; + imgReader.setDecideFormatFromContent(true); + QList picsPaths = QList(); + + if (searchCustomPics) { + QDirIterator it(customPicsPath, QDirIterator::Subdirectories | QDirIterator::FollowSymlinks); + + // Recursively check all subdirectories of the CUSTOM folder + while (it.hasNext()) { + QString thisPath(it.next()); + QFileInfo thisFileInfo(thisPath); + + if (thisFileInfo.isFile() && + (thisFileInfo.fileName() == correctedCardname || thisFileInfo.completeBaseName() == correctedCardname || + thisFileInfo.baseName() == correctedCardname)) { + picsPaths << thisPath; // Card found in the CUSTOM directory, somewhere + } + } + } + + if (!setName.isEmpty()) { + picsPaths << picsPath + "/" + setName + "/" + correctedCardname + // We no longer store downloaded images there, but don't just ignore + // stuff that old versions have put there. + << picsPath + "/downloadedPics/" + setName + "/" + correctedCardname; + } + + // Iterates through the list of paths, searching for images with the desired + // name with any QImageReader-supported + // extension + for (const auto &_picsPath : picsPaths) { + imgReader.setFileName(_picsPath); + if (imgReader.read(&image)) { + qCDebug(PictureLoaderWorkerLog).nospace() + << "[card: " << correctedCardname << " set: " << setName << "]: Picture found on disk."; + imageLoaded(cardBeingLoaded.getCard(), image); + return true; + } + imgReader.setFileName(_picsPath + ".full"); + if (imgReader.read(&image)) { + qCDebug(PictureLoaderWorkerLog).nospace() + << "[card: " << correctedCardname << " set: " << setName << "]: Picture.full found on disk."; + imageLoaded(cardBeingLoaded.getCard(), image); + return true; + } + imgReader.setFileName(_picsPath + ".xlhq"); + if (imgReader.read(&image)) { + qCDebug(PictureLoaderWorkerLog).nospace() + << "[card: " << correctedCardname << " set: " << setName << "]: Picture.xlhq found on disk."; + imageLoaded(cardBeingLoaded.getCard(), image); + return true; + } + } + + return false; +} + +void PictureLoaderWorker::startNextPicDownload() +{ + if (cardsToDownload.isEmpty()) { + cardBeingDownloaded.clear(); + downloadRunning = false; + return; + } + + downloadRunning = true; + + cardBeingDownloaded = cardsToDownload.takeFirst(); + + QString picUrl = cardBeingDownloaded.getCurrentUrl(); + + if (picUrl.isEmpty()) { + downloadRunning = false; + picDownloadFailed(); + } else { + QUrl url(picUrl); + qCDebug(PictureLoaderWorkerLog).nospace() << "[card: " << cardBeingDownloaded.getCard()->getCorrectedName() + << " set: " << cardBeingDownloaded.getSetName() + << "]: Trying to fetch picture from url " << url.toDisplayString(); + makeRequest(url); + } +} + +void PictureLoaderWorker::picDownloadFailed() +{ + /* Take advantage of short circuiting here to call the nextUrl until one + is not available. Only once nextUrl evaluates to false will this move + on to nextSet. If the Urls for a particular card are empty, this will + effectively go through the sets for that card. */ + if (cardBeingDownloaded.nextUrl() || cardBeingDownloaded.nextSet()) { + mutex.lock(); + loadQueue.prepend(cardBeingDownloaded); + mutex.unlock(); + } else { + qCWarning(PictureLoaderWorkerLog).nospace() + << "[card: " << cardBeingDownloaded.getCard()->getCorrectedName() + << " set: " << cardBeingDownloaded.getSetName() << "]: Picture NOT found, " + << (picDownload ? "download failed" : "downloads disabled") + << ", no more url combinations to try: BAILING OUT"; + imageLoaded(cardBeingDownloaded.getCard(), QImage()); + cardBeingDownloaded.clear(); + } + emit startLoadQueue(); +} + +bool PictureLoaderWorker::imageIsBlackListed(const QByteArray &picData) +{ + QString md5sum = QCryptographicHash::hash(picData, QCryptographicHash::Md5).toHex(); + return md5Blacklist.contains(md5sum); +} + +QNetworkReply *PictureLoaderWorker::makeRequest(const QUrl &url) +{ + // Check if the redirect is cached + QUrl cachedRedirect = getCachedRedirect(url); + if (!cachedRedirect.isEmpty()) { + qCDebug(PictureLoaderWorkerLog).nospace() + << "[card: " << cardBeingDownloaded.getCard()->getCorrectedName() + << " set: " << cardBeingDownloaded.getSetName() << "]: Using cached redirect for " << url.toDisplayString() + << " to " << cachedRedirect.toDisplayString(); + return makeRequest(cachedRedirect); // Use the cached redirect + } + + QNetworkRequest req(url); + + if (!picDownload) { + req.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::AlwaysCache); + } + + QNetworkReply *reply = networkManager->get(req); + + connect(reply, &QNetworkReply::finished, this, [this, reply, url]() { + QVariant redirectTarget = reply->attribute(QNetworkRequest::RedirectionTargetAttribute); + + if (redirectTarget.isValid()) { + QUrl redirectUrl = redirectTarget.toUrl(); + if (redirectUrl.isRelative()) { + redirectUrl = url.resolved(redirectUrl); + } + + cacheRedirect(url, redirectUrl); + qCDebug(PictureLoaderWorkerLog).nospace() + << "[card: " << cardBeingDownloaded.getCard()->getCorrectedName() + << " set: " << cardBeingDownloaded.getSetName() << "]: Caching redirect from " << url.toDisplayString() + << " to " << redirectUrl.toDisplayString(); + } + + reply->deleteLater(); + }); + + return reply; +} + +void PictureLoaderWorker::cacheRedirect(const QUrl &originalUrl, const QUrl &redirectUrl) +{ + redirectCache[originalUrl] = qMakePair(redirectUrl, QDateTime::currentDateTimeUtc()); + saveRedirectCache(); +} + +QUrl PictureLoaderWorker::getCachedRedirect(const QUrl &originalUrl) const +{ + if (redirectCache.contains(originalUrl)) { + return redirectCache[originalUrl].first; + } + return {}; +} + +void PictureLoaderWorker::loadRedirectCache() +{ + QSettings settings(cacheFilePath, QSettings::IniFormat); + + redirectCache.clear(); + int size = settings.beginReadArray(REDIRECT_HEADER_NAME); + for (int i = 0; i < size; ++i) { + settings.setArrayIndex(i); + QUrl originalUrl = settings.value(REDIRECT_ORIGINAL_URL).toUrl(); + QUrl redirectUrl = settings.value(REDIRECT_URL).toUrl(); + QDateTime timestamp = settings.value(REDIRECT_TIMESTAMP).toDateTime(); + + if (originalUrl.isValid() && redirectUrl.isValid()) { + redirectCache[originalUrl] = qMakePair(redirectUrl, timestamp); + } + } + settings.endArray(); +} + +void PictureLoaderWorker::saveRedirectCache() const +{ + QSettings settings(cacheFilePath, QSettings::IniFormat); + + settings.beginWriteArray(REDIRECT_HEADER_NAME, static_cast(redirectCache.size())); + int index = 0; + for (auto it = redirectCache.cbegin(); it != redirectCache.cend(); ++it) { + settings.setArrayIndex(index++); + settings.setValue(REDIRECT_ORIGINAL_URL, it.key()); + settings.setValue(REDIRECT_URL, it.value().first); + settings.setValue(REDIRECT_TIMESTAMP, it.value().second); + } + settings.endArray(); +} + +void PictureLoaderWorker::cleanStaleEntries() +{ + QDateTime now = QDateTime::currentDateTimeUtc(); + + auto it = redirectCache.begin(); + while (it != redirectCache.end()) { + if (it.value().second.addDays(SettingsCache::instance().getRedirectCacheTtl()) < now) { + it = redirectCache.erase(it); // Remove stale entry + } else { + ++it; + } + } +} + +void PictureLoaderWorker::picDownloadFinished(QNetworkReply *reply) +{ + bool isFromCache = reply->attribute(QNetworkRequest::SourceIsFromCacheAttribute).toBool(); + + if (reply->error()) { + if (isFromCache) { + qCDebug(PictureLoaderWorkerLog).nospace() + << "[card: " << cardBeingDownloaded.getCard()->getName() << " set: " << cardBeingDownloaded.getSetName() + << "]: Removing corrupted cache file for url " << reply->url().toDisplayString() << " and retrying (" + << reply->errorString() << ")"; + + networkManager->cache()->remove(reply->url()); + + makeRequest(reply->url()); + } else { + qCDebug(PictureLoaderWorkerLog).nospace() + << "[card: " << cardBeingDownloaded.getCard()->getName() << " set: " << cardBeingDownloaded.getSetName() + << "]: " << (picDownload ? "Download" : "Cache search") << " failed for url " + << reply->url().toDisplayString() << " (" << reply->errorString() << ")"; + + picDownloadFailed(); + startNextPicDownload(); + } + + reply->deleteLater(); + return; + } + + // List of status codes from https://doc.qt.io/qt-6/qnetworkreply.html#redirected + int statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + if (statusCode == 301 || statusCode == 302 || statusCode == 303 || statusCode == 305 || statusCode == 307 || + statusCode == 308) { + QUrl redirectUrl = reply->header(QNetworkRequest::LocationHeader).toUrl(); + qCDebug(PictureLoaderWorkerLog).nospace() + << "[card: " << cardBeingDownloaded.getCard()->getName() << " set: " << cardBeingDownloaded.getSetName() + << "]: following " << (isFromCache ? "cached redirect" : "redirect") << " to " + << redirectUrl.toDisplayString(); + makeRequest(redirectUrl); + reply->deleteLater(); + return; + } + + // peek is used to keep the data in the buffer for use by QImageReader + const QByteArray &picData = reply->peek(reply->size()); + + if (imageIsBlackListed(picData)) { + qCDebug(PictureLoaderWorkerLog).nospace() + << "[card: " << cardBeingDownloaded.getCard()->getName() << " set: " << cardBeingDownloaded.getSetName() + << "]: Picture found, but blacklisted, will consider it as not found"; + + picDownloadFailed(); + reply->deleteLater(); + startNextPicDownload(); + return; + } + + QImage testImage; + + QImageReader imgReader; + imgReader.setDecideFormatFromContent(true); + imgReader.setDevice(reply); + + bool logSuccessMessage = false; + + static const int riffHeaderSize = 12; // RIFF_HEADER_SIZE from webp/format_constants.h + auto replyHeader = reply->peek(riffHeaderSize); + + if (replyHeader.startsWith("RIFF") && replyHeader.endsWith("WEBP")) { + auto imgBuf = QBuffer(this); + imgBuf.setData(reply->readAll()); + + auto movie = QMovie(&imgBuf); + movie.start(); + movie.stop(); + + imageLoaded(cardBeingDownloaded.getCard(), movie.currentImage()); + logSuccessMessage = true; + } else if (imgReader.read(&testImage)) { + imageLoaded(cardBeingDownloaded.getCard(), testImage); + logSuccessMessage = true; + } else { + qCDebug(PictureLoaderWorkerLog).nospace() + << "[card: " << cardBeingDownloaded.getCard()->getName() << " set: " << cardBeingDownloaded.getSetName() + << "]: Possible " << (isFromCache ? "cached" : "downloaded") << " picture at " + << reply->url().toDisplayString() << " could not be loaded: " << reply->errorString(); + + picDownloadFailed(); + } + + if (logSuccessMessage) { + qCInfo(PictureLoaderWorkerLog).nospace() + << "[card: " << cardBeingDownloaded.getCard()->getName() << " set: " << cardBeingDownloaded.getSetName() + << "]: Image successfully " << (isFromCache ? "loaded from cached" : "downloaded from") << " url " + << reply->url().toDisplayString(); + } + + reply->deleteLater(); + startNextPicDownload(); +} + +void PictureLoaderWorker::enqueueImageLoad(CardInfoPtr card) +{ + QMutexLocker locker(&mutex); + + // avoid queueing the same card more than once + if (!card || card == cardBeingLoaded.getCard() || card == cardBeingDownloaded.getCard()) { + return; + } + + for (const PictureToLoad &pic : loadQueue) { + if (pic.getCard() == card) + return; + } + + for (const PictureToLoad &pic : cardsToDownload) { + if (pic.getCard() == card) + return; + } + + loadQueue.append(PictureToLoad(card)); + emit startLoadQueue(); +} + +void PictureLoaderWorker::picDownloadChanged() +{ + QMutexLocker locker(&mutex); + picDownload = SettingsCache::instance().getPicDownload(); +} + +void PictureLoaderWorker::picsPathChanged() +{ + QMutexLocker locker(&mutex); + picsPath = SettingsCache::instance().getPicsPath(); + customPicsPath = SettingsCache::instance().getCustomPicsPath(); +} + +void PictureLoaderWorker::setOverrideAllCardArtWithPersonalPreference(bool _overrideAllCardArtWithPersonalPreference) +{ + overrideAllCardArtWithPersonalPreference = _overrideAllCardArtWithPersonalPreference; +} + +void PictureLoaderWorker::clearNetworkCache() +{ + networkManager->cache()->clear(); +} diff --git a/cockatrice/src/client/ui/picture_loader/picture_loader_worker.h b/cockatrice/src/client/ui/picture_loader/picture_loader_worker.h new file mode 100644 index 000000000..e85ede8f1 --- /dev/null +++ b/cockatrice/src/client/ui/picture_loader/picture_loader_worker.h @@ -0,0 +1,78 @@ +#ifndef PICTURE_LOADER_WORKER_H +#define PICTURE_LOADER_WORKER_H + +#include "../../../game/cards/card_database.h" +#include "picture_to_load.h" + +#include +#include +#include +#include + +#define REDIRECT_HEADER_NAME "redirects" +#define REDIRECT_ORIGINAL_URL "original" +#define REDIRECT_URL "redirect" +#define REDIRECT_TIMESTAMP "timestamp" +#define REDIRECT_CACHE_FILENAME "cache.ini" + +inline Q_LOGGING_CATEGORY(PictureLoaderWorkerLog, "picture_loader.worker"); + +class PictureLoaderWorker : public QObject +{ + Q_OBJECT +public: + explicit PictureLoaderWorker(); + ~PictureLoaderWorker() override; + + void enqueueImageLoad(CardInfoPtr card); + void clearNetworkCache(); + +private: + static QStringList md5Blacklist; + + QThread *pictureLoaderThread; + QString picsPath, customPicsPath; + QList loadQueue; + QMutex mutex; + QNetworkAccessManager *networkManager; + QHash> redirectCache; // Stores redirect and timestamp + QString cacheFilePath; // Path to persistent storage + static constexpr int CacheTTLInDays = 30; // TODO: Make user configurable + QList cardsToDownload; + PictureToLoad cardBeingLoaded; + PictureToLoad cardBeingDownloaded; + bool picDownload, downloadRunning, loadQueueRunning; + bool overrideAllCardArtWithPersonalPreference; + void startNextPicDownload(); + + /** Emit the `imageLoaded` signal and return `true` if a picture is found on + disk, return `false` otherwise. + + If `searchCustomPics` is `true`, the CUSTOM folder is searched for a + matching image first; otherwise, only the set-based folders are used. */ + bool cardImageExistsOnDisk(QString &setName, QString &correctedCardName, bool searchCustomPics); + + bool imageIsBlackListed(const QByteArray &); + QNetworkReply *makeRequest(const QUrl &url); + void cacheRedirect(const QUrl &originalUrl, const QUrl &redirectUrl); + QUrl getCachedRedirect(const QUrl &originalUrl) const; + void loadRedirectCache(); + void saveRedirectCache() const; + void cleanStaleEntries(); + +private slots: + void picDownloadFinished(QNetworkReply *reply); + void picDownloadFailed(); + + void picDownloadChanged(); + void picsPathChanged(); + void setOverrideAllCardArtWithPersonalPreference(bool _overrideAllCardArtWithPersonalPreference); +public slots: + void processLoadQueue(); + +signals: + void startLoadQueue(); + void imageLoaded(CardInfoPtr card, const QImage &image); +}; + +#endif // PICTURE_LOADER_WORKER_H diff --git a/cockatrice/src/client/ui/picture_loader/picture_to_load.cpp b/cockatrice/src/client/ui/picture_loader/picture_to_load.cpp new file mode 100644 index 000000000..9800d7cc5 --- /dev/null +++ b/cockatrice/src/client/ui/picture_loader/picture_to_load.cpp @@ -0,0 +1,241 @@ +#include "picture_to_load.h" + +#include "../../../settings/cache_settings.h" + +#include +#include +#include +#include +#include + +PictureToLoad::PictureToLoad(CardInfoPtr _card) + : card(std::move(_card)), urlTemplates(SettingsCache::instance().downloads().getAllURLs()) +{ + if (card) { + for (const auto &cardInfoPerSetList : card->getSets()) { + for (const auto &set : cardInfoPerSetList) { + sortedSets << set.getPtr(); + } + } + if (sortedSets.empty()) { + sortedSets << CardSet::newInstance("", "", "", QDate()); + } + std::sort(sortedSets.begin(), sortedSets.end(), SetDownloadPriorityComparator()); + + // If the user hasn't disabled arts other than their personal preference... + if (!SettingsCache::instance().getOverrideAllCardArtWithPersonalPreference()) { + // If the pixmapCacheKey corresponds to a specific set, we have to try to load it first. + for (const auto &cardInfoPerSetList : card->getSets()) { + for (const auto &set : cardInfoPerSetList) { + if (QLatin1String("card_") + card->getName() + QString("_") + QString(set.getProperty("uuid")) == + card->getPixmapCacheKey()) { + long long setIndex = sortedSets.indexOf(set.getPtr()); + CardSetPtr setForCardProviderID = sortedSets.takeAt(setIndex); + sortedSets.prepend(setForCardProviderID); + } + } + } + } + // The first time called, nextSet will also populate the Urls for the first set. + nextSet(); + } +} + +void PictureToLoad::populateSetUrls() +{ + /* currentSetUrls is a list, populated each time a new set is requested for a particular card + and Urls are removed from it as a download is attempted from each one. Custom Urls for + a set are given higher priority, so should be placed first in the list. */ + currentSetUrls.clear(); + + if (card && currentSet) { + QString setCustomURL = card->getCustomPicURL(currentSet->getShortName()); + + if (!setCustomURL.isEmpty()) { + currentSetUrls.append(setCustomURL); + } + } + + for (const QString &urlTemplate : urlTemplates) { + QString transformedUrl = transformUrl(urlTemplate); + + if (!transformedUrl.isEmpty()) { + currentSetUrls.append(transformedUrl); + } + } + + /* Call nextUrl to make sure currentUrl is up-to-date + but we don't need the result here. */ + (void)nextUrl(); +} + +bool PictureToLoad::nextSet() +{ + if (!sortedSets.isEmpty()) { + currentSet = sortedSets.takeFirst(); + populateSetUrls(); + return true; + } + currentSet = {}; + return false; +} + +bool PictureToLoad::nextUrl() +{ + if (!currentSetUrls.isEmpty()) { + currentUrl = currentSetUrls.takeFirst(); + return true; + } + currentUrl = QString(); + return false; +} + +QString PictureToLoad::getSetName() const +{ + if (currentSet) { + return currentSet->getCorrectedShortName(); + } else { + return QString(); + } +} + +static int parse(const QString &urlTemplate, + const QString &propType, + const QString &cardName, + const QString &setName, + std::function getProperty, + QMap &transformMap) +{ + static const QRegularExpression rxFillWith("^(.+)_fill_with_(.+)$"); + static const QRegularExpression rxSubStr("^(.+)_substr_(\\d+)_(\\d+)$"); + + const QRegularExpression rxCardProp("!" + propType + ":([^!]+)!"); + + auto matches = rxCardProp.globalMatch(urlTemplate); + while (matches.hasNext()) { + auto match = matches.next(); + QString templatePropertyName = match.captured(1); + auto fillMatch = rxFillWith.match(templatePropertyName); + QString cardPropertyName; + QString fillWith; + int subStrPos = 0; + int subStrLen = -1; + if (fillMatch.hasMatch()) { + cardPropertyName = fillMatch.captured(1); + fillWith = fillMatch.captured(2); + } else { + fillWith = QString(); + auto subStrMatch = rxSubStr.match(templatePropertyName); + if (subStrMatch.hasMatch()) { + cardPropertyName = subStrMatch.captured(1); + subStrPos = subStrMatch.captured(2).toInt(); + subStrLen = subStrMatch.captured(3).toInt(); + } else { + cardPropertyName = templatePropertyName; + } + } + QString propertyValue = getProperty(cardPropertyName); + if (propertyValue.isEmpty()) { + qCDebug(PictureToLoadLog).nospace() + << "PictureLoader: [card: " << cardName << " set: " << setName << "]: Requested " << propType + << "property (" << cardPropertyName << ") for Url template (" << urlTemplate << ") is not available"; + return 1; + } else { + int propLength = propertyValue.length(); + if (subStrLen > 0) { + if (subStrPos + subStrLen > propLength) { + qCDebug(PictureToLoadLog).nospace() + << "PictureLoader: [card: " << cardName << " set: " << setName << "]: Requested " << propType + << " property (" << cardPropertyName << ") for Url template (" << urlTemplate + << ") is smaller than substr specification (" << subStrPos << " + " << subStrLen << " > " + << propLength << ")"; + return 1; + } else { + propertyValue = propertyValue.mid(subStrPos, subStrLen); + propLength = subStrLen; + } + } + + if (!fillWith.isEmpty()) { + int fillLength = fillWith.length(); + if (fillLength < propLength) { + qCDebug(PictureToLoadLog).nospace() + << "PictureLoader: [card: " << cardName << " set: " << setName << "]: Requested " << propType + << " property (" << cardPropertyName << ") for Url template (" << urlTemplate + << ") is longer than fill specification (" << fillWith << ")"; + return 1; + } else { + + propertyValue = fillWith.left(fillLength - propLength) + propertyValue; + } + } + + transformMap["!" + propType + ":" + templatePropertyName + "!"] = propertyValue; + } + } + return 0; +} + +QString PictureToLoad::transformUrl(const QString &urlTemplate) const +{ + /* This function takes Url templates and substitutes actual card details + into the url. This is used for making Urls with follow a predictable format + for downloading images. If information is requested by the template that is + not populated for this specific card/set combination, an empty string is returned.*/ + + CardSetPtr set = getCurrentSet(); + + QMap transformMap = QMap(); + QString setName = getSetName(); + + // name + QString cardName = card->getName(); + transformMap["!name!"] = cardName; + transformMap["!name_lower!"] = card->getName().toLower(); + transformMap["!corrected_name!"] = card->getCorrectedName(); + transformMap["!corrected_name_lower!"] = card->getCorrectedName().toLower(); + + // card properties + if (parse( + urlTemplate, "prop", cardName, setName, [&](const QString &name) { return card->getProperty(name); }, + transformMap)) { + return QString(); + } + + if (set) { + transformMap["!setcode!"] = set->getShortName(); + transformMap["!setcode_lower!"] = set->getShortName().toLower(); + transformMap["!setname!"] = set->getLongName(); + transformMap["!setname_lower!"] = set->getLongName().toLower(); + + if (parse( + urlTemplate, "set", cardName, setName, + [&](const QString &name) { return card->getSetProperty(set->getShortName(), name); }, transformMap)) { + return QString(); + } + } + + // language setting + transformMap["!sflang!"] = QString(QCoreApplication::translate( + "PictureLoader", "en", "code for scryfall's language property, not available for all languages")); + + QString transformedUrl = urlTemplate; + for (const QString &prop : transformMap.keys()) { + if (transformedUrl.contains(prop)) { + if (!transformMap[prop].isEmpty()) { + transformedUrl.replace(prop, QUrl::toPercentEncoding(transformMap[prop])); + } else { + /* This means the template is requesting information that is not + * populated in this card, so it should return an empty string, + * indicating an invalid Url. + */ + qCDebug(PictureToLoadLog).nospace() + << "PictureLoader: [card: " << cardName << " set: " << setName << "]: Requested information (" + << prop << ") for Url template (" << urlTemplate << ") is not available"; + return QString(); + } + } + } + + return transformedUrl; +} diff --git a/cockatrice/src/client/ui/picture_loader/picture_to_load.h b/cockatrice/src/client/ui/picture_loader/picture_to_load.h new file mode 100644 index 000000000..96a114cd4 --- /dev/null +++ b/cockatrice/src/client/ui/picture_loader/picture_to_load.h @@ -0,0 +1,64 @@ +#ifndef PICTURE_TO_LOAD_H +#define PICTURE_TO_LOAD_H + +#include "../../../game/cards/card_database.h" + +#include + +inline Q_LOGGING_CATEGORY(PictureToLoadLog, "picture_loader.picture_to_load"); + +class PictureToLoad +{ +private: + class SetDownloadPriorityComparator + { + public: + /* + * Returns true if a has higher download priority than b + * Enabled sets have priority over disabled sets + * Both groups follows the user-defined order + */ + inline bool operator()(const CardSetPtr &a, const CardSetPtr &b) const + { + if (a->getEnabled()) { + return !b->getEnabled() || a->getSortKey() < b->getSortKey(); + } else { + return !b->getEnabled() && a->getSortKey() < b->getSortKey(); + } + } + }; + + CardInfoPtr card; + QList sortedSets; + QList urlTemplates; + QList currentSetUrls; + QString currentUrl; + CardSetPtr currentSet; + +public: + explicit PictureToLoad(CardInfoPtr _card = CardInfoPtr()); + + CardInfoPtr getCard() const + { + return card; + } + void clear() + { + card.clear(); + } + QString getCurrentUrl() const + { + return currentUrl; + } + CardSetPtr getCurrentSet() const + { + return currentSet; + } + QString getSetName() const; + QString transformUrl(const QString &urlTemplate) const; + bool nextSet(); + bool nextUrl(); + void populateSetUrls(); +}; + +#endif // PICTURE_TO_LOAD_H diff --git a/cockatrice/src/client/ui/pixel_map_generator.cpp b/cockatrice/src/client/ui/pixel_map_generator.cpp index 09dc2d1ae..25bd67264 100644 --- a/cockatrice/src/client/ui/pixel_map_generator.cpp +++ b/cockatrice/src/client/ui/pixel_map_generator.cpp @@ -3,9 +3,75 @@ #include "pb/serverinfo_user.pb.h" #include -#include +#include +#include #include #include +#include + +#define DEFAULT_COLOR_UNREGISTERED "#32c8ec"; +#define DEFAULT_COLOR_REGISTERED "#5ed900"; +#define DEFAULT_COLOR_MODERATOR_LEFT "#ffffff"; +#define DEFAULT_COLOR_MODERATOR_RIGHT "#000000"; +#define DEFAULT_COLOR_ADMIN "#ff2701"; + +/** + * Loads in an svg from file and scales it without affecting image quality. + * + * @param svgPath The path to the svg file, with file extension. + * @param size The desired size of the pixmap. + * @param expandOnly If true, then keep the size of the initial pixmap to at least the svg size. + * + * @return The svg loaded into a Pixmap with the given size, or an empty Pixmap if the loading failed. + */ +static QPixmap loadSvg(const QString &svgPath, const QSize &size, bool expandOnly = false) +{ + QSvgRenderer svgRenderer(svgPath); + + if (!svgRenderer.isValid()) { + qCWarning(PixelMapGeneratorLog) << "Failed to load" << svgPath; + return {}; + } + + // If expandOnly, make sure the pixmap is at least as large as the svg, so that we don't lose any detail. + // QIcon.pixmap(size) will automatically scale down the image, but it won't scale it up. + QSize pixmapSize = expandOnly ? svgRenderer.defaultSize().expandedTo(size) : size; + QPixmap pix(pixmapSize); + pix.fill(Qt::transparent); + + QPainter pixPainter(&pix); + svgRenderer.render(&pixPainter); + + // Converting the pixmap to a QIcon and back is the easiest way to scale down a svg without affecting image quality + if (expandOnly) { + return QIcon(pix).pixmap(size); + } + + return pix; +} + +/** + * Try to load path image from non-SVG formats, otherwise fall back to SVG. + * This is to allow custom themes to support non-SVG format type overrides, since SVG requires custom loading. + * @param path The path to the file, with no file extension. File formats will be automatically detected. + * @param size The desired size of the pixmap. + * @param expandOnly If true, then keep the size of the initial pixmap to at least the size (Only relevant if SVG). + * + * @return The loaded image into a Pixmap with the given size, or an empty Pixmap if the loading failed. + */ +static QPixmap tryLoadImage(const QString &path, const QSize &size, bool expandOnly = false) +{ + const auto formats = {"png", "jpg"}; + + QPixmap returnPixmap; + for (const auto &format : formats) { + if (returnPixmap.load(path, format)) { + return returnPixmap.scaled(size, Qt::KeepAspectRatio, Qt::SmoothTransformation); + } + } + + return loadSvg(path + ".svg", size, expandOnly); +} QMap PhasePixmapGenerator::pmCache; @@ -15,8 +81,7 @@ QPixmap PhasePixmapGenerator::generatePixmap(int height, QString name) if (pmCache.contains(key)) return pmCache.value(key); - QPixmap pixmap = - QPixmap("theme:phases/" + name).scaled(height, height, Qt::KeepAspectRatio, Qt::SmoothTransformation); + QPixmap pixmap = tryLoadImage("theme:phases/" + name, QSize(height, height)); pmCache.insert(key, pixmap); return pixmap; @@ -26,20 +91,25 @@ QMap CounterPixmapGenerator::pmCache; QPixmap CounterPixmapGenerator::generatePixmap(int height, QString name, bool highlight) { + // The colorless counter is named "x" by the server but the file is named "general.svg" + if (name == "x") { + name = "general"; + } + if (highlight) name.append("_highlight"); QString key = name + QString::number(height); if (pmCache.contains(key)) return pmCache.value(key); - QPixmap pixmap = - QPixmap("theme:counters/" + name).scaled(height, height, Qt::KeepAspectRatio, Qt::SmoothTransformation); + QPixmap pixmap = tryLoadImage("theme:counters/" + name, QSize(height, height)); + + // fall back to colorless counter if the name can't be found if (pixmap.isNull()) { name = "general"; if (highlight) name.append("_highlight"); - pixmap = - QPixmap("theme:counters/" + name).scaled(height, height, Qt::KeepAspectRatio, Qt::SmoothTransformation); + pixmap = tryLoadImage("theme:counters/" + name, QSize(height, height)); } pmCache.insert(key, pixmap); @@ -83,12 +153,17 @@ QPixmap CountryPixmapGenerator::generatePixmap(int height, const QString &countr return pmCache.value(key); int width = height * 2; - QPixmap pixmap = QPixmap("theme:countries/" + countryCode.toLower()) - .scaled(width, height, Qt::KeepAspectRatio, Qt::SmoothTransformation); + QPixmap pixmap = tryLoadImage("theme:countries/" + countryCode.toLower(), QSize(width, height), true); QPainter painter(&pixmap); painter.setPen(Qt::black); + + // width/height offset was determined through trial-and-error +#ifdef Q_OS_MACOS + painter.drawRect(0, 0, pixmap.width() / 2, pixmap.height() / 2); +#else painter.drawRect(0, 0, pixmap.width() - 1, pixmap.height() - 1); +#endif pmCache.insert(key, pixmap); return pixmap; @@ -96,40 +171,183 @@ QPixmap CountryPixmapGenerator::generatePixmap(int height, const QString &countr QMap CountryPixmapGenerator::pmCache; -QPixmap UserLevelPixmapGenerator::generatePixmap(int height, UserLevelFlags userLevel, bool isBuddy, QString privLevel) +/** + * Updates tags in the svg + * + * @param elem The svg + * @param tagName tag with attribute to update + * @param attrName attribute to be updated + * @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, + const QString &idName, + const QString &attrValue) { + if (elem.tagName().compare(tagName) == 0) { + if (elem.attribute("id").compare(idName) == 0) { + elem.setAttribute(attrName, attrValue); + } + } - QString key = QString::number(height * 10000) + ":" + (short)userLevel + ":" + (short)isBuddy + ":" + privLevel; - if (pmCache.contains(key)) - return pmCache.value(key); - - QString levelString; - if (userLevel.testFlag(ServerInfo_User::IsAdmin)) { - levelString = "admin"; - if (privLevel.toLower() == "vip") - levelString.append("_" + privLevel.toLower()); - } else if (userLevel.testFlag(ServerInfo_User::IsModerator)) { - levelString = "moderator"; - if (privLevel.toLower() == "vip") - levelString.append("_" + privLevel.toLower()); - } else if (userLevel.testFlag(ServerInfo_User::IsRegistered)) { - levelString = "registered"; - if (privLevel.toLower() != "none") - levelString.append("_" + privLevel.toLower()); - } else - levelString = "normal"; - - if (isBuddy) - levelString.append("_buddy"); - - QPixmap pixmap = QPixmap("theme:userlevels/" + levelString) - .scaled(height, height, Qt::KeepAspectRatio, Qt::SmoothTransformation); - - pmCache.insert(key, pixmap); - return pixmap; + for (int i = 0; i < elem.childNodes().count(); i++) { + if (!elem.childNodes().at(i).isElement()) { + continue; + } + auto docElem = elem.childNodes().at(i).toElement(); + setAttrRecur(docElem, tagName, attrName, idName, attrValue); + } } -QMap UserLevelPixmapGenerator::pmCache; +/** + * Loads the usericon svg and fills in its colors. + * The image is kept as a QIcon to preserve the image quality. + * + * Call icon.pixmap(w, h) in order to convert this icon into a pixmap with the given dimensions. + * Avoid scaling the pixmap in other ways, as that destroys image quality. + * + * @param minSize If the dimensions of the source svg is smaller than this, then it will be scaled up to this size + */ +static QIcon loadAndColorSvg(const QString &iconPath, + const QString &colorLeft, + const std::optional &colorRight, + const int minSize) +{ + QFile file(iconPath); + if (!file.open(QIODevice::ReadOnly)) { + qCWarning(PixelMapGeneratorLog) << "Unable to open" << iconPath; + return {}; + } + + const auto &baData = file.readAll(); + QDomDocument doc; + doc.setContent(baData); + + auto docElem = doc.documentElement(); + + setAttrRecur(docElem, "path", "fill", "left", colorLeft); + if (colorRight.has_value()) { + setAttrRecur(docElem, "path", "fill", "right", colorRight.value()); + } + + QSvgRenderer svgRenderer(doc.toByteArray()); + + QPixmap pix(svgRenderer.defaultSize().expandedTo(QSize(minSize, minSize))); + pix.fill(Qt::transparent); + + QPainter pixPainter(&pix); + svgRenderer.render(&pixPainter); + + return QIcon(pix); +} + +QPixmap UserLevelPixmapGenerator::generatePixmap(int height, + UserLevelFlags userLevel, + ServerInfo_User::PawnColorsOverride pawnColorsOverride, + bool isBuddy, + const QString &privLevel) +{ + return generateIcon(height, userLevel, pawnColorsOverride, isBuddy, privLevel).pixmap(height, height); +} + +QIcon UserLevelPixmapGenerator::generateIcon(int minHeight, + UserLevelFlags userLevel, + ServerInfo_User::PawnColorsOverride pawnColorsOverride, + bool isBuddy, + const QString &privLevel) +{ + std::optional colorLeft = std::nullopt; + if (pawnColorsOverride.has_left_side()) { + colorLeft = QString::fromStdString(pawnColorsOverride.left_side()); + } + + std::optional colorRight = std::nullopt; + if (pawnColorsOverride.has_right_side()) { + colorRight = QString::fromStdString(pawnColorsOverride.right_side()); + } + + QString key = QString::number(minHeight * 10000) + ":" + static_cast(userLevel) + ":" + + static_cast(isBuddy) + ":" + privLevel.toLower() + ":" + colorLeft.value_or("") + ":" + + colorRight.value_or(""); + + if (iconCache.contains(key)) { + return iconCache.value(key); + } + + QIcon icon = colorLeft.has_value() + ? generateIconWithColorOverride(minHeight, isBuddy, userLevel, privLevel, colorLeft, colorRight) + : generateIconDefault(minHeight, userLevel, isBuddy, privLevel); + + iconCache.insert(key, icon); + return icon; +} + +static QString getIconType(const bool isBuddy, const UserLevelFlags &userLevelFlags, const QString &privLevel) +{ + if (isBuddy) { + return "star"; + } + + if (userLevelFlags.testFlag(ServerInfo_User_UserLevelFlag_IsJudge)) { + return "pawn_judge"; + } + + if (!privLevel.isEmpty() && privLevel.toLower() != "none") { + return QString("pawn_%1").arg(privLevel.toLower()); + } + + return "pawn"; +} + +QIcon UserLevelPixmapGenerator::generateIconDefault(int height, + UserLevelFlags userLevel, + bool isBuddy, + const QString &privLevel) +{ + const auto &iconType = getIconType(isBuddy, userLevel, privLevel); + + QString arity = "single"; + QString colorLeft; + std::optional colorRight = std::nullopt; + + if (userLevel.testFlag(ServerInfo_User::IsAdmin)) { + colorLeft = DEFAULT_COLOR_ADMIN; + } else if (userLevel.testFlag(ServerInfo_User::IsModerator)) { + colorLeft = DEFAULT_COLOR_MODERATOR_LEFT; + colorRight = DEFAULT_COLOR_MODERATOR_RIGHT; + arity = "double"; + } else if (userLevel.testFlag(ServerInfo_User::IsRegistered)) { + colorLeft = DEFAULT_COLOR_REGISTERED; + } else { + colorLeft = DEFAULT_COLOR_UNREGISTERED; + } + + const auto &iconPath = QString("theme:usericons/%1_%2.svg").arg(iconType, arity); + return loadAndColorSvg(iconPath, colorLeft, colorRight, height); +} + +QIcon UserLevelPixmapGenerator::generateIconWithColorOverride(int height, + bool isBuddy, + const UserLevelFlags &userLevelFlags, + const QString &privLevel, + const std::optional &colorLeft, + const std::optional &colorRight) +{ + const auto &iconType = getIconType(isBuddy, userLevelFlags, privLevel); + const QString &arity = colorRight.has_value() ? "double" : "single"; + const auto &iconPath = QString("theme:usericons/%1_%2.svg").arg(iconType, arity); + return loadAndColorSvg(iconPath, colorLeft.value(), colorRight, height); +} + +QMap UserLevelPixmapGenerator::iconCache; QPixmap LockPixmapGenerator::generatePixmap(int height) { @@ -138,14 +356,29 @@ QPixmap LockPixmapGenerator::generatePixmap(int height) if (pmCache.contains(key)) return pmCache.value(key); - QPixmap pixmap = QPixmap("theme:icons/lock").scaled(height, height, Qt::KeepAspectRatio, Qt::SmoothTransformation); + QPixmap pixmap = tryLoadImage("theme:icons/lock", QSize(height, height), true); pmCache.insert(key, pixmap); return pixmap; } QMap LockPixmapGenerator::pmCache; -const QPixmap loadColorAdjustedPixmap(QString name) +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 DropdownIconPixmapGenerator::pmCache; + +QPixmap loadColorAdjustedPixmap(const QString &name) { if (qApp->palette().windowText().color().lightness() > 200) { QImage img(name); diff --git a/cockatrice/src/client/ui/pixel_map_generator.h b/cockatrice/src/client/ui/pixel_map_generator.h index 65f9a96fc..0380de5ee 100644 --- a/cockatrice/src/client/ui/pixel_map_generator.h +++ b/cockatrice/src/client/ui/pixel_map_generator.h @@ -3,9 +3,13 @@ #include "user_level.h" +#include +#include #include #include +inline Q_LOGGING_CATEGORY(PixelMapGeneratorLog, "pixel_map_generator"); + class PhasePixmapGenerator { private: @@ -61,13 +65,31 @@ public: class UserLevelPixmapGenerator { private: - static QMap pmCache; + static QMap iconCache; + + static QIcon generateIconDefault(int height, UserLevelFlags userLevel, bool isBuddy, const QString &privLevel); + static QIcon generateIconWithColorOverride(int height, + bool isBuddy, + const UserLevelFlags &userLevelFlags, + const QString &privLevel, + const std::optional &colorLeft, + const std::optional &colorRight); public: - static QPixmap generatePixmap(int height, UserLevelFlags userLevel, bool isBuddy, QString privLevel = "NONE"); + static QPixmap generatePixmap(int height, + UserLevelFlags userLevel, + ServerInfo_User::PawnColorsOverride pawnColors, + bool isBuddy, + const QString &privLevel); + + static QIcon generateIcon(int minHeight, + UserLevelFlags userLevel, + ServerInfo_User::PawnColorsOverride pawnColors, + bool isBuddy, + const QString &privLevel); static void clear() { - pmCache.clear(); + iconCache.clear(); } }; @@ -84,6 +106,19 @@ public: } }; -const QPixmap loadColorAdjustedPixmap(QString name); +class DropdownIconPixmapGenerator +{ +private: + static QMap pmCache; + +public: + static QPixmap generatePixmap(int height, bool expanded); + static void clear() + { + pmCache.clear(); + } +}; + +QPixmap loadColorAdjustedPixmap(const QString &name); #endif diff --git a/cockatrice/src/client/ui/theme_manager.cpp b/cockatrice/src/client/ui/theme_manager.cpp index 249032dfa..0cff534a2 100644 --- a/cockatrice/src/client/ui/theme_manager.cpp +++ b/cockatrice/src/client/ui/theme_manager.cpp @@ -32,7 +32,7 @@ void ThemeManager::ensureThemeDirectoryExists() { if (SettingsCache::instance().getThemeName().isEmpty() || !getAvailableThemes().contains(SettingsCache::instance().getThemeName())) { - qDebug() << "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(); - qDebug() << "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); } diff --git a/cockatrice/src/client/ui/theme_manager.h b/cockatrice/src/client/ui/theme_manager.h index 3ab518b92..20854b961 100644 --- a/cockatrice/src/client/ui/theme_manager.h +++ b/cockatrice/src/client/ui/theme_manager.h @@ -3,10 +3,14 @@ #include #include +#include #include #include #include #include +#include + +inline Q_LOGGING_CATEGORY(ThemeManagerLog, "theme_manager"); typedef QMap QStringMap; typedef QMap QBrushMap; @@ -19,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 brushes; QStringMap availableThemes; /* Internal cache for multiple backgrounds */ - QBrushMap tableBgBrushesCache, stackBgBrushesCache, playerBgBrushesCache, handBgBrushesCache; + std::array brushesCache; protected: void ensureThemeDirectoryExists(); @@ -33,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: diff --git a/cockatrice/src/client/ui/widgets/cards/additional_info/color_identity_widget.cpp b/cockatrice/src/client/ui/widgets/cards/additional_info/color_identity_widget.cpp new file mode 100644 index 000000000..59388bfc3 --- /dev/null +++ b/cockatrice/src/client/ui/widgets/cards/additional_info/color_identity_widget.cpp @@ -0,0 +1,125 @@ +#include "color_identity_widget.h" + +#include "../../../../../settings/cache_settings.h" +#include "mana_symbol_widget.h" + +#include +#include +#include +#include +#include +#include + +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 manaSymbols = findChildren(); + + 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; +} diff --git a/cockatrice/src/client/ui/widgets/cards/additional_info/color_identity_widget.h b/cockatrice/src/client/ui/widgets/cards/additional_info/color_identity_widget.h new file mode 100644 index 000000000..8518aa1a5 --- /dev/null +++ b/cockatrice/src/client/ui/widgets/cards/additional_info/color_identity_widget.h @@ -0,0 +1,29 @@ +#ifndef COLOR_IDENTITY_WIDGET_H +#define COLOR_IDENTITY_WIDGET_H + +#include "../../../../../game/cards/card_database.h" + +#include +#include + +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 diff --git a/cockatrice/src/client/ui/widgets/cards/additional_info/mana_cost_widget.cpp b/cockatrice/src/client/ui/widgets/cards/additional_info/mana_cost_widget.cpp new file mode 100644 index 000000000..e5d1127aa --- /dev/null +++ b/cockatrice/src/client/ui/widgets/cards/additional_info/mana_cost_widget.cpp @@ -0,0 +1,76 @@ +#include "mana_cost_widget.h" + +#include "mana_symbol_widget.h" + +#include +#include +#include +#include +#include +#include + +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 manaSymbols = findChildren(); + + 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; +} diff --git a/cockatrice/src/client/ui/widgets/cards/additional_info/mana_cost_widget.h b/cockatrice/src/client/ui/widgets/cards/additional_info/mana_cost_widget.h new file mode 100644 index 000000000..0872183f3 --- /dev/null +++ b/cockatrice/src/client/ui/widgets/cards/additional_info/mana_cost_widget.h @@ -0,0 +1,24 @@ +#ifndef MANA_COST_WIDGET_H +#define MANA_COST_WIDGET_H + +#include "../../../../../game/cards/card_database.h" + +#include +#include + +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 diff --git a/cockatrice/src/client/ui/widgets/cards/additional_info/mana_symbol_widget.cpp b/cockatrice/src/client/ui/widgets/cards/additional_info/mana_symbol_widget.cpp new file mode 100644 index 000000000..dd4d1c1f6 --- /dev/null +++ b/cockatrice/src/client/ui/widgets/cards/additional_info/mana_symbol_widget.cpp @@ -0,0 +1,70 @@ +#include "mana_symbol_widget.h" + +#include "../../../../../settings/cache_settings.h" + +#include + +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); +} diff --git a/cockatrice/src/client/ui/widgets/cards/additional_info/mana_symbol_widget.h b/cockatrice/src/client/ui/widgets/cards/additional_info/mana_symbol_widget.h new file mode 100644 index 000000000..6ecf54b34 --- /dev/null +++ b/cockatrice/src/client/ui/widgets/cards/additional_info/mana_symbol_widget.h @@ -0,0 +1,43 @@ +#ifndef MANA_SYMBOL_WIDGET_H +#define MANA_SYMBOL_WIDGET_H + +#include +#include + +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 diff --git a/cockatrice/src/client/ui/widgets/cards/card_info_display_widget.cpp b/cockatrice/src/client/ui/widgets/cards/card_info_display_widget.cpp index 6e774a5a4..780ef53fe 100644 --- a/cockatrice/src/client/ui/widgets/cards/card_info_display_widget.cpp +++ b/cockatrice/src/client/ui/widgets/cards/card_info_display_widget.cpp @@ -11,7 +11,10 @@ #include #include -CardInfoDisplayWidget::CardInfoDisplayWidget(const QString &cardName, QWidget *parent, Qt::WindowFlags flags) +CardInfoDisplayWidget::CardInfoDisplayWidget(const QString &cardName, + const QString &providerId, + QWidget *parent, + Qt::WindowFlags flags) : QFrame(parent, flags), aspectRatio((qreal)CARD_HEIGHT / (qreal)CARD_WIDTH), info(nullptr) { setContentsMargins(3, 3, 3, 3); @@ -37,7 +40,7 @@ CardInfoDisplayWidget::CardInfoDisplayWidget(const QString &cardName, QWidget *p pic->setFixedHeight(pixmapHeight); setFixedWidth(pixmapWidth + 150); - setCard(cardName); + setCard(cardName, providerId); // ensure our parent gets a valid size to position us correctly resize(width(), sizeHint().height()); @@ -55,9 +58,9 @@ void CardInfoDisplayWidget::setCard(CardInfoPtr card) pic->setCard(info); } -void CardInfoDisplayWidget::setCard(const QString &cardName) +void CardInfoDisplayWidget::setCard(const QString &cardName, const QString &providerId) { - setCard(CardDatabaseManager::getInstance()->guessCard(cardName)); + setCard(CardDatabaseManager::getInstance()->guessCard(cardName, providerId)); if (info == nullptr) { text->setInvalidCardName(cardName); } diff --git a/cockatrice/src/client/ui/widgets/cards/card_info_display_widget.h b/cockatrice/src/client/ui/widgets/cards/card_info_display_widget.h index 21498d10c..46d42b2bc 100644 --- a/cockatrice/src/client/ui/widgets/cards/card_info_display_widget.h +++ b/cockatrice/src/client/ui/widgets/cards/card_info_display_widget.h @@ -22,11 +22,14 @@ private: CardInfoTextWidget *text; public: - explicit CardInfoDisplayWidget(const QString &cardName, QWidget *parent = nullptr, Qt::WindowFlags f = {}); + explicit CardInfoDisplayWidget(const QString &cardName, + const QString &providerId, + QWidget *parent = nullptr, + Qt::WindowFlags f = {}); public slots: void setCard(CardInfoPtr card); - void setCard(const QString &cardName); + void setCard(const QString &cardName, const QString &providerId = QString()); void setCard(AbstractCardItem *card); private slots: diff --git a/cockatrice/src/client/ui/widgets/cards/card_info_frame_widget.cpp b/cockatrice/src/client/ui/widgets/cards/card_info_frame_widget.cpp index fd9c84750..714ad20ca 100644 --- a/cockatrice/src/client/ui/widgets/cards/card_info_frame_widget.cpp +++ b/cockatrice/src/client/ui/widgets/cards/card_info_frame_widget.cpp @@ -3,6 +3,7 @@ #include "../../../../game/cards/card_database_manager.h" #include "../../../../game/cards/card_item.h" #include "../../../../settings/cache_settings.h" +#include "card_info_display_widget.h" #include "card_info_picture_widget.h" #include "card_info_text_widget.h" @@ -11,11 +12,13 @@ #include CardInfoFrameWidget::CardInfoFrameWidget(const QString &cardName, QWidget *parent) - : QTabWidget(parent), info(nullptr), cardTextOnly(false) + : QTabWidget(parent), info(nullptr), viewTransformationButton(nullptr), cardTextOnly(false) { setContentsMargins(3, 3, 3, 3); pic = new CardInfoPictureWidget(); pic->setObjectName("pic"); + connect(pic, &CardInfoPictureWidget::cardChanged, this, qOverload(&CardInfoFrameWidget::setCard)); + text = new CardInfoTextWidget(); text->setObjectName("text"); connect(text, SIGNAL(linkActivated(const QString &)), this, SLOT(setCard(const QString &))); @@ -66,30 +69,82 @@ void CardInfoFrameWidget::retranslateUi() setTabText(ImageOnlyView, tr("Image")); setTabText(TextOnlyView, tr("Description")); setTabText(ImageAndTextView, tr("Both")); + + if (viewTransformationButton) { + viewTransformationButton->setText(tr("View transformation")); + } } -void CardInfoFrameWidget::setViewMode(int mode) +void CardInfoFrameWidget::setViewTransformationButtonVisibility(bool visible) { - if (currentIndex() != mode) - setCurrentIndex(mode); + if (!viewTransformationButton && visible) { + viewTransformationButton = new QPushButton(); + viewTransformationButton->setObjectName("viewTransformationButton"); + connect(viewTransformationButton, &QPushButton::clicked, this, &CardInfoFrameWidget::viewTransformation); + refreshLayout(); + } else if (viewTransformationButton && !visible) { + // Deleting a widget automatically removes it from its parent + viewTransformationButton->deleteLater(); + viewTransformationButton = nullptr; + } +} - switch (mode) { +/** + * Adds the widgets to the layouts that are relevant to the currently active tab. + * + * QWidgets can only have one parent, so we need to re-parent the shared widgets whenever we switch tabs. + */ +void CardInfoFrameWidget::refreshLayout() +{ + switch (currentIndex()) { case ImageOnlyView: case TextOnlyView: + // We need to always parent all widgets, even the ones that aren't visible, + // since an unparented widget becomes free-floating. tab1Layout->addWidget(pic); + if (viewTransformationButton) { + tab1Layout->addWidget(viewTransformationButton); + } tab2Layout->addWidget(text); break; case ImageAndTextView: splitter->addWidget(pic); + if (viewTransformationButton) { + splitter->addWidget(viewTransformationButton); + } splitter->addWidget(text); break; default: break; } + retranslateUi(); +} + +void CardInfoFrameWidget::setViewMode(int mode) +{ + if (currentIndex() != mode) { + setCurrentIndex(mode); + } + + refreshLayout(); SettingsCache::instance().setCardInfoViewMode(mode); } +static bool hasTransformation(const CardInfoPtr &info) +{ + if (!info) { + return false; + } + + for (const auto &cardRelation : info->getAllRelatedCards()) { + if (cardRelation->getDoesTransform()) { + return true; + } + } + return false; +} + void CardInfoFrameWidget::setCard(CardInfoPtr card) { if (info) { @@ -102,6 +157,8 @@ void CardInfoFrameWidget::setCard(CardInfoPtr card) connect(info.data(), SIGNAL(destroyed()), this, SLOT(clearCard())); } + setViewTransformationButtonVisibility(hasTransformation(info)); + text->setCard(info); pic->setCard(info); } @@ -123,6 +180,19 @@ void CardInfoFrameWidget::setCard(AbstractCardItem *card) } } +void CardInfoFrameWidget::viewTransformation() +{ + if (info) { + const auto &cardRelations = info->getAllRelatedCards(); + for (const auto &cardRelation : cardRelations) { + if (cardRelation->getDoesTransform()) { + setCard(cardRelation->getName()); + break; + } + } + } +} + void CardInfoFrameWidget::clearCard() { setCard((CardInfoPtr) nullptr); diff --git a/cockatrice/src/client/ui/widgets/cards/card_info_frame_widget.h b/cockatrice/src/client/ui/widgets/cards/card_info_frame_widget.h index a9a4a0597..2b6012f1f 100644 --- a/cockatrice/src/client/ui/widgets/cards/card_info_frame_widget.h +++ b/cockatrice/src/client/ui/widgets/cards/card_info_frame_widget.h @@ -3,6 +3,7 @@ #include "../../../../game/cards/card_database.h" +#include #include class AbstractCardItem; @@ -18,11 +19,15 @@ private: CardInfoPtr info; CardInfoPictureWidget *pic; CardInfoTextWidget *text; + QPushButton *viewTransformationButton; bool cardTextOnly; QWidget *tab1, *tab2, *tab3; QVBoxLayout *tab1Layout, *tab2Layout, *tab3Layout; QSplitter *splitter; + void setViewTransformationButtonVisibility(bool visible); + void refreshLayout(); + public: enum ViewMode { @@ -30,7 +35,12 @@ public: TextOnlyView, ImageAndTextView }; + explicit CardInfoFrameWidget(const QString &cardName = QString(), QWidget *parent = nullptr); + CardInfoPtr getInfo() + { + return info; + } void retranslateUi(); public slots: @@ -38,6 +48,7 @@ public slots: void setCard(const QString &cardName); void setCard(const QString &cardName, const QString &providerId); void setCard(AbstractCardItem *card); + void viewTransformation(); void clearCard(); void setViewMode(int mode); }; diff --git a/cockatrice/src/client/ui/widgets/cards/card_info_picture_enlarged_widget.cpp b/cockatrice/src/client/ui/widgets/cards/card_info_picture_enlarged_widget.cpp index 47b8dddce..775390182 100644 --- a/cockatrice/src/client/ui/widgets/cards/card_info_picture_enlarged_widget.cpp +++ b/cockatrice/src/client/ui/widgets/cards/card_info_picture_enlarged_widget.cpp @@ -1,6 +1,7 @@ #include "card_info_picture_enlarged_widget.h" -#include "../../picture_loader.h" +#include "../../../../settings/cache_settings.h" +#include "../../picture_loader/picture_loader.h" #include #include @@ -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 diff --git a/cockatrice/src/client/ui/widgets/cards/card_info_picture_widget.cpp b/cockatrice/src/client/ui/widgets/cards/card_info_picture_widget.cpp index 8b71c9000..f4f699171 100644 --- a/cockatrice/src/client/ui/widgets/cards/card_info_picture_widget.cpp +++ b/cockatrice/src/client/ui/widgets/cards/card_info_picture_widget.cpp @@ -1,8 +1,13 @@ #include "card_info_picture_widget.h" +#include "../../../../game/cards/card_database_manager.h" #include "../../../../game/cards/card_item.h" -#include "../../picture_loader.h" +#include "../../../../settings/cache_settings.h" +#include "../../../tabs/tab_supervisor.h" +#include "../../picture_loader/picture_loader.h" +#include "../../window_main.h" +#include #include #include #include @@ -38,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(); + }); } /** @@ -92,13 +103,15 @@ void CardInfoPictureWidget::resizeEvent(QResizeEvent *event) */ void CardInfoPictureWidget::setScaleFactor(const int scale) { - const int newWidth = baseWidth + scale * 20; + const int newWidth = baseWidth * scale / 100; const int newHeight = static_cast(newWidth * aspectRatio); scaleFactor = scale; setFixedSize(newWidth, newHeight); updatePixmap(); + + emit cardScaleFactorChanged(scale); } /** @@ -119,10 +132,11 @@ void CardInfoPictureWidget::updatePixmap() */ void CardInfoPictureWidget::loadPixmap() { + PictureLoader::getCardBackLoadingInProgressPixmap(resizedPixmap, size()); if (info) { PictureLoader::getPixmap(resizedPixmap, info, size()); } else { - PictureLoader::getCardBackPixmap(resizedPixmap, size()); + PictureLoader::getCardBackLoadingFailedPixmap(resizedPixmap, size()); } pixmapDirty = false; @@ -138,6 +152,7 @@ void CardInfoPictureWidget::loadPixmap() void CardInfoPictureWidget::paintEvent(QPaintEvent *event) { QWidget::paintEvent(event); + if (width() == 0 || height() == 0) { return; } @@ -146,15 +161,47 @@ void CardInfoPictureWidget::paintEvent(QPaintEvent *event) loadPixmap(); } - const QSize scaledSize = resizedPixmap.size().scaled(size(), Qt::KeepAspectRatio); - const QPoint topLeft{(width() - scaledSize.width()) / 2, (height() - scaledSize.height()) / 2}; - const qreal radius = 0.05 * scaledSize.width(); + QPixmap transformedPixmap = resizedPixmap; // Default pixmap + if (SettingsCache::instance().getAutoRotateSidewaysLayoutCards()) { + if (info && info->getLandscapeOrientation()) { + // Rotate pixmap 90 degrees to the left + QTransform transform; + transform.rotate(90); + transformedPixmap = resizedPixmap.transformed(transform, Qt::SmoothTransformation); + } + } + // Handle DPI scaling + qreal dpr = devicePixelRatio(); // Get the actual scaling factor + QSize availableSize = size() * dpr; // Convert to physical pixel size + + // Compute final scaled size + QSize pixmapSize = transformedPixmap.size(); + QSize scaledSize = pixmapSize.scaled(availableSize, Qt::KeepAspectRatio); + + // 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((availableSize.width() - scaledSize.width()) / (2 * dpr)); + int targetY = static_cast((availableSize.height() - scaledSize.height()) / (2 * dpr)); + int targetW = static_cast(scaledSize.width() / dpr); + int targetH = static_cast(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(targetRect.width()) : 0.; + + // Draw the pixmap with rounded corners QStylePainter painter(this); QPainterPath shape; - shape.addRoundedRect(QRect(topLeft, scaledSize), radius, radius); + shape.addRoundedRect(targetRect, radius, radius); painter.setClipPath(shape); - painter.drawItemPixmap(QRect(topLeft, scaledSize), Qt::AlignCenter, resizedPixmap); + + // Draw the pre-scaled pixmap directly + painter.drawPixmap(targetRect, finalPixmap); } /** @@ -214,6 +261,103 @@ void CardInfoPictureWidget::mouseMoveEvent(QMouseEvent *event) } } +void CardInfoPictureWidget::mousePressEvent(QMouseEvent *event) +{ + QWidget::mousePressEvent(event); + if (event->button() == Qt::RightButton) { + createRightClickMenu()->popup(QCursor::pos()); + } +} + +QMenu *CardInfoPictureWidget::createRightClickMenu() +{ + auto *cardMenu = new QMenu(this); + + if (!info) { + return cardMenu; + } + + cardMenu->addMenu(createViewRelatedCardsMenu()); + cardMenu->addMenu(createAddToOpenDeckMenu()); + + return cardMenu; +} + +QMenu *CardInfoPictureWidget::createViewRelatedCardsMenu() +{ + auto viewRelatedCards = new QMenu(tr("View related cards")); + + QList relatedCards = info->getAllRelatedCards(); + + auto relatedCardExists = [](const CardRelation *cardRelation) { + return CardDatabaseManager::getInstance()->getCard(cardRelation->getName()) != nullptr; + }; + + bool atLeastOneGoodRelationFound = std::any_of(relatedCards.begin(), relatedCards.end(), relatedCardExists); + + if (!atLeastOneGoodRelationFound) { + viewRelatedCards->setEnabled(false); + return viewRelatedCards; + } + + for (const auto &relatedCard : relatedCards) { + const auto &relatedCardName = relatedCard->getName(); + QAction *viewCard = viewRelatedCards->addAction(relatedCardName); + connect(viewCard, &QAction::triggered, this, [this, &relatedCardName] { + emit cardChanged(CardDatabaseManager::getInstance()->getCard(relatedCardName)); + }); + viewRelatedCards->addAction(viewCard); + } + + 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(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 = findMainWindow(); + QList deckEditorTabs = mainWindow->getTabSupervisor()->getDeckEditorTabs(); + + if (deckEditorTabs.isEmpty()) { + addToOpenDeckMenu->setEnabled(false); + return addToOpenDeckMenu; + } + + for (auto &deckEditorTab : deckEditorTabs) { + auto *addCardMenu = addToOpenDeckMenu->addMenu(deckEditorTab->getTabText()); + + QAction *addCard = addCardMenu->addAction(tr("Mainboard")); + connect(addCard, &QAction::triggered, this, [this, deckEditorTab] { + deckEditorTab->updateCard(info); + deckEditorTab->actAddCard(info); + }); + + QAction *addCardSideboard = addCardMenu->addAction(tr("Sideboard")); + connect(addCardSideboard, &QAction::triggered, this, [this, deckEditorTab] { + deckEditorTab->updateCard(info); + deckEditorTab->actAddCardToSideboard(info); + }); + } + + return addToOpenDeckMenu; +} + /** * @brief Displays the enlarged version of the card's pixmap near the cursor. * diff --git a/cockatrice/src/client/ui/widgets/cards/card_info_picture_widget.h b/cockatrice/src/client/ui/widgets/cards/card_info_picture_widget.h index e18df00d8..a74028a7f 100644 --- a/cockatrice/src/client/ui/widgets/cards/card_info_picture_widget.h +++ b/cockatrice/src/client/ui/widgets/cards/card_info_picture_widget.h @@ -7,7 +7,10 @@ #include #include +inline Q_LOGGING_CATEGORY(CardInfoPictureWidgetLog, "card_info_picture_widget"); + class AbstractCardItem; +class QMenu; class CardInfoPictureWidget : public QWidget { @@ -29,6 +32,8 @@ public slots: signals: void hoveredOnCard(CardInfoPtr hoveredCard); + void cardScaleFactorChanged(int _scale); + void cardChanged(CardInfoPtr card); protected: void resizeEvent(QResizeEvent *event) override; @@ -40,6 +45,7 @@ protected: #endif void leaveEvent(QEvent *event) override; void mouseMoveEvent(QMouseEvent *event) override; + void mousePressEvent(QMouseEvent *event) override; void loadPixmap(); [[nodiscard]] const QPixmap &getResizedPixmap() const { @@ -62,6 +68,10 @@ private: CardInfoPictureEnlargedWidget *enlargedPixmapWidget; int enlargedPixmapOffset = 10; QTimer *hoverTimer; + + QMenu *createRightClickMenu(); + QMenu *createViewRelatedCardsMenu(); + QMenu *createAddToOpenDeckMenu(); }; #endif diff --git a/cockatrice/src/client/ui/widgets/cards/card_info_picture_with_text_overlay_widget.cpp b/cockatrice/src/client/ui/widgets/cards/card_info_picture_with_text_overlay_widget.cpp index f2e9442d0..255ab4738 100644 --- a/cockatrice/src/client/ui/widgets/cards/card_info_picture_with_text_overlay_widget.cpp +++ b/cockatrice/src/client/ui/widgets/cards/card_info_picture_with_text_overlay_widget.cpp @@ -96,16 +96,12 @@ void CardInfoPictureWithTextOverlayWidget::paintEvent(QPaintEvent *event) // Call the base class's paintEvent to draw the card image CardInfoPictureWidget::paintEvent(event); - // Now add the custom text overlay on top of the image + // If no overlay text, skip drawing the text if (overlayText.isEmpty()) { return; } - QStylePainter painter(this); - // Set text properties - QFont font = painter.font(); - font.setPointSize(fontSize); - painter.setFont(font); + QStylePainter painter(this); // Get the pixmap from the base class using the getter const QPixmap &pixmap = getResizedPixmap(); @@ -118,13 +114,34 @@ void CardInfoPictureWithTextOverlayWidget::paintEvent(QPaintEvent *event) const QPoint topLeft{(width() - scaledSize.width()) / 2, (height() - scaledSize.height()) / 2}; const QRect pixmapRect(topLeft, scaledSize); - // Prepare text wrapping - const QFontMetrics fontMetrics(font); - const int lineHeight = fontMetrics.height(); - const int textWidth = pixmapRect.width(); - QString wrappedText; + // Calculate the optimal font size + QFont font = painter.font(); + int optimalFontSize = fontSize; // Start with the user-defined font size + const QFontMetrics baseMetrics(font); + int textWidth = pixmapRect.width(); - // Break the text into multiple lines to fit within the pixmap width + // Reduce the font size until the text fits within the pixmap's width + do { + font.setPointSize(optimalFontSize); + QFontMetrics fm(font); + int currentWidth = 0; + for (const QString &word : overlayText.split(' ')) { + currentWidth = std::max(currentWidth, fm.horizontalAdvance(word)); + } + + if (currentWidth <= textWidth) { + break; + } + + --optimalFontSize; + } while (optimalFontSize > 1); + + // Apply the calculated font size + painter.setFont(font); + + // Wrap the text to fit within the pixmap width + const QFontMetrics fontMetrics(font); + QString wrappedText; QString currentLine; QStringList words = overlayText.split(' '); for (const QString &word : words) { @@ -141,13 +158,22 @@ void CardInfoPictureWithTextOverlayWidget::paintEvent(QPaintEvent *event) wrappedText += currentLine; // Calculate total text block height - const int totalTextHeight = static_cast(wrappedText.count('\n')) * lineHeight + lineHeight; + int totalTextHeight = wrappedText.count('\n') * fontMetrics.height() + fontMetrics.height(); + + // Adjust font size if the total text height exceeds the pixmap height + while (totalTextHeight > pixmapRect.height() && optimalFontSize > 1) { + --optimalFontSize; + font.setPointSize(optimalFontSize); + painter.setFont(font); + const QFontMetrics newMetrics(font); + totalTextHeight = wrappedText.count('\n') * newMetrics.height() + newMetrics.height(); + } // Set up the text layout options QTextOption textOption; textOption.setAlignment(textAlignment); - // Create a text rectangle centered within the pixmap rect + // Create a text rectangle centered vertically within the pixmap rect auto textRect = QRect(pixmapRect.left(), pixmapRect.top(), pixmapRect.width(), totalTextHeight); textRect.moveTop((pixmapRect.height() - totalTextHeight) / 2 + pixmapRect.top()); @@ -169,7 +195,6 @@ void CardInfoPictureWithTextOverlayWidget::drawOutlinedText(QPainter &painter, const QString &text, const QTextOption &textOption) const { - // Draw the black outline (outlineColor) painter.setPen(outlineColor); for (int dx = -1; dx <= 1; ++dx) { for (int dy = -1; dy <= 1; ++dy) { @@ -180,7 +205,7 @@ void CardInfoPictureWithTextOverlayWidget::drawOutlinedText(QPainter &painter, } } - // Draw the main text (textColor) + // Draw the main text painter.setPen(textColor); painter.drawText(textRect, text, textOption); } diff --git a/cockatrice/src/client/ui/widgets/cards/card_info_picture_with_text_overlay_widget.h b/cockatrice/src/client/ui/widgets/cards/card_info_picture_with_text_overlay_widget.h index 0290ce3eb..44107362a 100644 --- a/cockatrice/src/client/ui/widgets/cards/card_info_picture_with_text_overlay_widget.h +++ b/cockatrice/src/client/ui/widgets/cards/card_info_picture_with_text_overlay_widget.h @@ -7,7 +7,7 @@ #include #include -class CardInfoPictureWithTextOverlayWidget final : public CardInfoPictureWidget +class CardInfoPictureWithTextOverlayWidget : public CardInfoPictureWidget { Q_OBJECT diff --git a/cockatrice/src/client/ui/widgets/cards/card_size_widget.cpp b/cockatrice/src/client/ui/widgets/cards/card_size_widget.cpp new file mode 100644 index 000000000..be231e67a --- /dev/null +++ b/cockatrice/src/client/ui/widgets/cards/card_size_widget.cpp @@ -0,0 +1,68 @@ +#include "card_size_widget.h" + +#include "../../../../settings/cache_settings.h" +#include "../printing_selector/printing_selector.h" +#include "../visual_deck_storage/visual_deck_storage_widget.h" + +/** + * @class CardSizeWidget + * @brief A widget for adjusting card sizes using a slider. + * + * 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) +{ + cardSizeLayout = new QHBoxLayout(this); + cardSizeLayout->setContentsMargins(9, 0, 9, 0); + setLayout(cardSizeLayout); + + cardSizeLabel = new QLabel(tr("Card Size"), this); + cardSizeLabel->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); + + cardSizeSlider = new QSlider(Qt::Horizontal, this); + cardSizeSlider->setRange(50, 250); ///< Slider range for card size adjustment. + cardSizeSlider->setValue(defaultValue); ///< Initial slider value. + + cardSizeLayout->addWidget(cardSizeLabel); + cardSizeLayout->addWidget(cardSizeSlider); + + if (flowWidget != nullptr) { + connect(cardSizeSlider, &QSlider::valueChanged, flowWidget, &FlowWidget::setMinimumSizeToMaxSizeHint); + } + + // Debounce setup + debounceTimer.setSingleShot(true); + connect(&debounceTimer, &QTimer::timeout, this, [this]() { + // Check the type of the parent widget + if (qobject_cast(parentWidget())) { + SettingsCache::instance().setPrintingSelectorCardSize(pendingValue); + } else if (qobject_cast(parentWidget())) { + SettingsCache::instance().setVisualDeckStorageCardSize(pendingValue); + } + }); + + connect(cardSizeSlider, &QSlider::valueChanged, this, &CardSizeWidget::updateCardSizeSetting); +} + +/** + * @brief Updates the card size setting in the application's cache. + * + * @param newValue The new card size value set by the slider. + */ +void CardSizeWidget::updateCardSizeSetting(int newValue) +{ + pendingValue = newValue; + debounceTimer.start(300); // 300ms debounce time +} + +/** + * @brief Gets the slider widget used for adjusting the card size. + * + * @return A pointer to the QSlider object. + */ +QSlider *CardSizeWidget::getSlider() const +{ + return cardSizeSlider; +} diff --git a/cockatrice/src/client/ui/widgets/cards/card_size_widget.h b/cockatrice/src/client/ui/widgets/cards/card_size_widget.h new file mode 100644 index 000000000..d79359f23 --- /dev/null +++ b/cockatrice/src/client/ui/widgets/cards/card_size_widget.h @@ -0,0 +1,33 @@ +#ifndef CARD_SIZE_WIDGET_H +#define CARD_SIZE_WIDGET_H + +#include "../general/layout_containers/flow_widget.h" + +#include +#include +#include +#include +#include + +class CardSizeWidget : public QWidget +{ + Q_OBJECT + +public: + explicit CardSizeWidget(QWidget *parent, FlowWidget *flowWidget = nullptr, int defaultValue = 100); + [[nodiscard]] QSlider *getSlider() const; + +public slots: + void updateCardSizeSetting(int newValue); + +private: + QWidget *parent; + FlowWidget *flowWidget; + QHBoxLayout *cardSizeLayout; + QLabel *cardSizeLabel; + QSlider *cardSizeSlider; + QTimer debounceTimer; // Debounce timer + int pendingValue; // Stores the latest slider value +}; + +#endif // CARD_SIZE_WIDGET_H diff --git a/cockatrice/src/client/ui/widgets/cards/deck_preview_card_picture_widget.cpp b/cockatrice/src/client/ui/widgets/cards/deck_preview_card_picture_widget.cpp new file mode 100644 index 000000000..bf51a232d --- /dev/null +++ b/cockatrice/src/client/ui/widgets/cards/deck_preview_card_picture_widget.cpp @@ -0,0 +1,53 @@ +#include "deck_preview_card_picture_widget.h" + +#include +#include +#include +#include +#include +#include +#include + +/** + * @brief Constructs a CardPictureWithTextOverlay widget. + * @param parent The parent widget. + * @param hoverToZoomEnabled If this widget will spawn a larger widget when hovered over. + * @param textColor The color of the overlay text. + * @param outlineColor The color of the outline around the text. + * @param fontSize The font size of the overlay text. + * @param alignment The alignment of the text within the overlay. + * @param _deckLoader The Deck Loader holding the Deck associated with this preview. + * + * Sets the widget's size policy and default border style. + */ +DeckPreviewCardPictureWidget::DeckPreviewCardPictureWidget(QWidget *parent, + const bool hoverToZoomEnabled, + const QColor &textColor, + const QColor &outlineColor, + const int fontSize, + const Qt::Alignment alignment) + : CardInfoPictureWithTextOverlayWidget(parent, hoverToZoomEnabled, textColor, outlineColor, fontSize, alignment) +{ + singleClickTimer = new QTimer(this); + singleClickTimer->setSingleShot(true); + connect(singleClickTimer, &QTimer::timeout, this, [this]() { emit imageClicked(lastMouseEvent, this); }); +} + +void DeckPreviewCardPictureWidget::mousePressEvent(QMouseEvent *event) +{ + if (event->button() == Qt::LeftButton) { + lastMouseEvent = event; + singleClickTimer->start(QApplication::doubleClickInterval()); + } else { + emit imageClicked(event, this); + event->accept(); + } +} + +void DeckPreviewCardPictureWidget::mouseDoubleClickEvent(QMouseEvent *event) +{ + if (event->button() == Qt::LeftButton) { + singleClickTimer->stop(); // Prevent single-click logic + emit imageDoubleClicked(lastMouseEvent, this); + } +} diff --git a/cockatrice/src/client/ui/widgets/cards/deck_preview_card_picture_widget.h b/cockatrice/src/client/ui/widgets/cards/deck_preview_card_picture_widget.h new file mode 100644 index 000000000..3fafc73e0 --- /dev/null +++ b/cockatrice/src/client/ui/widgets/cards/deck_preview_card_picture_widget.h @@ -0,0 +1,35 @@ +#ifndef DECK_PREVIEW_CARD_PICTURE_WIDGET_H +#define DECK_PREVIEW_CARD_PICTURE_WIDGET_H + +#include "card_info_picture_with_text_overlay_widget.h" + +#include +#include +#include + +class DeckPreviewCardPictureWidget final : public CardInfoPictureWithTextOverlayWidget +{ + Q_OBJECT + +public: + explicit DeckPreviewCardPictureWidget(QWidget *parent, + bool hoverToZoomEnabled = false, + const QColor &textColor = Qt::white, + const QColor &outlineColor = Qt::black, + int fontSize = 12, + Qt::Alignment alignment = Qt::AlignCenter); + +signals: + void imageClicked(QMouseEvent *event, DeckPreviewCardPictureWidget *instance); + void imageDoubleClicked(QMouseEvent *event, DeckPreviewCardPictureWidget *instance); + +private: + QTimer *singleClickTimer; + QMouseEvent *lastMouseEvent = nullptr; // Store the last mouse event + +protected: + void mousePressEvent(QMouseEvent *event) override; + void mouseDoubleClickEvent(QMouseEvent *event) override; +}; + +#endif // DECK_PREVIEW_CARD_PICTURE_WIDGET_H diff --git a/cockatrice/src/client/ui/widgets/deck_editor/deck_editor_card_info_dock_widget.cpp b/cockatrice/src/client/ui/widgets/deck_editor/deck_editor_card_info_dock_widget.cpp new file mode 100644 index 000000000..0fe4ae76d --- /dev/null +++ b/cockatrice/src/client/ui/widgets/deck_editor/deck_editor_card_info_dock_widget.cpp @@ -0,0 +1,46 @@ +#include "deck_editor_card_info_dock_widget.h" + +#include "../cards/card_info_frame_widget.h" + +#include + +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(); +} \ No newline at end of file diff --git a/cockatrice/src/client/ui/widgets/deck_editor/deck_editor_card_info_dock_widget.h b/cockatrice/src/client/ui/widgets/deck_editor/deck_editor_card_info_dock_widget.h new file mode 100644 index 000000000..455df1e88 --- /dev/null +++ b/cockatrice/src/client/ui/widgets/deck_editor/deck_editor_card_info_dock_widget.h @@ -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 + +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 diff --git a/cockatrice/src/client/ui/widgets/deck_editor/deck_editor_database_display_widget.cpp b/cockatrice/src/client/ui/widgets/deck_editor/deck_editor_database_display_widget.cpp new file mode 100644 index 000000000..94c6609be --- /dev/null +++ b/cockatrice/src/client/ui/widgets/deck_editor/deck_editor_database_display_widget.cpp @@ -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 +#include +#include +#include +#include +#include +#include + +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), "

\\2

") + .replace(QRegularExpression("^(##)(.*)", opts), "

\\2

") + .replace(QRegularExpression("^(#)(.*)", opts), "

\\2

") + .replace(QRegularExpression("^------*", opts), "
") + .replace(QRegularExpression(R"(\[([^[]+)\]\(([^\)]+)\))", opts), R"(\1)"); + + 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")); +} \ No newline at end of file diff --git a/cockatrice/src/client/ui/widgets/deck_editor/deck_editor_database_display_widget.h b/cockatrice/src/client/ui/widgets/deck_editor/deck_editor_database_display_widget.h new file mode 100644 index 000000000..9022a2cb7 --- /dev/null +++ b/cockatrice/src/client/ui/widgets/deck_editor/deck_editor_database_display_widget.h @@ -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 +#include + +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 diff --git a/cockatrice/src/client/ui/widgets/deck_editor/deck_editor_deck_dock_widget.cpp b/cockatrice/src/client/ui/widgets/deck_editor/deck_editor_deck_dock_widget.cpp new file mode 100644 index 000000000..a89f73aed --- /dev/null +++ b/cockatrice/src/client/ui/widgets/deck_editor/deck_editor_deck_dock_widget.cpp @@ -0,0 +1,533 @@ +#include "deck_editor_deck_dock_widget.h" + +#include "../../../../game/cards/card_database_manager.h" +#include "../../../../settings/cache_settings.h" + +#include +#include +#include +#include +#include +#include +#include + +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::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> bannerCardSet; + InnerDecklistNode *listRoot = deckModel->getDeckList()->getRoot(); + for (int i = 0; i < listRoot->size(); i++) { + InnerDecklistNode *currentZone = dynamic_cast(listRoot->at(i)); + for (int j = 0; j < currentZone->size(); j++) { + DecklistCardNode *currentCard = dynamic_cast(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(currentCard->getName(), currentCard->getCardProviderId())); + } + } + } + } + + QList> pairList = bannerCardSet.values(); + + // Sort QList by the first() element of the QPair + std::sort(pairList.begin(), pairList.end(), [](const QPair &a, const QPair &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(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")); +} \ No newline at end of file diff --git a/cockatrice/src/client/ui/widgets/deck_editor/deck_editor_deck_dock_widget.h b/cockatrice/src/client/ui/widgets/deck_editor/deck_editor_deck_dock_widget.h new file mode 100644 index 000000000..0500b5fca --- /dev/null +++ b/cockatrice/src/client/ui/widgets/deck_editor/deck_editor_deck_dock_widget.h @@ -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 +#include +#include +#include +#include + +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 diff --git a/cockatrice/src/client/ui/widgets/deck_editor/deck_editor_filter_dock_widget.cpp b/cockatrice/src/client/ui/widgets/deck_editor/deck_editor_filter_dock_widget.cpp new file mode 100644 index 000000000..c60b1733b --- /dev/null +++ b/cockatrice/src/client/ui/widgets/deck_editor/deck_editor_filter_dock_widget.cpp @@ -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 +#include +#include + +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")); +} \ No newline at end of file diff --git a/cockatrice/src/client/ui/widgets/deck_editor/deck_editor_filter_dock_widget.h b/cockatrice/src/client/ui/widgets/deck_editor/deck_editor_filter_dock_widget.h new file mode 100644 index 000000000..9cd8d8d6f --- /dev/null +++ b/cockatrice/src/client/ui/widgets/deck_editor/deck_editor_filter_dock_widget.h @@ -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 +#include + +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 diff --git a/cockatrice/src/client/ui/widgets/deck_editor/deck_editor_printing_selector_dock_widget.cpp b/cockatrice/src/client/ui/widgets/deck_editor/deck_editor_printing_selector_dock_widget.cpp new file mode 100644 index 000000000..732c74cf2 --- /dev/null +++ b/cockatrice/src/client/ui/widgets/deck_editor/deck_editor_printing_selector_dock_widget.cpp @@ -0,0 +1,41 @@ +#include "deck_editor_printing_selector_dock_widget.h" + +#include "../../../tabs/abstract_tab_deck_editor.h" + +#include + +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")); +} diff --git a/cockatrice/src/client/ui/widgets/deck_editor/deck_editor_printing_selector_dock_widget.h b/cockatrice/src/client/ui/widgets/deck_editor/deck_editor_printing_selector_dock_widget.h new file mode 100644 index 000000000..ffc4297cb --- /dev/null +++ b/cockatrice/src/client/ui/widgets/deck_editor/deck_editor_printing_selector_dock_widget.h @@ -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 + +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 diff --git a/cockatrice/src/client/ui/widgets/general/display/banner_widget.cpp b/cockatrice/src/client/ui/widgets/general/display/banner_widget.cpp new file mode 100644 index 000000000..1d70a72b5 --- /dev/null +++ b/cockatrice/src/client/ui/widgets/general/display/banner_widget.cpp @@ -0,0 +1,101 @@ +#include "banner_widget.h" + +#include "../../../../../client/ui/pixel_map_generator.h" + +#include +#include +#include +#include + +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->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) +{ + QWidget::mousePressEvent(event); + if (clickable) { + emit buddyVisibilityChanged(); + } +} + +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()); + } +} + +void BannerWidget::paintEvent(QPaintEvent *event) +{ + Q_UNUSED(event); + + QPainter painter(this); + + // Calculate alpha based on transparency percentage + int alpha = (255 * transparency) / 100; + + // Determine gradient direction + QLinearGradient gradient; + if (gradientOrientation == Qt::Vertical) { + gradient = QLinearGradient(rect().topLeft(), rect().bottomLeft()); + } else { + gradient = QLinearGradient(rect().topLeft(), rect().topRight()); + } + + // Set neutral gradient colors with calculated transparency + gradient.setColorAt(0, QColor(200, 200, 200, alpha)); // Light grey with alpha + gradient.setColorAt(1, QColor(100, 100, 100, alpha / 1.5)); // Darker grey, slightly more transparent + + // Fill the widget background with the gradient + painter.fillRect(rect(), gradient); +} diff --git a/cockatrice/src/client/ui/widgets/general/display/banner_widget.h b/cockatrice/src/client/ui/widgets/general/display/banner_widget.h new file mode 100644 index 000000000..2f7db1726 --- /dev/null +++ b/cockatrice/src/client/ui/widgets/general/display/banner_widget.h @@ -0,0 +1,43 @@ +#ifndef BANNER_WIDGET_H +#define BANNER_WIDGET_H + +#include +#include +#include + +class BannerWidget : public QWidget +{ + Q_OBJECT + +public: + explicit BannerWidget(QWidget *parent, + const QString &text, + Qt::Orientation orientation = Qt::Vertical, + int transparency = 80); + void mousePressEvent(QMouseEvent *event) override; + void setText(const QString &text) const; + void setClickable(bool _clickable); + void setBuddy(QWidget *_buddy); + QString getText() const + { + return bannerLabel->text(); + } + +protected: + void paintEvent(QPaintEvent *event) override; + +private: + QLabel *iconLabel; + QLabel *bannerLabel; + Qt::Orientation gradientOrientation; + int transparency; // Transparency percentage for the gradient + QWidget *buddy = nullptr; + bool clickable = true; +signals: + void buddyVisibilityChanged(); +private slots: + void toggleBuddyVisibility() const; + void updateDropdownIconState() const; +}; + +#endif // BANNER_WIDGET_H diff --git a/cockatrice/src/client/ui/widgets/general/display/dynamic_font_size_label.cpp b/cockatrice/src/client/ui/widgets/general/display/dynamic_font_size_label.cpp new file mode 100644 index 000000000..ed4db1fd3 --- /dev/null +++ b/cockatrice/src/client/ui/widgets/general/display/dynamic_font_size_label.cpp @@ -0,0 +1,137 @@ +#include "dynamic_font_size_label.h" +#define FONT_PRECISION (0.5) + +#include +#include + +DynamicFontSizeLabel::DynamicFontSizeLabel(QWidget *parent, Qt::WindowFlags f) : QLabel(parent, f) +{ + setIndent(0); +} + +void DynamicFontSizeLabel::mousePressEvent(QMouseEvent *event) +{ + Q_UNUSED(event) + emit clicked(); +} + +void DynamicFontSizeLabel::paintEvent(QPaintEvent *event) +{ + // QElapsedTimer timer; + // timer.start(); + + QFont newFont = font(); + float fontSize = getWidgetMaximumFontSize(this, this->text()); + newFont.setPointSizeF(fontSize); + setFont(newFont); + // qDebug() << "Font size set to" << fontSize; + + QLabel::paintEvent(event); + // LOG(true, "Paint delay" << ((float)timer.nsecsElapsed())/1000000.0 << " mS"); +} + +float DynamicFontSizeLabel::getWidgetMaximumFontSize(QWidget *widget, const QString &text) +{ + QFont font = widget->font(); + const QRect widgetRect = widget->contentsRect(); + const float widgetWidth = widgetRect.width(); + const float widgetHeight = widgetRect.height(); + + QRectF newFontSizeRect; + float currentSize = font.pointSizeF(); + + float step = currentSize / 2.0; + + /* If too small, increase step */ + if (step <= FONT_PRECISION) { + step = FONT_PRECISION * 4.0; + } + + float lastTestedSize = currentSize; + + float currentHeight = 0; + float currentWidth = 0; + if (text == "") { + return currentSize; + } + + if (currentSize < 0) { + return 1; + } + + /* Only stop when step is small enough and new size is smaller than QWidget */ + while (step > FONT_PRECISION || (currentHeight > widgetHeight) || (currentWidth > widgetWidth)) { + /* Keep last tested value */ + lastTestedSize = currentSize; + + /* Test label with its font */ + font.setPointSizeF(currentSize); + /* Use font metrics to test */ + QFontMetricsF fm(font); + + /* Check if widget is QLabel */ + QLabel *label = qobject_cast(widget); + if (label) { + newFontSizeRect = + fm.boundingRect(widgetRect, (label->wordWrap() ? Qt::TextWordWrap : 0) | label->alignment(), text); + } else { + newFontSizeRect = fm.boundingRect(widgetRect, 0, text); + } + + currentHeight = newFontSizeRect.height(); + currentWidth = newFontSizeRect.width(); + + /* If new font size is too big, decrease it */ + if ((currentHeight > widgetHeight) || (currentWidth > widgetWidth)) { + // qDebug() << "-- contentsRect()" << label->contentsRect() << "rect"<< label->rect() << " newFontSizeRect" + // << newFontSizeRect << "Tight" << text << currentSize; + currentSize -= step; + /* if step is small enough, keep it constant, so it converge to biggest font size */ + if (step > FONT_PRECISION) { + step /= 2.0; + } + /* Do not allow negative size */ + if (currentSize <= 0) { + break; + } + } + /* If new font size is smaller than maximum possible size, increase it */ + else { + // qDebug() << "++ contentsRect()" << label->contentsRect() << "rect"<< label->rect() << " newFontSizeRect" + // << newFontSizeRect << "Tight" << text << currentSize; + currentSize += step; + } + } + return lastTestedSize; +} + +void DynamicFontSizeLabel::setTextColor(QColor color) +{ + if (color.isValid() && color != textColor) { + textColor = color; + setStyleSheet("color : " + color.name() + ";"); + } +} + +QColor DynamicFontSizeLabel::getTextColor() +{ + return textColor; +} + +void DynamicFontSizeLabel::setTextAndColor(const QString &text, QColor color) +{ + setTextColor(color); + setText(text); +} + +/* Do not give any size hint as it it changes during paintEvent */ +QSize DynamicFontSizeLabel::minimumSizeHint() const +{ + return QWidget::minimumSizeHint(); +} + +/* Do not give any size hint as it it changes during paintEvent */ +QSize DynamicFontSizeLabel::sizeHint() const +{ + return QWidget::sizeHint(); +} \ No newline at end of file diff --git a/cockatrice/src/client/ui/widgets/general/display/dynamic_font_size_label.h b/cockatrice/src/client/ui/widgets/general/display/dynamic_font_size_label.h new file mode 100644 index 000000000..2f66227e9 --- /dev/null +++ b/cockatrice/src/client/ui/widgets/general/display/dynamic_font_size_label.h @@ -0,0 +1,41 @@ +#ifndef DYNAMICFONTSIZELABEL_H +#define DYNAMICFONTSIZELABEL_H + +#include +#include + +class DynamicFontSizeLabel : public QLabel +{ + Q_OBJECT + +public: + explicit DynamicFontSizeLabel(QWidget *parent = NULL, Qt::WindowFlags f = Qt::WindowFlags()); + + ~DynamicFontSizeLabel() + { + } + + static float getWidgetMaximumFontSize(QWidget *widget, const QString &text); + + /* This method overwrite stylesheet */ + void setTextColor(QColor color); + QColor getTextColor(); + void setTextAndColor(const QString &text, QColor color = QColor::Invalid); +signals: + void clicked(); + +protected: + void mousePressEvent(QMouseEvent *event); + QColor textColor; + + // QWidget interface +protected: + void paintEvent(QPaintEvent *event); + + // QWidget interface +public: + QSize minimumSizeHint() const; + QSize sizeHint() const; +}; + +#endif // DYNAMICFONTSIZELABEL_H \ No newline at end of file diff --git a/cockatrice/src/client/ui/widgets/general/display/dynamic_font_size_push_button.cpp b/cockatrice/src/client/ui/widgets/general/display/dynamic_font_size_push_button.cpp new file mode 100644 index 000000000..fce4512e8 --- /dev/null +++ b/cockatrice/src/client/ui/widgets/general/display/dynamic_font_size_push_button.cpp @@ -0,0 +1,80 @@ +#include "dynamic_font_size_push_button.h" + +#include "dynamic_font_size_label.h" + +#include +#include + +DynamicFontSizePushButton::DynamicFontSizePushButton(QWidget *parent) : QPushButton(parent) +{ +} + +void DynamicFontSizePushButton::paintEvent(QPaintEvent *event) +{ + // Call the base class paintEvent to preserve any other painting behavior + QPushButton::paintEvent(event); + + // Adjust the font size dynamically based on the text + QFont newFont = font(); + float fontSize = DynamicFontSizeLabel::getWidgetMaximumFontSize(this, this->text()); + newFont.setPointSizeF(fontSize); + setFont(newFont); + + // Get painter for custom painting + QPainter painter(this); + painter.setRenderHint(QPainter::Antialiasing); + + // Paint the background with a linear gradient (normal state) + QLinearGradient gradient(0, 0, 0, height()); + if (isDown()) { + // Pressed state + gradient.setColorAt(0, QColor(128, 128, 128)); + gradient.setColorAt(1, QColor(64, 64, 64)); + } else if (underMouse()) { + // Hover state + gradient.setColorAt(0, QColor(96, 96, 96)); + gradient.setColorAt(1, QColor(48, 48, 48)); + } else { + // Normal state + gradient.setColorAt(0, QColor(64, 64, 64)); // start color + gradient.setColorAt(1, QColor(32, 32, 32)); // end color + } + painter.setBrush(gradient); + painter.setPen(Qt::NoPen); // No border + painter.drawRect(rect()); + + // Paint the button text + painter.setPen(QPen(textColor.isValid() ? textColor : QColor(255, 255, 255))); // Set text color + painter.drawText(rect(), Qt::AlignCenter, text()); +} + +void DynamicFontSizePushButton::setTextColor(QColor color) +{ + if (color.isValid() && color != textColor) { + textColor = color; + update(); // Request a repaint to update the text color + } +} + +void DynamicFontSizePushButton::setTextAndColor(const QString &text, QColor color) +{ + setTextColor(color); + setText(text); +} + +QColor DynamicFontSizePushButton::getTextColor() +{ + return textColor; +} + +/* Do not give any size hint as it it changes during paintEvent */ +QSize DynamicFontSizePushButton::minimumSizeHint() const +{ + return QWidget::minimumSizeHint(); +} + +/* Do not give any size hint as it it changes during paintEvent */ +QSize DynamicFontSizePushButton::sizeHint() const +{ + return QWidget::sizeHint(); +} diff --git a/cockatrice/src/client/ui/widgets/general/display/dynamic_font_size_push_button.h b/cockatrice/src/client/ui/widgets/general/display/dynamic_font_size_push_button.h new file mode 100644 index 000000000..eb33a7d80 --- /dev/null +++ b/cockatrice/src/client/ui/widgets/general/display/dynamic_font_size_push_button.h @@ -0,0 +1,29 @@ +#ifndef DYNAMICFONTSIZEPUSHBUTTON_H +#define DYNAMICFONTSIZEPUSHBUTTON_H + +#include +#include +#include + +class DynamicFontSizePushButton : public QPushButton +{ +public: + explicit DynamicFontSizePushButton(QWidget *parent = NULL); + + /* This method overwrite stylesheet */ + void setTextColor(QColor color); + QColor getTextColor(); + void setTextAndColor(const QString &text, QColor color = QColor::Invalid); + + // QWidget interface + QSize minimumSizeHint() const; + QSize sizeHint() const; + +protected: + void paintEvent(QPaintEvent *event); + +private: + QColor textColor; +}; + +#endif // DYNAMICFONTSIZEPUSHBUTTON_H \ No newline at end of file diff --git a/cockatrice/src/client/ui/widgets/general/display/shadow_background_label.cpp b/cockatrice/src/client/ui/widgets/general/display/shadow_background_label.cpp new file mode 100644 index 000000000..fbca8df8d --- /dev/null +++ b/cockatrice/src/client/ui/widgets/general/display/shadow_background_label.cpp @@ -0,0 +1,63 @@ +#include "shadow_background_label.h" + +#include +#include + +/** + * @class ShadowBackgroundLabel + * @brief A QLabel with a semi-transparent black shadowed background and rounded corners. + * + * This label provides a styled appearance with centered white text and a translucent + * rounded background, making it suitable for overlay or emphasis in a UI. + */ +ShadowBackgroundLabel::ShadowBackgroundLabel(QWidget *parent, const QString &text) : QLabel(parent) +{ + setAttribute(Qt::WA_TranslucentBackground); // Allows transparency. + setText("" + text + ""); ///< Ensures the text is rendered in white. + setAlignment(Qt::AlignCenter); ///< Centers the text within the label. + setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); ///< Ensures minimum size constraints. +} + +/** + * @brief Handles resizing of the label. + * + * Ensures the label updates its appearance when resized by triggering a repaint. + * + * @param event The resize event containing new size information. + */ +void ShadowBackgroundLabel::resizeEvent(QResizeEvent *event) +{ + QLabel::resizeEvent(event); + update(); // Repaint borders explicitly. +} + +/** + * @brief Custom paint event for drawing the label's background. + * + * Renders a semi-transparent black rounded rectangle as the background + * and then delegates text rendering to QLabel. + * + * @param event The paint event for the widget. + */ +void ShadowBackgroundLabel::paintEvent(QPaintEvent *event) +{ + QPainter painter(this); + + // Enable antialiasing for smoother edges. + painter.setRenderHint(QPainter::Antialiasing, true); + + // Set semi-transparent black brush and disable border pen. + painter.setBrush(QColor(0, 0, 0, 128)); // Semi-transparent black. + painter.setPen(Qt::NoPen); // No border. + + // Adjust the rectangle to account for margins. + QRect adjustedRect = this->rect(); + int margin = contentsMargins().left(); // Assuming equal margins. + adjustedRect.adjust(margin, margin, -margin, -margin); + + // Draw a rounded rectangle with a corner radius of 5. + painter.drawRoundedRect(adjustedRect, 5, 5); + + // Delegate text rendering to QLabel. + QLabel::paintEvent(event); +} diff --git a/cockatrice/src/client/ui/widgets/general/display/shadow_background_label.h b/cockatrice/src/client/ui/widgets/general/display/shadow_background_label.h new file mode 100644 index 000000000..992c8e81c --- /dev/null +++ b/cockatrice/src/client/ui/widgets/general/display/shadow_background_label.h @@ -0,0 +1,18 @@ +#ifndef STYLEDLABEL_H +#define STYLEDLABEL_H + +#include + +class ShadowBackgroundLabel : public QLabel +{ + Q_OBJECT + +public: + explicit ShadowBackgroundLabel(QWidget *parent, const QString &text); + +protected: + void resizeEvent(QResizeEvent *event) override; + void paintEvent(QPaintEvent *event) override; // Custom painting logic +}; + +#endif // STYLEDLABEL_H diff --git a/cockatrice/src/client/ui/widgets/general/layout_containers/flow_widget.cpp b/cockatrice/src/client/ui/widgets/general/layout_containers/flow_widget.cpp index 7bcb6ea75..13e1f15ea 100644 --- a/cockatrice/src/client/ui/widgets/general/layout_containers/flow_widget.cpp +++ b/cockatrice/src/client/ui/widgets/general/layout_containers/flow_widget.cpp @@ -5,11 +5,8 @@ #include "flow_widget.h" -#include "../../../layouts/flow_layout.h" -#include "../../../layouts/horizontal_flow_layout.h" -#include "../../../layouts/vertical_flow_layout.h" - #include +#include #include #include #include @@ -22,48 +19,60 @@ * @param verticalPolicy The vertical scroll bar policy for the scroll area. */ FlowWidget::FlowWidget(QWidget *parent, + const Qt::Orientation _flowDirection, const Qt::ScrollBarPolicy horizontalPolicy, const Qt::ScrollBarPolicy verticalPolicy) - : QWidget(parent) + : QWidget(parent), flowDirection(_flowDirection) { // Main Widget and Layout - this->setMinimumSize(0, 0); - this->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); - main_layout = new QHBoxLayout(); - this->setLayout(main_layout); - - // Flow Layout inside the scroll area - container = new QWidget(); - - if (horizontalPolicy != Qt::ScrollBarAlwaysOff && verticalPolicy == Qt::ScrollBarAlwaysOff) { - flow_layout = new HorizontalFlowLayout(container); - } else if (horizontalPolicy == Qt::ScrollBarAlwaysOff && verticalPolicy != Qt::ScrollBarAlwaysOff) { - flow_layout = new VerticalFlowLayout(container); + if (_flowDirection == Qt::Horizontal) { + this->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum); + this->setMinimumWidth(0); } else { - flow_layout = new FlowLayout(container, 0, 0, 0); + this->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Expanding); + this->setMinimumHeight(0); + } + mainLayout = new QHBoxLayout(this); + this->setLayout(mainLayout); + + if (horizontalPolicy != Qt::ScrollBarAlwaysOff || verticalPolicy != Qt::ScrollBarAlwaysOff) { + // Scroll Area, which should expand as much as possible, since it should be the only direct child widget. + scrollArea = new QScrollArea(this); + scrollArea->setWidgetResizable(true); + scrollArea->setMinimumSize(0, 0); + scrollArea->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + // Set scrollbar policies + scrollArea->setHorizontalScrollBarPolicy(horizontalPolicy); + scrollArea->setVerticalScrollBarPolicy(verticalPolicy); + } else { + scrollArea = nullptr; } - container->setLayout(flow_layout); + // Flow Layout inside the scroll area + if (horizontalPolicy == Qt::ScrollBarAlwaysOff && verticalPolicy == Qt::ScrollBarAlwaysOff) { + container = new QWidget(this); + } else { + container = new QWidget(scrollArea); + } + + flowLayout = new FlowLayout(container, flowDirection); + + container->setLayout(flowLayout); // The container should expand as much as possible, trusting the scrollArea to constrain it. - container->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); - container->setMinimumSize(0, 0); - - // Scroll Area, which should expand as much as possible, since it should be the only direct child widget. - scrollArea = new QScrollArea(); - scrollArea->setWidgetResizable(true); - scrollArea->setMinimumSize(0, 0); - scrollArea->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); - - // Set scrollbar policies - scrollArea->setHorizontalScrollBarPolicy(horizontalPolicy); - scrollArea->setVerticalScrollBarPolicy(verticalPolicy); + if (_flowDirection == Qt::Horizontal) { + container->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); + container->setMinimumWidth(0); + } else { + container->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); + container->setMinimumHeight(0); + } // Use the FlowLayout container directly if we disable the ScrollArea if (horizontalPolicy == Qt::ScrollBarAlwaysOff && verticalPolicy == Qt::ScrollBarAlwaysOff) { - main_layout->addWidget(container); + mainLayout->addWidget(container); } else { scrollArea->setWidget(container); - main_layout->addWidget(scrollArea); + mainLayout->addWidget(scrollArea); } } @@ -76,16 +85,12 @@ FlowWidget::FlowWidget(QWidget *parent, */ void FlowWidget::addWidget(QWidget *widget_to_add) const { - // Adjust size policy if scrollbars are disabled - if (scrollArea->horizontalScrollBarPolicy() == Qt::ScrollBarAlwaysOff) { - widget_to_add->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Preferred); - } - if (scrollArea->verticalScrollBarPolicy() == Qt::ScrollBarAlwaysOff) { - widget_to_add->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Minimum); - } + flowLayout->addWidget(widget_to_add); +} - // Add the widget to the flow layout - this->flow_layout->addWidget(widget_to_add); +void FlowWidget::removeWidget(QWidget *widgetToRemove) const +{ + flowLayout->removeWidget(widgetToRemove); } /** @@ -95,23 +100,15 @@ void FlowWidget::addWidget(QWidget *widget_to_add) const */ void FlowWidget::clearLayout() { - if (flow_layout != nullptr) { + if (flowLayout != nullptr) { QLayoutItem *item; - while ((item = flow_layout->takeAt(0)) != nullptr) { + while ((item = flowLayout->takeAt(0)) != nullptr) { item->widget()->deleteLater(); // Delete the widget delete item; // Delete the layout item } } else { - if (scrollArea->horizontalScrollBarPolicy() != Qt::ScrollBarAlwaysOff && - scrollArea->verticalScrollBarPolicy() == Qt::ScrollBarAlwaysOff) { - flow_layout = new HorizontalFlowLayout(container); - } else if (scrollArea->horizontalScrollBarPolicy() == Qt::ScrollBarAlwaysOff && - scrollArea->verticalScrollBarPolicy() != Qt::ScrollBarAlwaysOff) { - flow_layout = new VerticalFlowLayout(container); - } else { - flow_layout = new FlowLayout(container, 0, 0, 0); - } - this->container->setLayout(flow_layout); + flowLayout = new FlowLayout(container, flowDirection); + container->setLayout(flowLayout); } } @@ -126,16 +123,58 @@ void FlowWidget::resizeEvent(QResizeEvent *event) { QWidget::resizeEvent(event); + qCDebug(FlowWidgetSizeLog) << event->size(); + // Trigger the layout to recalculate - if (flow_layout != nullptr) { - flow_layout->invalidate(); // Marks the layout as dirty and requires recalculation - flow_layout->activate(); // Recalculate the layout based on the new size + if (flowLayout != nullptr) { + flowLayout->invalidate(); // Marks the layout as dirty and requires recalculation + flowLayout->activate(); // Recalculate the layout based on the new size } // Ensure the scroll area and its content adjust correctly - if (scrollArea != nullptr) { - if (scrollArea->widget() != nullptr) { - scrollArea->widget()->adjustSize(); + if (scrollArea != nullptr && scrollArea->widget() != nullptr) { + qCDebug(FlowWidgetSizeLog) << "Got a scrollarea: " << scrollArea->widget()->size(); + scrollArea->widget()->adjustSize(); + } else { + container->adjustSize(); + } +} + +/** + * @brief Sets the minimum size for all widgets inside the FlowWidget to the maximum sizeHint of all of them. + */ +void FlowWidget::setMinimumSizeToMaxSizeHint() +{ + QSize maxSize(0, 0); // Initialize to a zero size + + // Iterate over all widgets in the flow layout to find the maximum sizeHint + for (int i = 0; i < flowLayout->count(); ++i) { + if (QLayoutItem *item = flowLayout->itemAt(i)) { + if (QWidget *widget = item->widget()) { + // Update the max size based on the sizeHint of each widget + QSize widgetSizeHint = widget->sizeHint(); + maxSize.setWidth(qMax(maxSize.width(), widgetSizeHint.width())); + maxSize.setHeight(qMax(maxSize.height(), widgetSizeHint.height())); + } + } + } + + // Set the minimum size for all widgets to the max sizeHint + for (int i = 0; i < flowLayout->count(); ++i) { + if (QLayoutItem *item = flowLayout->itemAt(i)) { + if (QWidget *widget = item->widget()) { + widget->setMinimumSize(maxSize); + } } } } + +QLayoutItem *FlowWidget::itemAt(int index) const +{ + return flowLayout->itemAt(index); +} + +int FlowWidget::count() const +{ + return flowLayout->count(); +} \ No newline at end of file diff --git a/cockatrice/src/client/ui/widgets/general/layout_containers/flow_widget.h b/cockatrice/src/client/ui/widgets/general/layout_containers/flow_widget.h index a52cca6e2..75ccbf345 100644 --- a/cockatrice/src/client/ui/widgets/general/layout_containers/flow_widget.h +++ b/cockatrice/src/client/ui/widgets/general/layout_containers/flow_widget.h @@ -3,26 +3,40 @@ #include "../../../layouts/flow_layout.h" #include +#include #include #include +inline Q_LOGGING_CATEGORY(FlowWidgetLog, "flow_widget", QtInfoMsg); +inline Q_LOGGING_CATEGORY(FlowWidgetSizeLog, "flow_widget.size", QtInfoMsg); + class FlowWidget final : public QWidget { Q_OBJECT public: - FlowWidget(QWidget *parent, Qt::ScrollBarPolicy horizontalPolicy, Qt::ScrollBarPolicy verticalPolicy); + FlowWidget(QWidget *parent, + Qt::Orientation orientation, + Qt::ScrollBarPolicy horizontalPolicy, + Qt::ScrollBarPolicy verticalPolicy); void addWidget(QWidget *widget_to_add) const; + void removeWidget(QWidget *widgetToRemove) const; void clearLayout(); + [[nodiscard]] int count() const; + [[nodiscard]] QLayoutItem *itemAt(int index) const; QScrollArea *scrollArea; +public slots: + void setMinimumSizeToMaxSizeHint(); + protected: void resizeEvent(QResizeEvent *event) override; private: - QHBoxLayout *main_layout; - FlowLayout *flow_layout; + Qt::Orientation flowDirection; + QHBoxLayout *mainLayout; + FlowLayout *flowLayout; QWidget *container; }; diff --git a/cockatrice/src/client/ui/widgets/printing_selector/all_zones_card_amount_widget.cpp b/cockatrice/src/client/ui/widgets/printing_selector/all_zones_card_amount_widget.cpp new file mode 100644 index 000000000..77c7dc6cd --- /dev/null +++ b/cockatrice/src/client/ui/widgets/printing_selector/all_zones_card_amount_widget.cpp @@ -0,0 +1,120 @@ +#include "all_zones_card_amount_widget.h" + +#include "../general/display/shadow_background_label.h" + +#include + +/** + * @brief Constructor for the AllZonesCardAmountWidget class. + * + * Initializes the widget with its layout and sets up the connections and necessary + * UI elements for managing card counts in both the mainboard and sideboard zones. + * + * @param parent The parent widget. + * @param deckEditor Pointer to the TabDeckEditor. + * @param deckModel Pointer to the DeckListModel. + * @param deckView Pointer to the QTreeView for the deck display. + * @param cardSizeSlider Pointer to the QSlider used for dynamic font resizing. + * @param rootCard The root card for the widget. + * @param setInfoForCard The set information for the card. + */ +AllZonesCardAmountWidget::AllZonesCardAmountWidget(QWidget *parent, + AbstractTabDeckEditor *deckEditor, + DeckListModel *deckModel, + QTreeView *deckView, + QSlider *cardSizeSlider, + CardInfoPtr rootCard, + CardInfoPerSet setInfoForCard) + : QWidget(parent), deckEditor(deckEditor), deckModel(deckModel), deckView(deckView), cardSizeSlider(cardSizeSlider), + rootCard(rootCard), setInfoForCard(setInfoForCard) +{ + layout = new QVBoxLayout(this); + layout->setAlignment(Qt::AlignHCenter); + setLayout(layout); + setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + setContentsMargins(5, 5, 5, 5); // Padding around the text + + zoneLabelMainboard = new ShadowBackgroundLabel(this, tr("Mainboard")); + buttonBoxMainboard = new CardAmountWidget(this, deckEditor, deckModel, deckView, cardSizeSlider, rootCard, + setInfoForCard, DECK_ZONE_MAIN); + zoneLabelSideboard = new ShadowBackgroundLabel(this, tr("Sideboard")); + buttonBoxSideboard = new CardAmountWidget(this, deckEditor, deckModel, deckView, cardSizeSlider, rootCard, + setInfoForCard, DECK_ZONE_SIDE); + + layout->addWidget(zoneLabelMainboard, 0, Qt::AlignHCenter | Qt::AlignBottom); + layout->addWidget(buttonBoxMainboard, 0, Qt::AlignHCenter | Qt::AlignTop); + layout->addSpacing(25); + layout->addWidget(zoneLabelSideboard, 0, Qt::AlignHCenter | Qt::AlignBottom); + layout->addWidget(buttonBoxSideboard, 0, Qt::AlignHCenter | Qt::AlignTop); + + connect(cardSizeSlider, &QSlider::valueChanged, this, &AllZonesCardAmountWidget::adjustFontSize); + + QTimer::singleShot(10, this, [this]() { adjustFontSize(this->cardSizeSlider->value()); }); + + setMouseTracking(true); +} + +/** + * @brief Adjusts the font size of the zone labels based on the slider value. + * + * This method calculates the new font size as a percentage of the original font size + * based on the slider value and applies it to the zone label text. + * + * @param scalePercentage The scale percentage from the slider. + */ +void AllZonesCardAmountWidget::adjustFontSize(int scalePercentage) +{ + const int minFontSize = 8; // Minimum font size + const int maxFontSize = 32; // Maximum font size + const int basePercentage = 100; // Scale at 100% + + int newFontSize = minFontSize + (scalePercentage - basePercentage) * (maxFontSize - minFontSize) / 225; + newFontSize = std::clamp(newFontSize, minFontSize, maxFontSize); + + // Update the font labels + QFont zoneLabelFont = zoneLabelMainboard->font(); + zoneLabelFont.setPointSize(newFontSize); + zoneLabelMainboard->setFont(zoneLabelFont); + zoneLabelSideboard->setFont(zoneLabelFont); + + // Repaint the widget (if necessary) + repaint(); +} + +/** + * @brief Gets the card count in the mainboard zone. + * + * @return The number of cards in the mainboard. + */ +int AllZonesCardAmountWidget::getMainboardAmount() +{ + return buttonBoxMainboard->countCardsInZone(DECK_ZONE_MAIN); +} + +/** + * @brief Gets the card count in the sideboard zone. + * + * @return The number of cards in the sideboard. + */ +int AllZonesCardAmountWidget::getSideboardAmount() +{ + return buttonBoxSideboard->countCardsInZone(DECK_ZONE_SIDE); +} + +/** + * @brief Handles the event when the mouse enters the widget. + * + * This method is triggered when the mouse enters the widget's area, allowing for updates + * or interactions such as UI feedback or layout changes. + * + * @param event The event information for the mouse entry. + */ +#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) +void AllZonesCardAmountWidget::enterEvent(QEnterEvent *event) +#else +void AllZonesCardAmountWidget::enterEvent(QEvent *event) +#endif +{ + QWidget::enterEvent(event); + update(); +} diff --git a/cockatrice/src/client/ui/widgets/printing_selector/all_zones_card_amount_widget.h b/cockatrice/src/client/ui/widgets/printing_selector/all_zones_card_amount_widget.h new file mode 100644 index 000000000..94399b1d6 --- /dev/null +++ b/cockatrice/src/client/ui/widgets/printing_selector/all_zones_card_amount_widget.h @@ -0,0 +1,46 @@ +#ifndef ALL_ZONES_CARD_AMOUNT_WIDGET_H +#define ALL_ZONES_CARD_AMOUNT_WIDGET_H +#include "../../../../deck/deck_list_model.h" +#include "../../../../deck/deck_loader.h" +#include "card_amount_widget.h" + +#include +#include + +class AllZonesCardAmountWidget : public QWidget +{ + Q_OBJECT +public: + explicit AllZonesCardAmountWidget(QWidget *parent, + AbstractTabDeckEditor *deckEditor, + DeckListModel *deckModel, + QTreeView *deckView, + QSlider *cardSizeSlider, + CardInfoPtr rootCard, + CardInfoPerSet setInfoForCard); + int getMainboardAmount(); + int getSideboardAmount(); +#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) + void enterEvent(QEnterEvent *event) override; +#else + void enterEvent(QEvent *event) override; +#endif + +public slots: + void adjustFontSize(int scalePercentage); + +private: + QVBoxLayout *layout; + AbstractTabDeckEditor *deckEditor; + DeckListModel *deckModel; + QTreeView *deckView; + QSlider *cardSizeSlider; + CardInfoPtr rootCard; + CardInfoPerSet setInfoForCard; + QLabel *zoneLabelMainboard; + CardAmountWidget *buttonBoxMainboard; + QLabel *zoneLabelSideboard; + CardAmountWidget *buttonBoxSideboard; +}; + +#endif // ALL_ZONES_CARD_AMOUNT_WIDGET_H diff --git a/cockatrice/src/client/ui/widgets/printing_selector/card_amount_widget.cpp b/cockatrice/src/client/ui/widgets/printing_selector/card_amount_widget.cpp new file mode 100644 index 000000000..9a295c207 --- /dev/null +++ b/cockatrice/src/client/ui/widgets/printing_selector/card_amount_widget.cpp @@ -0,0 +1,296 @@ +#include "card_amount_widget.h" + +#include +#include + +/** + * @brief Constructs a widget for displaying and controlling the card count in a specific zone. + * + * @param parent The parent widget. + * @param deckEditor Pointer to the TabDeckEditor instance. + * @param deckModel Pointer to the DeckListModel instance. + * @param deckView Pointer to the QTreeView displaying the deck. + * @param cardSizeSlider Pointer to the QSlider for adjusting font size. + * @param rootCard The root card to manage within the widget. + * @param setInfoForCard Card set information for the root card. + * @param zoneName The zone name (e.g., DECK_ZONE_MAIN or DECK_ZONE_SIDE). + */ +CardAmountWidget::CardAmountWidget(QWidget *parent, + AbstractTabDeckEditor *deckEditor, + DeckListModel *deckModel, + QTreeView *deckView, + QSlider *cardSizeSlider, + CardInfoPtr &rootCard, + CardInfoPerSet &setInfoForCard, + const QString &zoneName) + : QWidget(parent), deckEditor(deckEditor), deckModel(deckModel), deckView(deckView), cardSizeSlider(cardSizeSlider), + rootCard(rootCard), setInfoForCard(setInfoForCard), zoneName(zoneName), hovered(false) +{ + layout = new QHBoxLayout(this); + layout->setContentsMargins(0, 0, 0, 0); + layout->setSpacing(10); + this->setLayout(layout); + this->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); + layout->setAlignment(Qt::AlignHCenter); + + incrementButton = new DynamicFontSizePushButton(this); + incrementButton->setTextAndColor("+", Qt::white); + decrementButton = new DynamicFontSizePushButton(this); + decrementButton->setTextAndColor("-", Qt::white); + + incrementButton->setFixedSize(parentWidget()->size().width() / 3, parentWidget()->size().height() / 9); + decrementButton->setFixedSize(parentWidget()->size().width() / 3, parentWidget()->size().height() / 9); + + // Set up connections based on the zone (Mainboard or Sideboard) + if (zoneName == DECK_ZONE_MAIN) { + connect(incrementButton, &QPushButton::clicked, this, &CardAmountWidget::addPrintingMainboard); + connect(decrementButton, &QPushButton::clicked, this, &CardAmountWidget::removePrintingMainboard); + } else if (zoneName == DECK_ZONE_SIDE) { + connect(incrementButton, &QPushButton::clicked, this, &CardAmountWidget::addPrintingSideboard); + connect(decrementButton, &QPushButton::clicked, this, &CardAmountWidget::removePrintingSideboard); + } + + cardCountInZone = new QLabel(QString::number(countCardsInZone(zoneName)), this); + cardCountInZone->setAlignment(Qt::AlignCenter); + + layout->addWidget(decrementButton); + layout->addWidget(cardCountInZone); + layout->addWidget(incrementButton); + + // React to model changes + connect(deckModel, &DeckListModel::dataChanged, this, &CardAmountWidget::updateCardCount); + connect(deckModel, &QAbstractItemModel::rowsRemoved, this, &CardAmountWidget::updateCardCount); + + // Connect slider for dynamic font size adjustment + connect(cardSizeSlider, &QSlider::valueChanged, this, &CardAmountWidget::adjustFontSize); +} + +/** + * @brief Handles the painting of the widget, drawing a semi-transparent background. + * + * @param event The paint event. + */ +void CardAmountWidget::paintEvent(QPaintEvent *event) +{ + QPainter painter(this); + painter.setRenderHint(QPainter::Antialiasing); + + // Draw semi-transparent black background + painter.setBrush(QBrush(QColor(0, 0, 0, 128))); + painter.setPen(Qt::NoPen); + painter.drawRect(rect()); + + QWidget::paintEvent(event); +} + +void CardAmountWidget::showEvent(QShowEvent *event) +{ + QWidget::showEvent(event); + + adjustFontSize(this->cardSizeSlider->value()); + updateCardCount(); + + if (parentWidget()) { + int width = parentWidget()->size().width(); + int height = parentWidget()->size().height(); + + incrementButton->setFixedSize(width / 3, height / 9); + decrementButton->setFixedSize(width / 3, height / 9); + } +} + +/** + * @brief Adjusts the font size of the card count label based on the slider value. + * + * @param scalePercentage The percentage value from the slider for scaling the font size. + */ +void CardAmountWidget::adjustFontSize(int scalePercentage) +{ + const int minFontSize = 8; ///< Minimum font size + const int maxFontSize = 32; ///< Maximum font size + const int basePercentage = 100; ///< Scale at 100% + + int newFontSize = minFontSize + (scalePercentage - basePercentage) * (maxFontSize - minFontSize) / 225; + newFontSize = std::clamp(newFontSize, minFontSize, maxFontSize); + + // Update the font for card count label + QFont cardCountFont = cardCountInZone->font(); + cardCountFont.setPointSize(newFontSize); + cardCountInZone->setFont(cardCountFont); + + incrementButton->setFixedSize(parentWidget()->size().width() / 3, parentWidget()->size().height() / 9); + decrementButton->setFixedSize(parentWidget()->size().width() / 3, parentWidget()->size().height() / 9); + + // Repaint the widget + repaint(); +} + +/** + * @brief Updates the card count display in the widget. + */ +void CardAmountWidget::updateCardCount() +{ + cardCountInZone->setText("" + QString::number(countCardsInZone(zoneName)) + ""); + layout->invalidate(); + layout->activate(); +} + +/** + * @brief Adds a printing of the card to the specified zone (Mainboard or Sideboard). + * + * @param zone The zone to add the card to (DECK_ZONE_MAIN or DECK_ZONE_SIDE). + */ +void CardAmountWidget::addPrinting(const QString &zone) +{ + auto newCardIndex = deckModel->addCard(rootCard->getName(), setInfoForCard, zone); + recursiveExpand(newCardIndex); + QModelIndex find_card = deckModel->findCard(rootCard->getName(), zone); + if (find_card.isValid() && find_card != newCardIndex) { + auto amount = deckModel->data(find_card, Qt::DisplayRole); + for (int i = 0; i < amount.toInt() - 1; i++) { + deckModel->addCard(rootCard->getName(), setInfoForCard, zone); + } + deckModel->removeRow(find_card.row(), find_card.parent()); + } + newCardIndex = deckModel->findCard(rootCard->getName(), zone, setInfoForCard.getProperty("uuid"), + setInfoForCard.getProperty("num")); + deckView->setCurrentIndex(newCardIndex); + deckView->setFocus(Qt::FocusReason::MouseFocusReason); + deckEditor->setModified(true); +} + +/** + * @brief Adds a printing to the mainboard zone. + */ +void CardAmountWidget::addPrintingMainboard() +{ + addPrinting(DECK_ZONE_MAIN); +} + +/** + * @brief Adds a printing to the sideboard zone. + */ +void CardAmountWidget::addPrintingSideboard() +{ + addPrinting(DECK_ZONE_SIDE); +} + +/** + * @brief Removes a printing from the mainboard zone. + */ +void CardAmountWidget::removePrintingMainboard() +{ + decrementCardHelper(DECK_ZONE_MAIN); +} + +/** + * @brief Removes a printing from the sideboard zone. + */ +void CardAmountWidget::removePrintingSideboard() +{ + decrementCardHelper(DECK_ZONE_SIDE); +} + +/** + * @brief Recursively expands the card in the deck view starting from the given index. + * + * @param index The model index of the card to expand. + */ +void CardAmountWidget::recursiveExpand(const QModelIndex &index) +{ + if (index.parent().isValid()) { + recursiveExpand(index.parent()); + } + deckView->expand(index); +} + +/** + * @brief Offsets the card count at the specified index by the given amount. + * + * @param idx The model index of the card. + * @param offset The amount to add or subtract from the card count. + */ +void CardAmountWidget::offsetCountAtIndex(const QModelIndex &idx, int offset) +{ + if (!idx.isValid() || offset == 0) { + return; + } + + const QModelIndex numberIndex = idx.sibling(idx.row(), 0); + const int count = deckModel->data(numberIndex, Qt::EditRole).toInt(); + const int new_count = count + offset; + deckView->setCurrentIndex(numberIndex); + if (new_count <= 0) { + deckModel->removeRow(idx.row(), idx.parent()); + } else { + deckModel->setData(numberIndex, new_count, Qt::EditRole); + } + deckEditor->setModified(true); +} + +/** + * @brief Helper function to decrement the card count for a given zone. + * + * @param zone The zone from which to remove the card (DECK_ZONE_MAIN or DECK_ZONE_SIDE). + */ +void CardAmountWidget::decrementCardHelper(const QString &zone) +{ + QModelIndex idx = deckModel->findCard(rootCard->getName(), zone, setInfoForCard.getProperty("uuid"), + setInfoForCard.getProperty("num")); + offsetCountAtIndex(idx, -1); + deckEditor->setModified(true); +} + +/** + * @brief Counts the number of cards in a specific zone (mainboard or sideboard). + * + * @param deckZone The name of the zone (e.g., DECK_ZONE_MAIN or DECK_ZONE_SIDE). + * @return The number of cards in the zone. + */ +int CardAmountWidget::countCardsInZone(const QString &deckZone) +{ + if (setInfoForCard.getProperty("uuid").isEmpty()) { + return 0; // Cards without uuids/providerIds CANNOT match another card, they are undefined for us. + } + + if (!deckModel) { + return -1; + } + + DeckList *decklist = deckModel->getDeckList(); + if (!decklist) { + return -1; + } + + InnerDecklistNode *listRoot = decklist->getRoot(); + if (!listRoot) { + return -1; + } + + int count = 0; + + for (auto *i : *listRoot) { + auto *countCurrentZone = dynamic_cast(i); + if (!countCurrentZone) { + continue; + } + + if (countCurrentZone->getName() != deckZone) { + continue; + } + + for (auto *cardNode : *countCurrentZone) { + auto *currentCard = dynamic_cast(cardNode); + if (!currentCard) { + continue; + } + + for (int k = 0; k < currentCard->getNumber(); ++k) { + if (currentCard->getCardProviderId() == setInfoForCard.getProperty("uuid")) { + count++; + } + } + } + } + return count; +} \ No newline at end of file diff --git a/cockatrice/src/client/ui/widgets/printing_selector/card_amount_widget.h b/cockatrice/src/client/ui/widgets/printing_selector/card_amount_widget.h new file mode 100644 index 000000000..a051d2ea2 --- /dev/null +++ b/cockatrice/src/client/ui/widgets/printing_selector/card_amount_widget.h @@ -0,0 +1,65 @@ +#ifndef CARD_AMOUNT_WIDGET_H +#define CARD_AMOUNT_WIDGET_H + +#include "../../../../deck/deck_list_model.h" +#include "../../../../deck/deck_loader.h" +#include "../../../../game/cards/card_database.h" +#include "../../../tabs/abstract_tab_deck_editor.h" +#include "../general/display/dynamic_font_size_push_button.h" + +#include +#include +#include +#include +#include + +class CardAmountWidget : public QWidget +{ + Q_OBJECT +public: + explicit CardAmountWidget(QWidget *parent, + AbstractTabDeckEditor *deckEditor, + DeckListModel *deckModel, + QTreeView *deckView, + QSlider *cardSizeSlider, + CardInfoPtr &rootCard, + CardInfoPerSet &setInfoForCard, + const QString &zoneName); + int countCardsInZone(const QString &deckZone); + +public slots: + void updateCardCount(); + void addPrinting(const QString &zone); + +protected: + void paintEvent(QPaintEvent *event) override; + void showEvent(QShowEvent *event) override; + +private: + AbstractTabDeckEditor *deckEditor; + DeckListModel *deckModel; + QTreeView *deckView; + QSlider *cardSizeSlider; + CardInfoPtr rootCard; + CardInfoPerSet setInfoForCard; + QString zoneName; + QHBoxLayout *layout; + DynamicFontSizePushButton *incrementButton; + DynamicFontSizePushButton *decrementButton; + QLabel *cardCountInZone; + + bool hovered; + + void offsetCountAtIndex(const QModelIndex &idx, int offset); + void decrementCardHelper(const QString &zoneName); + void recursiveExpand(const QModelIndex &index); + +private slots: + void addPrintingMainboard(); + void addPrintingSideboard(); + void removePrintingMainboard(); + void removePrintingSideboard(); + void adjustFontSize(int scalePercentage); +}; + +#endif // CARD_AMOUNT_WIDGET_H diff --git a/cockatrice/src/client/ui/widgets/printing_selector/printing_selector.cpp b/cockatrice/src/client/ui/widgets/printing_selector/printing_selector.cpp new file mode 100644 index 000000000..ef3816049 --- /dev/null +++ b/cockatrice/src/client/ui/widgets/printing_selector/printing_selector.cpp @@ -0,0 +1,268 @@ +#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 +#include +#include + +/** + * @brief Constructs a PrintingSelector widget to display and manage card printings. + * + * This constructor initializes the PrintingSelector widget, setting up various child widgets + * such as sorting tools, search bar, card size options, and navigation controls. It also connects + * signals and slots to update the display when the deck model changes, and loads available printings + * for the selected card. + * + * @param parent The parent widget for the PrintingSelector. + * @param deckEditor The TabDeckEditor instance used for managing the deck. + * @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, AbstractTabDeckEditor *_deckEditor) + : QWidget(parent), deckEditor(_deckEditor), deckModel(deckEditor->deckDockWidget->deckModel), + deckView(deckEditor->deckDockWidget->deckView) +{ + setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + 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); + + flowWidget = new FlowWidget(this, Qt::Horizontal, Qt::ScrollBarAlwaysOff, Qt::ScrollBarAsNeeded); + + sortToolBar = new PrintingSelectorCardSortingWidget(this); + 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); + + sortAndOptionsLayout->addWidget(searchBar); + sortAndOptionsLayout->addWidget(displayOptionsWidget); + + layout->addWidget(sortAndOptionsContainer); + + layout->addWidget(flowWidget); + + cardSelectionBar = new PrintingSelectorCardSelectionWidget(this); + cardSelectionBar->setVisible(SettingsCache::instance().getPrintingSelectorNavigationButtonsVisible()); + layout->addWidget(cardSelectionBar); + + // 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("Warning: 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() +{ + // Delay the update to avoid race conditions + QTimer::singleShot(100, this, &PrintingSelector::updateDisplay); +} + +/** + * @brief Updates the display by clearing the layout and loading new sets for the current card. + */ +void PrintingSelector::updateDisplay() +{ + widgetLoadingBufferTimer->stop(); + widgetLoadingBufferTimer->deleteLater(); + widgetLoadingBufferTimer = new QTimer(this); + flowWidget->clearLayout(); + if (selectedCard != nullptr) { + setWindowTitle(selectedCard->getName()); + } + getAllSetsForCurrentCard(); +} + +/** + * @brief Sets the current card for the selector and updates the display. + * + * @param newCard The new card to set. + * @param _currentZone The current zone the card is in. + */ +void PrintingSelector::setCard(const CardInfoPtr &newCard, const QString &_currentZone) +{ + if (newCard.isNull()) { + return; + } + + // we don't need to redraw the widget if the card is the same + if (!selectedCard.isNull() && selectedCard->getName() == newCard->getName()) { + return; + } + + selectedCard = newCard; + currentZone = _currentZone; + if (isVisible()) { + updateDisplay(); + } + flowWidget->setMinimumSizeToMaxSizeHint(); + flowWidget->scrollArea->verticalScrollBar()->setValue(0); + flowWidget->repaint(); +} + +/** + * @brief Selects the previous card in the list. + */ +void PrintingSelector::selectPreviousCard() +{ + selectCard(-1); +} + +/** + * @brief Selects the next card in the list. + */ +void PrintingSelector::selectNextCard() +{ + selectCard(1); +} + +/** + * @brief Selects a card based on the change direction. + * + * @param changeBy The direction to change, -1 for previous, 1 for next. + */ +void PrintingSelector::selectCard(const int changeBy) +{ + if (changeBy == 0) { + return; + } + + // Get the current index of the selected item + auto deckViewCurrentIndex = deckView->currentIndex(); + + auto nextIndex = deckViewCurrentIndex.siblingAtRow(deckViewCurrentIndex.row() + changeBy); + if (!nextIndex.isValid()) { + nextIndex = deckViewCurrentIndex; + + // Increment to the next valid index, skipping header rows + AbstractDecklistNode *node; + do { + if (changeBy > 0) { + nextIndex = deckView->indexBelow(nextIndex); + } else { + nextIndex = deckView->indexAbove(nextIndex); + } + node = static_cast(nextIndex.internalPointer()); + } while (node && node->isDeckHeader()); + } + + if (nextIndex.isValid()) { + deckView->setCurrentIndex(nextIndex); + deckView->setFocus(Qt::FocusReason::MouseFocusReason); + } +} + +/** + * @brief Loads and displays all sets for the current selected card. + */ +void PrintingSelector::getAllSetsForCurrentCard() +{ + if (selectedCard.isNull()) { + return; + } + + CardInfoPerSetMap cardInfoPerSets = selectedCard->getSets(); + const QList sortedSets = sortToolBar->sortSets(cardInfoPerSets); + const QList filteredSets = + sortToolBar->filterSets(sortedSets, searchBar->getSearchText().trimmed().toLower()); + QList setsToUse; + + if (SettingsCache::instance().getBumpSetsWithCardsInDeckToTop()) { + setsToUse = sortToolBar->prependPrintingsInDeck(filteredSets, selectedCard, deckModel); + } else { + setsToUse = filteredSets; + } + setsToUse = sortToolBar->prependPinnedPrintings(setsToUse, selectedCard->getName()); + + // Defer widget creation + currentIndex = 0; + + connect(widgetLoadingBufferTimer, &QTimer::timeout, this, [=, this]() mutable { + for (int i = 0; i < BATCH_SIZE && currentIndex < setsToUse.size(); ++i, ++currentIndex) { + auto *cardDisplayWidget = new PrintingSelectorCardDisplayWidget(this, deckEditor, deckModel, deckView, + cardSizeWidget->getSlider(), selectedCard, + setsToUse[currentIndex], currentZone); + flowWidget->addWidget(cardDisplayWidget); + cardDisplayWidget->clampSetNameToPicture(); + connect(cardDisplayWidget, &PrintingSelectorCardDisplayWidget::cardPreferenceChanged, this, + &PrintingSelector::updateDisplay); + } + + // Stop timer when done + if (currentIndex >= setsToUse.size()) { + widgetLoadingBufferTimer->stop(); + } + }); + currentIndex = 0; + widgetLoadingBufferTimer->start(0); // Process as soon as possible +} + +/** + * @brief Toggles the visibility of the navigation buttons. + * + * @param _state The visibility state to set. + */ +void PrintingSelector::toggleVisibilityNavigationButtons(bool _state) +{ + cardSelectionBar->setVisible(_state); +} diff --git a/cockatrice/src/client/ui/widgets/printing_selector/printing_selector.h b/cockatrice/src/client/ui/widgets/printing_selector/printing_selector.h new file mode 100644 index 000000000..ea2e69a5f --- /dev/null +++ b/cockatrice/src/client/ui/widgets/printing_selector/printing_selector.h @@ -0,0 +1,65 @@ +#ifndef PRINTING_SELECTOR_H +#define PRINTING_SELECTOR_H + +#include "../../../../deck/deck_list_model.h" +#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 +#include +#include +#include +#include + +#define BATCH_SIZE 10 + +class PrintingSelectorCardSearchWidget; +class PrintingSelectorCardSelectionWidget; +class PrintingSelectorCardSortingWidget; +class PrintingSelectorViewOptionsWidget; +class AbstractTabDeckEditor; +class PrintingSelector : public QWidget +{ + Q_OBJECT + +public: + 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 toggleVisibilityNavigationButtons(bool _state); + +private slots: + void printingsInDeckChanged(); + +private: + QVBoxLayout *layout; + SettingsButtonWidget *displayOptionsWidget; + QWidget *sortAndOptionsContainer; + QHBoxLayout *sortAndOptionsLayout; + QCheckBox *navigationCheckBox; + QLabel *warningLabel; + PrintingSelectorCardSortingWidget *sortToolBar; + PrintingSelectorCardSearchWidget *searchBar; + FlowWidget *flowWidget; + CardSizeWidget *cardSizeWidget; + PrintingSelectorCardSelectionWidget *cardSelectionBar; + AbstractTabDeckEditor *deckEditor; + DeckListModel *deckModel; + QTreeView *deckView; + CardInfoPtr selectedCard; + QString currentZone; + QTimer *widgetLoadingBufferTimer; + int currentIndex = 0; + void selectCard(int changeBy); +}; + +#endif // PRINTING_SELECTOR_H diff --git a/cockatrice/src/client/ui/widgets/printing_selector/printing_selector_card_display_widget.cpp b/cockatrice/src/client/ui/widgets/printing_selector/printing_selector_card_display_widget.cpp new file mode 100644 index 000000000..cebf645cb --- /dev/null +++ b/cockatrice/src/client/ui/widgets/printing_selector/printing_selector_card_display_widget.cpp @@ -0,0 +1,76 @@ +#include "printing_selector_card_display_widget.h" + +#include "card_amount_widget.h" +#include "printing_selector_card_overlay_widget.h" +#include "set_name_and_collectors_number_display_widget.h" + +#include +#include +#include +#include + +/** + * @brief Constructs a PrintingSelectorCardDisplayWidget to display card information. + * + * This widget is responsible for displaying the selected card's printing information, including + * the card's image and set details. It also handles the layout of the card's display, including + * its size, set name, and collectors number. The card is displayed within a `QVBoxLayout` with + * two main components: the overlay (which combines the card image and buttons) and the set name and collectors number + * 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. + */ +PrintingSelectorCardDisplayWidget::PrintingSelectorCardDisplayWidget(QWidget *parent, + AbstractTabDeckEditor *_deckEditor, + DeckListModel *_deckModel, + QTreeView *_deckView, + QSlider *_cardSizeSlider, + CardInfoPtr _rootCard, + const CardInfoPerSet &_setInfoForCard, + QString &_currentZone) + : QWidget(parent), deckEditor(_deckEditor), deckModel(_deckModel), deckView(_deckView), + cardSizeSlider(_cardSizeSlider), rootCard(std::move(_rootCard)), setInfoForCard(_setInfoForCard), + currentZone(_currentZone) +{ + layout = new QVBoxLayout(this); + setLayout(layout); + setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + + // Create the overlay widget for the card display + overlayWidget = new PrintingSelectorCardOverlayWidget(this, deckEditor, deckModel, deckView, cardSizeSlider, + rootCard, setInfoForCard); + connect(overlayWidget, &PrintingSelectorCardOverlayWidget::cardPreferenceChanged, this, + [this]() { emit cardPreferenceChanged(); }); + + // Create the widget to display the set name and collector's number + const QString combinedSetName = + QString(setInfoForCard.getPtr()->getLongName() + " (" + setInfoForCard.getPtr()->getShortName() + ")"); + setNameAndCollectorsNumberDisplayWidget = new SetNameAndCollectorsNumberDisplayWidget( + this, combinedSetName, setInfoForCard.getProperty("num"), cardSizeSlider); + + // Add the widgets to the layout + layout->addWidget(overlayWidget, 0, Qt::AlignHCenter); + layout->addWidget(setNameAndCollectorsNumberDisplayWidget, 1, Qt::AlignHCenter | Qt::AlignBottom); +} + +/** + * @brief Adjusts the width of the set name display to fit the card overlay widget. + * + * This method ensures that the set name and collector's number display widget does not exceed + * the width of the card's overlay widget. It clamps the set name widget to match the width of + * the overlay widget and updates the display. + */ +void PrintingSelectorCardDisplayWidget::clampSetNameToPicture() +{ + if (overlayWidget != nullptr && setNameAndCollectorsNumberDisplayWidget != nullptr) { + setNameAndCollectorsNumberDisplayWidget->setMaximumWidth(overlayWidget->width()); + } + update(); +} diff --git a/cockatrice/src/client/ui/widgets/printing_selector/printing_selector_card_display_widget.h b/cockatrice/src/client/ui/widgets/printing_selector/printing_selector_card_display_widget.h new file mode 100644 index 000000000..32e1e925d --- /dev/null +++ b/cockatrice/src/client/ui/widgets/printing_selector/printing_selector_card_display_widget.h @@ -0,0 +1,47 @@ +#ifndef PRINTING_SELECTOR_CARD_DISPLAY_WIDGET_H +#define PRINTING_SELECTOR_CARD_DISPLAY_WIDGET_H + +#include "../../../../deck/deck_list_model.h" +#include "../../../../game/cards/card_database.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 +#include + +class PrintingSelectorCardDisplayWidget : public QWidget +{ + Q_OBJECT + +public: + PrintingSelectorCardDisplayWidget(QWidget *parent, + AbstractTabDeckEditor *_deckEditor, + DeckListModel *_deckModel, + QTreeView *_deckView, + QSlider *_cardSizeSlider, + CardInfoPtr _rootCard, + const CardInfoPerSet &_setInfoForCard, + QString &_currentZone); + +public slots: + void clampSetNameToPicture(); + +signals: + void cardPreferenceChanged(); + +private: + QVBoxLayout *layout; + SetNameAndCollectorsNumberDisplayWidget *setNameAndCollectorsNumberDisplayWidget; + AbstractTabDeckEditor *deckEditor; + DeckListModel *deckModel; + QTreeView *deckView; + QSlider *cardSizeSlider; + CardInfoPtr rootCard; + CardInfoPtr setCard; + CardInfoPerSet setInfoForCard; + QString currentZone; + PrintingSelectorCardOverlayWidget *overlayWidget; +}; + +#endif // PRINTING_SELECTOR_CARD_DISPLAY_WIDGET_H diff --git a/cockatrice/src/client/ui/widgets/printing_selector/printing_selector_card_overlay_widget.cpp b/cockatrice/src/client/ui/widgets/printing_selector/printing_selector_card_overlay_widget.cpp new file mode 100644 index 000000000..5604046cd --- /dev/null +++ b/cockatrice/src/client/ui/widgets/printing_selector/printing_selector_card_overlay_widget.cpp @@ -0,0 +1,208 @@ +#include "printing_selector_card_overlay_widget.h" + +#include "../../../../game/cards/card_database_manager.h" +#include "../../../../settings/cache_settings.h" +#include "printing_selector_card_display_widget.h" + +#include +#include +#include +#include + +/** + * @brief Constructs a PrintingSelectorCardOverlayWidget for displaying a card overlay. + * + * This widget is responsible for showing the card's image and providing interactive features such + * as a context menu and the ability to adjust the card's scale. It includes the card's image as well + * 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. + */ +PrintingSelectorCardOverlayWidget::PrintingSelectorCardOverlayWidget(QWidget *parent, + AbstractTabDeckEditor *_deckEditor, + DeckListModel *_deckModel, + QTreeView *_deckView, + QSlider *_cardSizeSlider, + CardInfoPtr _rootCard, + const CardInfoPerSet &_setInfoForCard) + : QWidget(parent), deckEditor(_deckEditor), deckModel(_deckModel), deckView(_deckView), + cardSizeSlider(_cardSizeSlider), rootCard(std::move(_rootCard)), setInfoForCard(_setInfoForCard) +{ + // Set up the main layout + auto *mainLayout = new QVBoxLayout(this); + mainLayout->setContentsMargins(0, 0, 0, 0); + mainLayout->setSpacing(0); + setLayout(mainLayout); + + // Add CardInfoPictureWidget + cardInfoPicture = new CardInfoPictureWidget(this); + cardInfoPicture->setMinimumSize(0, 0); + cardInfoPicture->setScaleFactor(cardSizeSlider->value()); + setCard = CardDatabaseManager::getInstance()->getCardByNameAndProviderId(rootCard->getName(), + setInfoForCard.getProperty("uuid")); + cardInfoPicture->setCard(setCard); + mainLayout->addWidget(cardInfoPicture); + + // Add AllZonesCardAmountWidget + allZonesCardAmountWidget = + new AllZonesCardAmountWidget(this, deckEditor, deckModel, deckView, cardSizeSlider, setCard, setInfoForCard); + + allZonesCardAmountWidget->raise(); // Ensure it's on top of the picture + // Set initial visibility based on amounts + if (allZonesCardAmountWidget->getMainboardAmount() > 0 || allZonesCardAmountWidget->getSideboardAmount() > 0) { + allZonesCardAmountWidget->setVisible(true); + } else { + allZonesCardAmountWidget->setVisible(false); + } + + // Attempt to cast the parent to PrintingSelectorCardDisplayWidget + if (const auto *parentWidget = qobject_cast(parent)) { + connect(cardInfoPicture, &CardInfoPictureWidget::cardScaleFactorChanged, parentWidget, + &PrintingSelectorCardDisplayWidget::clampSetNameToPicture); + } + + connect(cardSizeSlider, &QSlider::valueChanged, cardInfoPicture, &CardInfoPictureWidget::setScaleFactor); +} + +/** + * @brief Handles the mouse press event for right-clicks to show the context menu. + * + * If the right mouse button is pressed, a custom context menu will appear. For other mouse buttons, + * the event is passed to the base class for default handling. + * + * @param event The mouse event triggered by the user. + */ +void PrintingSelectorCardOverlayWidget::mousePressEvent(QMouseEvent *event) +{ + if (event->button() == Qt::RightButton) { + customMenu(event->pos()); + } else { + QWidget::mousePressEvent(event); // Pass other events to the base class + } +} + +/** + * @brief Resizes the overlay widget to match the card's size. + * + * This method ensures that the amount widget matches the card's size when the overlay widget is resized. + * It also resizes the card info picture widget to match the new size. + * + * @param event The resize event triggered when the widget is resized. + */ +void PrintingSelectorCardOverlayWidget::resizeEvent(QResizeEvent *event) +{ + // Ensure the amount widget matches the parent size + QWidget::resizeEvent(event); + if (allZonesCardAmountWidget) { + allZonesCardAmountWidget->resize(cardInfoPicture->size()); + } + resize(cardInfoPicture->size()); +} + +/** + * @brief Handles the mouse enter event when the cursor enters the overlay widget area. + * + * When the cursor enters the widget, the card information is updated, and the card amount widget + * is displayed if the amounts are zero for both the mainboard and sideboard. + * + * @param event The event triggered when the mouse enters the widget. + */ +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) +void PrintingSelectorCardOverlayWidget::enterEvent(QEnterEvent *event) +#else +void PrintingSelectorCardOverlayWidget::enterEvent(QEvent *event) +#endif +{ + QWidget::enterEvent(event); + deckEditor->updateCard(setCard); + + // Check if either mainboard or sideboard amount is greater than 0 + if (allZonesCardAmountWidget->getMainboardAmount() > 0 || allZonesCardAmountWidget->getSideboardAmount() > 0) { + // Don't change visibility if amounts are greater than 0 + return; + } + + // Show the widget if amounts are 0 + allZonesCardAmountWidget->setVisible(true); +} + +/** + * @brief Handles the mouse leave event when the cursor leaves the overlay widget area. + * + * When the cursor leaves the widget, the card amount widget is hidden if both the mainboard and sideboard + * amounts are zero. + * + * @param event The event triggered when the mouse leaves the widget. + */ +void PrintingSelectorCardOverlayWidget::leaveEvent(QEvent *event) +{ + QWidget::leaveEvent(event); + + // Check if either mainboard or sideboard amount is greater than 0 + if (allZonesCardAmountWidget->getMainboardAmount() > 0 || allZonesCardAmountWidget->getSideboardAmount() > 0) { + // Don't hide the widget if amounts are greater than 0 + return; + } + + // Hide the widget if amounts are 0 + allZonesCardAmountWidget->setVisible(false); +} + +/** + * @brief Creates and shows a custom context menu when the right mouse button is clicked. + * + * The context menu includes an option to show related cards, which displays a submenu with actions + * for each related card. When an action is triggered, the card information is updated, and the + * printing selector is shown. + * + * @param point The position of the mouse when the right-click occurred. + */ +void PrintingSelectorCardOverlayWidget::customMenu(QPoint point) +{ + QMenu menu; + + auto *preferenceMenu = new QMenu(tr("Preference")); + menu.addMenu(preferenceMenu); + + const auto &preferredProviderId = + SettingsCache::instance().cardOverrides().getCardPreferenceOverride(rootCard->getName()); + const auto &cardProviderId = setInfoForCard.getProperty("uuid"); + + if (preferredProviderId.isEmpty() || preferredProviderId != cardProviderId) { + auto *pinAction = preferenceMenu->addAction(tr("Pin Printing")); + connect(pinAction, &QAction::triggered, this, [this, cardProviderId]() { + SettingsCache::instance().cardOverrides().setCardPreferenceOverride(rootCard->getName(), cardProviderId); + emit cardPreferenceChanged(); + }); + } else { + auto *unpinAction = preferenceMenu->addAction(tr("Unpin Printing")); + connect(unpinAction, &QAction::triggered, this, [this, cardProviderId]() { + SettingsCache::instance().cardOverrides().deleteCardPreferenceOverride(rootCard->getName()); + emit cardPreferenceChanged(); + }); + } + + // filling out the related cards submenu + auto *relatedMenu = new QMenu(tr("Show Related cards")); + menu.addMenu(relatedMenu); + auto relatedCards = rootCard->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, [this, relatedCardName] { + deckEditor->updateCard(CardDatabaseManager::getInstance()->getCard(relatedCardName)); + deckEditor->showPrintingSelector(); + }); + } + } + menu.exec(this->mapToGlobal(point)); +} diff --git a/cockatrice/src/client/ui/widgets/printing_selector/printing_selector_card_overlay_widget.h b/cockatrice/src/client/ui/widgets/printing_selector/printing_selector_card_overlay_widget.h new file mode 100644 index 000000000..5ab27a274 --- /dev/null +++ b/cockatrice/src/client/ui/widgets/printing_selector/printing_selector_card_overlay_widget.h @@ -0,0 +1,51 @@ +#ifndef PRINTING_SELECTOR_CARD_OVERLAY_WIDGET_H +#define PRINTING_SELECTOR_CARD_OVERLAY_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/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" + +class PrintingSelectorCardOverlayWidget : public QWidget +{ + Q_OBJECT + +public: + explicit PrintingSelectorCardOverlayWidget(QWidget *parent, + AbstractTabDeckEditor *_deckEditor, + DeckListModel *_deckModel, + QTreeView *_deckView, + QSlider *_cardSizeSlider, + CardInfoPtr _rootCard, + const CardInfoPerSet &_setInfoForCard); + +protected: + void resizeEvent(QResizeEvent *event) override; +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + void enterEvent(QEnterEvent *event) override; +#else + void enterEvent(QEvent *event) override; +#endif + void leaveEvent(QEvent *event) override; + void mousePressEvent(QMouseEvent *event) override; + void customMenu(QPoint point); + +signals: + void cardPreferenceChanged(); + +private: + CardInfoPictureWidget *cardInfoPicture; + AllZonesCardAmountWidget *allZonesCardAmountWidget; + AbstractTabDeckEditor *deckEditor; + DeckListModel *deckModel; + QTreeView *deckView; + QSlider *cardSizeSlider; + CardInfoPtr rootCard; + CardInfoPtr setCard; + CardInfoPerSet setInfoForCard; +}; + +#endif // PRINTING_SELECTOR_CARD_OVERLAY_WIDGET_H diff --git a/cockatrice/src/client/ui/widgets/printing_selector/printing_selector_card_search_widget.cpp b/cockatrice/src/client/ui/widgets/printing_selector/printing_selector_card_search_widget.cpp new file mode 100644 index 000000000..3c42f06dc --- /dev/null +++ b/cockatrice/src/client/ui/widgets/printing_selector/printing_selector_card_search_widget.cpp @@ -0,0 +1,39 @@ +#include "printing_selector_card_search_widget.h" + +/** + * @brief Constructs a PrintingSelectorCardSearchWidget for searching cards by set name or set code. + * + * This widget provides a search bar that allows users to search for cards by either their set name + * or set code. It uses a debounced timer to trigger the search action after the user stops typing. + * + * @param parent The parent PrintingSelector widget that will handle the search results. + */ +PrintingSelectorCardSearchWidget::PrintingSelectorCardSearchWidget(PrintingSelector *parent) : parent(parent) +{ + layout = new QHBoxLayout(this); + layout->setContentsMargins(9, 0, 9, 0); + setLayout(layout); + + searchBar = new QLineEdit(this); + searchBar->setPlaceholderText(tr("Search by set name or set code")); + layout->addWidget(searchBar); + + // Add a debounce timer for the search bar to limit frequent updates + searchDebounceTimer = new QTimer(this); + searchDebounceTimer->setSingleShot(true); + connect(searchBar, &QLineEdit::textChanged, this, [this]() { + searchDebounceTimer->start(300); // 300ms debounce + }); + + connect(searchDebounceTimer, &QTimer::timeout, parent, &PrintingSelector::updateDisplay); +} + +/** + * @brief Retrieves the current text in the search bar. + * + * @return The text entered by the user in the search bar. + */ +QString PrintingSelectorCardSearchWidget::getSearchText() +{ + return searchBar->text(); +} diff --git a/cockatrice/src/client/ui/widgets/printing_selector/printing_selector_card_search_widget.h b/cockatrice/src/client/ui/widgets/printing_selector/printing_selector_card_search_widget.h new file mode 100644 index 000000000..737d9b7ba --- /dev/null +++ b/cockatrice/src/client/ui/widgets/printing_selector/printing_selector_card_search_widget.h @@ -0,0 +1,25 @@ +#ifndef PRINTING_SELECTOR_CARD_SEARCH_WIDGET_H +#define PRINTING_SELECTOR_CARD_SEARCH_WIDGET_H + +#include "printing_selector.h" + +#include +#include +#include + +class PrintingSelectorCardSearchWidget : public QWidget +{ + Q_OBJECT + +public: + explicit PrintingSelectorCardSearchWidget(PrintingSelector *parent); + QString getSearchText(); + +private: + QHBoxLayout *layout; + PrintingSelector *parent; + QLineEdit *searchBar; + QTimer *searchDebounceTimer; +}; + +#endif // PRINTING_SELECTOR_CARD_SEARCH_WIDGET_H diff --git a/cockatrice/src/client/ui/widgets/printing_selector/printing_selector_card_selection_widget.cpp b/cockatrice/src/client/ui/widgets/printing_selector/printing_selector_card_selection_widget.cpp new file mode 100644 index 000000000..b4cace6d0 --- /dev/null +++ b/cockatrice/src/client/ui/widgets/printing_selector/printing_selector_card_selection_widget.cpp @@ -0,0 +1,38 @@ +#include "printing_selector_card_selection_widget.h" + +/** + * @brief Constructs a PrintingSelectorCardSelectionWidget for navigating through cards in the deck. + * + * This widget provides buttons that allow users to navigate between cards in the deck. + * It includes buttons for moving to the previous and next card in the deck. + * + * @param parent The parent PrintingSelector widget responsible for managing card selection. + */ +PrintingSelectorCardSelectionWidget::PrintingSelectorCardSelectionWidget(PrintingSelector *parent) : parent(parent) +{ + cardSelectionBarLayout = new QHBoxLayout(this); + cardSelectionBarLayout->setContentsMargins(9, 0, 9, 0); + + previousCardButton = new QPushButton(this); + previousCardButton->setText(tr("Previous Card in Deck")); + + nextCardButton = new QPushButton(this); + nextCardButton->setText(tr("Next Card in Deck")); + + connectSignals(); + + cardSelectionBarLayout->addWidget(previousCardButton); + cardSelectionBarLayout->addWidget(nextCardButton); +} + +/** + * @brief Connects the signals from the buttons to the appropriate slots in the parent widget. + * + * This method connects the click signals of the previous and next card buttons to + * the selectPreviousCard and selectNextCard slots in the parent PrintingSelector widget. + */ +void PrintingSelectorCardSelectionWidget::connectSignals() +{ + connect(previousCardButton, &QPushButton::clicked, parent, &PrintingSelector::selectPreviousCard); + connect(nextCardButton, &QPushButton::clicked, parent, &PrintingSelector::selectNextCard); +} diff --git a/cockatrice/src/client/ui/widgets/printing_selector/printing_selector_card_selection_widget.h b/cockatrice/src/client/ui/widgets/printing_selector/printing_selector_card_selection_widget.h new file mode 100644 index 000000000..4bb2b048f --- /dev/null +++ b/cockatrice/src/client/ui/widgets/printing_selector/printing_selector_card_selection_widget.h @@ -0,0 +1,26 @@ +#ifndef PRINTING_SELECTOR_CARD_SELECTION_WIDGET_H +#define PRINTING_SELECTOR_CARD_SELECTION_WIDGET_H + +#include "printing_selector.h" + +#include +#include +#include + +class PrintingSelectorCardSelectionWidget : public QWidget +{ + Q_OBJECT + +public: + explicit PrintingSelectorCardSelectionWidget(PrintingSelector *parent); + + void connectSignals(); + +private: + PrintingSelector *parent; + QHBoxLayout *cardSelectionBarLayout; + QPushButton *previousCardButton; + QPushButton *nextCardButton; +}; + +#endif // PRINTING_SELECTOR_CARD_SELECTION_WIDGET_H diff --git a/cockatrice/src/client/ui/widgets/printing_selector/printing_selector_card_sorting_widget.cpp b/cockatrice/src/client/ui/widgets/printing_selector/printing_selector_card_sorting_widget.cpp new file mode 100644 index 000000000..faa3fd6ff --- /dev/null +++ b/cockatrice/src/client/ui/widgets/printing_selector/printing_selector_card_sorting_widget.cpp @@ -0,0 +1,231 @@ +#include "printing_selector_card_sorting_widget.h" + +#include "../../../../settings/cache_settings.h" +#include "../../../../utility/card_set_comparator.h" + +const QString PrintingSelectorCardSortingWidget::SORT_OPTIONS_ALPHABETICAL = tr("Alphabetical"); +const QString PrintingSelectorCardSortingWidget::SORT_OPTIONS_PREFERENCE = tr("Preference"); +const QString PrintingSelectorCardSortingWidget::SORT_OPTIONS_RELEASE_DATE = tr("Release Date"); + +const QStringList PrintingSelectorCardSortingWidget::SORT_OPTIONS = {SORT_OPTIONS_ALPHABETICAL, SORT_OPTIONS_PREFERENCE, + SORT_OPTIONS_RELEASE_DATE}; + +/** + * @brief A widget for sorting and filtering card sets in the Printing Selector. + * + * This widget allows users to choose sorting options for the card sets, such as alphabetical order, release date, or + * user-defined preferences. It also allows users to toggle the sorting order between ascending and descending. + */ +PrintingSelectorCardSortingWidget::PrintingSelectorCardSortingWidget(PrintingSelector *parent) : parent(parent) +{ + setMinimumWidth(300); + sortToolBar = new QHBoxLayout(this); + + sortOptionsSelector = new QComboBox(this); + sortOptionsSelector->setFocusPolicy(Qt::StrongFocus); + sortOptionsSelector->addItems(SORT_OPTIONS); + sortOptionsSelector->setCurrentIndex(SettingsCache::instance().getPrintingSelectorSortOrder()); + connect(sortOptionsSelector, &QComboBox::currentTextChanged, this, + &PrintingSelectorCardSortingWidget::updateSortSetting); + connect(sortOptionsSelector, &QComboBox::currentTextChanged, parent, &PrintingSelector::updateDisplay); + sortToolBar->addWidget(sortOptionsSelector); + + toggleSortOrder = new QPushButton(this); + toggleSortOrder->setText(tr("Descending")); + descendingSort = true; + connect(toggleSortOrder, &QPushButton::clicked, this, &PrintingSelectorCardSortingWidget::updateSortOrder); + sortToolBar->addWidget(toggleSortOrder); +} + +/** + * @brief Updates the sorting order (ascending or descending). + * + * This function toggles the sort order between ascending and descending and updates the display. + */ +void PrintingSelectorCardSortingWidget::updateSortOrder() +{ + if (descendingSort) { + toggleSortOrder->setText(tr("Ascending")); + } else { + toggleSortOrder->setText(tr("Descending")); + } + descendingSort = !descendingSort; + parent->updateDisplay(); +} + +/** + * @brief Updates the sorting setting in the application settings. + * + * This function saves the selected sorting option (from the combobox) to the application settings. + */ +void PrintingSelectorCardSortingWidget::updateSortSetting() +{ + SettingsCache::instance().setPrintingSelectorSortOrder(sortOptionsSelector->currentIndex()); +} + +/** + * @brief Sorts a list of card sets based on the selected sorting option. + * + * This function sorts the card sets according to the selected sorting option in the combobox. The options include: + * - Alphabetical + * - Preference + * - Release Date + * - Contained in Deck + * - Potential Cards in Deck + * + * @param cardInfoPerSets The list of card sets to be sorted. + * @return A sorted list of card sets. + */ +QList PrintingSelectorCardSortingWidget::sortSets(const CardInfoPerSetMap &cardInfoPerSets) +{ + QList sortedSets; + + for (const auto &cardInfoPerSetList : cardInfoPerSets) { + for (const auto &set : cardInfoPerSetList) { + sortedSets << set.getPtr(); + break; + } + } + + if (sortedSets.empty()) { + sortedSets << CardSet::newInstance("", "", "", QDate()); + } + + if (sortOptionsSelector->currentText() == SORT_OPTIONS_PREFERENCE) { + std::sort(sortedSets.begin(), sortedSets.end(), SetPriorityComparator()); + std::reverse(sortedSets.begin(), sortedSets.end()); + } else if (sortOptionsSelector->currentText() == SORT_OPTIONS_RELEASE_DATE) { + std::sort(sortedSets.begin(), sortedSets.end(), SetReleaseDateComparator()); + } + + QList sortedCardInfoPerSets; + // Reconstruct sorted list of CardInfoPerSet + for (const auto &set : sortedSets) { + for (auto it = cardInfoPerSets.begin(); it != cardInfoPerSets.end(); ++it) { + for (const auto &cardInfoPerSet : it.value()) { + if (cardInfoPerSet.getPtr() == set) { + if (!sortedCardInfoPerSets.contains(cardInfoPerSet)) { + sortedCardInfoPerSets << cardInfoPerSet; + } + } + } + } + } + + if (descendingSort) { + std::reverse(sortedCardInfoPerSets.begin(), sortedCardInfoPerSets.end()); + } + + return sortedCardInfoPerSets; +} + +/** + * @brief Filters a list of card sets based on the search text. + * + * This function filters the given list of card sets by comparing their long and short names with the provided search + * text. If the search text matches either the long or short name of a card set, that set is included in the filtered + * list. + * + * @param sets The list of card sets to be filtered. + * @param searchText The search text used to filter the card sets. + * @return A filtered list of card sets. + */ +QList PrintingSelectorCardSortingWidget::filterSets(const QList &sets, + const QString &searchText) +{ + if (searchText.isEmpty()) { + return sets; + } + + QList filteredSets; + + for (const auto &set : sets) { + const QString longName = set.getPtr()->getLongName().toLower(); + const QString shortName = set.getPtr()->getShortName().toLower(); + + if (longName.contains(searchText) || shortName.contains(searchText)) { + filteredSets << set; + } + } + + return filteredSets; +} + +QList PrintingSelectorCardSortingWidget::prependPinnedPrintings(const QList &sets, + const QString &cardName) +{ + auto setsToUse = sets; + const auto &cardProviderId = SettingsCache::instance().cardOverrides().getCardPreferenceOverride(cardName); + if (!cardProviderId.isEmpty()) { + for (int i = 0; i < setsToUse.size(); ++i) { + const auto &card = setsToUse[i]; + if (card.getProperty("uuid") == cardProviderId) { + setsToUse.move(i, 0); + break; + } + } + } + + return setsToUse; +} + +/** + * @brief Prepend card printings that are contained in the deck to the list of card sets. + * + * This function adjusts the list of card sets by moving the printings that are already contained in the deck to the + * beginning of the list, sorted by the count of cards in the deck. + * + * @param sets The original list of card sets. + * @param selectedCard The currently selected card. + * @param deckModel The model representing the deck. + * @return A list of card sets with the printings contained in the deck prepended. + */ +QList PrintingSelectorCardSortingWidget::prependPrintingsInDeck(const QList &sets, + const CardInfoPtr &selectedCard, + DeckListModel *deckModel) +{ + if (!selectedCard) { + return {}; + } + + CardInfoPerSetMap cardInfoPerSets = selectedCard->getSets(); + QList> countList; + + // Collect sets with their counts + for (const auto &cardInfoPerSetList : cardInfoPerSets) { + for (const auto &cardInfoPerSet : cardInfoPerSetList) { + QModelIndex find_card = + deckModel->findCard(selectedCard->getName(), DECK_ZONE_MAIN, cardInfoPerSet.getProperty("uuid")); + if (find_card.isValid()) { + int count = + deckModel->data(find_card, Qt::DisplayRole).toInt(); // Ensure the count is treated as an integer + if (count > 0) { + countList.append(qMakePair(cardInfoPerSet, count)); + } + } + break; + } + } + + // Sort sets by count in descending numerical order + std::sort(countList.begin(), countList.end(), + [](const QPair &a, const QPair &b) { + return a.second > b.second; // Ensure numerical comparison + }); + + // Create a copy of the original list to modify + QList result = sets; + + // Prepend sorted sets and remove them from the original list + for (const auto &pair : countList) { + auto it = std::find_if(result.begin(), result.end(), [&pair](const CardInfoPerSet &item) { + return item.getProperty("uuid") == pair.first.getProperty("uuid"); + }); + if (it != result.end()) { + result.erase(it); // Remove the matching entry + } + result.prepend(pair.first); // Prepend the sorted item + } + + return result; +} diff --git a/cockatrice/src/client/ui/widgets/printing_selector/printing_selector_card_sorting_widget.h b/cockatrice/src/client/ui/widgets/printing_selector/printing_selector_card_sorting_widget.h new file mode 100644 index 000000000..4c8d304aa --- /dev/null +++ b/cockatrice/src/client/ui/widgets/printing_selector/printing_selector_card_sorting_widget.h @@ -0,0 +1,40 @@ +#ifndef PRINTING_SELECTOR_CARD_SORTING_WIDGET_H +#define PRINTING_SELECTOR_CARD_SORTING_WIDGET_H + +#include "printing_selector.h" + +#include +#include +#include + +class PrintingSelectorCardSortingWidget : public QWidget +{ + Q_OBJECT +public: + explicit PrintingSelectorCardSortingWidget(PrintingSelector *parent); + QList sortSets(const CardInfoPerSetMap &cardInfoPerSets); + QList filterSets(const QList &sets, const QString &searchText); + QList prependPinnedPrintings(const QList &sets, const QString &cardName); + QList prependPrintingsInDeck(const QList &sets, + const CardInfoPtr &selectedCard, + DeckListModel *deckModel); + +public slots: + void updateSortOrder(); + void updateSortSetting(); + +private: + PrintingSelector *parent; + QHBoxLayout *sortToolBar; + static const QString SORT_OPTIONS_ALPHABETICAL; + static const QString SORT_OPTIONS_PREFERENCE; + static const QString SORT_OPTIONS_RELEASE_DATE; + static const QString SORT_OPTIONS_CONTAINED_IN_DECK; + static const QString SORT_OPTIONS_POTENTIAL_CARDS; + static const QStringList SORT_OPTIONS; + QComboBox *sortOptionsSelector; + bool descendingSort; + QPushButton *toggleSortOrder; +}; + +#endif // PRINTING_SELECTOR_CARD_SORTING_WIDGET_H diff --git a/cockatrice/src/client/ui/widgets/printing_selector/set_name_and_collectors_number_display_widget.cpp b/cockatrice/src/client/ui/widgets/printing_selector/set_name_and_collectors_number_display_widget.cpp new file mode 100644 index 000000000..b1a6a61c1 --- /dev/null +++ b/cockatrice/src/client/ui/widgets/printing_selector/set_name_and_collectors_number_display_widget.cpp @@ -0,0 +1,102 @@ +#include "set_name_and_collectors_number_display_widget.h" + +#include + +/** + * @class SetNameAndCollectorsNumberDisplayWidget + * @brief A widget to display the set name and collectors number with adjustable font size. + * + * This widget displays the set name and collectors number on two separate labels. The font size is resized dynamically + * when the card size is changed. + */ +SetNameAndCollectorsNumberDisplayWidget::SetNameAndCollectorsNumberDisplayWidget(QWidget *parent, + const QString &_setName, + const QString &_collectorsNumber, + QSlider *_cardSizeSlider) + : QWidget(parent) +{ + // Set up the layout for the widget + layout = new QVBoxLayout(this); + setLayout(layout); + + // Set the widget's size policy and minimum size + setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Preferred); + setMinimumSize(QWidget::sizeHint()); + + // Create and configure the set name label + setName = new QLabel(_setName); + setName->setWordWrap(true); + setName->setAlignment(Qt::AlignHCenter | Qt::AlignBottom); + setName->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Expanding); + + // Create and configure the collectors number label + collectorsNumber = new QLabel(_collectorsNumber); + collectorsNumber->setAlignment(Qt::AlignHCenter | Qt::AlignBottom); + collectorsNumber->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); + + // Store the card size slider and connect its signal to the font size adjustment slot + cardSizeSlider = _cardSizeSlider; + connect(cardSizeSlider, &QSlider::valueChanged, this, &SetNameAndCollectorsNumberDisplayWidget::adjustFontSize); + + // Add labels to the layout + layout->addWidget(setName); + layout->addWidget(collectorsNumber); +} + +/** + * @brief Adjusts the font size of the labels based on the slider value. + * + * This method adjusts the font size of the set name and collectors number labels + * according to the scale percentage provided by the slider. The font size is clamped + * to a range between the defined minimum and maximum font sizes. + * + * @param scalePercentage The scale percentage from the slider. + */ +void SetNameAndCollectorsNumberDisplayWidget::adjustFontSize(int scalePercentage) +{ + // Define the base font size and the range + const int minFontSize = 8; // Minimum font size + const int maxFontSize = 32; // Maximum font size + const int basePercentage = 100; // Scale at 100% + + // Calculate the new font size + int newFontSize = minFontSize + (scalePercentage - basePercentage) * (maxFontSize - minFontSize) / 225; + + // Clamp the font size to the defined range + newFontSize = std::clamp(newFontSize, minFontSize, maxFontSize); + + // Update the fonts for both labels + QFont setNameFont = setName->font(); + setNameFont.setPointSize(newFontSize); + setName->setFont(setNameFont); + + QFont collectorsNumberFont = collectorsNumber->font(); + collectorsNumberFont.setPointSize(newFontSize); + collectorsNumber->setFont(collectorsNumberFont); + + // Optionally trigger a resize to accommodate new font size + adjustSize(); +} + +/** + * @brief Handles resize events to adjust the height of the set name label. + * + * This method calculates the height required to display the set name label with word wrapping. + * It adjusts the minimum height of the set name label to fit the text. + * + * @param event The resize event. + */ +void SetNameAndCollectorsNumberDisplayWidget::resizeEvent(QResizeEvent *event) +{ + QWidget::resizeEvent(event); // Ensure the parent class handles the event first + + QFontMetrics fm(setName->font()); + int labelWidth = setName->width(); // Get the current width of the QLabel + QString text = setName->text(); // The text to be rendered + + // Calculate the height required to render the text with word wrapping + int textHeight = fm.boundingRect(0, 0, labelWidth, 0, Qt::TextWordWrap, text).height(); + + // Set the minimum height to accommodate the required text height + setName->setMinimumHeight(textHeight); +} diff --git a/cockatrice/src/client/ui/widgets/printing_selector/set_name_and_collectors_number_display_widget.h b/cockatrice/src/client/ui/widgets/printing_selector/set_name_and_collectors_number_display_widget.h new file mode 100644 index 000000000..dfaa98caa --- /dev/null +++ b/cockatrice/src/client/ui/widgets/printing_selector/set_name_and_collectors_number_display_widget.h @@ -0,0 +1,29 @@ +#ifndef SET_NAME_AND_COLLECTORS_NUMBER_DISPLAY_WIDGET_H +#define SET_NAME_AND_COLLECTORS_NUMBER_DISPLAY_WIDGET_H + +#include +#include +#include +#include + +class SetNameAndCollectorsNumberDisplayWidget : public QWidget +{ + Q_OBJECT +public: + SetNameAndCollectorsNumberDisplayWidget(QWidget *parent, + const QString &setName, + const QString &collectorsNumber, + QSlider *cardSizeSlider); + void resizeEvent(QResizeEvent *event) override; + +public slots: + void adjustFontSize(int scalePercentage); + +private: + QVBoxLayout *layout; + QLabel *setName; + QLabel *collectorsNumber; + QSlider *cardSizeSlider; +}; + +#endif // SET_NAME_AND_COLLECTORS_NUMBER_DISPLAY_WIDGET_H diff --git a/cockatrice/src/client/ui/widgets/quick_settings/settings_button_widget.cpp b/cockatrice/src/client/ui/widgets/quick_settings/settings_button_widget.cpp new file mode 100644 index 000000000..5d3307e52 --- /dev/null +++ b/cockatrice/src/client/ui/widgets/quick_settings/settings_button_widget.cpp @@ -0,0 +1,80 @@ +#include "settings_button_widget.h" + +#include +#include +#include +#include + +SettingsButtonWidget::SettingsButtonWidget(QWidget *parent) + : QWidget(parent), button(new QToolButton(this)), popup(new SettingsPopupWidget(this)) +{ + + button->setIcon(QPixmap("theme:icons/cogwheel")); + button->setCheckable(true); + button->setFixedSize(32, 32); + connect(button, &QToolButton::clicked, this, &SettingsButtonWidget::togglePopup); + connect(popup, &SettingsPopupWidget::aboutToClose, this, &SettingsButtonWidget::onPopupClosed); + + layout = new QHBoxLayout(this); + layout->addWidget(button); + layout->setContentsMargins(0, 0, 0, 0); + setLayout(layout); +} + +void SettingsButtonWidget::addSettingsWidget(QWidget *toAdd) const +{ + popup->addSettingsWidget(toAdd); +} + +void SettingsButtonWidget::togglePopup() +{ + if (popup->isVisible()) { + popup->close(); + } else { + // Ensure popup size is known before positioning + popup->adjustSize(); + QSize popupSize = popup->size(); + + // Get button position + QPoint buttonGlobalPos = button->mapToGlobal(QPoint(0, button->height())); + + // Get screen geometry + QScreen *screen = QApplication::screenAt(buttonGlobalPos); + QRect screenGeom = screen ? screen->availableGeometry() : QApplication::primaryScreen()->availableGeometry(); + + int x = buttonGlobalPos.x(); + int y = buttonGlobalPos.y(); + + // Adjust X position if popup overflows the right side of the screen + if (x + popupSize.width() > screenGeom.right()) { + x = buttonGlobalPos.x() - popupSize.width() + button->width(); // Move left + } + + // Adjust Y position if popup overflows the bottom of the screen + if (y + popupSize.height() > screenGeom.bottom()) { + y = buttonGlobalPos.y() - popupSize.height() - button->height(); // Move up + } + + popup->move(x, y); + popup->show(); + popup->setFocus(); + button->setChecked(true); // Ensure button is checked when popup is visible + } +} + +void SettingsButtonWidget::onPopupClosed() const +{ + button->setChecked(false); // Ensure button unchecks when popup closes +} + +void SettingsButtonWidget::mousePressEvent(QMouseEvent *event) +{ +#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) + if (popup->isVisible() && !popup->geometry().contains(event->globalPosition().toPoint())) { +#else + if (popup->isVisible() && !popup->geometry().contains(event->globalPos())) { +#endif + popup->close(); + } + QWidget::mousePressEvent(event); +} diff --git a/cockatrice/src/client/ui/widgets/quick_settings/settings_button_widget.h b/cockatrice/src/client/ui/widgets/quick_settings/settings_button_widget.h new file mode 100644 index 000000000..7e07f573c --- /dev/null +++ b/cockatrice/src/client/ui/widgets/quick_settings/settings_button_widget.h @@ -0,0 +1,30 @@ +#ifndef SETTINGS_BUTTON_WIDGET_H +#define SETTINGS_BUTTON_WIDGET_H + +#include "settings_popup_widget.h" + +#include +#include + +class SettingsButtonWidget : public QWidget +{ + Q_OBJECT + +public: + explicit SettingsButtonWidget(QWidget *parent = nullptr); + void addSettingsWidget(QWidget *toAdd) const; + +protected: + void mousePressEvent(QMouseEvent *event) override; + +private slots: + void togglePopup(); + void onPopupClosed() const; + +private: + QHBoxLayout *layout; + QToolButton *button; + SettingsPopupWidget *popup; +}; + +#endif // SETTINGS_BUTTON_WIDGET_H diff --git a/cockatrice/src/client/ui/widgets/quick_settings/settings_popup_widget.cpp b/cockatrice/src/client/ui/widgets/quick_settings/settings_popup_widget.cpp new file mode 100644 index 000000000..10b2c327c --- /dev/null +++ b/cockatrice/src/client/ui/widgets/quick_settings/settings_popup_widget.cpp @@ -0,0 +1,37 @@ +#include "settings_popup_widget.h" + +#include +#include +#include + +SettingsPopupWidget::SettingsPopupWidget(QWidget *parent) : QWidget(parent, Qt::Popup | Qt::FramelessWindowHint) +{ + layout = new QVBoxLayout(this); +} + +void SettingsPopupWidget::addSettingsWidget(QWidget *toAdd) const +{ + layout->addWidget(toAdd); +} + +void SettingsPopupWidget::focusOutEvent(QFocusEvent *event) +{ + if (!this->isAncestorOf(QApplication::focusWidget())) { + close(); + } + QWidget::focusOutEvent(event); +} + +void SettingsPopupWidget::closeEvent(QCloseEvent *event) +{ + emit aboutToClose(); + QWidget::closeEvent(event); +} + +void SettingsPopupWidget::paintEvent(QPaintEvent *event) +{ + QPainter painter(this); + painter.setPen(Qt::gray); + painter.drawRect(rect().adjusted(0, 0, -1, -1)); + QWidget::paintEvent(event); +} \ No newline at end of file diff --git a/cockatrice/src/client/ui/widgets/quick_settings/settings_popup_widget.h b/cockatrice/src/client/ui/widgets/quick_settings/settings_popup_widget.h new file mode 100644 index 000000000..291063662 --- /dev/null +++ b/cockatrice/src/client/ui/widgets/quick_settings/settings_popup_widget.h @@ -0,0 +1,27 @@ +#ifndef SETTINGS_POPUP_WIDGET_H +#define SETTINGS_POPUP_WIDGET_H + +#include +#include +#include + +class SettingsPopupWidget : public QWidget +{ + Q_OBJECT + +public: + explicit SettingsPopupWidget(QWidget *parent = nullptr); + void addSettingsWidget(QWidget *toAdd) const; + +signals: + void aboutToClose(); + +protected: + void focusOutEvent(QFocusEvent *event) override; + void closeEvent(QCloseEvent *event) override; + void paintEvent(QPaintEvent *event) override; + + QVBoxLayout *layout; +}; + +#endif // SETTINGSPOPUP_H diff --git a/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_color_identity_filter_widget.cpp b/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_color_identity_filter_widget.cpp new file mode 100644 index 000000000..79fcc01fb --- /dev/null +++ b/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_color_identity_filter_widget.cpp @@ -0,0 +1,120 @@ +#include "deck_preview_color_identity_filter_widget.h" + +#include "../../cards/additional_info/mana_symbol_widget.h" +#include "deck_preview_widget.h" + +#include +#include + +DeckPreviewColorIdentityFilterWidget::DeckPreviewColorIdentityFilterWidget(VisualDeckStorageWidget *parent) + : QWidget(parent), layout(new QHBoxLayout(this)) +{ + setLayout(layout); + layout->setSpacing(5); + layout->setContentsMargins(0, 0, 0, 0); + + QString fullColorIdentity = "WUBRG"; + for (const QChar &color : fullColorIdentity) { + auto *manaSymbol = new ManaSymbolWidget(this, color, false, true); + manaSymbol->setMaximumWidth(25); + + layout->addWidget(manaSymbol); + + // Initialize the activeColors map + activeColors[color] = false; + + // Connect the color toggled signal + connect(manaSymbol, &ManaSymbolWidget::colorToggled, this, + &DeckPreviewColorIdentityFilterWidget::handleColorToggled); + } + + toggleButton = new QPushButton(this); + toggleButton->setCheckable(true); + layout->addWidget(toggleButton); + + // Connect the button's toggled signal + connect(toggleButton, &QPushButton::toggled, this, &DeckPreviewColorIdentityFilterWidget::updateFilterMode); + connect(this, &DeckPreviewColorIdentityFilterWidget::activeColorsChanged, parent, + &VisualDeckStorageWidget::updateColorFilter); + connect(this, &DeckPreviewColorIdentityFilterWidget::filterModeChanged, parent, + &VisualDeckStorageWidget::updateColorFilter); + + // Call retranslateUi to set the initial text + retranslateUi(); +} + +void DeckPreviewColorIdentityFilterWidget::retranslateUi() +{ + // Set the toggle button text based on the current mode + toggleButton->setText(exactMatchMode ? tr("Mode: Exact Match") : tr("Mode: Includes")); +} + +void DeckPreviewColorIdentityFilterWidget::handleColorToggled(QChar color, bool active) +{ + activeColors[color] = active; + emit activeColorsChanged(); +} + +void DeckPreviewColorIdentityFilterWidget::updateFilterMode(bool checked) +{ + exactMatchMode = checked; // Toggle between modes + retranslateUi(); // Update the button text + emit filterModeChanged(exactMatchMode); +} + +void DeckPreviewColorIdentityFilterWidget::filterWidgets(QList widgets) +{ + // Check if no colors are active + bool noColorsActive = true; + for (auto it = activeColors.constBegin(); it != activeColors.constEnd(); ++it) { + if (it.value()) { + noColorsActive = false; + break; + } + } + + // If no colors are active, return the unfiltered list of widgets + if (noColorsActive) { + for (DeckPreviewWidget *previewWidget : widgets) { + previewWidget->filteredByColor = false; + } + } + + for (const auto &widget : widgets) { + QString colorIdentity = widget->getColorIdentity(); + + bool matchesFilter = true; + if (exactMatchMode) { + // Exact match mode: active colors must exactly match colorIdentity + + // Create a set of active colors + QSet activeColorSet; + for (auto it = activeColors.constBegin(); it != activeColors.constEnd(); ++it) { + if (it.value()) { + activeColorSet.insert(it.key().toUpper()); // Use uppercase for uniformity + } + } + + // Create a set of colors from the color identity string + QSet colorIdentitySet; + for (const QChar &color : colorIdentity) { + colorIdentitySet.insert(color.toUpper()); // Ensure case uniformity + } + + // Compare the sets: the sets must match exactly + if (activeColorSet != colorIdentitySet) { + matchesFilter = false; + } + } else { + // Includes mode: colorIdentity must contain all active colors + for (auto it = activeColors.constBegin(); it != activeColors.constEnd(); ++it) { + if (it.value() && !colorIdentity.contains(it.key())) { + matchesFilter = false; + break; + } + } + } + + widget->filteredByColor = !matchesFilter; + } +} diff --git a/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_color_identity_filter_widget.h b/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_color_identity_filter_widget.h new file mode 100644 index 000000000..415d946cd --- /dev/null +++ b/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_color_identity_filter_widget.h @@ -0,0 +1,39 @@ +#ifndef DECK_PREVIEW_COLOR_IDENTITY_FILTER_WIDGET_H +#define DECK_PREVIEW_COLOR_IDENTITY_FILTER_WIDGET_H + +#include "../visual_deck_storage_widget.h" + +#include +#include +#include +#include +#include + +class DeckPreviewWidget; +class VisualDeckStorageWidget; + +class DeckPreviewColorIdentityFilterWidget : public QWidget +{ + Q_OBJECT + +public: + explicit DeckPreviewColorIdentityFilterWidget(VisualDeckStorageWidget *parent); + void retranslateUi(); + void filterWidgets(QList widgets); + +signals: + void filterModeChanged(bool exactMatchMode); + void activeColorsChanged(); + +private slots: + void handleColorToggled(QChar color, bool active); + void updateFilterMode(bool checked); + +private: + QHBoxLayout *layout; + QPushButton *toggleButton; + QMap activeColors; + bool exactMatchMode = false; // Default to "includes" mode +}; + +#endif // DECK_PREVIEW_COLOR_IDENTITY_FILTER_WIDGET_H diff --git a/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_deck_tags_display_widget.cpp b/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_deck_tags_display_widget.cpp new file mode 100644 index 000000000..97825ff21 --- /dev/null +++ b/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_deck_tags_display_widget.cpp @@ -0,0 +1,163 @@ +#include "deck_preview_deck_tags_display_widget.h" + +#include "../../../../../dialogs/dlg_convert_deck_to_cod_format.h" +#include "../../../../../settings/cache_settings.h" +#include "../../../../tabs/tab_deck_editor.h" +#include "../../general/layout_containers/flow_widget.h" +#include "deck_preview_tag_addition_widget.h" +#include "deck_preview_tag_dialog.h" +#include "deck_preview_tag_display_widget.h" +#include "deck_preview_widget.h" + +#include +#include +#include + +DeckPreviewDeckTagsDisplayWidget::DeckPreviewDeckTagsDisplayWidget(QWidget *_parent, DeckList *_deckList) + : QWidget(_parent), deckList(nullptr) +{ + + setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); + + // Create layout + auto *layout = new QHBoxLayout(this); + layout->setContentsMargins(0, 0, 0, 0); + + setFixedHeight(100); + + flowWidget = new FlowWidget(this, Qt::Horizontal, Qt::ScrollBarAlwaysOff, Qt::ScrollBarAsNeeded); + + if (_deckList) { + connectDeckList(_deckList); + } + + layout->addWidget(flowWidget); +} + +void DeckPreviewDeckTagsDisplayWidget::connectDeckList(DeckList *_deckList) +{ + if (deckList) { + disconnect(deckList, &DeckList::deckTagsChanged, this, &DeckPreviewDeckTagsDisplayWidget::refreshTags); + } + + deckList = _deckList; + connect(deckList, &DeckList::deckTagsChanged, this, &DeckPreviewDeckTagsDisplayWidget::refreshTags); + + refreshTags(); +} + +void DeckPreviewDeckTagsDisplayWidget::refreshTags() +{ + flowWidget->clearLayout(); + + for (const QString &tag : deckList->getTags()) { + flowWidget->addWidget(new DeckPreviewTagDisplayWidget(this, tag)); + } + + auto tagAdditionWidget = new DeckPreviewTagAdditionWidget(this, tr("Edit tags ...")); + connect(tagAdditionWidget, &DeckPreviewTagAdditionWidget::tagClicked, this, + &DeckPreviewDeckTagsDisplayWidget::openTagEditDlg); + flowWidget->addWidget(tagAdditionWidget); +} + +/** + * Gets the filepath of all files (no directories) in target directory and all subdirectories + */ +static QStringList getAllFiles(const QString &filePath) +{ + QStringList allFiles; + + // QDirIterator with QDir::Files ensures only files are listed (no directories) + QDirIterator it(filePath, QDir::Files, QDirIterator::Subdirectories | QDirIterator::FollowSymlinks); + + while (it.hasNext()) { + allFiles << it.next(); // Add each file path to the list + } + + return allFiles; +} + +void DeckPreviewDeckTagsDisplayWidget::openTagEditDlg() +{ + if (qobject_cast(parentWidget())) { + auto *deckPreviewWidget = qobject_cast(parentWidget()); + QStringList knownTags = deckPreviewWidget->visualDeckStorageWidget->tagFilterWidget->getAllKnownTags(); + QStringList activeTags = deckList->getTags(); + + bool canAddTags = true; + + if (DeckLoader::getFormatFromName(deckPreviewWidget->filePath) != DeckLoader::CockatriceFormat) { + canAddTags = false; + // Retrieve saved preference if the prompt is disabled + if (!SettingsCache::instance().getVisualDeckStoragePromptForConversion()) { + if (SettingsCache::instance().getVisualDeckStorageAlwaysConvert()) { + deckPreviewWidget->deckLoader->convertToCockatriceFormat(deckPreviewWidget->filePath); + deckPreviewWidget->filePath = deckPreviewWidget->deckLoader->getLastFileName(); + deckPreviewWidget->refreshBannerCardText(); + canAddTags = true; + } + } else { + // Show the dialog to the user + DialogConvertDeckToCodFormat conversionDialog(parentWidget()); + if (conversionDialog.exec() == QDialog::Accepted) { + deckPreviewWidget->deckLoader->convertToCockatriceFormat(deckPreviewWidget->filePath); + deckPreviewWidget->filePath = deckPreviewWidget->deckLoader->getLastFileName(); + deckPreviewWidget->refreshBannerCardText(); + canAddTags = true; + + if (conversionDialog.dontAskAgain()) { + SettingsCache::instance().setVisualDeckStoragePromptForConversion(Qt::CheckState::Unchecked); + SettingsCache::instance().setVisualDeckStorageAlwaysConvert(Qt::CheckState::Checked); + } + } else { + SettingsCache::instance().setVisualDeckStorageAlwaysConvert(Qt::CheckState::Unchecked); + + if (conversionDialog.dontAskAgain()) { + SettingsCache::instance().setVisualDeckStoragePromptForConversion(Qt::CheckState::Unchecked); + } else { + SettingsCache::instance().setVisualDeckStoragePromptForConversion(Qt::CheckState::Checked); + } + } + } + } + + if (canAddTags) { + DeckPreviewTagDialog dialog(knownTags, activeTags); + if (dialog.exec() == QDialog::Accepted) { + QStringList updatedTags = dialog.getActiveTags(); + deckList->setTags(updatedTags); + deckPreviewWidget->deckLoader->saveToFile(deckPreviewWidget->filePath, DeckLoader::CockatriceFormat); + } + } + } else if (parentWidget()) { + // If we're the child of a TabDeckEditor, we are buried under a ton of childWidgets in the DeckInfoDock. + QWidget *currentParent = parentWidget(); + while (currentParent) { + if (qobject_cast(currentParent)) { + break; + } + currentParent = currentParent->parentWidget(); + } + if (qobject_cast(currentParent)) { + auto *deckEditor = qobject_cast(currentParent); + QStringList knownTags; + QStringList allFiles = getAllFiles(SettingsCache::instance().getDeckPath()); + DeckLoader loader; + for (const QString &file : allFiles) { + loader.loadFromFile(file, DeckLoader::getFormatFromName(file), false); + QStringList tags = loader.getTags(); + knownTags.append(tags); + knownTags.removeDuplicates(); + } + + QStringList activeTags = deckList->getTags(); + + DeckPreviewTagDialog dialog(knownTags, activeTags); + if (dialog.exec() == QDialog::Accepted) { + QStringList updatedTags = dialog.getActiveTags(); + deckList->setTags(updatedTags); + deckEditor->setModified(true); + } + } + } +} diff --git a/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_deck_tags_display_widget.h b/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_deck_tags_display_widget.h new file mode 100644 index 000000000..cf8917d14 --- /dev/null +++ b/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_deck_tags_display_widget.h @@ -0,0 +1,24 @@ +#ifndef DECK_PREVIEW_DECK_TAGS_DISPLAY_WIDGET_H +#define DECK_PREVIEW_DECK_TAGS_DISPLAY_WIDGET_H + +#include "../../../../../deck/deck_loader.h" +#include "deck_preview_widget.h" + +#include + +class DeckPreviewWidget; +class DeckPreviewDeckTagsDisplayWidget : public QWidget +{ + Q_OBJECT + +public: + explicit DeckPreviewDeckTagsDisplayWidget(QWidget *_parent, DeckList *_deckList); + void connectDeckList(DeckList *_deckList); + void refreshTags(); + DeckList *deckList; + FlowWidget *flowWidget; + +public slots: + void openTagEditDlg(); +}; +#endif // DECK_PREVIEW_DECK_TAGS_DISPLAY_WIDGET_H diff --git a/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_tag_addition_widget.cpp b/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_tag_addition_widget.cpp new file mode 100644 index 000000000..2e575a842 --- /dev/null +++ b/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_tag_addition_widget.cpp @@ -0,0 +1,89 @@ +#include "deck_preview_tag_addition_widget.h" + +#include "../../../../../settings/cache_settings.h" +#include "../../../../tabs/abstract_tab_deck_editor.h" +#include "deck_preview_tag_dialog.h" + +#include +#include +#include +#include +#include + +DeckPreviewTagAdditionWidget::DeckPreviewTagAdditionWidget(QWidget *_parent, QString _tagName) + : QWidget(_parent), tagName_(std::move(_tagName)) +{ + // Create layout + auto *layout = new QHBoxLayout(this); + layout->setContentsMargins(5, 5, 5, 5); + layout->setSpacing(5); + + // Adjust widget size + setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); +} + +QSize DeckPreviewTagAdditionWidget::sizeHint() const +{ + // Calculate the size based on the tag name + QFontMetrics fm(font()); + int textWidth = fm.horizontalAdvance(tagName_); + int width = textWidth + 50; // Add extra padding + int height = fm.height() + 10; // Height based on font size + padding + + return {width, height}; +} + +void DeckPreviewTagAdditionWidget::mousePressEvent(QMouseEvent *event) +{ + if (event->button() == Qt::LeftButton) { + emit tagClicked(); + } + QWidget::mousePressEvent(event); +} + +void DeckPreviewTagAdditionWidget::paintEvent(QPaintEvent *event) +{ + QPainter painter(this); + + // Set background color + QColor backgroundColor = Qt::lightGray; + painter.setBrush(backgroundColor); + painter.setPen(Qt::NoPen); + + // Draw background + painter.drawRect(rect()); + + // Draw border + QColor borderColor = Qt::gray; + QPen borderPen(borderColor, 1); + painter.setPen(borderPen); + painter.drawRect(rect().adjusted(0, 0, -1, -1)); // Adjust for pen width + + // Calculate font size based on widget height + QFont font = painter.font(); + int fontSize = std::max(10, height() / 2); // Ensure a minimum font size of 10 + font.setPointSize(fontSize); + painter.setFont(font); + + // Calculate text rect with margin + int margin = 10; // Left and right margins + QRect textRect(margin, 0, width() - margin * 2, height()); + + // Draw the text with a black border for better legibility + painter.setPen(Qt::black); + + // Draw text border by offsetting + for (int dx = -1; dx <= 1; ++dx) { + for (int dy = -1; dy <= 1; ++dy) { + if (dx != 0 || dy != 0) { + painter.drawText(textRect.translated(dx, dy), Qt::AlignLeft | Qt::AlignVCenter, tagName_); + } + } + } + + // Draw the actual text + painter.setPen(Qt::white); + painter.drawText(textRect, Qt::AlignLeft | Qt::AlignVCenter, tagName_); + + QWidget::paintEvent(event); +} diff --git a/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_tag_addition_widget.h b/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_tag_addition_widget.h new file mode 100644 index 000000000..bdf4a0103 --- /dev/null +++ b/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_tag_addition_widget.h @@ -0,0 +1,30 @@ +#ifndef DECK_PREVIEW_TAG_ADDITION_WIDGET_H +#define DECK_PREVIEW_TAG_ADDITION_WIDGET_H + +#include "deck_preview_deck_tags_display_widget.h" + +#include +#include +#include + +class DeckPreviewTagAdditionWidget : public QWidget +{ + Q_OBJECT + +public: + explicit DeckPreviewTagAdditionWidget(QWidget *_parent, QString _tagName); + [[nodiscard]] QSize sizeHint() const override; + +signals: + void tagClicked(); // Emitted when the tag is clicked + void tagClosed(); // Emitted when the close button is clicked + +protected: + void mousePressEvent(QMouseEvent *event) override; + void paintEvent(QPaintEvent *event) override; + +private: + QString tagName_; +}; + +#endif // DECK_PREVIEW_TAG_ADDITION_WIDGET_H diff --git a/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_tag_dialog.cpp b/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_tag_dialog.cpp new file mode 100644 index 000000000..815545aab --- /dev/null +++ b/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_tag_dialog.cpp @@ -0,0 +1,223 @@ +#include "deck_preview_tag_dialog.h" + +#include "deck_preview_tag_item_widget.h" + +#include +#include +#include +#include +#include +#include +#include + +DeckPreviewTagDialog::DeckPreviewTagDialog(const QStringList &knownTags, const QStringList &activeTags, QWidget *parent) + : QDialog(parent), activeTags_(activeTags) +{ + resize(400, 500); + + QStringList defaultTags = { + // Strategies + "🏃️ Aggro", + "🧙‍️ Control", + "⚔️ Midrange", + "🌀 Combo", + "🪓 Mill", + "🔒 Stax", + "🗺️ Landfall", + "🛡️ Pillowfort", + "🌱 Ramp", + "⚡ Storm", + "💀 Aristocrats", + "☠️ Reanimator", + "👹 Sacrifice", + "🔥 Burn", + "🌟 Lifegain", + "🔮 Spellslinger", + "👥 Tokens", + "🎭 Blink", + "⏳ Time Manipulation", + "🌍 Domain", + "💫 Proliferate", + "📜 Saga", + "🎲 Chaos", + "🪄 Auras", + "🔫 Pingers", + + // Themes + "👑 Monarch", + "🚀 Vehicles", + "💉 Infect", + "🩸 Madness", + "🌀 Morph", + + // Card Types + "⚔️ Creature", + "💎 Artifact", + "🌔 Enchantment", + "📖 Sorcery", + "⚡ Instant", + "🌌 Planeswalker", + "🌏 Land", + "🪄 Aura", + + // Kindred Types + "🐉 Kindred", + "🧙 Humans", + "⚔️ Soldiers", + "🛡️ Knights", + "🎻 Bards", + "🧝 Elves", + "🌲 Dryads", + "😇 Angels", + "🎩 Wizards", + "🧛 Vampires", + "🦴 Skeletons", + "💀 Zombies", + "👹 Demons", + "👾 Eldrazi", + "🐉 Dragons", + "🐠 Merfolk", + "🦁 Cats", + "🐺 Wolves", + "🐺 Werewolves", + "🦇 Bats", + "🐀 Rats", + "🦅 Birds", + "🦗 Insects", + "🍄 Fungus", + "🐚 Sea Creatures", + "🐗 Boars", + "🦊 Foxes", + "🦄 Unicorns", + "🐘 Elephants", + "🐻 Bears", + "🦏 Rhinos", + "🦂 Scorpions", + }; + + // Merge knownTags with defaultTags, ensuring no duplicates + QStringList combinedTags = defaultTags + knownTags + activeTags; + combinedTags.removeDuplicates(); + + // Main layout + auto *mainLayout = new QVBoxLayout(this); + + // Filter bar + filterInput_ = new QLineEdit(this); + mainLayout->addWidget(filterInput_); + + connect(filterInput_, &QLineEdit::textChanged, this, &DeckPreviewTagDialog::filterTags); + + // Instruction label + instructionLabel = new QLabel(this); + instructionLabel->setWordWrap(true); + mainLayout->addWidget(instructionLabel); + + // Tag list view + tagListView_ = new QListWidget(this); + mainLayout->addWidget(tagListView_); + + // Populate combined tags + for (const auto &tag : combinedTags) { + auto *item = new QListWidgetItem(tagListView_); + auto *tagWidget = new DeckPreviewTagItemWidget(tag, activeTags.contains(tag), this); + tagListView_->addItem(item); + tagListView_->setItemWidget(item, tagWidget); + + connect(tagWidget->checkBox(), &QCheckBox::toggled, this, &DeckPreviewTagDialog::onCheckboxStateChanged); + } + + // Add tag input layout + auto *addTagLayout = new QHBoxLayout(); + newTagInput_ = new QLineEdit(this); + addTagButton_ = new QPushButton(this); + addTagLayout->addWidget(newTagInput_); + addTagLayout->addWidget(addTagButton_); + mainLayout->addLayout(addTagLayout); + + connect(addTagButton_, &QPushButton::clicked, this, &DeckPreviewTagDialog::addTag); + + // OK and Cancel buttons + auto *buttonLayout = new QHBoxLayout(); + okButton = new QPushButton(this); + cancelButton = new QPushButton(this); + buttonLayout->addStretch(); + buttonLayout->addWidget(okButton); + buttonLayout->addWidget(cancelButton); + mainLayout->addLayout(buttonLayout); + + connect(okButton, &QPushButton::clicked, this, &DeckPreviewTagDialog::accept); + connect(cancelButton, &QPushButton::clicked, this, &DeckPreviewTagDialog::reject); + retranslateUi(); +} + +void DeckPreviewTagDialog::retranslateUi() +{ + setWindowTitle(tr("Deck Tags Manager")); + instructionLabel->setText(tr("Manage your deck tags. Check or uncheck tags as needed, or add new ones:")); + newTagInput_->setPlaceholderText(tr("Add a new tag (e.g., Aggro️)")); + addTagButton_->setText(tr("Add Tag")); + filterInput_->setPlaceholderText(tr("Filter tags...")); + okButton->setText(tr("OK")); + cancelButton->setText(tr("Cancel")); +} + +QStringList DeckPreviewTagDialog::getActiveTags() const +{ + return activeTags_; +} + +void DeckPreviewTagDialog::addTag() +{ + QString newTag = newTagInput_->text().trimmed(); + if (newTag.isEmpty()) { + QMessageBox::warning(this, tr("Invalid Input"), tr("Tag name cannot be empty!")); + return; + } + + // Prevent duplicate tags + for (int i = 0; i < tagListView_->count(); ++i) { + auto *item = tagListView_->item(i); + auto *tagWidget = qobject_cast(tagListView_->itemWidget(item)); + if (tagWidget && tagWidget->checkBox()->text() == newTag) { + QMessageBox::warning(this, tr("Duplicate Tag"), tr("This tag already exists.")); + return; + } + } + + // Add the new tag + auto *item = new QListWidgetItem(tagListView_); + auto *tagWidget = new DeckPreviewTagItemWidget(newTag, true, this); + tagListView_->addItem(item); + tagListView_->setItemWidget(item, tagWidget); + activeTags_.append(newTag); + + connect(tagWidget->checkBox(), &QCheckBox::toggled, this, &DeckPreviewTagDialog::onCheckboxStateChanged); + + // Clear the input field + newTagInput_->clear(); +} + +void DeckPreviewTagDialog::filterTags(const QString &text) +{ + for (int i = 0; i < tagListView_->count(); ++i) { + auto *item = tagListView_->item(i); + auto *tagWidget = qobject_cast(tagListView_->itemWidget(item)); + if (tagWidget) { + bool matches = tagWidget->checkBox()->text().contains(text, Qt::CaseInsensitive); + item->setHidden(!matches); + } + } +} + +void DeckPreviewTagDialog::onCheckboxStateChanged() +{ + activeTags_.clear(); + for (int i = 0; i < tagListView_->count(); ++i) { + auto *item = tagListView_->item(i); + auto *tagWidget = qobject_cast(tagListView_->itemWidget(item)); + if (tagWidget && tagWidget->checkBox()->isChecked()) { + activeTags_.append(tagWidget->checkBox()->text()); + } + } +} diff --git a/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_tag_dialog.h b/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_tag_dialog.h new file mode 100644 index 000000000..8b528d445 --- /dev/null +++ b/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_tag_dialog.h @@ -0,0 +1,39 @@ +#ifndef DECK_PREVIEW_TAG_DIALOG_H +#define DECK_PREVIEW_TAG_DIALOG_H + +#include +#include +#include +#include +#include +#include +#include + +class DeckPreviewTagDialog : public QDialog +{ + Q_OBJECT + +public: + explicit DeckPreviewTagDialog(const QStringList &knownTags, + const QStringList &activeTags, + QWidget *parent = nullptr); + QStringList getActiveTags() const; + void filterTags(const QString &text); + +private slots: + void addTag(); + void onCheckboxStateChanged(); + void retranslateUi(); + +private: + QLabel *instructionLabel; + QListWidget *tagListView_; + QLineEdit *filterInput_; + QLineEdit *newTagInput_; + QPushButton *addTagButton_; + QPushButton *okButton; + QPushButton *cancelButton; + QStringList activeTags_; +}; + +#endif // DECK_PREVIEW_TAG_DIALOG_H diff --git a/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_tag_display_widget.cpp b/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_tag_display_widget.cpp new file mode 100644 index 000000000..324cc7288 --- /dev/null +++ b/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_tag_display_widget.cpp @@ -0,0 +1,110 @@ +#include "deck_preview_tag_display_widget.h" + +#include +#include +#include +#include + +DeckPreviewTagDisplayWidget::DeckPreviewTagDisplayWidget(QWidget *parent, const QString &_tagName) + : QWidget(parent), tagName(_tagName), isSelected(false) +{ + // Create layout + auto *layout = new QHBoxLayout(this); + layout->setContentsMargins(5, 5, 5, 5); + layout->setSpacing(5); + + // Add a stretch spacer for text and close button separation + // layout->addStretch(); // Ensures the close button stays at the far-right side + + // Create close button + // closeButton = new QPushButton("x", this); + // closeButton->setFixedSize(16, 16); // Small square button + // closeButton->setFocusPolicy(Qt::NoFocus); + + // Set font for close button to ensure the "x" appears correctly + // QFont closeButtonFont = closeButton->font(); + // closeButtonFont.setPointSize(10); // Adjust the size to make the "x" clear + // closeButton->setFont(closeButtonFont); + + // layout->addWidget(closeButton); + + // Adjust widget size + setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); + + // Connect close button to the remove signal + // connect(closeButton, &QPushButton::clicked, this, &DeckPreviewTagDisplayWidget::tagClosed); +} + +QSize DeckPreviewTagDisplayWidget::sizeHint() const +{ + // Calculate the size based on the tag name and close button + QFontMetrics fm(font()); + int textWidth = fm.horizontalAdvance(tagName); + int width = textWidth + 50; // Add extra padding + int height = fm.height() + 10; // Height based on font size + padding + + return QSize(width, height); +} + +void DeckPreviewTagDisplayWidget::mousePressEvent(QMouseEvent *event) +{ + if (event->button() == Qt::LeftButton) { + setSelected(!isSelected); + emit tagClicked(); + } + QWidget::mousePressEvent(event); +} + +void DeckPreviewTagDisplayWidget::setSelected(bool selected) +{ + isSelected = selected; + update(); // Trigger a repaint +} + +void DeckPreviewTagDisplayWidget::paintEvent(QPaintEvent *event) +{ + QPainter painter(this); + + // Set background color + QColor backgroundColor = isSelected ? QColor(173, 216, 230) : Qt::white; + painter.setBrush(backgroundColor); + painter.setPen(Qt::NoPen); + + // Draw background + painter.drawRect(rect()); + + // Draw border + QColor borderColor = isSelected ? Qt::blue : Qt::gray; + QPen borderPen(borderColor, isSelected ? 2 : 1); + painter.setPen(borderPen); + painter.drawRect(rect().adjusted(0, 0, -1, -1)); // Adjust for pen width + + // Calculate font size based on widget height + QFont font = painter.font(); + int fontSize = std::max(10, height() / 2); // Ensure a minimum font size of 10 + font.setPointSize(fontSize); + painter.setFont(font); + + // Calculate text rect to avoid overlap with the close button + // int closeButtonWidth = closeButton->width(); + int margin = 10; // Left and right margins + QRect textRect(margin, 0, width() - margin * 2, height()); + + // Draw the text with a black border for better legibility + painter.setPen(Qt::black); + + // Draw text border by offsetting + for (int dx = -1; dx <= 1; ++dx) { + for (int dy = -1; dy <= 1; ++dy) { + if (dx != 0 || dy != 0) { + painter.drawText(textRect.translated(dx, dy), Qt::AlignLeft | Qt::AlignVCenter, tagName); + } + } + } + + // Draw the actual text + painter.setPen(Qt::white); + painter.drawText(textRect, Qt::AlignLeft | Qt::AlignVCenter, tagName); + + QWidget::paintEvent(event); +} diff --git a/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_tag_display_widget.h b/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_tag_display_widget.h new file mode 100644 index 000000000..10cc39baf --- /dev/null +++ b/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_tag_display_widget.h @@ -0,0 +1,67 @@ +#ifndef DECK_PREVIEW_TAG_DISPLAY_WIDGET_H +#define DECK_PREVIEW_TAG_DISPLAY_WIDGET_H + +#include +#include +#include +#include + +class DeckPreviewTagDisplayWidget : public QWidget +{ + Q_OBJECT + +public: + /** + * @brief Constructor for DeckPreviewTagDisplayWidget. + * @param parent The parent widget. + * @param tagName The name of the tag to display. + */ + explicit DeckPreviewTagDisplayWidget(QWidget *parent = nullptr, const QString &tagName = ""); + QSize sizeHint() const override; + QString getTagName() const + { + return tagName; + } + bool getSelected() const + { + return isSelected; + } + + /** + * @brief Sets the selected state of the tag. + * @param selected True if the tag is selected, false otherwise. + */ + void setSelected(bool selected); + +signals: + /** + * @brief Emitted when the tag is clicked. + */ + void tagClicked(); + + /** + * @brief Emitted when the close button is clicked. + */ + void tagClosed(); + +protected: + /** + * @brief Custom paint event for drawing the widget. + * @param event The paint event. + */ + void paintEvent(QPaintEvent *event) override; + + /** + * @brief Custom mouse press event handler. + * @param event The mouse event. + */ + void mousePressEvent(QMouseEvent *event) override; + +private: + QLabel *tagLabel; ///< Label for displaying the tag name. + QPushButton *closeButton; ///< Button to close/remove the tag. + QString tagName; ///< The name of the tag. + bool isSelected; ///< Indicates whether the tag is selected. +}; + +#endif // DECK_PREVIEW_TAG_DISPLAY_WIDGET_H diff --git a/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_tag_item_widget.cpp b/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_tag_item_widget.cpp new file mode 100644 index 000000000..d9ea9fc30 --- /dev/null +++ b/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_tag_item_widget.cpp @@ -0,0 +1,20 @@ +#include "deck_preview_tag_item_widget.h" + +DeckPreviewTagItemWidget::DeckPreviewTagItemWidget(const QString &tagName, bool isChecked, QWidget *parent) + : QWidget(parent), checkBox_(new QCheckBox(this)) +{ + auto *layout = new QHBoxLayout(this); + layout->setContentsMargins(0, 0, 0, 0); + layout->setSpacing(5); + + checkBox_->setText(tagName); // Set the tag name as the checkbox label + checkBox_->setChecked(isChecked); // Set the initial state of the checkbox + + layout->addWidget(checkBox_); // Add the checkbox to the layout + setLayout(layout); // Set the layout of this widget +} + +QCheckBox *DeckPreviewTagItemWidget::checkBox() const +{ + return checkBox_; // Return the checkbox widget +} diff --git a/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_tag_item_widget.h b/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_tag_item_widget.h new file mode 100644 index 000000000..60cd8ba44 --- /dev/null +++ b/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_tag_item_widget.h @@ -0,0 +1,22 @@ +#ifndef DECK_PREVIEW_TAG_ITEM_WIDGET_H +#define DECK_PREVIEW_TAG_ITEM_WIDGET_H + +#include +#include +#include + +class DeckPreviewTagItemWidget : public QWidget +{ + Q_OBJECT +public: + // Constructor: Initializes the tag item widget with a tag name and initial checkbox state + DeckPreviewTagItemWidget(const QString &tagName, bool isChecked, QWidget *parent = nullptr); + + // Accessor for the checkbox widget + QCheckBox *checkBox() const; + +private: + QCheckBox *checkBox_; // Checkbox to represent the tag's state +}; + +#endif // DECK_PREVIEW_TAG_ITEM_WIDGET_H diff --git a/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_widget.cpp b/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_widget.cpp new file mode 100644 index 000000000..35f434bdf --- /dev/null +++ b/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_widget.cpp @@ -0,0 +1,406 @@ +#include "deck_preview_widget.h" + +#include "../../../../../game/cards/card_database_manager.h" +#include "../../../../../settings/cache_settings.h" +#include "../../cards/additional_info/color_identity_widget.h" +#include "../../cards/deck_preview_card_picture_widget.h" +#include "deck_preview_deck_tags_display_widget.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +DeckPreviewWidget::DeckPreviewWidget(QWidget *_parent, + VisualDeckStorageWidget *_visualDeckStorageWidget, + const QString &_filePath) + : QWidget(_parent), visualDeckStorageWidget(_visualDeckStorageWidget), filePath(_filePath), + colorIdentityWidget(nullptr), deckTagsDisplayWidget(nullptr) +{ + layout = new QVBoxLayout(this); + setLayout(layout); + + deckLoader = new DeckLoader(); + deckLoader->setParent(this); + connect(deckLoader, &DeckLoader::loadFinished, this, &DeckPreviewWidget::initializeUi); + connect(deckLoader, &DeckLoader::loadFinished, visualDeckStorageWidget->tagFilterWidget, + &VisualDeckStorageTagFilterWidget::refreshTags); + deckLoader->loadFromFileAsync(filePath, DeckLoader::getFormatFromName(filePath), false); + + bannerCardDisplayWidget = new DeckPreviewCardPictureWidget(this); + + connect(bannerCardDisplayWidget, &DeckPreviewCardPictureWidget::imageClicked, this, + &DeckPreviewWidget::imageClickedEvent); + connect(bannerCardDisplayWidget, &DeckPreviewCardPictureWidget::imageDoubleClicked, this, + &DeckPreviewWidget::imageDoubleClickedEvent); + + connect(&SettingsCache::instance(), &SettingsCache::visualDeckStorageShowTagsOnDeckPreviewsChanged, this, + &DeckPreviewWidget::updateTagsVisibility); + connect(&SettingsCache::instance(), &SettingsCache::visualDeckStorageShowBannerCardComboBoxChanged, this, + &DeckPreviewWidget::updateBannerCardComboBoxVisibility); + + layout->addWidget(bannerCardDisplayWidget); +} + +void DeckPreviewWidget::retranslateUi() +{ + bannerCardLabel->setText(tr("Banner Card")); +} + +void DeckPreviewWidget::resizeEvent(QResizeEvent *event) +{ + QWidget::resizeEvent(event); + if (bannerCardDisplayWidget == nullptr) { + return; + } + QList widgets = findChildren(); + for (QWidget *widget : widgets) { + widget->setMaximumWidth(bannerCardDisplayWidget->width()); + } +} + +void DeckPreviewWidget::initializeUi(const bool deckLoadSuccess) +{ + if (!deckLoadSuccess) { + return; + } + auto bannerCard = deckLoader->getBannerCard().first.isEmpty() + ? CardInfoPtr() + : CardDatabaseManager::getInstance()->getCardByNameAndProviderId( + deckLoader->getBannerCard().first, deckLoader->getBannerCard().second); + + bannerCardDisplayWidget->setCard(bannerCard); + bannerCardDisplayWidget->setFontSize(24); + refreshBannerCardText(); + setFilePath(deckLoader->getLastFileName()); + + colorIdentityWidget = new ColorIdentityWidget(this, getColorIdentity()); + deckTagsDisplayWidget = new DeckPreviewDeckTagsDisplayWidget(this, deckLoader); + + bannerCardLabel = new QLabel(this); + bannerCardLabel->setObjectName("bannerCardLabel"); + bannerCardComboBox = new QComboBox(this); + bannerCardComboBox->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); + bannerCardComboBox->setObjectName("bannerCardComboBox"); + bannerCardComboBox->setCurrentText(deckLoader->getBannerCard().first); + bannerCardComboBox->installEventFilter(new NoScrollFilter()); + connect(bannerCardComboBox, QOverload::of(&QComboBox::currentIndexChanged), this, + &DeckPreviewWidget::setBannerCard); + + updateBannerCardComboBox(); + updateBannerCardComboBoxVisibility(SettingsCache::instance().getVisualDeckStorageShowBannerCardComboBox()); + updateTagsVisibility(SettingsCache::instance().getVisualDeckStorageShowTagsOnDeckPreviews()); + + layout->addWidget(colorIdentityWidget); + layout->addWidget(deckTagsDisplayWidget); + layout->addWidget(bannerCardLabel); + layout->addWidget(bannerCardComboBox); + + retranslateUi(); +} + +void DeckPreviewWidget::updateVisibility() +{ + setHidden(!checkVisibility()); +} + +bool DeckPreviewWidget::checkVisibility() const +{ + if (filteredBySearch || filteredByColor || filteredByTags) { + return false; + } + return true; +} + +void DeckPreviewWidget::updateBannerCardComboBoxVisibility(bool visible) +{ + if (bannerCardComboBox == nullptr) { + return; + } + + if (visible) { + bannerCardComboBox->setVisible(true); + bannerCardLabel->setVisible(true); + } else { + bannerCardComboBox->setHidden(true); + bannerCardLabel->setHidden(true); + } +} + +void DeckPreviewWidget::updateTagsVisibility(bool visible) +{ + if (deckTagsDisplayWidget == nullptr) { + return; + } + + if (visible) { + deckTagsDisplayWidget->setVisible(true); + } else { + deckTagsDisplayWidget->setHidden(true); + } +} + +QString DeckPreviewWidget::getColorIdentity() +{ + QStringList cardList = deckLoader->getCardList(); + if (cardList.isEmpty()) { + return {}; + } + + QSet colorSet; // A set to collect unique color symbols (e.g., W, U, B, R, G) + + for (const QString &cardName : cardList) { + CardInfoPtr currentCard = CardDatabaseManager::getInstance()->getCard(cardName); + if (currentCard) { + QString colors = currentCard->getColors(); // Assuming this returns something like "WUB" + for (const QChar &color : colors) { + colorSet.insert(color); + } + } + } + + // Ensure the color identity is in WUBRG order + QString colorIdentity; + const QString wubrgOrder = "WUBRG"; + for (const QChar &color : wubrgOrder) { + if (colorSet.contains(color)) { + colorIdentity.append(color); + } + } + + return colorIdentity; +} + +void DeckPreviewWidget::setFilePath(const QString &_filePath) +{ + filePath = _filePath; +} + +void DeckPreviewWidget::refreshBannerCardText() +{ + bannerCardDisplayWidget->setOverlayText( + deckLoader->getName().isEmpty() ? QFileInfo(deckLoader->getLastFileName()).fileName() : deckLoader->getName()); +} + +void DeckPreviewWidget::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> bannerCardSet; + InnerDecklistNode *listRoot = deckLoader->getRoot(); + for (auto i : *listRoot) { + auto *currentZone = dynamic_cast(i); + for (auto j : *currentZone) { + auto *currentCard = dynamic_cast(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(currentCard->getName(), currentCard->getCardProviderId())); + } + } + } + } + + QList> pairList = bannerCardSet.values(); + + // Sort QList by the first() element of the QPair + std::sort(pairList.begin(), pairList.end(), [](const QPair &a, const QPair &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(deckLoader->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 DeckPreviewWidget::setBannerCard(int /* changedIndex */) +{ + QVariantMap itemData = bannerCardComboBox->itemData(bannerCardComboBox->currentIndex()).toMap(); + deckLoader->setBannerCard(QPair(itemData["name"].toString(), itemData["uuid"].toString())); + deckLoader->saveToFile(filePath, DeckLoader::getFormatFromName(filePath)); + bannerCardDisplayWidget->setCard(CardDatabaseManager::getInstance()->getCardByNameAndProviderId( + itemData["name"].toString(), itemData["uuid"].toString())); +} + +void DeckPreviewWidget::imageClickedEvent(QMouseEvent *event, DeckPreviewCardPictureWidget *instance) +{ + Q_UNUSED(instance); + + if (event && event->button() == Qt::RightButton) { + createRightClickMenu()->popup(QCursor::pos()); + } +} + +void DeckPreviewWidget::imageDoubleClickedEvent(QMouseEvent *event, DeckPreviewCardPictureWidget *instance) +{ + Q_UNUSED(event); + Q_UNUSED(instance); + emit deckLoadRequested(filePath); +} + +QMenu *DeckPreviewWidget::createRightClickMenu() +{ + auto *menu = new QMenu(this); + menu->setAttribute(Qt::WA_DeleteOnClose); + + connect(menu->addAction(tr("Open in deck editor")), &QAction::triggered, this, + [this] { emit openDeckEditor(deckLoader); }); + + connect(menu->addAction(tr("Edit Tags")), &QAction::triggered, deckTagsDisplayWidget, + &DeckPreviewDeckTagsDisplayWidget::openTagEditDlg); + + addSetBannerCardMenu(menu); + + menu->addSeparator(); + + connect(menu->addAction(tr("Rename Deck")), &QAction::triggered, this, &DeckPreviewWidget::actRenameDeck); + + auto saveToClipboardMenu = menu->addMenu(tr("Save Deck to Clipboard")); + + connect(saveToClipboardMenu->addAction(tr("Annotated")), &QAction::triggered, this, + [this] { deckLoader->saveToClipboard(true, true); }); + connect(saveToClipboardMenu->addAction(tr("Annotated (No set info)")), &QAction::triggered, this, + [this] { deckLoader->saveToClipboard(true, false); }); + connect(saveToClipboardMenu->addAction(tr("Not Annotated")), &QAction::triggered, this, + [this] { deckLoader->saveToClipboard(false, true); }); + connect(saveToClipboardMenu->addAction(tr("Not Annotated (No set info)")), &QAction::triggered, this, + [this] { deckLoader->saveToClipboard(false, false); }); + + menu->addSeparator(); + + connect(menu->addAction(tr("Rename File")), &QAction::triggered, this, &DeckPreviewWidget::actRenameFile); + + connect(menu->addAction(tr("Delete File")), &QAction::triggered, this, &DeckPreviewWidget::actDeleteFile); + + return menu; +} + +/** + * Adds the "Set Banner Card" submenu to the given menu. Does nothing if bannerCardComboBox is null. + * @param menu The menu to add the submenu to + */ +void DeckPreviewWidget::addSetBannerCardMenu(QMenu *menu) +{ + if (!bannerCardComboBox) { + return; + } + + auto bannerCardMenu = menu->addMenu(tr("Set Banner Card")); + + for (int i = 0; i < bannerCardComboBox->count(); ++i) { + auto action = bannerCardMenu->addAction(bannerCardComboBox->itemText(i)); + connect(action, &QAction::triggered, this, [this, i] { bannerCardComboBox->setCurrentIndex(i); }); + + // the checkability is purely for visuals + action->setCheckable(true); + action->setChecked(bannerCardComboBox->currentIndex() == i); + } +} + +void DeckPreviewWidget::actRenameDeck() +{ + // read input + const QString oldName = deckLoader->getName(); + + bool ok; + QString newName = QInputDialog::getText(this, "Rename deck", tr("New name:"), QLineEdit::Normal, oldName, &ok); + if (!ok || oldName == newName) { + return; + } + + // write change + deckLoader->setName(newName); + deckLoader->saveToFile(filePath, DeckLoader::getFormatFromName(filePath)); + + // update VDS + refreshBannerCardText(); +} + +void DeckPreviewWidget::actRenameFile() +{ + // read input + const auto info = QFileInfo(filePath); + const QString oldName = info.baseName(); + + bool ok; + QString newName = QInputDialog::getText(this, "Rename file", tr("New name:"), QLineEdit::Normal, oldName, &ok); + if (!ok || newName.isEmpty() || oldName == newName) { + return; + } + + QString newFileName = newName; + if (!info.suffix().isEmpty()) { + newFileName += "." + info.suffix(); + } + + // write change + const QString newFilePath = QFileInfo(info.dir(), newFileName).filePath(); + if (!QFile::rename(info.filePath(), newFilePath)) { + QMessageBox::critical(this, tr("Error"), tr("Rename failed")); + return; + } + + deckLoader->setLastFileName(newFilePath); + + // update VDS + setFilePath(newFilePath); + refreshBannerCardText(); +} + +void DeckPreviewWidget::actDeleteFile() +{ + // read input + auto res = QMessageBox::warning(this, tr("Delete file"), tr("Are you sure you want to delete the selected file?"), + QMessageBox::Yes | QMessageBox::No); + if (res != QMessageBox::Yes) { + return; + } + + // write change + if (!QFile::remove(QFileInfo(filePath).filePath())) { + QMessageBox::critical(this, tr("Error"), tr("Delete failed")); + return; + } + + // update VDS + this->deleteLater(); +} diff --git a/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_widget.h b/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_widget.h new file mode 100644 index 000000000..a9df635e5 --- /dev/null +++ b/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_widget.h @@ -0,0 +1,83 @@ +#ifndef DECK_PREVIEW_WIDGET_H +#define DECK_PREVIEW_WIDGET_H + +#include "../../../../../deck/deck_loader.h" +#include "../../cards/additional_info/color_identity_widget.h" +#include "../../cards/deck_preview_card_picture_widget.h" +#include "../visual_deck_storage_widget.h" +#include "deck_preview_deck_tags_display_widget.h" + +#include +#include +#include +#include + +class QMenu; +class VisualDeckStorageWidget; +class DeckPreviewDeckTagsDisplayWidget; + +class DeckPreviewWidget final : public QWidget +{ + Q_OBJECT +public: + explicit DeckPreviewWidget(QWidget *_parent, + VisualDeckStorageWidget *_visualDeckStorageWidget, + const QString &_filePath); + void retranslateUi(); + QString getColorIdentity(); + + VisualDeckStorageWidget *visualDeckStorageWidget; + QVBoxLayout *layout; + QString filePath; + DeckLoader *deckLoader; + DeckPreviewCardPictureWidget *bannerCardDisplayWidget = nullptr; + ColorIdentityWidget *colorIdentityWidget = nullptr; + DeckPreviewDeckTagsDisplayWidget *deckTagsDisplayWidget = nullptr; + QLabel *bannerCardLabel = nullptr; + QComboBox *bannerCardComboBox = nullptr; + bool filteredBySearch = false; + bool filteredByColor = false; + bool filteredByTags = false; + bool checkVisibility() const; + +signals: + void deckLoadRequested(const QString &filePath); + void openDeckEditor(const DeckLoader *deck); + +public slots: + void setFilePath(const QString &filePath); + void refreshBannerCardText(); + void updateBannerCardComboBox(); + void setBannerCard(int); + void imageClickedEvent(QMouseEvent *event, DeckPreviewCardPictureWidget *instance); + void imageDoubleClickedEvent(QMouseEvent *event, DeckPreviewCardPictureWidget *instance); + void initializeUi(bool deckLoadSuccess); + void updateVisibility(); + void updateBannerCardComboBoxVisibility(bool visible); + void updateTagsVisibility(bool visible); + void resizeEvent(QResizeEvent *event) override; + +private: + QMenu *createRightClickMenu(); + void addSetBannerCardMenu(QMenu *menu); + +private slots: + void actRenameDeck(); + void actRenameFile(); + void actDeleteFile(); +}; + +class NoScrollFilter : public QObject +{ + Q_OBJECT +protected: + bool eventFilter(QObject *obj, QEvent *event) override + { + if (event->type() == QEvent::Wheel) { + return true; // Blocks the event + } + return QObject::eventFilter(obj, event); + } +}; + +#endif // DECK_PREVIEW_WIDGET_H diff --git a/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_folder_display_widget.cpp b/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_folder_display_widget.cpp new file mode 100644 index 000000000..319244f85 --- /dev/null +++ b/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_folder_display_widget.cpp @@ -0,0 +1,224 @@ +#include "visual_deck_storage_folder_display_widget.h" + +#include "../../../../settings/cache_settings.h" +#include "deck_preview/deck_preview_widget.h" + +#include +#include + +VisualDeckStorageFolderDisplayWidget::VisualDeckStorageFolderDisplayWidget( + QWidget *parent, + VisualDeckStorageWidget *_visualDeckStorageWidget, + QString _filePath, + bool canBeHidden, + bool _showFolders) + : QWidget(parent), showFolders(_showFolders), visualDeckStorageWidget(_visualDeckStorageWidget), filePath(_filePath) +{ + layout = new QVBoxLayout(this); + setLayout(layout); + + header = new BannerWidget(this, ""); + header->setClickable(canBeHidden); + header->setHidden(!showFolders); + layout->addWidget(header); + + container = new QWidget(this); + containerLayout = new QVBoxLayout(container); + container->setLayout(containerLayout); + + header->setBuddy(container); + + layout->addWidget(container); + + flowWidget = new FlowWidget(this, Qt::Horizontal, Qt::ScrollBarAlwaysOff, Qt::ScrollBarAlwaysOff); + containerLayout->addWidget(flowWidget); + + createWidgetsForFiles(); + createWidgetsForFolders(); + + refreshUi(); +} + +void VisualDeckStorageFolderDisplayWidget::refreshUi() +{ + QString bannerText = tr("Deck Storage"); + QString deckPath = SettingsCache::instance().getDeckPath(); + if (filePath != deckPath) { + QString relativePath = filePath; + + if (filePath.startsWith(deckPath)) { + relativePath = filePath.mid(deckPath.length()); // Remove the deckPath prefix + if (relativePath.startsWith('/')) { + relativePath.remove(0, 1); // Remove leading '/' if it exists + } + } + + bannerText = relativePath; + } + header->setText(bannerText); +} + +/** + * Gets all files in the directory that have an accepted decklist file extension + * + * @param filePath The directory to search through + * @param recursive Whether to search through subdirectories + */ +static QStringList getAllFiles(const QString &filePath, bool recursive) +{ + QStringList allFiles; + + // QDirIterator with QDir::Files ensures only files are listed (no directories) + auto flags = + recursive ? QDirIterator::Subdirectories | QDirIterator::FollowSymlinks : QDirIterator::NoIteratorFlags; + QDirIterator it(filePath, DeckLoader::ACCEPTED_FILE_EXTENSIONS, QDir::Files, flags); + + while (it.hasNext()) { + allFiles << it.next(); // Add each file path to the list + } + + return allFiles; +} + +void VisualDeckStorageFolderDisplayWidget::createWidgetsForFiles() +{ + QList allDecks; + for (const QString &file : getAllFiles(filePath, !showFolders)) { + auto *display = new DeckPreviewWidget(flowWidget, visualDeckStorageWidget, file); + + connect(display, &DeckPreviewWidget::deckLoadRequested, visualDeckStorageWidget, + &VisualDeckStorageWidget::deckLoadRequested); + connect(display, &DeckPreviewWidget::openDeckEditor, visualDeckStorageWidget, + &VisualDeckStorageWidget::openDeckEditor); + connect(visualDeckStorageWidget->cardSizeWidget->getSlider(), &QSlider::valueChanged, + display->bannerCardDisplayWidget, &CardInfoPictureWidget::setScaleFactor); + display->bannerCardDisplayWidget->setScaleFactor(visualDeckStorageWidget->cardSizeWidget->getSlider()->value()); + allDecks.append(display); + } + + flowWidget->clearLayout(); // Clear existing widgets in the flow layout + + for (DeckPreviewWidget *deck : allDecks) { + flowWidget->addWidget(deck); + } +} + +/** + * Updates the visibility of this folder and all its DeckPreviewWidgets + * + * @param recursive Also update the visibility of all subfolders and their DeckPreviewWidgets. + */ +void VisualDeckStorageFolderDisplayWidget::updateVisibility(bool recursive) +{ + bool atLeastOneWidgetVisible = checkVisibility(); + if (atLeastOneWidgetVisible) { + setVisible(true); + for (DeckPreviewWidget *display : flowWidget->findChildren()) { + display->updateVisibility(); + } + if (recursive) { + for (auto *subFolder : findChildren()) { + subFolder->updateVisibility(false); + } + } + } else { + setVisible(false); + } +} + +bool VisualDeckStorageFolderDisplayWidget::checkVisibility() +{ + bool atLeastOneWidgetVisible = false; + if (flowWidget) { + // Iterate through all DeckPreviewWidgets + for (DeckPreviewWidget *display : flowWidget->findChildren()) { + if (display->checkVisibility()) { + atLeastOneWidgetVisible = true; + } + } + } + for (VisualDeckStorageFolderDisplayWidget *subFolder : findChildren()) { + if (subFolder->checkVisibility()) { + atLeastOneWidgetVisible = true; + } + } + return atLeastOneWidgetVisible; +} + +static QStringList getAllSubFolders(const QString &filePath) +{ + QStringList allFolders; + + // QDirIterator with QDir::Files ensures only files are listed (no directories) + QDirIterator it(filePath, QDir::Dirs | QDir::NoDotAndDotDot); + + while (it.hasNext()) { + allFolders << it.next(); // Add each file path to the list + } + + return allFolders; +} + +void VisualDeckStorageFolderDisplayWidget::createWidgetsForFolders() +{ + if (!showFolders) { + return; + } + + for (const QString &dir : getAllSubFolders(filePath)) { + auto *display = new VisualDeckStorageFolderDisplayWidget(this, visualDeckStorageWidget, dir, true, showFolders); + containerLayout->addWidget(display); + } +} + +void VisualDeckStorageFolderDisplayWidget::updateShowFolders(bool enabled) +{ + showFolders = enabled; + + if (!showFolders) { + flattenFolderStructure(); + } else { + // if setting was switched from disabled to enabled, we assume that there aren't any existing subfolders + createWidgetsForFiles(); + createWidgetsForFolders(); + } + + header->setHidden(!showFolders); +} + +/** + * Steals all DeckPreviewWidgets from this widget's nested subfolders, and deletes those subfolders + */ +void VisualDeckStorageFolderDisplayWidget::flattenFolderStructure() +{ + for (auto *subFolder : findChildren()) { + // steal all DeckPreviewWidgets from the subfolder + for (auto *deck : subFolder->getFlowWidget()->findChildren()) { + flowWidget->addWidget(deck); + } + + // delete the subfolder + subFolder->deleteLater(); + } +} + +QStringList VisualDeckStorageFolderDisplayWidget::gatherAllTagsFromFlowWidget() const +{ + QStringList allTags; + + if (flowWidget) { + // Iterate through all DeckPreviewWidgets + for (DeckPreviewWidget *display : flowWidget->findChildren()) { + // Get tags from each DeckPreviewWidget + QStringList tags = display->deckLoader->getTags(); + + // Add tags to the list while avoiding duplicates + allTags.append(tags); + } + } + + // Remove duplicates by calling 'removeDuplicates' + allTags.removeDuplicates(); + + return allTags; +} \ No newline at end of file diff --git a/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_folder_display_widget.h b/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_folder_display_widget.h new file mode 100644 index 000000000..747bcf8bd --- /dev/null +++ b/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_folder_display_widget.h @@ -0,0 +1,46 @@ +#ifndef VISUAL_DECK_STORAGE_FOLDER_DISPLAY_WIDGET_H +#define VISUAL_DECK_STORAGE_FOLDER_DISPLAY_WIDGET_H + +#include "../general/display/banner_widget.h" +#include "../general/layout_containers/flow_widget.h" +#include "visual_deck_storage_widget.h" + +#include +#include + +class VisualDeckStorageFolderDisplayWidget : public QWidget +{ + Q_OBJECT +public: + VisualDeckStorageFolderDisplayWidget(QWidget *parent, + VisualDeckStorageWidget *_visualDeckStorageWidget, + QString _filePath, + bool canBeHidden, + bool _showFolders); + void refreshUi(); + void createWidgetsForFiles(); + void createWidgetsForFolders(); + void flattenFolderStructure(); + QStringList gatherAllTagsFromFlowWidget() const; + FlowWidget *getFlowWidget() const + { + return flowWidget; + }; + +public slots: + void updateVisibility(bool recursive = true); + bool checkVisibility(); + void updateShowFolders(bool enabled); + +private: + bool showFolders; + QVBoxLayout *layout; + VisualDeckStorageWidget *visualDeckStorageWidget; + QString filePath; + BannerWidget *header; + QWidget *container; + QVBoxLayout *containerLayout; + FlowWidget *flowWidget; +}; + +#endif // VISUAL_DECK_STORAGE_FOLDER_DISPLAY_WIDGET_H diff --git a/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_search_widget.cpp b/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_search_widget.cpp new file mode 100644 index 000000000..9b7f65224 --- /dev/null +++ b/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_search_widget.cpp @@ -0,0 +1,77 @@ +#include "visual_deck_storage_search_widget.h" + +#include "../../../../settings/cache_settings.h" + +/** + * @brief Constructs a PrintingSelectorCardSearchWidget for searching cards by set name or set code. + * + * This widget provides a search bar that allows users to search for cards by either their set name + * or set code. It uses a debounced timer to trigger the search action after the user stops typing. + * + * @param parent The parent PrintingSelector widget that will handle the search results. + */ +VisualDeckStorageSearchWidget::VisualDeckStorageSearchWidget(VisualDeckStorageWidget *parent) : parent(parent) +{ + layout = new QHBoxLayout(this); + layout->setContentsMargins(0, 0, 0, 0); + setLayout(layout); + + searchBar = new QLineEdit(this); + searchBar->setPlaceholderText(tr("Search by filename")); + layout->addWidget(searchBar); + + // Add a debounce timer for the search bar to limit frequent updates + searchDebounceTimer = new QTimer(this); + searchDebounceTimer->setSingleShot(true); + connect(searchBar, &QLineEdit::textChanged, this, [this]() { + searchDebounceTimer->start(300); // 300ms debounce + }); + + connect(searchDebounceTimer, &QTimer::timeout, parent, &VisualDeckStorageWidget::updateSearchFilter); +} + +/** + * @brief Retrieves the current text in the search bar. + * + * @return The text entered by the user in the search bar. + */ +QString VisualDeckStorageSearchWidget::getSearchText() +{ + return searchBar->text(); +} + +/** + * Gets the filename used for the search. + * + * if includeFolderName is true, then this returns the relative filepath starting from the deck folder. + * If the file isn't in the deck folder, or includeFolderName is false, then this will just return the filename. + * + * @param filePath The filePath to convert into a search name + */ +static QString getFileSearchName(const QString &filePath, bool includeFolderName) +{ + QString deckPath = SettingsCache::instance().getDeckPath(); + if (includeFolderName && filePath.startsWith(deckPath)) { + return filePath.mid(deckPath.length()).toLower(); + } + + QFileInfo fileInfo(filePath); + QString fileName = fileInfo.fileName().toLower(); + return fileName; +} + +void VisualDeckStorageSearchWidget::filterWidgets(QList widgets, + const QString &searchText, + bool includeFolderName) +{ + if (searchText.isEmpty() || searchText.isNull()) { + for (auto widget : widgets) { + widget->filteredBySearch = false; + } + } + + for (auto file : widgets) { + QString fileSearchName = getFileSearchName(file->filePath, includeFolderName); + file->filteredBySearch = !fileSearchName.contains(searchText.toLower()); + } +} diff --git a/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_search_widget.h b/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_search_widget.h new file mode 100644 index 000000000..444ea9a27 --- /dev/null +++ b/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_search_widget.h @@ -0,0 +1,28 @@ +#ifndef VISUAL_DECK_STORAGE_SEARCH_WIDGET_H +#define VISUAL_DECK_STORAGE_SEARCH_WIDGET_H + +#include "visual_deck_storage_widget.h" + +#include +#include +#include +#include + +class VisualDeckStorageWidget; +class VisualDeckStorageSearchWidget : public QWidget +{ + Q_OBJECT + +public: + explicit VisualDeckStorageSearchWidget(VisualDeckStorageWidget *parent); + QString getSearchText(); + void filterWidgets(QList widgets, const QString &searchText, bool includeFolderName); + +private: + QHBoxLayout *layout; + VisualDeckStorageWidget *parent; + QLineEdit *searchBar; + QTimer *searchDebounceTimer; +}; + +#endif // VISUAL_DECK_STORAGE_SEARCH_WIDGET_H diff --git a/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_sort_widget.cpp b/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_sort_widget.cpp new file mode 100644 index 000000000..8b5356c08 --- /dev/null +++ b/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_sort_widget.cpp @@ -0,0 +1,112 @@ +#include "visual_deck_storage_sort_widget.h" + +#include "../../../../settings/cache_settings.h" + +/** + * @brief Constructs a PrintingSelectorCardSortWidget for searching cards by set name or set code. + * + * This widget provides a search bar that allows users to search for cards by either their set name + * or set code. It uses a debounced timer to trigger the search action after the user stops typing. + * + * @param parent The parent PrintingSelector widget that will handle the search results. + */ +VisualDeckStorageSortWidget::VisualDeckStorageSortWidget(VisualDeckStorageWidget *parent) + : parent(parent), sortOrder(Alphabetical) +{ + layout = new QHBoxLayout(this); + layout->setContentsMargins(0, 0, 0, 0); + setLayout(layout); + + // Initialize the ComboBox + sortComboBox = new QComboBox(this); + layout->addWidget(sortComboBox); + + // Need to retranslateUi first so that the sortComboBox actually has entries and doesn't get its currentIndex + // immediately capped to 0 when we try to set it + retranslateUi(); + + // Set the current sort order + sortComboBox->setCurrentIndex(SettingsCache::instance().getVisualDeckStorageSortingOrder()); + sortOrder = static_cast(sortComboBox->currentIndex()); + + // Connect sorting change signal to refresh the file list + connect(sortComboBox, QOverload::of(&QComboBox::currentIndexChanged), this, + &VisualDeckStorageSortWidget::updateSortOrder); + connect(this, &VisualDeckStorageSortWidget::sortOrderChanged, parent, &VisualDeckStorageWidget::updateSortOrder); +} + +void VisualDeckStorageSortWidget::retranslateUi() +{ + // Block signals to avoid triggering unnecessary updates while changing text + sortComboBox->blockSignals(true); + + int oldIndex = sortComboBox->currentIndex(); + + // Clear and repopulate the ComboBox with translated items + sortComboBox->clear(); + sortComboBox->addItem(tr("Sort Alphabetically (Deck Name)"), ByName); + sortComboBox->addItem(tr("Sort Alphabetically (Filename)"), Alphabetical); + sortComboBox->addItem(tr("Sort by Last Modified"), ByLastModified); + sortComboBox->addItem(tr("Sort by Last Loaded"), ByLastLoaded); + + // Restore the current index + sortComboBox->setCurrentIndex(oldIndex); + + // Re-enable signals + sortComboBox->blockSignals(false); +} + +void VisualDeckStorageSortWidget::updateSortOrder() +{ + sortOrder = static_cast(sortComboBox->currentIndex()); + SettingsCache::instance().setVisualDeckStorageSortingOrder(sortComboBox->currentIndex()); + emit sortOrderChanged(); +} + +void VisualDeckStorageSortWidget::sortFolder(VisualDeckStorageFolderDisplayWidget *folderWidget) +{ + auto children = + folderWidget->getFlowWidget()->findChildren(QString(), Qt::FindChildOption::FindDirectChildrenOnly); + for (auto widget : children) { + auto deckPreviewWidgets = + widget->findChildren(QString(), Qt::FindChildOption::FindDirectChildrenOnly); + auto newOrder = filterFiles(deckPreviewWidgets); + for (DeckPreviewWidget *previewWidget : newOrder) { + folderWidget->getFlowWidget()->removeWidget(previewWidget); + } + for (DeckPreviewWidget *previewWidget : newOrder) { + folderWidget->getFlowWidget()->addWidget(previewWidget); + } + } +} + +QList VisualDeckStorageSortWidget::filterFiles(QList widgets) +{ + // Sort the widgets list based on the current sort order + std::sort(widgets.begin(), widgets.end(), [this](DeckPreviewWidget *widget1, DeckPreviewWidget *widget2) { + if (!widget1 || !widget2) { + return false; // Handle null pointers gracefully + } + + QFileInfo info1(widget1->filePath); + QFileInfo info2(widget2->filePath); + + switch (sortOrder) { + case ByName: + return widget1->deckLoader->getName() < widget2->deckLoader->getName(); + case Alphabetical: + return QString::localeAwareCompare(info1.fileName(), info2.fileName()) <= 0; + case ByLastModified: + return info1.lastModified() > info2.lastModified(); + case ByLastLoaded: { + QDateTime time1 = QDateTime::fromString(widget1->deckLoader->getLastLoadedTimestamp()); + QDateTime time2 = QDateTime::fromString(widget2->deckLoader->getLastLoadedTimestamp()); + return time1 > time2; + } + } + + return false; // Default case, no sorting applied + }); + + return widgets; +} diff --git a/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_sort_widget.h b/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_sort_widget.h new file mode 100644 index 000000000..1aac55d2d --- /dev/null +++ b/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_sort_widget.h @@ -0,0 +1,41 @@ +#ifndef VISUAL_DECK_STORAGE_SORT_WIDGET_H +#define VISUAL_DECK_STORAGE_SORT_WIDGET_H + +#include "visual_deck_storage_widget.h" + +#include +#include +#include + +class VisualDeckStorageWidget; +class VisualDeckStorageFolderDisplayWidget; +class VisualDeckStorageSortWidget : public QWidget +{ + Q_OBJECT + +public: + explicit VisualDeckStorageSortWidget(VisualDeckStorageWidget *parent); + void retranslateUi(); + void updateSortOrder(); + void sortFolder(VisualDeckStorageFolderDisplayWidget *folderWidget); + QString getSearchText(); + QList filterFiles(QList widgets); + +signals: + void sortOrderChanged(); + +private: + enum SortOrder + { + ByName, + Alphabetical, + ByLastModified, + ByLastLoaded, + }; + QHBoxLayout *layout; + VisualDeckStorageWidget *parent; + SortOrder sortOrder; // Current sorting option + QComboBox *sortComboBox; +}; + +#endif // VISUAL_DECK_STORAGE_SORT_WIDGET_H diff --git a/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_tag_filter_widget.cpp b/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_tag_filter_widget.cpp new file mode 100644 index 000000000..983ab13bd --- /dev/null +++ b/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_tag_filter_widget.cpp @@ -0,0 +1,164 @@ +#include "visual_deck_storage_tag_filter_widget.h" + +#include "../general/layout_containers/flow_widget.h" +#include "deck_preview/deck_preview_tag_addition_widget.h" +#include "deck_preview/deck_preview_tag_display_widget.h" +#include "deck_preview/deck_preview_widget.h" + +#include +#include + +VisualDeckStorageTagFilterWidget::VisualDeckStorageTagFilterWidget(VisualDeckStorageWidget *_parent) + : QWidget(_parent), parent(_parent) +{ + + setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); + + // Create layout + auto *layout = new QHBoxLayout(this); + layout->setContentsMargins(5, 0, 5, 0); + + setFixedHeight(100); + + auto *flowWidget = new FlowWidget(this, Qt::Horizontal, Qt::ScrollBarAlwaysOff, Qt::ScrollBarAsNeeded); + + layout->addWidget(flowWidget); +} + +void VisualDeckStorageTagFilterWidget::showEvent(QShowEvent *event) +{ + QWidget::showEvent(event); + refreshTags(); +} + +void VisualDeckStorageTagFilterWidget::filterDecksBySelectedTags(const QList &deckPreviews) const +{ + // Collect selected tags from DeckPreviewTagDisplayWidget + QStringList selectedTags; + for (DeckPreviewTagDisplayWidget *tagWidget : findChildren()) { + if (tagWidget->getSelected()) { + selectedTags.append(tagWidget->getTagName()); + } + } + + // If no tags are selected, set all decks as visible + if (selectedTags.isEmpty()) { + for (DeckPreviewWidget *deckPreview : deckPreviews) { + deckPreview->filteredByTags = false; + } + return; + } + + // Filter DeckPreviewWidgets that contain all of the selected tags + QList filteredDecks; + for (DeckPreviewWidget *deckPreview : deckPreviews) { + QStringList deckTags = deckPreview->deckLoader->getTags(); + + // Check if all selectedTags are in deckTags + bool allTagsPresent = std::all_of(selectedTags.begin(), selectedTags.end(), + [&deckTags](const QString &tag) { return deckTags.contains(tag); }); + + deckPreview->filteredByTags = !allTagsPresent; + } +} + +void VisualDeckStorageTagFilterWidget::refreshTags() +{ + QStringList allTags = gatherAllTags(); + removeTagsNotInList(gatherAllTags()); + addTagsIfNotPresent(gatherAllTags()); + sortTags(); +} + +void VisualDeckStorageTagFilterWidget::removeTagsNotInList(const QStringList &tags) +{ + // Iterate through all DeckPreviewTagDisplayWidgets + for (DeckPreviewTagDisplayWidget *tagWidget : findChildren()) { + // If the tag is not in the provided tags list, remove the widget + if (!tags.contains(tagWidget->getTagName())) { + auto *flowWidget = findChild(); + flowWidget->removeWidget(tagWidget); + tagWidget->deleteLater(); // Safely delete the widget + } + } +} + +void VisualDeckStorageTagFilterWidget::addTagsIfNotPresent(const QStringList &tags) +{ + for (const QString &tag : tags) { + addTagIfNotPresent(tag); + } +} + +void VisualDeckStorageTagFilterWidget::addTagIfNotPresent(const QString &tag) +{ + // Check if the tag already exists in the flow widget + bool tagExists = false; + for (DeckPreviewTagDisplayWidget *tagWidget : findChildren()) { + if (tagWidget->getTagName() == tag) { + tagExists = true; + break; + } + } + + // If the tag doesn't exist, add a new DeckPreviewTagDisplayWidget + if (!tagExists) { + auto *newTagWidget = new DeckPreviewTagDisplayWidget(this, tag); + connect(newTagWidget, &DeckPreviewTagDisplayWidget::tagClicked, parent, + &VisualDeckStorageWidget::updateTagFilter); + connect(newTagWidget, &DeckPreviewTagDisplayWidget::tagClicked, this, + &VisualDeckStorageTagFilterWidget::refreshTags); + auto *flowWidget = findChild(); + flowWidget->addWidget(newTagWidget); + } +} + +void VisualDeckStorageTagFilterWidget::sortTags() +{ + auto *flowWidget = findChild(); + if (!flowWidget) + return; + + // Get all tag widgets + QList tagWidgets = findChildren(); + + // Sort widgets by tag name + std::sort(tagWidgets.begin(), tagWidgets.end(), [](DeckPreviewTagDisplayWidget *a, DeckPreviewTagDisplayWidget *b) { + return a->getTagName().toLower() < b->getTagName().toLower(); + }); + + // Clear and re-add widgets in sorted order + for (DeckPreviewTagDisplayWidget *tagWidget : tagWidgets) { + flowWidget->removeWidget(tagWidget); + } + for (DeckPreviewTagDisplayWidget *tagWidget : tagWidgets) { + flowWidget->addWidget(tagWidget); + } +} + +QStringList VisualDeckStorageTagFilterWidget::gatherAllTags() +{ + QStringList allTags; + QList deckWidgets = parent->findChildren(); + + for (DeckPreviewWidget *widget : deckWidgets) { + if (widget->checkVisibility()) { + allTags << widget->deckLoader->getTags(); + } + } + return allTags; +} + +QStringList VisualDeckStorageTagFilterWidget::getAllKnownTags() +{ + QStringList allTags; + + for (DeckPreviewTagDisplayWidget *tagWidget : findChildren()) { + allTags.append(tagWidget->getTagName()); + } + + // Remove duplicates by calling 'removeDuplicates' + allTags.removeDuplicates(); + + return allTags; +} diff --git a/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_tag_filter_widget.h b/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_tag_filter_widget.h new file mode 100644 index 000000000..a8726790e --- /dev/null +++ b/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_tag_filter_widget.h @@ -0,0 +1,29 @@ +#ifndef VISUAL_DECK_STORAGE_TAG_FILTER_WIDGET_H +#define VISUAL_DECK_STORAGE_TAG_FILTER_WIDGET_H + +#include "visual_deck_storage_widget.h" + +#include + +class VisualDeckStorageWidget; +class VisualDeckStorageTagFilterWidget : public QWidget +{ + Q_OBJECT + +public: + explicit VisualDeckStorageTagFilterWidget(VisualDeckStorageWidget *_parent); + QStringList gatherAllTags(); + void filterDecksBySelectedTags(const QList &deckPreviews) const; + void removeTagsNotInList(const QStringList &tags); + void addTagsIfNotPresent(const QStringList &tags); + void addTagIfNotPresent(const QString &tag); + void sortTags(); + QStringList getAllKnownTags(); + VisualDeckStorageWidget *parent; + +public slots: + void refreshTags(); + void showEvent(QShowEvent *event) override; +}; + +#endif // VISUAL_DECK_STORAGE_TAG_FILTER_WIDGET_H diff --git a/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_widget.cpp b/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_widget.cpp new file mode 100644 index 000000000..a1c75644c --- /dev/null +++ b/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_widget.cpp @@ -0,0 +1,269 @@ +#include "visual_deck_storage_widget.h" + +#include "../../../../game/cards/card_database_manager.h" +#include "../../../../settings/cache_settings.h" +#include "../quick_settings/settings_button_widget.h" +#include "deck_preview/deck_preview_widget.h" +#include "visual_deck_storage_folder_display_widget.h" +#include "visual_deck_storage_search_widget.h" +#include "visual_deck_storage_sort_widget.h" +#include "visual_deck_storage_tag_filter_widget.h" + +#include +#include +#include +#include +#include + +VisualDeckStorageWidget::VisualDeckStorageWidget(QWidget *parent) : QWidget(parent), folderWidget(nullptr) +{ + deckListModel = new DeckListModel(this); + deckListModel->setObjectName("visualDeckModel"); + + layout = new QVBoxLayout(this); + layout->setSpacing(0); + layout->setContentsMargins(9, 0, 9, 5); + setLayout(layout); + + // search bar row + searchAndSortContainer = new QWidget(this); + searchAndSortLayout = new QHBoxLayout(searchAndSortContainer); + searchAndSortLayout->setSpacing(3); + searchAndSortLayout->setContentsMargins(9, 0, 9, 0); + searchAndSortContainer->setLayout(searchAndSortLayout); + + deckPreviewColorIdentityFilterWidget = new DeckPreviewColorIdentityFilterWidget(this); + sortWidget = new VisualDeckStorageSortWidget(this); + searchWidget = new VisualDeckStorageSearchWidget(this); + + refreshButton = new QToolButton(this); + refreshButton->setIcon(QPixmap("theme:icons/reload")); + refreshButton->setFixedSize(32, 32); + connect(refreshButton, &QPushButton::clicked, this, &VisualDeckStorageWidget::refreshIfPossible); + + // quick settings menu + showFoldersCheckBox = new QCheckBox(this); + showFoldersCheckBox->setChecked(SettingsCache::instance().getVisualDeckStorageShowFolders()); + connect(showFoldersCheckBox, &QCheckBox::QT_STATE_CHANGED, this, &VisualDeckStorageWidget::updateShowFolders); + connect(showFoldersCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), + &SettingsCache::setVisualDeckStorageShowFolders); + + tagFilterVisibilityCheckBox = new QCheckBox(this); + tagFilterVisibilityCheckBox->setChecked(SettingsCache::instance().getVisualDeckStorageShowTagFilter()); + connect(tagFilterVisibilityCheckBox, &QCheckBox::QT_STATE_CHANGED, this, + &VisualDeckStorageWidget::updateTagsVisibility); + connect(tagFilterVisibilityCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), + &SettingsCache::setVisualDeckStorageShowTagFilter); + + tagsOnWidgetsVisibilityCheckBox = new QCheckBox(this); + tagsOnWidgetsVisibilityCheckBox->setChecked(SettingsCache::instance().getVisualDeckStorageShowTagsOnDeckPreviews()); + connect(tagsOnWidgetsVisibilityCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), + &SettingsCache::setVisualDeckStorageShowTagsOnDeckPreviews); + + drawUnusedColorIdentitiesCheckBox = new QCheckBox(this); + drawUnusedColorIdentitiesCheckBox->setChecked( + SettingsCache::instance().getVisualDeckStorageDrawUnusedColorIdentities()); + connect(drawUnusedColorIdentitiesCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), + &SettingsCache::setVisualDeckStorageDrawUnusedColorIdentities); + + bannerCardComboBoxVisibilityCheckBox = new QCheckBox(this); + bannerCardComboBoxVisibilityCheckBox->setChecked( + SettingsCache::instance().getVisualDeckStorageShowBannerCardComboBox()); + connect(bannerCardComboBoxVisibilityCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), + &SettingsCache::setVisualDeckStorageShowBannerCardComboBox); + + searchFolderNamesCheckBox = new QCheckBox(this); + searchFolderNamesCheckBox->setChecked(SettingsCache::instance().getVisualDeckStorageSearchFolderNames()); + connect(searchFolderNamesCheckBox, &QCheckBox::QT_STATE_CHANGED, this, + &VisualDeckStorageWidget::updateSearchFilter); + connect(searchFolderNamesCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), + &SettingsCache::setVisualDeckStorageSearchFolderNames); + + // color identity opacity selector + auto unusedColorIdentityOpacityWidget = new QWidget(this); + + unusedColorIdentitiesOpacityLabel = new QLabel(unusedColorIdentityOpacityWidget); + unusedColorIdentitiesOpacitySpinBox = new QSpinBox(unusedColorIdentityOpacityWidget); + + unusedColorIdentitiesOpacitySpinBox->setMinimum(0); + unusedColorIdentitiesOpacitySpinBox->setMaximum(100); + unusedColorIdentitiesOpacitySpinBox->setValue( + SettingsCache::instance().getVisualDeckStorageUnusedColorIdentitiesOpacity()); + connect(unusedColorIdentitiesOpacitySpinBox, QOverload::of(&QSpinBox::valueChanged), + &SettingsCache::instance(), &SettingsCache::setVisualDeckStorageUnusedColorIdentitiesOpacity); + + unusedColorIdentitiesOpacityLabel->setBuddy(unusedColorIdentitiesOpacitySpinBox); + + unusedColorIdentitiesOpacitySpinBox->setValue( + SettingsCache::instance().getVisualDeckStorageUnusedColorIdentitiesOpacity()); + + auto unusedColorIdentityOpacityLayout = new QHBoxLayout(unusedColorIdentityOpacityWidget); + unusedColorIdentityOpacityLayout->setContentsMargins(11, 0, 11, 0); + unusedColorIdentityOpacityLayout->addWidget(unusedColorIdentitiesOpacityLabel); + unusedColorIdentityOpacityLayout->addWidget(unusedColorIdentitiesOpacitySpinBox); + + // card size slider + cardSizeWidget = new CardSizeWidget(this, nullptr, SettingsCache::instance().getVisualDeckStorageCardSize()); + + quickSettingsWidget = new SettingsButtonWidget(this); + quickSettingsWidget->addSettingsWidget(showFoldersCheckBox); + quickSettingsWidget->addSettingsWidget(tagFilterVisibilityCheckBox); + quickSettingsWidget->addSettingsWidget(tagsOnWidgetsVisibilityCheckBox); + quickSettingsWidget->addSettingsWidget(bannerCardComboBoxVisibilityCheckBox); + quickSettingsWidget->addSettingsWidget(searchFolderNamesCheckBox); + quickSettingsWidget->addSettingsWidget(drawUnusedColorIdentitiesCheckBox); + quickSettingsWidget->addSettingsWidget(unusedColorIdentityOpacityWidget); + quickSettingsWidget->addSettingsWidget(cardSizeWidget); + + searchAndSortLayout->addWidget(deckPreviewColorIdentityFilterWidget); + searchAndSortLayout->addWidget(sortWidget); + searchAndSortLayout->addWidget(searchWidget); + searchAndSortLayout->addWidget(refreshButton); + searchAndSortLayout->addWidget(quickSettingsWidget); + + // tag filter box + tagFilterWidget = new VisualDeckStorageTagFilterWidget(this); + updateTagsVisibility(SettingsCache::instance().getVisualDeckStorageShowTagFilter()); + + // deck area + scrollArea = new QScrollArea(this); + scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); + scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + scrollArea->setWidgetResizable(true); + + // putting everything together + layout->addWidget(searchAndSortContainer); + layout->addWidget(tagFilterWidget); + layout->addWidget(scrollArea); + + connect(CardDatabaseManager::getInstance(), &CardDatabase::cardDatabaseLoadingFinished, this, + &VisualDeckStorageWidget::createRootFolderWidget); + + databaseLoadIndicator = new QLabel(this); + databaseLoadIndicator->setAlignment(Qt::AlignCenter); + + retranslateUi(); + + // Don't waste time processing the cards if they're going to get refreshed anyway once the db finishes loading + if (CardDatabaseManager::getInstance()->getLoadStatus() == LoadStatus::Ok) { + createRootFolderWidget(); + databaseLoadIndicator->setVisible(false); + } else { + scrollArea->setWidget(databaseLoadIndicator); + } +} + +void VisualDeckStorageWidget::refreshIfPossible() +{ + if (scrollArea->widget() != databaseLoadIndicator) { + createRootFolderWidget(); + } +} + +void VisualDeckStorageWidget::showEvent(QShowEvent *event) +{ + QWidget::showEvent(event); + if (scrollArea->widget() == folderWidget) { + scrollArea->widget()->setMaximumWidth(scrollArea->viewport()->width()); + scrollArea->widget()->adjustSize(); + } +} + +void VisualDeckStorageWidget::resizeEvent(QResizeEvent *event) +{ + QWidget::resizeEvent(event); + if (scrollArea->widget() == folderWidget) { + scrollArea->widget()->setMaximumWidth(scrollArea->viewport()->width()); + scrollArea->widget()->adjustSize(); + } +} + +void VisualDeckStorageWidget::retranslateUi() +{ + databaseLoadIndicator->setText(tr("Loading database ...")); + + showFoldersCheckBox->setText(tr("Show Folders")); + tagFilterVisibilityCheckBox->setText(tr("Show Tag Filter")); + tagsOnWidgetsVisibilityCheckBox->setText(tr("Show Tags On Deck Previews")); + bannerCardComboBoxVisibilityCheckBox->setText(tr("Show Banner Card Selection Option")); + searchFolderNamesCheckBox->setText(tr("Include Folder Names in Search")); + drawUnusedColorIdentitiesCheckBox->setText(tr("Draw unused Color Identities")); + unusedColorIdentitiesOpacityLabel->setText(tr("Unused Color Identities Opacity")); + unusedColorIdentitiesOpacitySpinBox->setSuffix("%"); +} + +/** + * Reapplies all sort and filter options by calling the appropriate update methods. + */ +void VisualDeckStorageWidget::reapplySortAndFilters() +{ + updateSortOrder(); + updateTagFilter(); + updateColorFilter(); + updateSearchFilter(); +} + +void VisualDeckStorageWidget::createRootFolderWidget() +{ + folderWidget = new VisualDeckStorageFolderDisplayWidget(this, this, SettingsCache::instance().getDeckPath(), false, + showFoldersCheckBox->isChecked()); + + scrollArea->setWidget(folderWidget); // this automatically destroys the old folderWidget + scrollArea->widget()->setMaximumWidth(scrollArea->viewport()->width()); + scrollArea->widget()->adjustSize(); + reapplySortAndFilters(); +} + +void VisualDeckStorageWidget::updateShowFolders(bool enabled) +{ + if (folderWidget) { + folderWidget->updateShowFolders(enabled); + reapplySortAndFilters(); + } +} + +void VisualDeckStorageWidget::updateSortOrder() +{ + if (folderWidget) { + sortWidget->sortFolder(folderWidget); + for (VisualDeckStorageFolderDisplayWidget *subFolderWidget : + folderWidget->findChildren()) { + sortWidget->sortFolder(subFolderWidget); + } + } +} + +void VisualDeckStorageWidget::updateTagFilter() +{ + if (folderWidget) { + tagFilterWidget->filterDecksBySelectedTags(folderWidget->findChildren()); + folderWidget->updateVisibility(); + } +} + +void VisualDeckStorageWidget::updateColorFilter() +{ + if (folderWidget) { + deckPreviewColorIdentityFilterWidget->filterWidgets(folderWidget->findChildren()); + folderWidget->updateVisibility(); + } +} + +void VisualDeckStorageWidget::updateSearchFilter() +{ + if (folderWidget) { + searchWidget->filterWidgets(folderWidget->findChildren(), searchWidget->getSearchText(), + searchFolderNamesCheckBox->isChecked()); + folderWidget->updateVisibility(); + } +} + +void VisualDeckStorageWidget::updateTagsVisibility(const bool visible) +{ + if (visible) { + tagFilterWidget->setVisible(true); + + } else { + tagFilterWidget->setHidden(true); + } +} \ No newline at end of file diff --git a/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_widget.h b/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_widget.h new file mode 100644 index 000000000..acab16710 --- /dev/null +++ b/cockatrice/src/client/ui/widgets/visual_deck_storage/visual_deck_storage_widget.h @@ -0,0 +1,76 @@ +#ifndef VISUAL_DECK_STORAGE_WIDGET_H +#define VISUAL_DECK_STORAGE_WIDGET_H + +#include "../../../../deck/deck_list_model.h" +#include "../../../ui/widgets/general/layout_containers/flow_widget.h" +#include "../cards/card_size_widget.h" +#include "../quick_settings/settings_button_widget.h" +#include "deck_preview/deck_preview_color_identity_filter_widget.h" +#include "deck_preview/deck_preview_widget.h" +#include "visual_deck_storage_folder_display_widget.h" +#include "visual_deck_storage_search_widget.h" +#include "visual_deck_storage_sort_widget.h" +#include "visual_deck_storage_tag_filter_widget.h" + +#include +#include + +class QSpinBox; +class VisualDeckStorageSearchWidget; +class VisualDeckStorageSortWidget; +class VisualDeckStorageTagFilterWidget; +class VisualDeckStorageFolderDisplayWidget; +class DeckPreviewColorIdentityFilterWidget; +class VisualDeckStorageWidget final : public QWidget +{ + Q_OBJECT +public: + explicit VisualDeckStorageWidget(QWidget *parent); + void refreshIfPossible(); + void retranslateUi(); + + CardSizeWidget *cardSizeWidget; + VisualDeckStorageTagFilterWidget *tagFilterWidget; + +public slots: + void createRootFolderWidget(); // Refresh the display of cards based on the current sorting option + void updateShowFolders(bool enabled); + void updateTagFilter(); + void updateColorFilter(); + void updateSearchFilter(); + void updateTagsVisibility(bool visible); + void updateSortOrder(); + void resizeEvent(QResizeEvent *event) override; + void showEvent(QShowEvent *event) override; + +signals: + void bannerCardsRefreshed(); + void deckLoadRequested(const QString &filePath); + void openDeckEditor(const DeckLoader *deck); + +private: + QVBoxLayout *layout; + QWidget *searchAndSortContainer; + QHBoxLayout *searchAndSortLayout; + DeckListModel *deckListModel; + QLabel *databaseLoadIndicator; + VisualDeckStorageSortWidget *sortWidget; + VisualDeckStorageSearchWidget *searchWidget; + DeckPreviewColorIdentityFilterWidget *deckPreviewColorIdentityFilterWidget; + QToolButton *refreshButton; + SettingsButtonWidget *quickSettingsWidget; + QCheckBox *showFoldersCheckBox; + QCheckBox *drawUnusedColorIdentitiesCheckBox; + QCheckBox *bannerCardComboBoxVisibilityCheckBox; + QCheckBox *tagFilterVisibilityCheckBox; + QCheckBox *tagsOnWidgetsVisibilityCheckBox; + QCheckBox *searchFolderNamesCheckBox; + QLabel *unusedColorIdentitiesOpacityLabel; + QSpinBox *unusedColorIdentitiesOpacitySpinBox; + QScrollArea *scrollArea; + VisualDeckStorageFolderDisplayWidget *folderWidget; + + void reapplySortAndFilters(); +}; + +#endif // VISUAL_DECK_STORAGE_WIDGET_H diff --git a/cockatrice/src/client/ui/window_main.cpp b/cockatrice/src/client/ui/window_main.cpp index 4de3b3cb3..89a686f61 100644 --- a/cockatrice/src/client/ui/window_main.cpp +++ b/cockatrice/src/client/ui/window_main.cpp @@ -40,6 +40,8 @@ #include "../../settings/cache_settings.h" #include "../../utility/logger.h" #include "../get_text_with_max.h" +#include "../network/client_update_checker.h" +#include "../network/release_channel.h" #include "../tabs/tab_game.h" #include "../tabs/tab_supervisor.h" #include "pb/event_connection_closed.pb.h" @@ -80,6 +82,11 @@ const QString MainWindow::appName = "Cockatrice"; const QStringList MainWindow::fileNameFilters = QStringList() << QObject::tr("Cockatrice card database (*.xml)") << QObject::tr("All files (*.*)"); +/** + * Replaces the tab-specific menus that are shown in the menuBar. + * + * @param newMenuList The tab-specific menus to show in the menuBar + */ void MainWindow::updateTabMenu(const QList &newMenuList) { for (auto &tabMenu : tabMenus) @@ -191,7 +198,7 @@ void MainWindow::activateAccepted() void MainWindow::actConnect() { dlgConnect = new DlgConnect(this); - connect(dlgConnect, SIGNAL(sigStartForgotPasswordRequest()), this, SLOT(actForgotPasswordRequest())); + connect(dlgConnect, &DlgConnect::sigStartForgotPasswordRequest, this, &MainWindow::actForgotPasswordRequest); if (dlgConnect->exec()) { client->connectToServer(dlgConnect->getHost(), static_cast(dlgConnect->getPort()), @@ -221,6 +228,11 @@ void MainWindow::actSinglePlayer() if (!ok) return; + startLocalGame(numberPlayers); +} + +void MainWindow::startLocalGame(int numberPlayers) +{ aConnect->setEnabled(false); aRegister->setEnabled(false); aForgotPassword->setEnabled(false); @@ -278,11 +290,6 @@ void MainWindow::localGameEnded() aSinglePlayer->setEnabled(true); } -void MainWindow::actDeckEditor() -{ - tabSupervisor->addDeckEditorTab(nullptr); -} - void MainWindow::actFullScreen(bool checked) { if (checked) @@ -354,6 +361,12 @@ void MainWindow::actViewLog() logviewDialog->activateWindow(); } +void MainWindow::actOpenSettingsFolder() +{ + QString dir = SettingsCache::instance().getSettingsPath(); + QDesktopServices::openUrl(QUrl::fromLocalFile(dir)); +} + void MainWindow::serverTimeout() { QMessageBox::critical(this, tr("Error"), tr("Server timeout")); @@ -646,7 +659,6 @@ void MainWindow::retranslateUi() aDisconnect->setText(tr("&Disconnect")); aSinglePlayer->setText(tr("Start &local game...")); aWatchReplay->setText(tr("&Watch replay...")); - aDeckEditor->setText(tr("&Deck editor")); aFullScreen->setText(tr("&Full screen")); aRegister->setText(tr("&Register to server...")); aForgotPassword->setText(tr("&Restore password...")); @@ -666,6 +678,9 @@ void MainWindow::retranslateUi() aOpenCustomFolder->setText(tr("Open custom image folder")); aOpenCustomsetsFolder->setText(tr("Open custom sets folder")); aAddCustomSet->setText(tr("Add custom sets/cards")); + aReloadCardDatabase->setText(tr("Reload card database")); + + tabsMenu->setTitle(tr("Tabs")); helpMenu->setTitle(tr("&Help")); aAbout->setText(tr("&About Cockatrice")); @@ -673,6 +688,7 @@ void MainWindow::retranslateUi() aUpdate->setText(tr("Check for Client Updates")); aCheckCardUpdates->setText(tr("Check for Card Updates...")); aViewLog->setText(tr("View &Debug Log")); + aOpenSettingsFolder->setText(tr("Open Settings Folder")); aShow->setText(tr("Show/Hide")); @@ -682,52 +698,54 @@ void MainWindow::retranslateUi() void MainWindow::createActions() { aConnect = new QAction(this); - connect(aConnect, SIGNAL(triggered()), this, SLOT(actConnect())); + connect(aConnect, &QAction::triggered, this, &MainWindow::actConnect); aDisconnect = new QAction(this); aDisconnect->setEnabled(false); - connect(aDisconnect, SIGNAL(triggered()), this, SLOT(actDisconnect())); + connect(aDisconnect, &QAction::triggered, this, &MainWindow::actDisconnect); aSinglePlayer = new QAction(this); - connect(aSinglePlayer, SIGNAL(triggered()), this, SLOT(actSinglePlayer())); + connect(aSinglePlayer, &QAction::triggered, this, &MainWindow::actSinglePlayer); aWatchReplay = new QAction(this); - connect(aWatchReplay, SIGNAL(triggered()), this, SLOT(actWatchReplay())); - aDeckEditor = new QAction(this); - connect(aDeckEditor, SIGNAL(triggered()), this, SLOT(actDeckEditor())); + connect(aWatchReplay, &QAction::triggered, this, &MainWindow::actWatchReplay); aFullScreen = new QAction(this); aFullScreen->setCheckable(true); - connect(aFullScreen, SIGNAL(toggled(bool)), this, SLOT(actFullScreen(bool))); + connect(aFullScreen, &QAction::toggled, this, &MainWindow::actFullScreen); aRegister = new QAction(this); - connect(aRegister, SIGNAL(triggered()), this, SLOT(actRegister())); + connect(aRegister, &QAction::triggered, this, &MainWindow::actRegister); aForgotPassword = new QAction(this); - connect(aForgotPassword, SIGNAL(triggered()), this, SLOT(actForgotPasswordRequest())); + connect(aForgotPassword, &QAction::triggered, this, &MainWindow::actForgotPasswordRequest); aSettings = new QAction(this); - connect(aSettings, SIGNAL(triggered()), this, SLOT(actSettings())); + connect(aSettings, &QAction::triggered, this, &MainWindow::actSettings); aExit = new QAction(this); - connect(aExit, SIGNAL(triggered()), this, SLOT(actExit())); + connect(aExit, &QAction::triggered, this, &MainWindow::actExit); aManageSets = new QAction(QString(), this); - connect(aManageSets, SIGNAL(triggered()), this, SLOT(actManageSets())); + connect(aManageSets, &QAction::triggered, this, &MainWindow::actManageSets); aEditTokens = new QAction(QString(), this); - connect(aEditTokens, SIGNAL(triggered()), this, SLOT(actEditTokens())); + connect(aEditTokens, &QAction::triggered, this, &MainWindow::actEditTokens); aOpenCustomFolder = new QAction(QString(), this); - connect(aOpenCustomFolder, SIGNAL(triggered()), this, SLOT(actOpenCustomFolder())); + connect(aOpenCustomFolder, &QAction::triggered, this, &MainWindow::actOpenCustomFolder); aOpenCustomsetsFolder = new QAction(QString(), this); - connect(aOpenCustomsetsFolder, SIGNAL(triggered()), this, SLOT(actOpenCustomsetsFolder())); + connect(aOpenCustomsetsFolder, &QAction::triggered, this, &MainWindow::actOpenCustomsetsFolder); aAddCustomSet = new QAction(QString(), this); - connect(aAddCustomSet, SIGNAL(triggered()), this, SLOT(actAddCustomSet())); + connect(aAddCustomSet, &QAction::triggered, this, &MainWindow::actAddCustomSet); + aReloadCardDatabase = new QAction(QString(), this); + connect(aReloadCardDatabase, &QAction::triggered, this, &MainWindow::actReloadCardDatabase); aAbout = new QAction(this); - connect(aAbout, SIGNAL(triggered()), this, SLOT(actAbout())); + connect(aAbout, &QAction::triggered, this, &MainWindow::actAbout); aTips = new QAction(this); - connect(aTips, SIGNAL(triggered()), this, SLOT(actTips())); + connect(aTips, &QAction::triggered, this, &MainWindow::actTips); aUpdate = new QAction(this); - connect(aUpdate, SIGNAL(triggered()), this, SLOT(actUpdate())); + connect(aUpdate, &QAction::triggered, this, &MainWindow::actUpdate); aCheckCardUpdates = new QAction(this); - connect(aCheckCardUpdates, SIGNAL(triggered()), this, SLOT(actCheckCardUpdates())); + connect(aCheckCardUpdates, &QAction::triggered, this, &MainWindow::actCheckCardUpdates); aViewLog = new QAction(this); - connect(aViewLog, SIGNAL(triggered()), this, SLOT(actViewLog())); + connect(aViewLog, &QAction::triggered, this, &MainWindow::actViewLog); + aOpenSettingsFolder = new QAction(this); + connect(aOpenSettingsFolder, &QAction::triggered, this, &MainWindow::actOpenSettingsFolder); aShow = new QAction(this); - connect(aShow, SIGNAL(triggered()), this, SLOT(actShow())); + connect(aShow, &QAction::triggered, this, &MainWindow::actShow); #if defined(__APPLE__) /* For OSX */ aSettings->setMenuRole(QAction::PreferencesRole); @@ -774,8 +792,6 @@ void MainWindow::createMenus() cockatriceMenu->addAction(aSinglePlayer); cockatriceMenu->addAction(aWatchReplay); cockatriceMenu->addSeparator(); - cockatriceMenu->addAction(aDeckEditor); - cockatriceMenu->addSeparator(); cockatriceMenu->addAction(aFullScreen); cockatriceMenu->addSeparator(); cockatriceMenu->addAction(aSettings); @@ -788,6 +804,10 @@ void MainWindow::createMenus() dbMenu->addAction(aOpenCustomFolder); dbMenu->addAction(aOpenCustomsetsFolder); dbMenu->addAction(aAddCustomSet); + dbMenu->addSeparator(); + dbMenu->addAction(aReloadCardDatabase); + + tabsMenu = menuBar()->addMenu(QString()); helpMenu = menuBar()->addMenu(QString()); helpMenu->addAction(aAbout); @@ -797,39 +817,37 @@ void MainWindow::createMenus() helpMenu->addAction(aCheckCardUpdates); helpMenu->addSeparator(); helpMenu->addAction(aViewLog); + helpMenu->addAction(aOpenSettingsFolder); } MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), localServer(nullptr), bHasActivated(false), askedForDbUpdater(false), cardUpdateProcess(nullptr), logviewDialog(nullptr) { - connect(&SettingsCache::instance(), SIGNAL(pixmapCacheSizeChanged(int)), this, SLOT(pixmapCacheSizeChanged(int))); + connect(&SettingsCache::instance(), &SettingsCache::pixmapCacheSizeChanged, this, + &MainWindow::pixmapCacheSizeChanged); pixmapCacheSizeChanged(SettingsCache::instance().getPixmapCacheSize()); client = new RemoteClient; - connect(client, SIGNAL(connectionClosedEventReceived(const Event_ConnectionClosed &)), this, - SLOT(processConnectionClosedEvent(const Event_ConnectionClosed &))); - connect(client, SIGNAL(serverShutdownEventReceived(const Event_ServerShutdown &)), this, - SLOT(processServerShutdownEvent(const Event_ServerShutdown &))); - connect(client, SIGNAL(loginError(Response::ResponseCode, QString, quint32, QList)), this, - SLOT(loginError(Response::ResponseCode, QString, quint32, QList))); - connect(client, SIGNAL(socketError(const QString &)), this, SLOT(socketError(const QString &))); - connect(client, SIGNAL(serverTimeout()), this, SLOT(serverTimeout())); - connect(client, SIGNAL(statusChanged(ClientStatus)), this, SLOT(statusChanged(ClientStatus))); - connect(client, SIGNAL(protocolVersionMismatch(int, int)), this, SLOT(protocolVersionMismatch(int, int))); - connect(client, SIGNAL(userInfoChanged(const ServerInfo_User &)), this, - SLOT(userInfoReceived(const ServerInfo_User &)), Qt::BlockingQueuedConnection); - connect(client, SIGNAL(notifyUserAboutUpdate()), this, SLOT(notifyUserAboutUpdate())); - connect(client, SIGNAL(registerAccepted()), this, SLOT(registerAccepted())); - connect(client, SIGNAL(registerAcceptedNeedsActivate()), this, SLOT(registerAcceptedNeedsActivate())); - connect(client, SIGNAL(registerError(Response::ResponseCode, QString, quint32)), this, - SLOT(registerError(Response::ResponseCode, QString, quint32))); - connect(client, SIGNAL(activateAccepted()), this, SLOT(activateAccepted())); - connect(client, SIGNAL(activateError()), this, SLOT(activateError())); - connect(client, SIGNAL(sigForgotPasswordSuccess()), this, SLOT(forgotPasswordSuccess())); - connect(client, SIGNAL(sigForgotPasswordError()), this, SLOT(forgotPasswordError())); - connect(client, SIGNAL(sigPromptForForgotPasswordReset()), this, SLOT(promptForgotPasswordReset())); - connect(client, SIGNAL(sigPromptForForgotPasswordChallenge()), this, SLOT(promptForgotPasswordChallenge())); + connect(client, &RemoteClient::connectionClosedEventReceived, this, &MainWindow::processConnectionClosedEvent); + connect(client, &RemoteClient::serverShutdownEventReceived, this, &MainWindow::processServerShutdownEvent); + connect(client, &RemoteClient::loginError, this, &MainWindow::loginError); + connect(client, &RemoteClient::socketError, this, &MainWindow::socketError); + connect(client, &RemoteClient::serverTimeout, this, &MainWindow::serverTimeout); + connect(client, &RemoteClient::statusChanged, this, &MainWindow::statusChanged); + connect(client, &RemoteClient::protocolVersionMismatch, this, &MainWindow::protocolVersionMismatch); + connect(client, &RemoteClient::userInfoChanged, this, &MainWindow::userInfoReceived, Qt::BlockingQueuedConnection); + connect(client, &RemoteClient::notifyUserAboutUpdate, this, &MainWindow::notifyUserAboutUpdate); + connect(client, &RemoteClient::registerAccepted, this, &MainWindow::registerAccepted); + connect(client, &RemoteClient::registerAcceptedNeedsActivate, this, &MainWindow::registerAcceptedNeedsActivate); + connect(client, &RemoteClient::registerError, this, &MainWindow::registerError); + connect(client, &RemoteClient::activateAccepted, this, &MainWindow::activateAccepted); + connect(client, &RemoteClient::activateError, this, &MainWindow::activateError); + connect(client, &RemoteClient::sigForgotPasswordSuccess, this, &MainWindow::forgotPasswordSuccess); + connect(client, &RemoteClient::sigForgotPasswordError, this, &MainWindow::forgotPasswordError); + connect(client, &RemoteClient::sigPromptForForgotPasswordReset, this, &MainWindow::promptForgotPasswordReset); + connect(client, &RemoteClient::sigPromptForForgotPasswordChallenge, this, + &MainWindow::promptForgotPasswordChallenge); clientThread = new QThread(this); client->moveToThread(clientThread); @@ -838,11 +856,11 @@ MainWindow::MainWindow(QWidget *parent) createActions(); createMenus(); - tabSupervisor = new TabSupervisor(client); - connect(tabSupervisor, SIGNAL(setMenu(QList)), this, SLOT(updateTabMenu(QList))); - connect(tabSupervisor, SIGNAL(localGameEnded()), this, SLOT(localGameEnded())); - connect(tabSupervisor, SIGNAL(showWindowIfHidden()), this, SLOT(showWindowIfHidden())); - tabSupervisor->addDeckEditorTab(nullptr); + tabSupervisor = new TabSupervisor(client, tabsMenu, this); + connect(tabSupervisor, &TabSupervisor::setMenu, this, &MainWindow::updateTabMenu); + connect(tabSupervisor, &TabSupervisor::localGameEnded, this, &MainWindow::localGameEnded); + connect(tabSupervisor, &TabSupervisor::showWindowIfHidden, this, &MainWindow::showWindowIfHidden); + tabSupervisor->initStartupTabs(); setCentralWidget(tabSupervisor); @@ -857,14 +875,16 @@ MainWindow::MainWindow(QWidget *parent) createTrayIcon(); } - connect(&SettingsCache::instance().shortcuts(), SIGNAL(shortCutChanged()), this, SLOT(refreshShortcuts())); + connect(&SettingsCache::instance().shortcuts(), &ShortcutsSettings::shortCutChanged, this, + &MainWindow::refreshShortcuts); refreshShortcuts(); - connect(CardDatabaseManager::getInstance(), SIGNAL(cardDatabaseLoadingFailed()), this, - SLOT(cardDatabaseLoadingFailed())); - connect(CardDatabaseManager::getInstance(), SIGNAL(cardDatabaseNewSetsFound(int, QStringList)), this, - SLOT(cardDatabaseNewSetsFound(int, QStringList))); - connect(CardDatabaseManager::getInstance(), SIGNAL(cardDatabaseAllNewSetsEnabled()), this, - SLOT(cardDatabaseAllNewSetsEnabled())); + + connect(CardDatabaseManager::getInstance(), &CardDatabase::cardDatabaseLoadingFailed, this, + &MainWindow::cardDatabaseLoadingFailed); + connect(CardDatabaseManager::getInstance(), &CardDatabase::cardDatabaseNewSetsFound, this, + &MainWindow::cardDatabaseNewSetsFound); + connect(CardDatabaseManager::getInstance(), &CardDatabase::cardDatabaseAllNewSetsEnabled, this, + &MainWindow::cardDatabaseAllNewSetsEnabled); tip = new DlgTipOfTheDay(); @@ -874,28 +894,39 @@ MainWindow::MainWindow(QWidget *parent) void MainWindow::startupConfigCheck() { + if (SettingsCache::instance().debug().getLocalGameOnStartup()) { + startLocalGame(SettingsCache::instance().debug().getLocalGamePlayerCount()); + } + + if (SettingsCache::instance().getCheckUpdatesOnStartup()) { + actCheckClientUpdates(); + } + if (SettingsCache::instance().getClientVersion() == CLIENT_INFO_NOT_SET) { // no config found, 99% new clean install - qDebug() << "Startup: old client version empty, assuming first start after clean install"; + qCInfo(WindowMainStartupVersionLog) + << "Startup: old client version empty, assuming first start after clean install"; alertForcedOracleRun(VERSION_STRING, false); + SettingsCache::instance().downloads().resetToDefaultURLs(); // populate the download urls SettingsCache::instance().setClientVersion(VERSION_STRING); } else if (SettingsCache::instance().getClientVersion() != VERSION_STRING) { // config found, from another (presumably older) version - qDebug() << "Startup: old client version" << SettingsCache::instance().getClientVersion() - << "differs, assuming first start after update"; + qCInfo(WindowMainStartupVersionLog) + << "Startup: old client version" << SettingsCache::instance().getClientVersion() + << "differs, assuming first start after update"; if (SettingsCache::instance().getNotifyAboutNewVersion()) { alertForcedOracleRun(VERSION_STRING, true); } else { const auto reloadOk0 = QtConcurrent::run([] { CardDatabaseManager::getInstance()->loadCardDatabases(); }); } - qDebug() << "[MainWindow] Migrating shortcuts after update detected."; + qCInfo(WindowMainStartupShortcutsLog) << "[MainWindow] Migrating shortcuts after update detected."; SettingsCache::instance().shortcuts().migrateShortcuts(); SettingsCache::instance().setClientVersion(VERSION_STRING); } else { // previous config from this version found - qDebug() << "Startup: found config with current version"; + qCInfo(WindowMainStartupVersionLog) << "Startup: found config with current version"; const auto reloadOk1 = QtConcurrent::run([] { CardDatabaseManager::getInstance()->loadCardDatabases(); }); // Run the tips dialog only on subsequent startups. @@ -987,7 +1018,7 @@ void MainWindow::closeEvent(QCloseEvent *event) return; bClosingDown = true; - if (!tabSupervisor->closeRequest()) { + if (!tabSupervisor->close()) { event->ignore(); bClosingDown = false; return; @@ -1007,10 +1038,11 @@ void MainWindow::changeEvent(QEvent *event) if (isActiveWindow() && !bHasActivated) { bHasActivated = true; if (!connectTo.isEmpty()) { - qDebug() << "Command line connect to " << connectTo; + qCInfo(WindowMainStartupAutoconnectLog) << "Command line connect to " << connectTo; client->connectToServer(connectTo.host(), connectTo.port(), connectTo.userName(), connectTo.password()); - } else if (SettingsCache::instance().servers().getAutoConnect()) { - qDebug() << "Attempting auto-connect..."; + } else if (SettingsCache::instance().servers().getAutoConnect() && + !SettingsCache::instance().debug().getLocalGameOnStartup()) { + qCInfo(WindowMainStartupAutoconnectLog) << "Attempting auto-connect..."; DlgConnect dlg(this); client->connectToServer(dlg.getHost(), static_cast(dlg.getPort()), dlg.getPlayerName(), dlg.getPassword()); @@ -1113,8 +1145,8 @@ void MainWindow::actCheckCardUpdates() connect(cardUpdateProcess, &QProcess::errorOccurred, this, &MainWindow::cardUpdateError); - connect(cardUpdateProcess, SIGNAL(finished(int, QProcess::ExitStatus)), this, - SLOT(cardUpdateFinished(int, QProcess::ExitStatus))); + connect(cardUpdateProcess, qOverload(&QProcess::finished), this, + &MainWindow::cardUpdateFinished); // full "run the update" command; leave empty if not present QString updaterCmd; @@ -1181,28 +1213,31 @@ void MainWindow::cardUpdateError(QProcess::ProcessError err) QString error; switch (err) { case QProcess::FailedToStart: - error = tr("failed to start."); + error = tr("Failed to start. The file might be missing, or permissions might be incorrect."); break; case QProcess::Crashed: - error = tr("crashed."); + error = tr("The process crashed some time after starting successfully."); + error += "\n\nError output:\n" + cardUpdateProcess->readAllStandardError(); break; case QProcess::Timedout: - error = tr("timed out."); + error = tr("Timed out. The process took too long to respond. The last waitFor...() function timed out."); break; case QProcess::WriteError: - error = tr("write error."); + error = tr("An error occurred when attempting to write to the process. For example, the process may " + "not be running, or it may have closed its input channel."); break; case QProcess::ReadError: - error = tr("read error."); + error = tr("An error occurred when attempting to read from the process. For example, the process may " + "not be running."); break; case QProcess::UnknownError: default: - error = tr("unknown error."); + error = tr("Unknown error occurred."); break; } exitCardDatabaseUpdate(); - QMessageBox::warning(this, tr("Error"), tr("The card database updater exited with an error: %1").arg(error)); + QMessageBox::warning(this, tr("Error"), tr("The card database updater exited with an error:\n%1").arg(error)); } void MainWindow::cardUpdateFinished(int, QProcess::ExitStatus) @@ -1217,6 +1252,21 @@ void MainWindow::actCheckServerUpdates() connect(hps, &HandlePublicServers::sigPublicServersDownloadedSuccessfully, [=]() { hps->deleteLater(); }); } +void MainWindow::actCheckClientUpdates() +{ + auto checker = new ClientUpdateChecker(this); + connect(checker, &ClientUpdateChecker::finishedCheck, this, &MainWindow::checkClientUpdatesFinished); + checker->check(); +} + +void MainWindow::checkClientUpdatesFinished(bool needToUpdate, bool /* isCompatible */, Release * /* release */) +{ + if (needToUpdate) { + DlgUpdate dlg(this); + dlg.exec(); + } +} + void MainWindow::refreshShortcuts() { ShortcutsSettings &shortcuts = SettingsCache::instance().shortcuts(); @@ -1224,7 +1274,6 @@ void MainWindow::refreshShortcuts() aDisconnect->setShortcuts(shortcuts.getShortcut("MainWindow/aDisconnect")); aSinglePlayer->setShortcuts(shortcuts.getShortcut("MainWindow/aSinglePlayer")); aWatchReplay->setShortcuts(shortcuts.getShortcut("MainWindow/aWatchReplay")); - aDeckEditor->setShortcuts(shortcuts.getShortcut("MainWindow/aDeckEditor")); aFullScreen->setShortcuts(shortcuts.getShortcut("MainWindow/aFullScreen")); aRegister->setShortcuts(shortcuts.getShortcut("MainWindow/aRegister")); aSettings->setShortcuts(shortcuts.getShortcut("MainWindow/aSettings")); @@ -1324,6 +1373,14 @@ int MainWindow::getNextCustomSetPrefix(QDir dataDir) return maxIndex + 1; } +void MainWindow::actReloadCardDatabase() +{ + const auto reloadOk1 = QtConcurrent::run([] { + CardDatabaseManager::getInstance()->loadCardDatabases(); + SettingsCache::instance().downloads().sync(); + }); +} + void MainWindow::actManageSets() { wndSets = new WndSets(this); diff --git a/cockatrice/src/client/ui/window_main.h b/cockatrice/src/client/ui/window_main.h index 1813b3b4a..9910cf7fb 100644 --- a/cockatrice/src/client/ui/window_main.h +++ b/cockatrice/src/client/ui/window_main.h @@ -24,12 +24,20 @@ #include "pb/response.pb.h" #include +#include #include #include #include #include #include +inline Q_LOGGING_CATEGORY(WindowMainLog, "window_main"); +inline Q_LOGGING_CATEGORY(WindowMainStartupLog, "window_main.startup"); +inline Q_LOGGING_CATEGORY(WindowMainStartupVersionLog, "window_main.startup.version"); +inline Q_LOGGING_CATEGORY(WindowMainStartupShortcutsLog, "window_main.startup.shortcuts"); +inline Q_LOGGING_CATEGORY(WindowMainStartupAutoconnectLog, "window_main.startup.autoconnect"); + +class Release; class DlgConnect; class DlgViewLog; class GameReplay; @@ -49,6 +57,7 @@ class MainWindow : public QMainWindow public slots: void actCheckCardUpdates(); void actCheckServerUpdates(); + void actCheckClientUpdates(); private slots: void updateTabMenu(const QList &newMenuList); void statusChanged(ClientStatus _status); @@ -71,7 +80,6 @@ private slots: void actDisconnect(); void actSinglePlayer(); void actWatchReplay(); - void actDeckEditor(); void actFullScreen(bool checked); void actRegister(); void actSettings(); @@ -81,6 +89,7 @@ private slots: void actTips(); void actUpdate(); void actViewLog(); + void actOpenSettingsFolder(); void forgotPasswordSuccess(); void forgotPasswordError(); void promptForgotPasswordReset(); @@ -95,9 +104,12 @@ private slots: void cardDatabaseNewSetsFound(int numUnknownSets, QStringList unknownSetsNames); void cardDatabaseAllNewSetsEnabled(); + void checkClientUpdatesFinished(bool needToUpdate, bool isCompatible, Release *release); + void actOpenCustomFolder(); void actOpenCustomsetsFolder(); void actAddCustomSet(); + void actReloadCardDatabase(); void actManageSets(); void actEditTokens(); @@ -121,11 +133,14 @@ private: }; void exitCardDatabaseUpdate(); + void startLocalGame(int numberPlayers); + QList tabMenus; - QMenu *cockatriceMenu, *dbMenu, *helpMenu, *trayIconMenu; - QAction *aConnect, *aDisconnect, *aSinglePlayer, *aWatchReplay, *aDeckEditor, *aFullScreen, *aSettings, *aExit, - *aAbout, *aTips, *aCheckCardUpdates, *aRegister, *aForgotPassword, *aUpdate, *aViewLog, *aManageSets, - *aEditTokens, *aOpenCustomFolder, *aOpenCustomsetsFolder, *aAddCustomSet, *aShow; + QMenu *cockatriceMenu, *dbMenu, *tabsMenu, *helpMenu, *trayIconMenu; + QAction *aConnect, *aDisconnect, *aSinglePlayer, *aWatchReplay, *aFullScreen, *aSettings, *aExit, *aAbout, *aTips, + *aCheckCardUpdates, *aRegister, *aForgotPassword, *aUpdate, *aViewLog, *aManageSets, *aEditTokens, + *aOpenCustomFolder, *aOpenCustomsetsFolder, *aAddCustomSet, *aReloadCardDatabase, *aShow, *aOpenSettingsFolder; + TabSupervisor *tabSupervisor; WndSets *wndSets; RemoteClient *client; @@ -148,6 +163,11 @@ public: } ~MainWindow() override; + TabSupervisor *getTabSupervisor() const + { + return tabSupervisor; + } + protected: void closeEvent(QCloseEvent *event) override; void changeEvent(QEvent *event) override; diff --git a/cockatrice/src/client/update_downloader.cpp b/cockatrice/src/client/update_downloader.cpp index 161137cbe..40186bec4 100644 --- a/cockatrice/src/client/update_downloader.cpp +++ b/cockatrice/src/client/update_downloader.cpp @@ -15,9 +15,9 @@ void UpdateDownloader::beginDownload(QUrl downloadUrl) originalUrl = downloadUrl; response = netMan->get(QNetworkRequest(downloadUrl)); - connect(response, SIGNAL(finished()), this, SLOT(fileFinished())); - connect(response, SIGNAL(downloadProgress(qint64, qint64)), this, SLOT(downloadProgress(qint64, qint64))); - connect(this, SIGNAL(stopDownload()), response, SLOT(abort())); + connect(response, &QNetworkReply::finished, this, &UpdateDownloader::fileFinished); + connect(response, &QNetworkReply::downloadProgress, this, &UpdateDownloader::downloadProgress); + connect(this, &UpdateDownloader::stopDownload, response, &QNetworkReply::abort); } void UpdateDownloader::downloadError(QNetworkReply::NetworkError) diff --git a/cockatrice/src/client/update_downloader.h b/cockatrice/src/client/update_downloader.h index bc10fe198..cafed0377 100644 --- a/cockatrice/src/client/update_downloader.h +++ b/cockatrice/src/client/update_downloader.h @@ -14,7 +14,7 @@ class UpdateDownloader : public QObject { Q_OBJECT public: - UpdateDownloader(QObject *parent); + explicit UpdateDownloader(QObject *parent); void beginDownload(QUrl url); signals: void downloadSuccessful(QUrl filepath); diff --git a/cockatrice/src/deck/custom_line_edit.cpp b/cockatrice/src/deck/custom_line_edit.cpp index 326d0a6c0..ba521c0fd 100644 --- a/cockatrice/src/deck/custom_line_edit.cpp +++ b/cockatrice/src/deck/custom_line_edit.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include LineEditUnfocusable::LineEditUnfocusable(QWidget *parent) : QLineEdit(parent) @@ -69,3 +70,19 @@ bool LineEditUnfocusable::eventFilter(QObject *watched, QEvent *event) return QLineEdit::eventFilter(watched, event); } + +void SearchLineEdit::keyPressEvent(QKeyEvent *event) +{ + // List of key events that must be handled by the card list instead of the search box + static const QVector forwardToTreeView = {Qt::Key_Up, Qt::Key_Down, Qt::Key_PageDown, Qt::Key_PageUp}; + // forward only if the search text is empty + static const QVector forwardWhenEmpty = {Qt::Key_Home, Qt::Key_End}; + Qt::Key key = static_cast(event->key()); + if (treeView) { + if (forwardToTreeView.contains(key)) + QCoreApplication::sendEvent(treeView, event); + if (text().isEmpty() && forwardWhenEmpty.contains(key)) + QCoreApplication::sendEvent(treeView, event); + } + LineEditUnfocusable::keyPressEvent(event); +} \ No newline at end of file diff --git a/cockatrice/src/deck/custom_line_edit.h b/cockatrice/src/deck/custom_line_edit.h index 510c0f3da..075ec012e 100644 --- a/cockatrice/src/deck/custom_line_edit.h +++ b/cockatrice/src/deck/custom_line_edit.h @@ -4,6 +4,7 @@ #include +class QTreeView; class QKeyEvent; class QWidget; class QString; @@ -25,4 +26,22 @@ protected: bool eventFilter(QObject *watched, QEvent *event) override; }; +class SearchLineEdit : public LineEditUnfocusable +{ +private: + QTreeView *treeView; + +protected: + void keyPressEvent(QKeyEvent *event) override; + +public: + SearchLineEdit() : treeView(nullptr) + { + } + void setTreeView(QTreeView *_treeView) + { + treeView = _treeView; + } +}; + #endif diff --git a/cockatrice/src/deck/deck_list_model.cpp b/cockatrice/src/deck/deck_list_model.cpp index a33e72c7e..0005777c9 100644 --- a/cockatrice/src/deck/deck_list_model.cpp +++ b/cockatrice/src/deck/deck_list_model.cpp @@ -1,13 +1,11 @@ #include "deck_list_model.h" -#include "../game/cards/card_database.h" #include "../game/cards/card_database_manager.h" #include "../main.h" #include "../settings/cache_settings.h" #include "deck_loader.h" #include -#include #include #include #include @@ -20,15 +18,15 @@ DeckListModel::DeckListModel(QObject *parent) : QAbstractItemModel(parent), lastKnownColumn(1), lastKnownOrder(Qt::AscendingOrder) { deckList = new DeckLoader; - connect(deckList, SIGNAL(deckLoaded()), this, SLOT(rebuildTree())); - connect(deckList, SIGNAL(deckHashChanged()), this, SIGNAL(deckHashChanged())); + deckList->setParent(this); + connect(deckList, &DeckLoader::deckLoaded, this, &DeckListModel::rebuildTree); + connect(deckList, &DeckLoader::deckHashChanged, this, &DeckListModel::deckHashChanged); root = new InnerDecklistNode; } DeckListModel::~DeckListModel() { delete root; - delete deckList; } void DeckListModel::rebuildTree() @@ -264,7 +262,7 @@ bool DeckListModel::setData(const QModelIndex &index, const QVariant &value, con } emitRecursiveUpdates(index); - deckList->updateDeckHash(); + deckList->refreshDeckHash(); return true; } @@ -309,8 +307,10 @@ InnerDecklistNode *DeckListModel::createNodeIfNeeded(const QString &name, InnerD return newNode; } -DecklistModelCardNode * -DeckListModel::findCardNode(const QString &cardName, const QString &zoneName, const QString &providerId) const +DecklistModelCardNode *DeckListModel::findCardNode(const QString &cardName, + const QString &zoneName, + const QString &providerId, + const QString &cardNumber) const { InnerDecklistNode *zoneNode, *typeNode; CardInfoPtr info; @@ -332,17 +332,18 @@ DeckListModel::findCardNode(const QString &cardName, const QString &zoneName, co return nullptr; } - if (providerId.isEmpty()) { - return dynamic_cast(typeNode->findChild(cardName)); - } - return dynamic_cast(typeNode->findCardChildByNameAndProviderId(cardName, providerId)); + return dynamic_cast( + typeNode->findCardChildByNameProviderIdAndNumber(cardName, providerId, cardNumber)); } -QModelIndex DeckListModel::findCard(const QString &cardName, const QString &zoneName, const QString &providerId) const +QModelIndex DeckListModel::findCard(const QString &cardName, + const QString &zoneName, + const QString &providerId, + const QString &cardNumber) const { DecklistModelCardNode *cardNode; - cardNode = findCardNode(cardName, zoneName, providerId); + cardNode = findCardNode(cardName, zoneName, providerId, cardNumber); if (!cardNode) { return {}; } @@ -357,7 +358,7 @@ QModelIndex DeckListModel::addPreferredPrintingCard(const QString &cardName, con } QModelIndex DeckListModel::addCard(const QString &cardName, - const CardInfoPerSet cardInfoSet, + const CardInfoPerSet &cardInfoSet, const QString &zoneName, bool abAddAnyway) { @@ -382,21 +383,22 @@ QModelIndex DeckListModel::addCard(const QString &cardName, InnerDecklistNode *cardTypeNode = createNodeIfNeeded(cardType, zoneNode); const QModelIndex parentIndex = nodeToIndex(cardTypeNode); - auto *cardNode = dynamic_cast( - cardTypeNode->findCardChildByNameAndProviderId(cardName, cardInfoSet.getProperty("uuid"))); + auto *cardNode = dynamic_cast(cardTypeNode->findCardChildByNameProviderIdAndNumber( + cardName, cardInfoSet.getProperty("uuid"), cardInfoSet.getProperty("num"))); + const auto cardSetName = cardInfoSet.getPtr().isNull() ? "" : cardInfoSet.getPtr()->getCorrectedShortName(); + if (!cardNode) { - auto *decklistCard = - deckList->addCard(cardInfo->getName(), zoneName, cardInfoSet.getPtr()->getCorrectedShortName(), - cardInfoSet.getProperty("num"), cardInfoSet.getProperty("uuid")); + auto *decklistCard = deckList->addCard(cardInfo->getName(), zoneName, cardSetName, + cardInfoSet.getProperty("num"), cardInfoSet.getProperty("uuid")); beginInsertRows(parentIndex, static_cast(cardTypeNode->size()), static_cast(cardTypeNode->size())); cardNode = new DecklistModelCardNode(decklistCard, cardTypeNode); endInsertRows(); } else { cardNode->setNumber(cardNode->getNumber() + 1); - cardNode->setCardSetShortName(cardInfoSet.getPtr()->getCorrectedShortName()); + cardNode->setCardSetShortName(cardSetName); cardNode->setCardCollectorNumber(cardInfoSet.getProperty("num")); cardNode->setCardProviderId(cardInfoSet.getProperty("uuid")); - deckList->updateDeckHash(); + deckList->refreshDeckHash(); } sort(lastKnownColumn, lastKnownOrder); emitRecursiveUpdates(parentIndex); @@ -468,12 +470,16 @@ void DeckListModel::cleanList() setDeckList(new DeckLoader); } +/** + * @param _deck The deck. Takes ownership of the object + */ void DeckListModel::setDeckList(DeckLoader *_deck) { - delete deckList; + deckList->deleteLater(); deckList = _deck; - connect(deckList, SIGNAL(deckLoaded()), this, SLOT(rebuildTree())); - connect(deckList, SIGNAL(deckHashChanged()), this, SIGNAL(deckHashChanged())); + deckList->setParent(this); + connect(deckList, &DeckLoader::deckLoaded, this, &DeckListModel::rebuildTree); + connect(deckList, &DeckLoader::deckHashChanged, this, &DeckListModel::deckHashChanged); rebuildTree(); } diff --git a/cockatrice/src/deck/deck_list_model.h b/cockatrice/src/deck/deck_list_model.h index 24eaea612..c7194a5da 100644 --- a/cockatrice/src/deck/deck_list_model.h +++ b/cockatrice/src/deck/deck_list_model.h @@ -66,6 +66,10 @@ public: { return dataNode; } + [[nodiscard]] bool isDeckHeader() const override + { + return false; + } }; class DeckListModel : public QAbstractItemModel @@ -90,10 +94,15 @@ public: Qt::ItemFlags flags(const QModelIndex &index) const override; bool setData(const QModelIndex &index, const QVariant &value, int role) override; bool removeRows(int row, int count, const QModelIndex &parent) override; - QModelIndex findCard(const QString &cardName, const QString &zoneName, const QString &providerId = "") const; + QModelIndex findCard(const QString &cardName, + const QString &zoneName, + const QString &providerId = "", + const QString &cardNumber = "") const; QModelIndex addPreferredPrintingCard(const QString &cardName, const QString &zoneName, bool abAddAnyway); - QModelIndex - addCard(const ::QString &cardName, CardInfoPerSet cardInfoSet, const QString &zoneName, bool abAddAnyway = false); + QModelIndex addCard(const ::QString &cardName, + const CardInfoPerSet &cardInfoSet, + const QString &zoneName, + bool abAddAnyway = false); void sort(int column, Qt::SortOrder order) override; void cleanList(); DeckLoader *getDeckList() const @@ -109,8 +118,10 @@ private: Qt::SortOrder lastKnownOrder; InnerDecklistNode *createNodeIfNeeded(const QString &name, InnerDecklistNode *parent); QModelIndex nodeToIndex(AbstractDecklistNode *node) const; - DecklistModelCardNode * - findCardNode(const QString &cardName, const QString &zoneName, const QString &providerId = "") const; + DecklistModelCardNode *findCardNode(const QString &cardName, + const QString &zoneName, + const QString &providerId = "", + const QString &cardNumber = "") const; void emitRecursiveUpdates(const QModelIndex &index); void sortHelper(InnerDecklistNode *node, Qt::SortOrder order); diff --git a/cockatrice/src/deck/deck_loader.cpp b/cockatrice/src/deck/deck_loader.cpp index 685c033a3..1c7136bf5 100644 --- a/cockatrice/src/deck/deck_loader.cpp +++ b/cockatrice/src/deck/deck_loader.cpp @@ -5,14 +5,21 @@ #include "../main.h" #include "decklist.h" +#include +#include #include +#include #include +#include +#include #include #include +#include -const QStringList DeckLoader::fileNameFilters = QStringList() - << QObject::tr("Common deck formats (*.cod *.dec *.dek *.txt *.mwDeck)") - << QObject::tr("All files (*.*)"); +const QStringList DeckLoader::ACCEPTED_FILE_EXTENSIONS = {"*.cod", "*.dec", "*.dek", "*.txt", "*.mwDeck"}; + +const QStringList DeckLoader::FILE_NAME_FILTERS = { + tr("Common deck formats (%1)").arg(ACCEPTED_FILE_EXTENSIONS.join(" ")), tr("All files (*.*)")}; DeckLoader::DeckLoader() : DeckList(), lastFileName(QString()), lastFileFormat(CockatriceFormat), lastRemoteDeckId(-1) { @@ -34,7 +41,7 @@ DeckLoader::DeckLoader(const DeckLoader &other) { } -bool DeckLoader::loadFromFile(const QString &fileName, FileFormat fmt) +bool DeckLoader::loadFromFile(const QString &fileName, FileFormat fmt, bool userRequest) { QFile file(fileName); if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { @@ -48,9 +55,9 @@ bool DeckLoader::loadFromFile(const QString &fileName, FileFormat fmt) break; case CockatriceFormat: { result = loadFromFile_Native(&file); - qDebug() << "Loaded from" << fileName << "-" << result; + qCInfo(DeckLoaderLog) << "Loaded from" << fileName << "-" << result; if (!result) { - qDebug() << "Retying as plain format"; + qCInfo(DeckLoaderLog) << "Retrying as plain format"; file.seek(0); result = loadFromFile_Plain(&file); fmt = PlainTextFormat; @@ -65,14 +72,65 @@ bool DeckLoader::loadFromFile(const QString &fileName, FileFormat fmt) if (result) { lastFileName = fileName; lastFileFormat = fmt; + if (userRequest) { + updateLastLoadedTimestamp(fileName, fmt); + } emit deckLoaded(); } - qDebug() << "Deck was loaded -" << result; + qCInfo(DeckLoaderLog) << "Deck was loaded -" << result; return result; } +bool DeckLoader::loadFromFileAsync(const QString &fileName, FileFormat fmt, bool userRequest) +{ + auto *watcher = new QFutureWatcher(this); + + connect(watcher, &QFutureWatcher::finished, this, [this, watcher, fileName, fmt, userRequest]() { + const bool result = watcher->result(); + watcher->deleteLater(); + + if (result) { + lastFileName = fileName; + lastFileFormat = fmt; + if (userRequest) { + updateLastLoadedTimestamp(fileName, fmt); + } + emit deckLoaded(); + } + + emit loadFinished(result); + }); + + QFuture future = QtConcurrent::run([=, this]() { + QFile file(fileName); + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { + return false; + } + + switch (fmt) { + case PlainTextFormat: + return loadFromFile_Plain(&file); + case CockatriceFormat: { + bool result = false; + result = loadFromFile_Native(&file); + if (!result) { + file.seek(0); + return loadFromFile_Plain(&file); + } + return result; + } + default: + return false; + break; + } + }); + + watcher->setFuture(future); + return true; // Return immediately to indicate the async task was started +} + bool DeckLoader::loadFromRemote(const QString &nativeString, int remoteDeckId) { bool result = loadFromString_Native(nativeString); @@ -107,9 +165,80 @@ bool DeckLoader::saveToFile(const QString &fileName, FileFormat fmt) lastFileName = fileName; lastFileFormat = fmt; } + + file.flush(); + file.close(); + return result; } +bool DeckLoader::updateLastLoadedTimestamp(const QString &fileName, FileFormat fmt) +{ + QFileInfo fileInfo(fileName); + if (!fileInfo.exists()) { + qCWarning(DeckLoaderLog) << "File does not exist:" << fileName; + return false; + } + + QDateTime originalTimestamp = fileInfo.lastModified(); + + // Open the file for writing + QFile file(fileName); + if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { + qCWarning(DeckLoaderLog) << "Failed to open file for writing:" << fileName; + return false; + } + + bool result = false; + + // Perform file modifications + switch (fmt) { + case PlainTextFormat: + result = saveToFile_Plain(&file); + break; + case CockatriceFormat: + setLastLoadedTimestamp(QDateTime::currentDateTime().toString()); + result = saveToFile_Native(&file); + break; + } + + file.close(); // Close the file to ensure changes are flushed + + if (result) { + lastFileName = fileName; + lastFileFormat = fmt; + + // Re-open the file and set the original timestamp + if (!file.open(QIODevice::ReadWrite)) { + qCWarning(DeckLoaderLog) << "Failed to re-open file to set timestamp:" << fileName; + return false; + } + + if (!file.setFileTime(originalTimestamp, QFileDevice::FileModificationTime)) { + qCWarning(DeckLoaderLog) << "Failed to set modification time for file:" << fileName; + file.close(); + return false; + } + + file.close(); + } + + return result; +} + +static QString getDomainForWebsite(DeckLoader::DecklistWebsite website) +{ + switch (website) { + case DeckLoader::DecklistOrg: + return "www.decklist.org"; + case DeckLoader::DecklistXyz: + return "www.decklist.xyz"; + default: + qCWarning(DeckLoaderLog) << "Invalid decklist website enum:" << website; + return ""; + } +} + // This struct is here to support the forEachCard function call, defined in decklist. It // requires a function to be called for each card, and passes an inner node and a card for // each card in the decklist. @@ -146,16 +275,27 @@ struct FormatDeckListForExport mainBoardCards += QString::number(card->getNumber()); mainBoardCards += "%20"; mainBoardCards += card->getName(); + if (!card->getCardSetShortName().isNull()) { + mainBoardCards += "%20"; + mainBoardCards += "(" + card->getCardSetShortName() + ")"; + } + if (!card->getCardCollectorNumber().isNull()) { + mainBoardCards += "%20"; + mainBoardCards += card->getCardCollectorNumber(); + } mainBoardCards += "%0A"; } } }; -// Export deck to decklist function, called to format the deck in a way to be sent to a server -QString DeckLoader::exportDeckToDecklist() +/** + * Export deck to decklist function, called to format the deck in a way to be sent to a server + * @param website The website we're sending the deck to + */ +QString DeckLoader::exportDeckToDecklist(DecklistWebsite website) { // Add the base url - QString deckString = "https://www.decklist.org/?"; + QString deckString = "https://" + getDomainForWebsite(website) + "/?"; // Create two strings to pass to function QString mainBoardCards, sideBoardCards; // Set up the struct to call. @@ -176,6 +316,73 @@ QString DeckLoader::exportDeckToDecklist() return deckString; } +// This struct is here to support the forEachCard function call, defined in decklist. +// It requires a function to be called for each card, and it will set the providerId. +struct SetProviderId +{ + // Main operator for struct, allowing the foreachcard to work. + SetProviderId() + { + } + + void operator()(const InnerDecklistNode *node, DecklistCardNode *card) const + { + Q_UNUSED(node); + // Retrieve the providerId based on setName and collectorNumber + QString providerId = + CardDatabaseManager::getInstance() + ->getSpecificSetForCard(card->getName(), card->getCardSetShortName(), card->getCardCollectorNumber()) + .getProperty("uuid"); + + // Set the providerId on the card + card->setCardProviderId(providerId); + } +}; + +/** + * This function iterates through each card in the decklist and sets the providerId + * on each card based on its set name and collector number. + */ +void DeckLoader::resolveSetNameAndNumberToProviderID() +{ + // Set up the struct to call. + SetProviderId setProviderId; + + // Call the forEachCard method for each card in the deck + forEachCard(setProviderId); +} + +// This struct is here to support the forEachCard function call, defined in decklist. +// It requires a function to be called for each card, and it will set the providerId. +struct ClearSetNameAndNumber +{ + // Main operator for struct, allowing the foreachcard to work. + ClearSetNameAndNumber() + { + } + + void operator()(const InnerDecklistNode *node, DecklistCardNode *card) const + { + Q_UNUSED(node); + // Set the providerId on the card + card->setCardSetShortName(nullptr); + card->setCardCollectorNumber(nullptr); + } +}; + +/** + * This function iterates through each card in the decklist and sets the providerId + * on each card based on its set name and collector number. + */ +void DeckLoader::clearSetNamesAndNumbers() +{ + // Set up the struct to call. + ClearSetNameAndNumber clearSetNameAndNumber; + + // Call the forEachCard method for each card in the deck + forEachCard(clearSetNameAndNumber); +} + DeckLoader::FileFormat DeckLoader::getFormatFromName(const QString &fileName) { if (fileName.endsWith(".cod", Qt::CaseInsensitive)) { @@ -184,7 +391,16 @@ DeckLoader::FileFormat DeckLoader::getFormatFromName(const QString &fileName) return PlainTextFormat; } -bool DeckLoader::saveToStream_Plain(QTextStream &out, bool addComments) +void DeckLoader::saveToClipboard(bool addComments, bool addSetNameAndNumber) const +{ + QString buffer; + QTextStream stream(&buffer); + saveToStream_Plain(stream, addComments, addSetNameAndNumber); + QApplication::clipboard()->setText(buffer, QClipboard::Clipboard); + QApplication::clipboard()->setText(buffer, QClipboard::Selection); +} + +bool DeckLoader::saveToStream_Plain(QTextStream &out, bool addComments, bool addSetNameAndNumber) const { if (addComments) { saveToStream_DeckHeader(out); @@ -194,7 +410,7 @@ bool DeckLoader::saveToStream_Plain(QTextStream &out, bool addComments) for (int i = 0; i < getRoot()->size(); i++) { const auto *zoneNode = dynamic_cast(getRoot()->at(i)); - saveToStream_DeckZone(out, zoneNode, addComments); + saveToStream_DeckZone(out, zoneNode, addComments, addSetNameAndNumber); // end of zone out << "\n"; @@ -203,7 +419,7 @@ bool DeckLoader::saveToStream_Plain(QTextStream &out, bool addComments) return true; } -void DeckLoader::saveToStream_DeckHeader(QTextStream &out) +void DeckLoader::saveToStream_DeckHeader(QTextStream &out) const { if (!getName().isEmpty()) { out << "// " << getName() << "\n\n"; @@ -211,14 +427,17 @@ void DeckLoader::saveToStream_DeckHeader(QTextStream &out) if (!getComments().isEmpty()) { QStringList commentRows = getComments().split(QRegularExpression("\n|\r\n|\r")); - foreach (QString row, commentRows) { + for (const QString &row : commentRows) { out << "// " << row << "\n"; } out << "\n"; } } -void DeckLoader::saveToStream_DeckZone(QTextStream &out, const InnerDecklistNode *zoneNode, bool addComments) +void DeckLoader::saveToStream_DeckZone(QTextStream &out, + const InnerDecklistNode *zoneNode, + bool addComments, + bool addSetNameAndNumber) const { // group cards by card type and count the subtotals QMultiMap cardsByType; @@ -247,14 +466,14 @@ void DeckLoader::saveToStream_DeckZone(QTextStream &out, const InnerDecklistNode } // print cards to stream - foreach (QString cardType, cardsByType.uniqueKeys()) { + for (const QString &cardType : cardsByType.uniqueKeys()) { if (addComments) { out << "// " << cardTotalByType[cardType] << " " << cardType << "\n"; } QList cards = cardsByType.values(cardType); - saveToStream_DeckZoneCards(out, zoneNode, cards, addComments); + saveToStream_DeckZoneCards(out, zoneNode, cards, addComments, addSetNameAndNumber); if (addComments) { out << "\n"; @@ -265,7 +484,8 @@ void DeckLoader::saveToStream_DeckZone(QTextStream &out, const InnerDecklistNode void DeckLoader::saveToStream_DeckZoneCards(QTextStream &out, const InnerDecklistNode *zoneNode, QList cards, - bool addComments) + bool addComments, + bool addSetNameAndNumber) const { // QMultiMap sorts values in reverse order for (int i = cards.size() - 1; i >= 0; --i) { @@ -275,10 +495,72 @@ void DeckLoader::saveToStream_DeckZoneCards(QTextStream &out, out << "SB: "; } - out << card->getNumber() << " " << card->getName() << "\n"; + if (card->getNumber()) { + out << card->getNumber(); + } + if (!card->getName().isNull() && !card->getName().isEmpty()) { + out << " " << card->getName(); + } + if (addSetNameAndNumber) { + if (!card->getCardSetShortName().isNull() && !card->getCardSetShortName().isEmpty()) { + out << " " + << "(" << card->getCardSetShortName() << ")"; + } + if (!card->getCardCollectorNumber().isNull()) { + out << " " << card->getCardCollectorNumber(); + } + } + out << "\n"; } } +bool DeckLoader::convertToCockatriceFormat(QString fileName) +{ + // Change the file extension to .cod + QFileInfo fileInfo(fileName); + QString newFileName = QDir::toNativeSeparators(fileInfo.path() + "/" + fileInfo.completeBaseName() + ".cod"); + + // Open the new file for writing + QFile file(newFileName); + if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { + qCWarning(DeckLoaderLog) << "Failed to open file for writing:" << newFileName; + return false; + } + + bool result = false; + + // Perform file modifications based on the detected format + switch (getFormatFromName(fileName)) { + case PlainTextFormat: + // Save in Cockatrice's native format + result = saveToFile_Native(&file); + break; + case CockatriceFormat: + qCInfo(DeckLoaderLog) << "File is already in Cockatrice format. No conversion needed."; + result = true; + break; + default: + qCWarning(DeckLoaderLog) << "Unsupported file format for conversion:" << fileName; + result = false; + break; + } + + file.close(); + + // Delete the old file if conversion was successful + if (result) { + if (!QFile::remove(fileName)) { + qCWarning(DeckLoaderLog) << "Failed to delete original file:" << fileName; + } else { + qCInfo(DeckLoaderLog) << "Original file deleted successfully:" << fileName; + } + lastFileName = newFileName; + lastFileFormat = CockatriceFormat; + } + + return result; +} + QString DeckLoader::getCardZoneFromName(QString cardName, QString currentZoneName) { CardInfoPtr card = CardDatabaseManager::getInstance()->getCard(cardName); diff --git a/cockatrice/src/deck/deck_loader.h b/cockatrice/src/deck/deck_loader.h index 61b695335..53dd1f711 100644 --- a/cockatrice/src/deck/deck_loader.h +++ b/cockatrice/src/deck/deck_loader.h @@ -3,11 +3,16 @@ #include "decklist.h" -class DeckLoader : public DeckList +#include + +inline Q_LOGGING_CATEGORY(DeckLoaderLog, "deck_loader") + + class DeckLoader : public DeckList { Q_OBJECT signals: void deckLoaded(); + void loadFinished(bool success); public: enum FileFormat @@ -15,7 +20,22 @@ public: PlainTextFormat, CockatriceFormat }; - static const QStringList fileNameFilters; + + /** + * Supported file extensions for decklist files + */ + static const QStringList ACCEPTED_FILE_EXTENSIONS; + + /** + * For use with `QFileDialog::setNameFilters` + */ + static const QStringList FILE_NAME_FILTERS; + + enum DecklistWebsite + { + DecklistOrg, + DecklistXyz + }; private: QString lastFileName; @@ -24,13 +44,17 @@ private: public: DeckLoader(); - DeckLoader(const QString &nativeString); - DeckLoader(const DeckList &other); + explicit DeckLoader(const QString &nativeString); + explicit DeckLoader(const DeckList &other); DeckLoader(const DeckLoader &other); const QString &getLastFileName() const { return lastFileName; } + void setLastFileName(const QString &_lastFileName) + { + lastFileName = _lastFileName; + } FileFormat getLastFileFormat() const { return lastFileFormat; @@ -40,23 +64,40 @@ public: return lastRemoteDeckId; } + bool hasNotBeenLoaded() const + { + return getLastFileName().isEmpty() && getLastRemoteDeckId() == -1; + } + + void clearSetNamesAndNumbers(); static FileFormat getFormatFromName(const QString &fileName); - bool loadFromFile(const QString &fileName, FileFormat fmt); + bool loadFromFile(const QString &fileName, FileFormat fmt, bool userRequest = false); + bool loadFromFileAsync(const QString &fileName, FileFormat fmt, bool userRequest); bool loadFromRemote(const QString &nativeString, int remoteDeckId); bool saveToFile(const QString &fileName, FileFormat fmt); - QString exportDeckToDecklist(); + bool updateLastLoadedTimestamp(const QString &fileName, FileFormat fmt); + QString exportDeckToDecklist(DecklistWebsite website); + + void resolveSetNameAndNumberToProviderID(); + + void saveToClipboard(bool addComments = true, bool addSetNameAndNumber = true) const; // overload - bool saveToStream_Plain(QTextStream &out, bool addComments = true); + bool saveToStream_Plain(QTextStream &out, bool addComments = true, bool addSetNameAndNumber = true) const; + bool convertToCockatriceFormat(QString fileName); protected: - void saveToStream_DeckHeader(QTextStream &out); - void saveToStream_DeckZone(QTextStream &out, const InnerDecklistNode *zoneNode, bool addComments = true); + void saveToStream_DeckHeader(QTextStream &out) const; + void saveToStream_DeckZone(QTextStream &out, + const InnerDecklistNode *zoneNode, + bool addComments = true, + bool addSetNameAndNumber = true) const; void saveToStream_DeckZoneCards(QTextStream &out, const InnerDecklistNode *zoneNode, QList cards, - bool addComments = true); + bool addComments = true, + bool addSetNameAndNumber = true) const; [[nodiscard]] QString getCardZoneFromName(QString cardName, QString currentZoneName) override; [[nodiscard]] QString getCompleteCardName(const QString &cardName) const override; }; diff --git a/cockatrice/src/deck/deck_stats_interface.cpp b/cockatrice/src/deck/deck_stats_interface.cpp index 038565370..dc2ab14df 100644 --- a/cockatrice/src/deck/deck_stats_interface.cpp +++ b/cockatrice/src/deck/deck_stats_interface.cpp @@ -14,7 +14,7 @@ DeckStatsInterface::DeckStatsInterface(CardDatabase &_cardDatabase, QObject *par : QObject(parent), cardDatabase(_cardDatabase) { manager = new QNetworkAccessManager(this); - connect(manager, SIGNAL(finished(QNetworkReply *)), this, SLOT(queryFinished(QNetworkReply *))); + connect(manager, &QNetworkAccessManager::finished, this, &DeckStatsInterface::queryFinished); } void DeckStatsInterface::queryFinished(QNetworkReply *reply) @@ -85,7 +85,7 @@ struct CopyIfNotAToken } }; -void DeckStatsInterface::copyDeckWithoutTokens(const DeckList &source, DeckList &destination) +void DeckStatsInterface::copyDeckWithoutTokens(DeckList &source, DeckList &destination) { CopyIfNotAToken copyIfNotAToken(cardDatabase, destination); source.forEachCard(copyIfNotAToken); diff --git a/cockatrice/src/deck/deck_stats_interface.h b/cockatrice/src/deck/deck_stats_interface.h index 4798ee67b..c3611df81 100644 --- a/cockatrice/src/deck/deck_stats_interface.h +++ b/cockatrice/src/deck/deck_stats_interface.h @@ -24,14 +24,14 @@ private: * closest non-token card instead. So we construct a new deck which has no * tokens. */ - void copyDeckWithoutTokens(const DeckList &source, DeckList &destination); + void copyDeckWithoutTokens(DeckList &source, DeckList &destination); private slots: void queryFinished(QNetworkReply *reply); void getAnalyzeRequestData(DeckList *deck, QByteArray *data); public: - DeckStatsInterface(CardDatabase &_cardDatabase, QObject *parent = nullptr); + explicit DeckStatsInterface(CardDatabase &_cardDatabase, QObject *parent = nullptr); void analyzeDeck(DeckList *deck); }; diff --git a/cockatrice/src/dialogs/dlg_connect.cpp b/cockatrice/src/dialogs/dlg_connect.cpp index d618d3ae4..496e26326 100644 --- a/cockatrice/src/dialogs/dlg_connect.cpp +++ b/cockatrice/src/dialogs/dlg_connect.cpp @@ -1,8 +1,6 @@ #include "dlg_connect.h" -#include "../server/user/user_info_connection.h" #include "../settings/cache_settings.h" -#include "../utility/macros.h" #include "trice_limits.h" #include @@ -22,7 +20,13 @@ DlgConnect::DlgConnect(QWidget *parent) : QDialog(parent) { previousHostButton = new QRadioButton(tr("Known Hosts"), this); previousHosts = new QComboBox(this); - previousHosts->installEventFilter(new DeleteHighlightedItemWhenShiftDelPressedEventFilter); + + btnDeleteServer = new QPushButton(this); + btnDeleteServer->setIcon(QPixmap("theme:icons/remove_row")); + btnDeleteServer->setToolTip(tr("Delete the currently selected saved server")); + btnDeleteServer->setFixedWidth(30); + + connect(btnDeleteServer, &QPushButton::clicked, this, &DlgConnect::actRemoveSavedServer); hps = new HandlePublicServers(this); btnRefreshServers = new QPushButton(this); @@ -80,8 +84,8 @@ DlgConnect::DlgConnect(QWidget *parent) : QDialog(parent) autoConnectCheckBox->setEnabled(false); } - connect(savePasswordCheckBox, SIGNAL(QT_STATE_CHANGED(QT_STATE_CHANGED_T)), this, - SLOT(passwordSaved(QT_STATE_CHANGED_T))); + connect(savePasswordCheckBox, &QCheckBox::QT_STATE_CHANGED, this, &DlgConnect::passwordSaved); + connect(autoConnectCheckBox, &QCheckBox::QT_STATE_CHANGED, &servers, &ServersSettings::setAutoConnect); serverIssuesLabel = new QLabel(tr("If you have any trouble connecting or registering then contact the server staff for help!")); @@ -100,6 +104,9 @@ DlgConnect::DlgConnect(QWidget *parent) : QDialog(parent) btnForgotPassword->setFixedWidth(30); connect(btnForgotPassword, SIGNAL(released()), this, SLOT(actForgotPassword())); + forgotPasswordLabel = new QLabel(tr("Forgot password?")); + forgotPasswordLabel->setBuddy(btnForgotPassword); + btnConnect = new QPushButton(tr("&Connect")); connect(btnConnect, SIGNAL(released()), this, SLOT(actOk())); @@ -109,6 +116,7 @@ DlgConnect::DlgConnect(QWidget *parent) : QDialog(parent) newHolderLayout = new QHBoxLayout; newHolderLayout->addWidget(previousHosts); + newHolderLayout->addWidget(btnDeleteServer); newHolderLayout->addWidget(btnRefreshServers); connectionLayout = new QGridLayout; @@ -131,13 +139,17 @@ DlgConnect::DlgConnect(QWidget *parent) : QDialog(parent) serverInfoLayout->addWidget(serverContactLabel, 1, 0); serverInfoLayout->addWidget(serverContactLink, 1, 1, 1, 3); + forgotPasswordLayout = new QHBoxLayout; + forgotPasswordLayout->addWidget(forgotPasswordLabel, 0, Qt::AlignLeft); + forgotPasswordLayout->addWidget(btnForgotPassword, 0, Qt::AlignLeft); + loginLayout = new QGridLayout; loginLayout->addWidget(playernameLabel, 0, 0); loginLayout->addWidget(playernameEdit, 0, 1, 1, 2); loginLayout->addWidget(passwordLabel, 1, 0); - loginLayout->addWidget(passwordEdit, 1, 1); - loginLayout->addWidget(btnForgotPassword, 1, 2); + loginLayout->addWidget(passwordEdit, 1, 1, 1, 2); loginLayout->addWidget(savePasswordCheckBox, 2, 1); + loginLayout->addLayout(forgotPasswordLayout, 3, 1); loginGroupBox = new QGroupBox(tr("Login")); loginGroupBox->setLayout(loginLayout); @@ -249,6 +261,16 @@ void DlgConnect::updateDisplayInfo(const QString &saveName) UserConnection_Information uci; QStringList _data = uci.getServerInfo(saveName); + if (_data.isEmpty()) { + _data << "" + << "" + << "" + << "" + << "" + << "" + << ""; + } + bool savePasswordStatus = (_data.at(5) == "1"); saveEdit->setText(_data.at(0)); @@ -324,7 +346,6 @@ void DlgConnect::actOk() } servers.setPrevioushostName(saveEdit->text()); - servers.setAutoConnect(autoConnectCheckBox->isChecked()); if (playernameEdit->text().isEmpty()) { QMessageBox::critical(this, tr("Connect Warning"), tr("The player name can't be empty.")); @@ -339,21 +360,6 @@ QString DlgConnect::getHost() const return hostEdit->text().trimmed(); } -bool DeleteHighlightedItemWhenShiftDelPressedEventFilter::eventFilter(QObject *obj, QEvent *event) -{ - if (event->type() == QEvent::KeyPress) { - auto *keyEvent = dynamic_cast(event); - - if (keyEvent->key() == Qt::Key_Delete) { - auto *combobox = reinterpret_cast(obj); - combobox->removeItem(combobox->currentIndex()); - return true; - } - } - - return QObject::eventFilter(obj, event); -} - void DlgConnect::actForgotPassword() { ServersSettings &servers = SettingsCache::instance().servers(); @@ -362,5 +368,10 @@ void DlgConnect::actForgotPassword() servers.setFPPlayerName(playernameEdit->text().trimmed()); emit sigStartForgotPasswordRequest(); - reject(); +} + +void DlgConnect::actRemoveSavedServer() +{ + SettingsCache::instance().servers().removeServer(hostEdit->text()); + previousHosts->removeItem(previousHosts->currentIndex()); } \ No newline at end of file diff --git a/cockatrice/src/dialogs/dlg_connect.h b/cockatrice/src/dialogs/dlg_connect.h index d91b1cc93..119e3b816 100644 --- a/cockatrice/src/dialogs/dlg_connect.h +++ b/cockatrice/src/dialogs/dlg_connect.h @@ -18,13 +18,6 @@ class QPushButton; class QRadioButton; class QVBoxLayout; -class DeleteHighlightedItemWhenShiftDelPressedEventFilter : public QObject -{ - Q_OBJECT -protected: - bool eventFilter(QObject *obj, QEvent *event) override; -}; - class DlgConnect : public QDialog { Q_OBJECT @@ -59,22 +52,23 @@ private slots: void previousHostSelected(bool state); void newHostSelected(bool state); void actForgotPassword(); + void actRemoveSavedServer(); void updateDisplayInfo(const QString &saveName); void preRebuildComboBoxList(); void rebuildComboBoxList(int failure = -1); private: QGridLayout *connectionLayout, *loginLayout, *serverInfoLayout, *grid; - QHBoxLayout *newHolderLayout; + QHBoxLayout *newHolderLayout, *forgotPasswordLayout; QGroupBox *loginGroupBox, *serverInfoGroupBox, *restrictionsGroupBox; QVBoxLayout *mainLayout; QLabel *hostLabel, *portLabel, *playernameLabel, *passwordLabel, *saveLabel, *serverIssuesLabel, - *serverContactLabel, *serverContactLink; + *serverContactLabel, *serverContactLink, *forgotPasswordLabel; QLineEdit *hostEdit, *portEdit, *playernameEdit, *passwordEdit, *saveEdit; QCheckBox *savePasswordCheckBox, *autoConnectCheckBox; QComboBox *previousHosts; QRadioButton *newHostButton, *previousHostButton; - QPushButton *btnConnect, *btnForgotPassword, *btnRefreshServers; + QPushButton *btnConnect, *btnForgotPassword, *btnRefreshServers, *btnDeleteServer; QMap> savedHostList; HandlePublicServers *hps; const QString placeHolderText = tr("Downloading..."); diff --git a/cockatrice/src/dialogs/dlg_convert_deck_to_cod_format.cpp b/cockatrice/src/dialogs/dlg_convert_deck_to_cod_format.cpp new file mode 100644 index 000000000..198fa259b --- /dev/null +++ b/cockatrice/src/dialogs/dlg_convert_deck_to_cod_format.cpp @@ -0,0 +1,40 @@ +#include "dlg_convert_deck_to_cod_format.h" + +#include +#include +#include +#include + +DialogConvertDeckToCodFormat::DialogConvertDeckToCodFormat(QWidget *parent) : QDialog(parent) +{ + layout = new QVBoxLayout(this); + label = new QLabel(); + layout->addWidget(label); + + dontAskAgainCheckbox = new QCheckBox(this); + layout->addWidget(dontAskAgainCheckbox); + + buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); + layout->addWidget(buttonBox); + + connect(buttonBox, &QDialogButtonBox::accepted, this, [this]() { accept(); }); + + connect(buttonBox, &QDialogButtonBox::rejected, this, [this]() { reject(); }); + + setLayout(layout); + retranslateUi(); +} + +void DialogConvertDeckToCodFormat::retranslateUi() +{ + setWindowTitle(tr("Deck Format Conversion")); + label->setText( + tr("You tried to add a tag to a .txt format deck.\n Tags can only be added to .cod format decks.\n Do " + "you want to convert the deck to the .cod format?")); + dontAskAgainCheckbox->setText(tr("Remember and automatically apply choice in the future")); +} + +bool DialogConvertDeckToCodFormat::dontAskAgain() const +{ + return dontAskAgainCheckbox->isChecked(); +} diff --git a/cockatrice/src/dialogs/dlg_convert_deck_to_cod_format.h b/cockatrice/src/dialogs/dlg_convert_deck_to_cod_format.h new file mode 100644 index 000000000..88a19591e --- /dev/null +++ b/cockatrice/src/dialogs/dlg_convert_deck_to_cod_format.h @@ -0,0 +1,29 @@ +#ifndef DIALOG_CONVERT_DECK_TO_COD_FORMAT_H +#define DIALOG_CONVERT_DECK_TO_COD_FORMAT_H + +#include +#include +#include +#include +#include + +class DialogConvertDeckToCodFormat : public QDialog +{ + Q_OBJECT + +public: + explicit DialogConvertDeckToCodFormat(QWidget *parent); + void retranslateUi(); + + bool dontAskAgain() const; + +private: + QVBoxLayout *layout; + QLabel *label; + QCheckBox *dontAskAgainCheckbox; + QDialogButtonBox *buttonBox; + + Q_DISABLE_COPY(DialogConvertDeckToCodFormat) +}; + +#endif // DIALOG_CONVERT_DECK_TO_COD_FORMAT_H diff --git a/cockatrice/src/dialogs/dlg_create_game.cpp b/cockatrice/src/dialogs/dlg_create_game.cpp index 536077de2..02f55447b 100644 --- a/cockatrice/src/dialogs/dlg_create_game.cpp +++ b/cockatrice/src/dialogs/dlg_create_game.cpp @@ -81,8 +81,7 @@ void DlgCreateGame::sharedCtor() spectatorsAllowedCheckBox = new QCheckBox(tr("&Spectators can watch")); spectatorsAllowedCheckBox->setChecked(true); - connect(spectatorsAllowedCheckBox, SIGNAL(QT_STATE_CHANGED(QT_STATE_CHANGED_T)), this, - SLOT(spectatorsAllowedChanged(QT_STATE_CHANGED_T))); + connect(spectatorsAllowedCheckBox, &QCheckBox::QT_STATE_CHANGED, this, &DlgCreateGame::spectatorsAllowedChanged); spectatorsNeedPasswordCheckBox = new QCheckBox(tr("Spectators &need a password to watch")); spectatorsCanTalkCheckBox = new QCheckBox(tr("Spectators can &chat")); spectatorsSeeEverythingCheckBox = new QCheckBox(tr("Spectators can see &hands")); @@ -96,12 +95,26 @@ void DlgCreateGame::sharedCtor() spectatorsGroupBox = new QGroupBox(tr("Spectators")); spectatorsGroupBox->setLayout(spectatorsLayout); + startingLifeTotalLabel = new QLabel(tr("Starting life total:")); + startingLifeTotalEdit = new QSpinBox(); + startingLifeTotalEdit->setMinimum(1); + startingLifeTotalEdit->setMaximum(99999); ///< Arbitrary but we can raise this when people start complaining. + startingLifeTotalEdit->setValue(20); + startingLifeTotalLabel->setBuddy(startingLifeTotalEdit); + + QGridLayout *gameSetupOptionsLayout = new QGridLayout; + gameSetupOptionsLayout->addWidget(startingLifeTotalLabel, 0, 0); + gameSetupOptionsLayout->addWidget(startingLifeTotalEdit, 0, 1); + gameSetupOptionsGroupBox = new QGroupBox(tr("Game setup options")); + gameSetupOptionsGroupBox->setLayout(gameSetupOptionsLayout); + QGridLayout *grid = new QGridLayout; grid->addWidget(generalGroupBox, 0, 0); grid->addWidget(joinRestrictionsGroupBox, 0, 1); grid->addWidget(gameTypeGroupBox, 1, 0); grid->addWidget(spectatorsGroupBox, 1, 1, Qt::AlignTop); - grid->addWidget(rememberGameSettings, 2, 0); + grid->addWidget(gameSetupOptionsGroupBox, 2, 0); + grid->addWidget(rememberGameSettings, 3, 0); buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok); connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject())); @@ -135,6 +148,7 @@ DlgCreateGame::DlgCreateGame(TabRoom *_room, const QMap &_gameType spectatorsCanTalkCheckBox->setChecked(SettingsCache::instance().getSpectatorsCanTalk()); spectatorsSeeEverythingCheckBox->setChecked(SettingsCache::instance().getSpectatorsCanSeeEverything()); createGameAsSpectatorCheckBox->setChecked(SettingsCache::instance().getCreateGameAsSpectator()); + startingLifeTotalEdit->setValue(SettingsCache::instance().getDefaultStartingLifeTotal()); if (!rememberGameSettings->isChecked()) { actReset(); @@ -166,6 +180,7 @@ DlgCreateGame::DlgCreateGame(const ServerInfo_Game &gameInfo, const QMapsetEnabled(false); spectatorsSeeEverythingCheckBox->setEnabled(false); createGameAsSpectatorCheckBox->setEnabled(false); + startingLifeTotalEdit->setEnabled(false); descriptionEdit->setText(QString::fromStdString(gameInfo.description())); maxPlayersEdit->setValue(gameInfo.max_players()); @@ -209,6 +224,8 @@ void DlgCreateGame::actReset() spectatorsSeeEverythingCheckBox->setChecked(false); createGameAsSpectatorCheckBox->setChecked(false); + startingLifeTotalEdit->setValue(20); + QMapIterator gameTypeCheckBoxIterator(gameTypeCheckBoxes); while (gameTypeCheckBoxIterator.hasNext()) { gameTypeCheckBoxIterator.next(); @@ -235,6 +252,7 @@ void DlgCreateGame::actOK() cmd.set_spectators_see_everything(spectatorsSeeEverythingCheckBox->isChecked()); cmd.set_join_as_judge(QApplication::keyboardModifiers() & Qt::ShiftModifier); cmd.set_join_as_spectator(createGameAsSpectatorCheckBox->isChecked()); + cmd.set_starting_life_total(startingLifeTotalEdit->value()); QString _gameTypes = QString(); QMapIterator gameTypeCheckBoxIterator(gameTypeCheckBoxes); @@ -257,6 +275,7 @@ void DlgCreateGame::actOK() SettingsCache::instance().setSpectatorsCanTalk(spectatorsCanTalkCheckBox->isChecked()); SettingsCache::instance().setSpectatorsCanSeeEverything(spectatorsSeeEverythingCheckBox->isChecked()); SettingsCache::instance().setCreateGameAsSpectator(createGameAsSpectatorCheckBox->isChecked()); + SettingsCache::instance().setDefaultStartingLifeTotal(startingLifeTotalEdit->value()); SettingsCache::instance().setGameTypes(_gameTypes); } PendingCommand *pend = room->prepareRoomCommand(cmd); diff --git a/cockatrice/src/dialogs/dlg_create_game.h b/cockatrice/src/dialogs/dlg_create_game.h index 4eb5a9e8e..e14278aef 100644 --- a/cockatrice/src/dialogs/dlg_create_game.h +++ b/cockatrice/src/dialogs/dlg_create_game.h @@ -35,10 +35,10 @@ private: QMap gameTypes; QMap gameTypeCheckBoxes; - QGroupBox *generalGroupBox, *spectatorsGroupBox; - QLabel *descriptionLabel, *passwordLabel, *maxPlayersLabel; + QGroupBox *generalGroupBox, *spectatorsGroupBox, *gameSetupOptionsGroupBox; + QLabel *descriptionLabel, *passwordLabel, *maxPlayersLabel, *startingLifeTotalLabel; QLineEdit *descriptionEdit, *passwordEdit; - QSpinBox *maxPlayersEdit; + QSpinBox *maxPlayersEdit, *startingLifeTotalEdit; QCheckBox *onlyBuddiesCheckBox, *onlyRegisteredCheckBox; QCheckBox *spectatorsAllowedCheckBox, *spectatorsNeedPasswordCheckBox, *spectatorsCanTalkCheckBox, *spectatorsSeeEverythingCheckBox, *createGameAsSpectatorCheckBox; diff --git a/cockatrice/src/dialogs/dlg_create_token.cpp b/cockatrice/src/dialogs/dlg_create_token.cpp index c14b27378..25d549868 100644 --- a/cockatrice/src/dialogs/dlg_create_token.cpp +++ b/cockatrice/src/dialogs/dlg_create_token.cpp @@ -31,7 +31,11 @@ DlgCreateToken::DlgCreateToken(const QStringList &_predefinedTokens, QWidget *pa nameLabel = new QLabel(tr("&Name:")); nameEdit = new QLineEdit(tr("Token")); nameEdit->setMaxLength(MAX_NAME_LENGTH); - nameEdit->selectAll(); + QTimer::singleShot(100, this, [=, this]() { + nameEdit->selectAll(); + nameEdit->setFocus(); + }); + connect(nameEdit, SIGNAL(textChanged(const QString &)), this, SLOT(updateSearch(const QString &))); nameLabel->setBuddy(nameEdit); diff --git a/cockatrice/src/dialogs/dlg_create_token.h b/cockatrice/src/dialogs/dlg_create_token.h index dec17109e..a226faaca 100644 --- a/cockatrice/src/dialogs/dlg_create_token.h +++ b/cockatrice/src/dialogs/dlg_create_token.h @@ -21,7 +21,7 @@ class DlgCreateToken : public QDialog { Q_OBJECT public: - DlgCreateToken(const QStringList &_predefinedTokens, QWidget *parent = nullptr); + explicit DlgCreateToken(const QStringList &_predefinedTokens, QWidget *parent = nullptr); QString getName() const; QString getColor() const; QString getPT() const; diff --git a/cockatrice/src/dialogs/dlg_edit_avatar.cpp b/cockatrice/src/dialogs/dlg_edit_avatar.cpp index da85da525..1c951e7cb 100644 --- a/cockatrice/src/dialogs/dlg_edit_avatar.cpp +++ b/cockatrice/src/dialogs/dlg_edit_avatar.cpp @@ -61,7 +61,7 @@ void DlgEditAvatar::actBrowse() imgReader.setDecideFormatFromContent(true); imgReader.setFileName(fileName); if (!imgReader.read(&image)) { - qDebug() << "Avatar image loading failed for file:" << fileName; + qCWarning(DlgEditAvatarLog) << "Avatar image loading failed for file:" << fileName; imageLabel->setText(tr("Invalid image chosen.")); return; } diff --git a/cockatrice/src/dialogs/dlg_edit_avatar.h b/cockatrice/src/dialogs/dlg_edit_avatar.h index a3fb56f3d..8a84349e2 100644 --- a/cockatrice/src/dialogs/dlg_edit_avatar.h +++ b/cockatrice/src/dialogs/dlg_edit_avatar.h @@ -4,6 +4,9 @@ #include #include #include +#include + +inline Q_LOGGING_CATEGORY(DlgEditAvatarLog, "dlg_edit_avatar"); class QLabel; class QPushButton; @@ -13,7 +16,7 @@ class DlgEditAvatar : public QDialog { Q_OBJECT public: - DlgEditAvatar(QWidget *parent = nullptr); + explicit DlgEditAvatar(QWidget *parent = nullptr); QByteArray getImage(); private slots: void actOk(); diff --git a/cockatrice/src/dialogs/dlg_edit_password.h b/cockatrice/src/dialogs/dlg_edit_password.h index 4fab9c8c7..e3fcce6ec 100644 --- a/cockatrice/src/dialogs/dlg_edit_password.h +++ b/cockatrice/src/dialogs/dlg_edit_password.h @@ -13,7 +13,7 @@ class DlgEditPassword : public QDialog { Q_OBJECT public: - DlgEditPassword(QWidget *parent = nullptr); + explicit DlgEditPassword(QWidget *parent = nullptr); QString getOldPassword() const { return oldPasswordEdit->text(); diff --git a/cockatrice/src/dialogs/dlg_edit_tokens.cpp b/cockatrice/src/dialogs/dlg_edit_tokens.cpp index 7d47f642c..208e9fe80 100644 --- a/cockatrice/src/dialogs/dlg_edit_tokens.cpp +++ b/cockatrice/src/dialogs/dlg_edit_tokens.cpp @@ -163,9 +163,9 @@ void DlgEditTokens::actAddToken() QString setName = CardDatabase::TOKENS_SETNAME; CardInfoPerSetMap sets; - sets.insert(setName, CardInfoPerSet(databaseModel->getDatabase()->getSet(setName))); + sets[setName].append(CardInfoPerSet(databaseModel->getDatabase()->getSet(setName))); CardInfoPtr card = CardInfo::newInstance(name, "", true, QVariantHash(), QList(), - QList(), sets, false, -1, false); + QList(), sets, false, false, -1, false); card->setCardType("Token"); databaseModel->getDatabase()->addCard(card); diff --git a/cockatrice/src/dialogs/dlg_edit_user.cpp b/cockatrice/src/dialogs/dlg_edit_user.cpp index 088a5fe71..029992d85 100644 --- a/cockatrice/src/dialogs/dlg_edit_user.cpp +++ b/cockatrice/src/dialogs/dlg_edit_user.cpp @@ -25,7 +25,7 @@ DlgEditUser::DlgEditUser(QWidget *parent, QString email, QString country, QStrin QStringList countries = SettingsCache::instance().getCountries(); int i = 1; - foreach (QString c, countries) { + for (const QString &c : countries) { countryEdit->addItem(QPixmap("theme:countries/" + c.toLower()), c); if (c == country) countryEdit->setCurrentIndex(i); diff --git a/cockatrice/src/dialogs/dlg_edit_user.h b/cockatrice/src/dialogs/dlg_edit_user.h index 3dc6c7ebc..db2ca60ac 100644 --- a/cockatrice/src/dialogs/dlg_edit_user.h +++ b/cockatrice/src/dialogs/dlg_edit_user.h @@ -13,10 +13,10 @@ class DlgEditUser : public QDialog { Q_OBJECT public: - DlgEditUser(QWidget *parent = nullptr, - QString email = QString(), - QString country = QString(), - QString realName = QString()); + explicit DlgEditUser(QWidget *parent = nullptr, + QString email = QString(), + QString country = QString(), + QString realName = QString()); QString getEmail() const { return emailEdit->text(); diff --git a/cockatrice/src/dialogs/dlg_filter_games.cpp b/cockatrice/src/dialogs/dlg_filter_games.cpp index 6b99d0046..f8e9aa7b2 100644 --- a/cockatrice/src/dialogs/dlg_filter_games.cpp +++ b/cockatrice/src/dialogs/dlg_filter_games.cpp @@ -6,12 +6,10 @@ #include #include #include -#include #include #include #include #include -#include DlgFilterGames::DlgFilterGames(const QMap &_allGameTypes, const GamesProxyModel *_gamesProxyModel, @@ -24,19 +22,19 @@ DlgFilterGames::DlgFilterGames(const QMap &_allGameTypes, {QTime(1, 0), tr("1 hour")}, {QTime(2, 0), tr("2 hours")}}) { - showBuddiesOnlyGames = new QCheckBox(tr("Show '&buddies only' games")); - showBuddiesOnlyGames->setChecked(gamesProxyModel->getShowBuddiesOnlyGames()); + hideBuddiesOnlyGames = new QCheckBox(tr("Hide 'buddies only' games")); + hideBuddiesOnlyGames->setChecked(gamesProxyModel->getHideBuddiesOnlyGames()); - showFullGames = new QCheckBox(tr("Show &full games")); - showFullGames->setChecked(gamesProxyModel->getShowFullGames()); + hideFullGames = new QCheckBox(tr("Hide full games")); + hideFullGames->setChecked(gamesProxyModel->getHideFullGames()); - showGamesThatStarted = new QCheckBox(tr("Show games &that have started")); - showGamesThatStarted->setChecked(gamesProxyModel->getShowGamesThatStarted()); + hideGamesThatStarted = new QCheckBox(tr("Hide games that have started")); + hideGamesThatStarted->setChecked(gamesProxyModel->getHideGamesThatStarted()); - showPasswordProtectedGames = new QCheckBox(tr("Show &password protected games")); - showPasswordProtectedGames->setChecked(gamesProxyModel->getShowPasswordProtectedGames()); + hidePasswordProtectedGames = new QCheckBox(tr("Hide password protected games")); + hidePasswordProtectedGames->setChecked(gamesProxyModel->getHidePasswordProtectedGames()); - hideIgnoredUserGames = new QCheckBox(tr("Hide '&ignored user' games")); + hideIgnoredUserGames = new QCheckBox(tr("Hide 'ignored user' games")); hideIgnoredUserGames->setChecked(gamesProxyModel->getHideIgnoredUserGames()); maxGameAgeComboBox = new QComboBox(); @@ -108,10 +106,10 @@ DlgFilterGames::DlgFilterGames(const QMap &_allGameTypes, maxPlayersGroupBox->setLayout(maxPlayersFilterLayout); auto *restrictionsLayout = new QGridLayout; - restrictionsLayout->addWidget(showFullGames, 0, 0); - restrictionsLayout->addWidget(showGamesThatStarted, 1, 0); - restrictionsLayout->addWidget(showPasswordProtectedGames, 2, 0); - restrictionsLayout->addWidget(showBuddiesOnlyGames, 3, 0); + restrictionsLayout->addWidget(hideFullGames, 0, 0); + restrictionsLayout->addWidget(hideGamesThatStarted, 1, 0); + restrictionsLayout->addWidget(hidePasswordProtectedGames, 2, 0); + restrictionsLayout->addWidget(hideBuddiesOnlyGames, 3, 0); restrictionsLayout->addWidget(hideIgnoredUserGames, 4, 0); auto *restrictionsGroupBox = new QGroupBox(tr("Restrictions")); @@ -185,34 +183,24 @@ void DlgFilterGames::toggleSpectatorCheckboxEnabledness(bool spectatorsEnabled) showOnlyIfSpectatorsCanSeeHands->setDisabled(!spectatorsEnabled); } -bool DlgFilterGames::getShowFullGames() const +bool DlgFilterGames::getHideFullGames() const { - return showFullGames->isChecked(); + return hideFullGames->isChecked(); } -bool DlgFilterGames::getShowGamesThatStarted() const +bool DlgFilterGames::getHideGamesThatStarted() const { - return showGamesThatStarted->isChecked(); + return hideGamesThatStarted->isChecked(); } -bool DlgFilterGames::getShowBuddiesOnlyGames() const +bool DlgFilterGames::getHideBuddiesOnlyGames() const { - return showBuddiesOnlyGames->isChecked(); + return hideBuddiesOnlyGames->isChecked(); } -void DlgFilterGames::setShowBuddiesOnlyGames(bool _showBuddiesOnlyGames) +bool DlgFilterGames::getHidePasswordProtectedGames() const { - showBuddiesOnlyGames->setChecked(_showBuddiesOnlyGames); -} - -bool DlgFilterGames::getShowPasswordProtectedGames() const -{ - return showPasswordProtectedGames->isChecked(); -} - -void DlgFilterGames::setShowPasswordProtectedGames(bool _passwordProtectedGamesHidden) -{ - showPasswordProtectedGames->setChecked(_passwordProtectedGamesHidden); + return hidePasswordProtectedGames->isChecked(); } bool DlgFilterGames::getHideIgnoredUserGames() const @@ -220,31 +208,16 @@ bool DlgFilterGames::getHideIgnoredUserGames() const return hideIgnoredUserGames->isChecked(); } -void DlgFilterGames::setHideIgnoredUserGames(bool _hideIgnoredUserGames) -{ - hideIgnoredUserGames->setChecked(_hideIgnoredUserGames); -} - QString DlgFilterGames::getGameNameFilter() const { return gameNameFilterEdit->text(); } -void DlgFilterGames::setGameNameFilter(const QString &_gameNameFilter) -{ - gameNameFilterEdit->setText(_gameNameFilter); -} - QString DlgFilterGames::getCreatorNameFilter() const { return creatorNameFilterEdit->text(); } -void DlgFilterGames::setCreatorNameFilter(const QString &_creatorNameFilter) -{ - creatorNameFilterEdit->setText(_creatorNameFilter); -} - QSet DlgFilterGames::getGameTypeFilter() const { QSet result; @@ -257,15 +230,6 @@ QSet DlgFilterGames::getGameTypeFilter() const return result; } -void DlgFilterGames::setGameTypeFilter(const QSet &_gameTypeFilter) -{ - QMapIterator i(gameTypeFilterCheckBoxes); - while (i.hasNext()) { - i.next(); - i.value()->setChecked(_gameTypeFilter.contains(i.key())); - } -} - int DlgFilterGames::getMaxPlayersFilterMin() const { return maxPlayersFilterMinSpinBox->value(); @@ -276,7 +240,7 @@ int DlgFilterGames::getMaxPlayersFilterMax() const return maxPlayersFilterMaxSpinBox->value(); } -const QTime &DlgFilterGames::getMaxGameAge() const +QTime DlgFilterGames::getMaxGameAge() const { int index = maxGameAgeComboBox->currentIndex(); if (index < 0 || index >= gameAgeMap.size()) { // index is out of bounds @@ -285,13 +249,6 @@ const QTime &DlgFilterGames::getMaxGameAge() const return gameAgeMap.keys().at(index); } -void DlgFilterGames::setMaxPlayersFilter(int _maxPlayersFilterMin, int _maxPlayersFilterMax) -{ - maxPlayersFilterMinSpinBox->setValue(_maxPlayersFilterMin); - maxPlayersFilterMaxSpinBox->setValue(_maxPlayersFilterMax == -1 ? maxPlayersFilterMaxSpinBox->maximum() - : _maxPlayersFilterMax); -} - bool DlgFilterGames::getShowOnlyIfSpectatorsCanWatch() const { return showOnlyIfSpectatorsCanWatch->isChecked(); diff --git a/cockatrice/src/dialogs/dlg_filter_games.h b/cockatrice/src/dialogs/dlg_filter_games.h index 0b1fd333c..3b3c01a41 100644 --- a/cockatrice/src/dialogs/dlg_filter_games.h +++ b/cockatrice/src/dialogs/dlg_filter_games.h @@ -21,10 +21,10 @@ class DlgFilterGames : public QDialog Q_OBJECT private: QGroupBox *generalGroupBox; - QCheckBox *showBuddiesOnlyGames; - QCheckBox *showFullGames; - QCheckBox *showGamesThatStarted; - QCheckBox *showPasswordProtectedGames; + QCheckBox *hideBuddiesOnlyGames; + QCheckBox *hideFullGames; + QCheckBox *hideGamesThatStarted; + QCheckBox *hidePasswordProtectedGames; QCheckBox *hideIgnoredUserGames; QLineEdit *gameNameFilterEdit; QLineEdit *creatorNameFilterEdit; @@ -50,12 +50,12 @@ public: const GamesProxyModel *_gamesProxyModel, QWidget *parent = nullptr); - bool getShowFullGames() const; - bool getShowGamesThatStarted() const; - bool getShowPasswordProtectedGames() const; + bool getHideFullGames() const; + bool getHideGamesThatStarted() const; + bool getHidePasswordProtectedGames() const; void setShowPasswordProtectedGames(bool _passwordProtectedGamesHidden); - bool getShowBuddiesOnlyGames() const; - void setShowBuddiesOnlyGames(bool _showBuddiesOnlyGames); + bool getHideBuddiesOnlyGames() const; + void setHideBuddiesOnlyGames(bool _hideBuddiesOnlyGames); bool getHideIgnoredUserGames() const; void setHideIgnoredUserGames(bool _hideIgnoredUserGames); QString getGameNameFilter() const; @@ -67,7 +67,7 @@ public: int getMaxPlayersFilterMin() const; int getMaxPlayersFilterMax() const; void setMaxPlayersFilter(int _maxPlayersFilterMin, int _maxPlayersFilterMax); - const QTime &getMaxGameAge() const; + QTime getMaxGameAge() const; const QMap gameAgeMap; bool getShowOnlyIfSpectatorsCanWatch() const; bool getShowSpectatorPasswordProtected() const; diff --git a/cockatrice/src/dialogs/dlg_forgot_password_challenge.h b/cockatrice/src/dialogs/dlg_forgot_password_challenge.h index 34aa237bd..73fb44c33 100644 --- a/cockatrice/src/dialogs/dlg_forgot_password_challenge.h +++ b/cockatrice/src/dialogs/dlg_forgot_password_challenge.h @@ -13,7 +13,7 @@ class DlgForgotPasswordChallenge : public QDialog { Q_OBJECT public: - DlgForgotPasswordChallenge(QWidget *parent = nullptr); + explicit DlgForgotPasswordChallenge(QWidget *parent = nullptr); QString getHost() const { return hostEdit->text(); diff --git a/cockatrice/src/dialogs/dlg_forgot_password_request.h b/cockatrice/src/dialogs/dlg_forgot_password_request.h index c423444c0..8d267042a 100644 --- a/cockatrice/src/dialogs/dlg_forgot_password_request.h +++ b/cockatrice/src/dialogs/dlg_forgot_password_request.h @@ -13,7 +13,7 @@ class DlgForgotPasswordRequest : public QDialog { Q_OBJECT public: - DlgForgotPasswordRequest(QWidget *parent = nullptr); + explicit DlgForgotPasswordRequest(QWidget *parent = nullptr); QString getHost() const { return hostEdit->text(); diff --git a/cockatrice/src/dialogs/dlg_forgot_password_reset.h b/cockatrice/src/dialogs/dlg_forgot_password_reset.h index ed018ee7f..e523e19c3 100644 --- a/cockatrice/src/dialogs/dlg_forgot_password_reset.h +++ b/cockatrice/src/dialogs/dlg_forgot_password_reset.h @@ -13,7 +13,7 @@ class DlgForgotPasswordReset : public QDialog { Q_OBJECT public: - DlgForgotPasswordReset(QWidget *parent = nullptr); + explicit DlgForgotPasswordReset(QWidget *parent = nullptr); QString getHost() const { return hostEdit->text(); diff --git a/cockatrice/src/dialogs/dlg_load_deck.cpp b/cockatrice/src/dialogs/dlg_load_deck.cpp new file mode 100644 index 000000000..91252a768 --- /dev/null +++ b/cockatrice/src/dialogs/dlg_load_deck.cpp @@ -0,0 +1,22 @@ +#include "dlg_load_deck.h" + +#include "../deck/deck_loader.h" +#include "../settings/cache_settings.h" + +DlgLoadDeck::DlgLoadDeck(QWidget *parent) : QFileDialog(parent, tr("Load Deck")) +{ + QString startingDir = SettingsCache::instance().recents().getLatestDeckDirPath(); + if (startingDir.isEmpty()) { + startingDir = SettingsCache::instance().getDeckPath(); + } + + setDirectory(startingDir); + setNameFilters(DeckLoader::FILE_NAME_FILTERS); + + connect(this, &DlgLoadDeck::accepted, this, &DlgLoadDeck::actAccepted); +} + +void DlgLoadDeck::actAccepted() +{ + SettingsCache::instance().recents().setLatestDeckDirPath(directory().absolutePath()); +} \ No newline at end of file diff --git a/cockatrice/src/dialogs/dlg_load_deck.h b/cockatrice/src/dialogs/dlg_load_deck.h new file mode 100644 index 000000000..13df97995 --- /dev/null +++ b/cockatrice/src/dialogs/dlg_load_deck.h @@ -0,0 +1,21 @@ + +#ifndef DLG_LOAD_DECK_H +#define DLG_LOAD_DECK_H + +#include + +/** + * The file dialog for "Load Deck" operations. + * Handles remembering the most recently used deck loading directory. + */ +class DlgLoadDeck : public QFileDialog +{ + Q_OBJECT + + void actAccepted(); + +public: + explicit DlgLoadDeck(QWidget *parent = nullptr); +}; + +#endif // DLG_LOAD_DECK_H diff --git a/cockatrice/src/dialogs/dlg_load_deck_from_clipboard.cpp b/cockatrice/src/dialogs/dlg_load_deck_from_clipboard.cpp index e3673d1a3..26f92f4e9 100644 --- a/cockatrice/src/dialogs/dlg_load_deck_from_clipboard.cpp +++ b/cockatrice/src/dialogs/dlg_load_deck_from_clipboard.cpp @@ -2,8 +2,10 @@ #include "../deck/deck_loader.h" #include "../settings/cache_settings.h" +#include "dlg_settings.h" #include +#include #include #include #include @@ -12,62 +14,165 @@ #include #include -DlgLoadDeckFromClipboard::DlgLoadDeckFromClipboard(QWidget *parent) : QDialog(parent), deckList(nullptr) +/** + * Creates the main layout and connects the signals that are common to all versions of this window + */ +AbstractDlgDeckTextEdit::AbstractDlgDeckTextEdit(QWidget *parent) : QDialog(parent) { contentsEdit = new QPlainTextEdit; refreshButton = new QPushButton(tr("&Refresh")); - connect(refreshButton, SIGNAL(clicked()), this, SLOT(actRefresh())); + connect(refreshButton, &QPushButton::clicked, this, &AbstractDlgDeckTextEdit::actRefresh); QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); buttonBox->addButton(refreshButton, QDialogButtonBox::ActionRole); - connect(buttonBox, SIGNAL(accepted()), this, SLOT(actOK())); - connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject())); + connect(buttonBox, &QDialogButtonBox::accepted, this, &AbstractDlgDeckTextEdit::actOK); + connect(buttonBox, &QDialogButtonBox::rejected, this, &AbstractDlgDeckTextEdit::reject); + + loadSetNameAndNumberCheckBox = new QCheckBox(tr("Parse Set Name and Number (if available)")); + loadSetNameAndNumberCheckBox->setChecked(true); + + auto *buttonLayout = new QHBoxLayout; + buttonLayout->addWidget(loadSetNameAndNumberCheckBox); + buttonLayout->addWidget(buttonBox); auto *mainLayout = new QVBoxLayout; mainLayout->addWidget(contentsEdit); - mainLayout->addWidget(buttonBox); + mainLayout->addLayout(buttonLayout); setLayout(mainLayout); - setWindowTitle(tr("Load deck from clipboard")); resize(500, 500); - actRefresh(); - connect(&SettingsCache::instance().shortcuts(), SIGNAL(shortCutChanged()), this, SLOT(refreshShortcuts())); + connect(&SettingsCache::instance().shortcuts(), &ShortcutsSettings::shortCutChanged, this, + &AbstractDlgDeckTextEdit::refreshShortcuts); refreshShortcuts(); } -void DlgLoadDeckFromClipboard::actRefresh() -{ - contentsEdit->setPlainText(QApplication::clipboard()->text()); -} - -void DlgLoadDeckFromClipboard::refreshShortcuts() +void AbstractDlgDeckTextEdit::refreshShortcuts() { refreshButton->setShortcut( SettingsCache::instance().shortcuts().getSingleShortcut("DlgLoadDeckFromClipboard/refreshButton")); } -void DlgLoadDeckFromClipboard::actOK() +/** + * Replaces the contents of the contentsEdit with the given text. + * @param text The text + */ +void AbstractDlgDeckTextEdit::setText(const QString &text) +{ + contentsEdit->setPlainText(text); +} + +/** + * Tries to load the current contents of the contentsEdit into the DeckLoader + * + * @param deckLoader The DeckLoader to load the deck into + * @return Whether the loading was successful + */ +bool AbstractDlgDeckTextEdit::loadIntoDeck(DeckLoader *deckLoader) const { QString buffer = contentsEdit->toPlainText(); + + if (buffer.contains("")) { + return deckLoader->loadFromString_Native(buffer); + } + QTextStream stream(&buffer); - auto *deckLoader = new DeckLoader; - if (buffer.contains("")) { - if (deckLoader->loadFromString_Native(buffer)) { - deckList = deckLoader; - accept(); + if (deckLoader->loadFromStream_Plain(stream, true)) { + if (loadSetNameAndNumberCheckBox->isChecked()) { + deckLoader->resolveSetNameAndNumberToProviderID(); } else { - QMessageBox::critical(this, tr("Error"), tr("Invalid deck list.")); - delete deckLoader; + deckLoader->clearSetNamesAndNumbers(); } - } else if (deckLoader->loadFromStream_Plain(stream)) { - deckList = deckLoader; + return true; + } + + return false; +} + +void AbstractDlgDeckTextEdit::keyPressEvent(QKeyEvent *event) +{ + if (event->key() == Qt::Key_Return && event->modifiers() & Qt::ControlModifier) { + event->accept(); + actOK(); + return; + } + QDialog::keyPressEvent(event); +} + +/** + * Creates the dialog window for the "Load deck from clipboard" action + * + * @param parent The parent widget + */ +DlgLoadDeckFromClipboard::DlgLoadDeckFromClipboard(QWidget *parent) : AbstractDlgDeckTextEdit(parent), deckList(nullptr) +{ + setWindowTitle(tr("Load deck from clipboard")); + + DlgLoadDeckFromClipboard::actRefresh(); +} + +void DlgLoadDeckFromClipboard::actRefresh() +{ + setText(QApplication::clipboard()->text()); +} + +void DlgLoadDeckFromClipboard::actOK() +{ + deckList = new DeckLoader; + deckList->setParent(this); + + if (loadIntoDeck(deckList)) { accept(); } else { QMessageBox::critical(this, tr("Error"), tr("Invalid deck list.")); - delete deckLoader; } } + +/** + * Creates the dialog window for the "Edit deck in clipboard" action + * + * @param deckList The existing deck in the deck editor. Copies the instance + * @param _annotated Whether to add annotations to the text that is loaded from the deck + * @param parent The parent widget + */ +DlgEditDeckInClipboard::DlgEditDeckInClipboard(const DeckLoader &deckList, bool _annotated, QWidget *parent) + : AbstractDlgDeckTextEdit(parent), annotated(_annotated) +{ + setWindowTitle(tr("Edit deck in clipboard")); + + deckLoader = new DeckLoader(deckList); + deckLoader->setParent(this); + + DlgEditDeckInClipboard::actRefresh(); +} + +/** + * Loads the contents of the DeckList into a String. Always loads it with addSetNameAndNumber=true + * @param deckList The deck to load + * @param addComments Whether to add annotations + * @return A QString + */ +static QString deckListToString(const DeckLoader *deckList, bool addComments) +{ + QString buffer; + QTextStream stream(&buffer); + deckList->saveToStream_Plain(stream, addComments); + return buffer; +} + +void DlgEditDeckInClipboard::actRefresh() +{ + setText(deckListToString(deckLoader, annotated)); +} + +void DlgEditDeckInClipboard::actOK() +{ + if (loadIntoDeck(deckLoader)) { + accept(); + } else { + QMessageBox::critical(this, tr("Error"), tr("Invalid deck list.")); + } +} \ No newline at end of file diff --git a/cockatrice/src/dialogs/dlg_load_deck_from_clipboard.h b/cockatrice/src/dialogs/dlg_load_deck_from_clipboard.h index 7a9833b41..ce2ccf720 100644 --- a/cockatrice/src/dialogs/dlg_load_deck_from_clipboard.h +++ b/cockatrice/src/dialogs/dlg_load_deck_from_clipboard.h @@ -1,31 +1,92 @@ #ifndef DLG_LOAD_DECK_FROM_CLIPBOARD_H #define DLG_LOAD_DECK_FROM_CLIPBOARD_H +#include #include class DeckLoader; class QPlainTextEdit; class QPushButton; -class DlgLoadDeckFromClipboard : public QDialog +/** + * Base class for dialog windows for actions that involve loading decks from text input. + */ +class AbstractDlgDeckTextEdit : public QDialog { Q_OBJECT +private: + QPlainTextEdit *contentsEdit; + QPushButton *refreshButton; + QCheckBox *loadSetNameAndNumberCheckBox; + private slots: - void actOK(); - void actRefresh(); void refreshShortcuts(); +public: + explicit AbstractDlgDeckTextEdit(QWidget *parent = nullptr); + + /** + * Gets the loaded deck. Only call this method after this dialog window has been successfully exec'd. + * + * The returned DeckLoader is parented to this object; make sure to take ownership of the DeckLoader if you intend + * to use it, since otherwise it will get destroyed once this dlg is destroyed + * @return The DeckLoader + */ + virtual DeckLoader *getDeckList() const = 0; + +protected: + void setText(const QString &text); + bool loadIntoDeck(DeckLoader *deckLoader) const; + void keyPressEvent(QKeyEvent *event) override; + +protected slots: + virtual void actOK() = 0; + virtual void actRefresh() = 0; +}; + +/** + * Dialog window for the "Load deck from clipboard" action + */ +class DlgLoadDeckFromClipboard : public AbstractDlgDeckTextEdit +{ + Q_OBJECT +protected slots: + void actOK() override; + void actRefresh() override; + private: DeckLoader *deckList; - QPlainTextEdit *contentsEdit; - QPushButton *refreshButton; public: explicit DlgLoadDeckFromClipboard(QWidget *parent = nullptr); - DeckLoader *getDeckList() const + + DeckLoader *getDeckList() const override { return deckList; } }; +/** + * Dialog window for the "Edit deck in clipboard" action + */ +class DlgEditDeckInClipboard : public AbstractDlgDeckTextEdit +{ + Q_OBJECT +protected slots: + void actOK() override; + void actRefresh() override; + +private: + DeckLoader *deckLoader; + bool annotated; + +public: + explicit DlgEditDeckInClipboard(const DeckLoader &deckList, bool _annotated, QWidget *parent = nullptr); + + DeckLoader *getDeckList() const override + { + return deckLoader; + } +}; + #endif diff --git a/cockatrice/src/dialogs/dlg_load_remote_deck.cpp b/cockatrice/src/dialogs/dlg_load_remote_deck.cpp index d49f5a593..d64d3a6ee 100644 --- a/cockatrice/src/dialogs/dlg_load_remote_deck.cpp +++ b/cockatrice/src/dialogs/dlg_load_remote_deck.cpp @@ -11,6 +11,7 @@ DlgLoadRemoteDeck::DlgLoadRemoteDeck(AbstractClient *_client, QWidget *parent) : QDialog(parent), client(_client) { dirView = new RemoteDeckList_TreeWidget(client); + dirView->refreshTree(); buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false); diff --git a/cockatrice/src/dialogs/dlg_load_remote_deck.h b/cockatrice/src/dialogs/dlg_load_remote_deck.h index 61dcd7a61..54be44c77 100644 --- a/cockatrice/src/dialogs/dlg_load_remote_deck.h +++ b/cockatrice/src/dialogs/dlg_load_remote_deck.h @@ -20,7 +20,7 @@ private slots: void currentItemChanged(const QModelIndex ¤t, const QModelIndex &previous); public: - DlgLoadRemoteDeck(AbstractClient *_client, QWidget *parent = nullptr); + explicit DlgLoadRemoteDeck(AbstractClient *_client, QWidget *parent = nullptr); int getDeckId() const; }; diff --git a/cockatrice/src/dialogs/dlg_manage_sets.cpp b/cockatrice/src/dialogs/dlg_manage_sets.cpp index b547a6ed0..05984c05c 100644 --- a/cockatrice/src/dialogs/dlg_manage_sets.cpp +++ b/cockatrice/src/dialogs/dlg_manage_sets.cpp @@ -1,13 +1,14 @@ #include "dlg_manage_sets.h" #include "../client/network/sets_model.h" -#include "../client/ui/picture_loader.h" +#include "../client/ui/picture_loader/picture_loader.h" #include "../deck/custom_line_edit.h" #include "../game/cards/card_database_manager.h" #include "../main.h" #include "../settings/cache_settings.h" #include +#include #include #include #include @@ -102,6 +103,7 @@ WndSets::WndSets(QWidget *parent) : QMainWindow(parent) view->sortByColumn(SetsModel::SortKeyCol, Qt::AscendingOrder); view->setColumnHidden(SetsModel::SortKeyCol, true); view->setColumnHidden(SetsModel::IsKnownCol, true); + view->setColumnHidden(SetsModel::PriorityCol, true); view->setRootIsDecorated(false); connect(view->header(), SIGNAL(sectionClicked(int)), this, SLOT(actSort(int))); @@ -161,6 +163,12 @@ WndSets::WndSets(QWidget *parent) : QMainWindow(parent) sortWarning->setLayout(sortWarningLayout); sortWarning->setVisible(false); + includeRebalancedCards = SettingsCache::instance().getIncludeRebalancedCards(); + QCheckBox *includeRebalancedCardsCheckBox = + new QCheckBox(tr("Include cards rebalanced for Alchemy [requires restart]")); + includeRebalancedCardsCheckBox->setChecked(includeRebalancedCards); + connect(includeRebalancedCardsCheckBox, &QAbstractButton::toggled, this, &WndSets::includeRebalancedCardsChanged); + buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); connect(buttonBox, SIGNAL(accepted()), this, SLOT(actSave())); connect(buttonBox, SIGNAL(rejected()), this, SLOT(actRestore())); @@ -174,8 +182,9 @@ WndSets::WndSets(QWidget *parent) : QMainWindow(parent) mainLayout->addWidget(enableSomeButton, 2, 1); mainLayout->addWidget(disableSomeButton, 2, 2); mainLayout->addWidget(sortWarning, 3, 1, 1, 2); - mainLayout->addWidget(hintsGroupBox, 4, 1, 1, 2); - mainLayout->addWidget(buttonBox, 5, 1, 1, 2); + mainLayout->addWidget(includeRebalancedCardsCheckBox, 4, 1, 1, 2); + mainLayout->addWidget(hintsGroupBox, 5, 1, 1, 2); + mainLayout->addWidget(buttonBox, 6, 1, 1, 2); mainLayout->setColumnStretch(1, 1); mainLayout->setColumnStretch(2, 1); @@ -238,9 +247,15 @@ void WndSets::rebuildMainLayout(int actionToTake) } } +void WndSets::includeRebalancedCardsChanged(bool _includeRebalancedCards) +{ + includeRebalancedCards = _includeRebalancedCards; +} + void WndSets::actSave() { model->save(CardDatabaseManager::getInstance()); + SettingsCache::instance().setIncludeRebalancedCards(includeRebalancedCards); PictureLoader::clearPixmapCache(); close(); } @@ -254,7 +269,7 @@ void WndSets::actRestore() void WndSets::actRestoreOriginalOrder() { view->header()->setSortIndicator(SORT_RESET, Qt::DescendingOrder); - model->sort(model->ReleaseDateCol, Qt::DescendingOrder); + model->restoreOriginalOrder(); sortWarning->setVisible(false); } diff --git a/cockatrice/src/dialogs/dlg_manage_sets.h b/cockatrice/src/dialogs/dlg_manage_sets.h index 079057bfb..50e29043b 100644 --- a/cockatrice/src/dialogs/dlg_manage_sets.h +++ b/cockatrice/src/dialogs/dlg_manage_sets.h @@ -44,6 +44,7 @@ private: void saveHeaderState(); void rebuildMainLayout(int actionToTake); bool setOrderIsSorted; + bool includeRebalancedCards; enum { NO_SETS_SELECTED, @@ -51,8 +52,8 @@ private: }; public: - WndSets(QWidget *parent = nullptr); - ~WndSets(); + explicit WndSets(QWidget *parent = nullptr); + ~WndSets() override; protected: void selectRows(QSet rows); @@ -73,6 +74,7 @@ private slots: void actDisableResetButton(const QString &filterText); void actSort(int index); void actIgnoreWarning(); + void includeRebalancedCardsChanged(bool _includeRebalancedCardsChanged); }; #endif diff --git a/cockatrice/src/dialogs/dlg_move_top_cards_until.cpp b/cockatrice/src/dialogs/dlg_move_top_cards_until.cpp new file mode 100644 index 000000000..edf593571 --- /dev/null +++ b/cockatrice/src/dialogs/dlg_move_top_cards_until.cpp @@ -0,0 +1,139 @@ +#include "dlg_move_top_cards_until.h" + +#include "../game/cards/card_database.h" +#include "../game/cards/card_database_manager.h" +#include "../game/filters/filter_string.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +DlgMoveTopCardsUntil::DlgMoveTopCardsUntil(QWidget *parent, QStringList exprs, uint _numberOfHits, bool autoPlay) + : QDialog(parent) +{ + exprLabel = new QLabel(tr("Card name (or search expressions):")); + + exprComboBox = new QComboBox(this); + exprComboBox->setFocus(); + exprComboBox->setEditable(true); + exprComboBox->setInsertPolicy(QComboBox::InsertAtTop); + exprComboBox->insertItems(0, exprs); + exprLabel->setBuddy(exprComboBox); + + numberOfHitsLabel = new QLabel(tr("Number of hits:")); + numberOfHitsEdit = new QSpinBox(this); + numberOfHitsEdit->setRange(1, 99); + numberOfHitsEdit->setValue(_numberOfHits); + numberOfHitsLabel->setBuddy(numberOfHitsEdit); + + auto *grid = new QGridLayout; + grid->addWidget(numberOfHitsLabel, 0, 0); + grid->addWidget(numberOfHitsEdit, 0, 1); + + autoPlayCheckBox = new QCheckBox(tr("Auto play hits")); + autoPlayCheckBox->setChecked(autoPlay); + + buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); + connect(buttonBox, &QDialogButtonBox::accepted, this, &DlgMoveTopCardsUntil::validateAndAccept); + connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); + + auto *mainLayout = new QVBoxLayout; + mainLayout->addWidget(exprLabel); + mainLayout->addWidget(exprComboBox); + mainLayout->addItem(grid); + mainLayout->addWidget(autoPlayCheckBox); + mainLayout->addWidget(buttonBox); + + setLayout(mainLayout); + setWindowTitle(tr("Put top cards on stack until...")); +} + +/** + * @brief Checks if a card matching the expr exists in the card database. + * + * @returns true if a card matching the expression exists. + */ +static bool matchExistsInDb(const FilterString &filterString) +{ + const auto *cardDatabase = CardDatabaseManager::getInstance(); + const auto &allCards = cardDatabase->getCardList(); + + const auto it = std::find_if(allCards.begin(), allCards.end(), + [&filterString](const CardInfoPtr &card) { return filterString.check(card); }); + + return it != allCards.end(); +} + +/** + * @brief Validates that a card matching the expr exists in the card database. + * If no match is found, then pop up a window to warn the user, giving them a chance to back out. + * + * @returns whether to proceed with the action + */ +bool DlgMoveTopCardsUntil::validateMatchExists(const FilterString &filterString) +{ + if (matchExistsInDb(filterString)) { + return true; + } + + const auto msg = tr("No cards matching the search expression exists in the card database. Proceed anyways?"); + const auto res = + QMessageBox::warning(this, tr("Cockatrice"), msg, QMessageBox::Yes | QMessageBox::No, QMessageBox::No); + if (res == QMessageBox::No) { + return false; + } + + return true; +} + +void DlgMoveTopCardsUntil::validateAndAccept() +{ + auto movingCardsUntilFilter = FilterString(exprComboBox->currentText()); + if (!movingCardsUntilFilter.valid()) { + QMessageBox::warning(this, tr("Invalid filter"), movingCardsUntilFilter.error(), QMessageBox::Ok); + return; + } + + if (!validateMatchExists(movingCardsUntilFilter)) { + return; + } + + // move currently selected text to top of history list + if (exprComboBox->currentIndex() != 0) { + QString currentExpr = exprComboBox->currentText(); + exprComboBox->removeItem(exprComboBox->currentIndex()); + exprComboBox->insertItem(0, currentExpr); + exprComboBox->setCurrentIndex(0); + } + + accept(); +} + +QString DlgMoveTopCardsUntil::getExpr() const +{ + return exprComboBox->currentText(); +} + +QStringList DlgMoveTopCardsUntil::getExprs() const +{ + QStringList exprs; + for (int i = 0; i < exprComboBox->count(); ++i) { + exprs.append(exprComboBox->itemText(i)); + } + return exprs; +} + +uint DlgMoveTopCardsUntil::getNumberOfHits() const +{ + return numberOfHitsEdit->text().toUInt(); +} + +bool DlgMoveTopCardsUntil::isAutoPlay() const +{ + return autoPlayCheckBox->isChecked(); +} diff --git a/cockatrice/src/dialogs/dlg_move_top_cards_until.h b/cockatrice/src/dialogs/dlg_move_top_cards_until.h new file mode 100644 index 000000000..d41d029d6 --- /dev/null +++ b/cockatrice/src/dialogs/dlg_move_top_cards_until.h @@ -0,0 +1,37 @@ +#ifndef DLG_MOVE_TOP_CARDS_UNTIL_H +#define DLG_MOVE_TOP_CARDS_UNTIL_H + +#include +#include +#include +#include +#include +#include + +class FilterString; + +class DlgMoveTopCardsUntil : public QDialog +{ + Q_OBJECT + + QLabel *exprLabel, *numberOfHitsLabel; + QComboBox *exprComboBox; + QSpinBox *numberOfHitsEdit; + QDialogButtonBox *buttonBox; + QCheckBox *autoPlayCheckBox; + + void validateAndAccept(); + bool validateMatchExists(const FilterString &filterString); + +public: + explicit DlgMoveTopCardsUntil(QWidget *parent = nullptr, + QStringList exprs = QStringList(), + uint numberOfHits = 1, + bool autoPlay = false); + QString getExpr() const; + QStringList getExprs() const; + uint getNumberOfHits() const; + bool isAutoPlay() const; +}; + +#endif // DLG_MOVE_TOP_CARDS_UNTIL_H diff --git a/cockatrice/src/dialogs/dlg_register.cpp b/cockatrice/src/dialogs/dlg_register.cpp index 1f0a715c6..6abf9dd9f 100644 --- a/cockatrice/src/dialogs/dlg_register.cpp +++ b/cockatrice/src/dialogs/dlg_register.cpp @@ -313,7 +313,7 @@ DlgRegister::DlgRegister(QWidget *parent) : QDialog(parent) countryEdit->addItem(QPixmap("theme:countries/zw"), "zw"); countryEdit->setCurrentIndex(0); QStringList countries = SettingsCache::instance().getCountries(); - foreach (QString c, countries) + for (const QString &c : countries) countryEdit->addItem(QPixmap("theme:countries/" + c.toLower()), c); realnameLabel = new QLabel(tr("Real name:")); diff --git a/cockatrice/src/dialogs/dlg_register.h b/cockatrice/src/dialogs/dlg_register.h index 51dd593a5..1d87212bf 100644 --- a/cockatrice/src/dialogs/dlg_register.h +++ b/cockatrice/src/dialogs/dlg_register.h @@ -13,7 +13,7 @@ class DlgRegister : public QDialog { Q_OBJECT public: - DlgRegister(QWidget *parent = nullptr); + explicit DlgRegister(QWidget *parent = nullptr); QString getHost() const { return hostEdit->text(); diff --git a/cockatrice/src/dialogs/dlg_settings.cpp b/cockatrice/src/dialogs/dlg_settings.cpp index cef4ceaee..0d5ea6d92 100644 --- a/cockatrice/src/dialogs/dlg_settings.cpp +++ b/cockatrice/src/dialogs/dlg_settings.cpp @@ -4,12 +4,14 @@ #include "../client/network/release_channel.h" #include "../client/network/spoiler_background_updater.h" #include "../client/sound_engine.h" -#include "../client/ui/picture_loader.h" +#include "../client/ui/picture_loader/picture_loader.h" #include "../client/ui/theme_manager.h" +#include "../deck/custom_line_edit.h" #include "../game/cards/card_database.h" #include "../game/cards/card_database_manager.h" #include "../main.h" #include "../settings/cache_settings.h" +#include "../settings/shortcut_treeview.h" #include "../utility/sequence_edit.h" #include @@ -32,16 +34,18 @@ #include #include #include +#include +#include #include #include #include #include #include -#include -#include +#include #define WIKI_CUSTOM_PIC_URL "https://github.com/Cockatrice/Cockatrice/wiki/Custom-Picture-Download-URLs" #define WIKI_CUSTOM_SHORTCUTS "https://github.com/Cockatrice/Cockatrice/wiki/Custom-Keyboard-Shortcuts" +#define WIKI_TRANSLATION_FAQ "https://github.com/Cockatrice/Cockatrice/wiki/Translation-FAQ" GeneralSettingsPage::GeneralSettingsPage() { @@ -61,33 +65,33 @@ GeneralSettingsPage::GeneralSettingsPage() // updates SettingsCache &settings = SettingsCache::instance(); - QList channels = settings.getUpdateReleaseChannels(); - foreach (ReleaseChannel *chan, channels) { - updateReleaseChannelBox.insertItem(chan->getIndex(), tr(chan->getName().toUtf8())); - } - updateReleaseChannelBox.setCurrentIndex(settings.getUpdateReleaseChannel()->getIndex()); - + startupUpdateCheckCheckBox.setChecked(settings.getCheckUpdatesOnStartup()); updateNotificationCheckBox.setChecked(settings.getNotifyAboutUpdates()); newVersionOracleCheckBox.setChecked(settings.getNotifyAboutNewVersion()); showTipsOnStartup.setChecked(settings.getShowTipsOnStartup()); + advertiseTranslationPageLabel.setTextInteractionFlags(Qt::LinksAccessibleByMouse); + advertiseTranslationPageLabel.setOpenExternalLinks(true); + connect(&languageBox, SIGNAL(currentIndexChanged(int)), this, SLOT(languageBoxChanged(int))); - connect(&updateReleaseChannelBox, SIGNAL(currentIndexChanged(int)), &settings, SLOT(setUpdateReleaseChannel(int))); - connect(&updateNotificationCheckBox, SIGNAL(QT_STATE_CHANGED(QT_STATE_CHANGED_T)), &settings, - SLOT(setNotifyAboutUpdate(QT_STATE_CHANGED_T))); - connect(&newVersionOracleCheckBox, SIGNAL(QT_STATE_CHANGED(QT_STATE_CHANGED_T)), &settings, - SLOT(setNotifyAboutNewVersion(QT_STATE_CHANGED_T))); + connect(&startupUpdateCheckCheckBox, &QCheckBox::QT_STATE_CHANGED, &settings, + &SettingsCache::setCheckUpdatesOnStartup); + connect(&updateNotificationCheckBox, &QCheckBox::QT_STATE_CHANGED, &settings, &SettingsCache::setNotifyAboutUpdate); + connect(&newVersionOracleCheckBox, &QCheckBox::QT_STATE_CHANGED, &settings, + &SettingsCache::setNotifyAboutNewVersion); connect(&showTipsOnStartup, SIGNAL(clicked(bool)), &settings, SLOT(setShowTipsOnStartup(bool))); auto *personalGrid = new QGridLayout; personalGrid->addWidget(&languageLabel, 0, 0); personalGrid->addWidget(&languageBox, 0, 1); - personalGrid->addWidget(&updateReleaseChannelLabel, 1, 0); - personalGrid->addWidget(&updateReleaseChannelBox, 1, 1); - personalGrid->addWidget(&updateNotificationCheckBox, 3, 0, 1, 2); - personalGrid->addWidget(&newVersionOracleCheckBox, 4, 0, 1, 2); - personalGrid->addWidget(&showTipsOnStartup, 5, 0, 1, 2); + personalGrid->addWidget(&advertiseTranslationPageLabel, 1, 1, Qt::AlignRight); + personalGrid->addWidget(&updateReleaseChannelLabel, 2, 0); + personalGrid->addWidget(&updateReleaseChannelBox, 2, 1); + personalGrid->addWidget(&startupUpdateCheckCheckBox, 4, 0, 1, 2); + personalGrid->addWidget(&updateNotificationCheckBox, 5, 0, 1, 2); + personalGrid->addWidget(&newVersionOracleCheckBox, 6, 0, 1, 2); + personalGrid->addWidget(&showTipsOnStartup, 7, 0, 1, 2); personalGroupBox = new QGroupBox; personalGroupBox->setLayout(personalGrid); @@ -122,6 +126,9 @@ GeneralSettingsPage::GeneralSettingsPage() QPushButton *tokenDatabasePathButton = new QPushButton("..."); connect(tokenDatabasePathButton, SIGNAL(clicked()), this, SLOT(tokenDatabasePathButtonClicked())); + // Required init here to avoid crashing on Portable builds + resetAllPathsButton = new QPushButton; + bool isPortable = settings.getIsPortableBuild(); if (isPortable) { deckPathEdit->setEnabled(false); @@ -138,7 +145,6 @@ GeneralSettingsPage::GeneralSettingsPage() customCardDatabasePathButton->setVisible(false); tokenDatabasePathButton->setVisible(false); } else { - resetAllPathsButton = new QPushButton(tr("Reset all paths")); connect(resetAllPathsButton, SIGNAL(clicked()), this, SLOT(resetAllPathsClicked())); allPathsResetLabel = new QLabel(tr("All paths have been reset")); allPathsResetLabel->setVisible(false); @@ -175,7 +181,17 @@ GeneralSettingsPage::GeneralSettingsPage() mainLayout->addWidget(pathsGroupBox); mainLayout->addStretch(); + GeneralSettingsPage::retranslateUi(); + + // connect the ReleaseChannel combo box only after the entries are inserted in retranslateUi + connect(&updateReleaseChannelBox, SIGNAL(currentIndexChanged(int)), &settings, + SLOT(setUpdateReleaseChannelIndex(int))); + updateReleaseChannelBox.setCurrentIndex(settings.getUpdateReleaseChannelIndex()); + setLayout(mainLayout); + + connect(&SettingsCache::instance(), &SettingsCache::langChanged, this, &GeneralSettingsPage::retranslateUi); + retranslateUi(); } QStringList GeneralSettingsPage::findQmFiles() @@ -193,7 +209,8 @@ QString GeneralSettingsPage::languageName(const QString &lang) QString appNameHint = translationPrefix + "_" + lang; bool appTranslationLoaded = qTranslator.load(appNameHint, translationPath); if (!appTranslationLoaded) { - qDebug() << "Unable to load" << translationPrefix << "translation" << appNameHint << "at" << translationPath; + qCWarning(DlgSettingsLog) << "Unable to load" << translationPrefix << "translation" << appNameHint << "at" + << translationPath; } return qTranslator.translate("i18n", DEFAULT_LANG_NAME); @@ -287,7 +304,8 @@ void GeneralSettingsPage::retranslateUi() } else { pathsGroupBox->setTitle(tr("Paths")); } - + advertiseTranslationPageLabel.setText( + QString("%2").arg(WIKI_TRANSLATION_FAQ).arg(tr("How to help with translations"))); deckPathLabel.setText(tr("Decks directory:")); replaysPathLabel.setText(tr("Replays directory:")); picsPathLabel.setText(tr("Pictures directory:")); @@ -295,14 +313,28 @@ void GeneralSettingsPage::retranslateUi() customCardDatabasePathLabel.setText(tr("Custom database directory:")); tokenDatabasePathLabel.setText(tr("Token database:")); updateReleaseChannelLabel.setText(tr("Update channel")); + startupUpdateCheckCheckBox.setText(tr("Check for client updates on startup")); updateNotificationCheckBox.setText(tr("Notify if a feature supported by the server is missing in my client")); newVersionOracleCheckBox.setText(tr("Automatically run Oracle when running a new version of Cockatrice")); showTipsOnStartup.setText(tr("Show tips on startup")); + resetAllPathsButton->setText(tr("Reset all paths")); + + const auto &settings = SettingsCache::instance(); + + // We can't change the strings after they're put into the QComboBox, so this is our workaround + int oldIndex = updateReleaseChannelBox.currentIndex(); + updateReleaseChannelBox.clear(); + for (ReleaseChannel *chan : settings.getUpdateReleaseChannels()) { + updateReleaseChannelBox.addItem(tr(chan->getName().toUtf8())); + } + updateReleaseChannelBox.setCurrentIndex(oldIndex); } AppearanceSettingsPage::AppearanceSettingsPage() { SettingsCache &settings = SettingsCache::instance(); + + // Theme settings QString themeName = SettingsCache::instance().getThemeName(); QStringList themeDirs = themeManager->getAvailableThemes().keys(); @@ -323,35 +355,76 @@ AppearanceSettingsPage::AppearanceSettingsPage() themeGroupBox = new QGroupBox; themeGroupBox->setLayout(themeGrid); + // Menu settings + showShortcutsCheckBox.setChecked(settings.getShowShortcuts()); + connect(&showShortcutsCheckBox, &QCheckBox::QT_STATE_CHANGED, this, &AppearanceSettingsPage::showShortcutsChanged); + + auto *menuGrid = new QGridLayout; + menuGrid->addWidget(&showShortcutsCheckBox, 0, 0); + + menuGroupBox = new QGroupBox; + menuGroupBox->setLayout(menuGrid); + + // Card rendering displayCardNamesCheckBox.setChecked(settings.getDisplayCardNames()); - connect(&displayCardNamesCheckBox, SIGNAL(QT_STATE_CHANGED(QT_STATE_CHANGED_T)), &settings, - SLOT(setDisplayCardNames(QT_STATE_CHANGED_T))); + connect(&displayCardNamesCheckBox, &QCheckBox::QT_STATE_CHANGED, &settings, &SettingsCache::setDisplayCardNames); + + autoRotateSidewaysLayoutCardsCheckBox.setChecked(settings.getAutoRotateSidewaysLayoutCards()); + connect(&autoRotateSidewaysLayoutCardsCheckBox, &QCheckBox::QT_STATE_CHANGED, &settings, + &SettingsCache::setAutoRotateSidewaysLayoutCards); + + overrideAllCardArtWithPersonalPreferenceCheckBox.setChecked(settings.getOverrideAllCardArtWithPersonalPreference()); + connect(&overrideAllCardArtWithPersonalPreferenceCheckBox, &QCheckBox::QT_STATE_CHANGED, &settings, + &SettingsCache::setOverrideAllCardArtWithPersonalPreference); + + bumpSetsWithCardsInDeckToTopCheckBox.setChecked(settings.getBumpSetsWithCardsInDeckToTop()); + connect(&bumpSetsWithCardsInDeckToTopCheckBox, &QCheckBox::QT_STATE_CHANGED, &settings, + &SettingsCache::setBumpSetsWithCardsInDeckToTop); cardScalingCheckBox.setChecked(settings.getScaleCards()); - connect(&cardScalingCheckBox, SIGNAL(QT_STATE_CHANGED(QT_STATE_CHANGED_T)), &settings, - SLOT(setCardScaling(QT_STATE_CHANGED_T))); + connect(&cardScalingCheckBox, &QCheckBox::QT_STATE_CHANGED, &settings, &SettingsCache::setCardScaling); + + roundCardCornersCheckBox.setChecked(settings.getRoundCardCorners()); + connect(&roundCardCornersCheckBox, &QAbstractButton::toggled, &settings, &SettingsCache::setRoundCardCorners); verticalCardOverlapPercentBox.setValue(settings.getStackCardOverlapPercent()); verticalCardOverlapPercentBox.setRange(0, 80); connect(&verticalCardOverlapPercentBox, SIGNAL(valueChanged(int)), &settings, SLOT(setStackCardOverlapPercent(int))); + cardViewInitialRowsMaxBox.setRange(1, 999); + cardViewInitialRowsMaxBox.setValue(SettingsCache::instance().getCardViewInitialRowsMax()); + connect(&cardViewInitialRowsMaxBox, qOverload(&QSpinBox::valueChanged), this, + &AppearanceSettingsPage::cardViewInitialRowsMaxChanged); + + cardViewExpandedRowsMaxBox.setRange(1, 999); + cardViewExpandedRowsMaxBox.setValue(SettingsCache::instance().getCardViewExpandedRowsMax()); + connect(&cardViewExpandedRowsMaxBox, qOverload(&QSpinBox::valueChanged), this, + &AppearanceSettingsPage::cardViewExpandedRowsMaxChanged); + auto *cardsGrid = new QGridLayout; cardsGrid->addWidget(&displayCardNamesCheckBox, 0, 0, 1, 2); - cardsGrid->addWidget(&cardScalingCheckBox, 1, 0, 1, 2); - cardsGrid->addWidget(&verticalCardOverlapPercentLabel, 2, 0, 1, 1); - cardsGrid->addWidget(&verticalCardOverlapPercentBox, 2, 1, 1, 1); + cardsGrid->addWidget(&autoRotateSidewaysLayoutCardsCheckBox, 1, 0, 1, 2); + cardsGrid->addWidget(&cardScalingCheckBox, 2, 0, 1, 2); + cardsGrid->addWidget(&roundCardCornersCheckBox, 3, 0, 1, 2); + cardsGrid->addWidget(&overrideAllCardArtWithPersonalPreferenceCheckBox, 4, 0, 1, 2); + cardsGrid->addWidget(&bumpSetsWithCardsInDeckToTopCheckBox, 5, 0, 1, 2); + cardsGrid->addWidget(&verticalCardOverlapPercentLabel, 6, 0, 1, 1); + cardsGrid->addWidget(&verticalCardOverlapPercentBox, 6, 1, 1, 1); + cardsGrid->addWidget(&cardViewInitialRowsMaxLabel, 7, 0); + cardsGrid->addWidget(&cardViewInitialRowsMaxBox, 7, 1); + cardsGrid->addWidget(&cardViewExpandedRowsMaxLabel, 8, 0); + cardsGrid->addWidget(&cardViewExpandedRowsMaxBox, 8, 1); cardsGroupBox = new QGroupBox; cardsGroupBox->setLayout(cardsGrid); + // Hand layout horizontalHandCheckBox.setChecked(settings.getHorizontalHand()); - connect(&horizontalHandCheckBox, SIGNAL(QT_STATE_CHANGED(QT_STATE_CHANGED_T)), &settings, - SLOT(setHorizontalHand(QT_STATE_CHANGED_T))); + connect(&horizontalHandCheckBox, &QCheckBox::QT_STATE_CHANGED, &settings, &SettingsCache::setHorizontalHand); leftJustifiedHandCheckBox.setChecked(settings.getLeftJustified()); - connect(&leftJustifiedHandCheckBox, SIGNAL(QT_STATE_CHANGED(QT_STATE_CHANGED_T)), &settings, - SLOT(setLeftJustified(QT_STATE_CHANGED_T))); + connect(&leftJustifiedHandCheckBox, &QCheckBox::QT_STATE_CHANGED, &settings, &SettingsCache::setLeftJustified); auto *handGrid = new QGridLayout; handGrid->addWidget(&horizontalHandCheckBox, 0, 0, 1, 2); @@ -360,9 +433,10 @@ AppearanceSettingsPage::AppearanceSettingsPage() handGroupBox = new QGroupBox; handGroupBox->setLayout(handGrid); + // table grid layout invertVerticalCoordinateCheckBox.setChecked(settings.getInvertVerticalCoordinate()); - connect(&invertVerticalCoordinateCheckBox, SIGNAL(QT_STATE_CHANGED(QT_STATE_CHANGED_T)), &settings, - SLOT(setInvertVerticalCoordinate(QT_STATE_CHANGED_T))); + connect(&invertVerticalCoordinateCheckBox, &QCheckBox::QT_STATE_CHANGED, &settings, + &SettingsCache::setInvertVerticalCoordinate); minPlayersForMultiColumnLayoutEdit.setMinimum(2); minPlayersForMultiColumnLayoutEdit.setValue(settings.getMinPlayersForMultiColumnLayout()); @@ -386,14 +460,19 @@ AppearanceSettingsPage::AppearanceSettingsPage() tableGroupBox = new QGroupBox; tableGroupBox->setLayout(tableGrid); + // putting it all together auto *mainLayout = new QVBoxLayout; mainLayout->addWidget(themeGroupBox); + mainLayout->addWidget(menuGroupBox); mainLayout->addWidget(cardsGroupBox); mainLayout->addWidget(handGroupBox); mainLayout->addWidget(tableGroupBox); mainLayout->addStretch(); setLayout(mainLayout); + + connect(&SettingsCache::instance(), &SettingsCache::langChanged, this, &AppearanceSettingsPage::retranslateUi); + retranslateUi(); } void AppearanceSettingsPage::themeBoxChanged(int index) @@ -416,17 +495,63 @@ void AppearanceSettingsPage::openThemeLocation() } } +void AppearanceSettingsPage::showShortcutsChanged(QT_STATE_CHANGED_T value) +{ + SettingsCache::instance().setShowShortcuts(value); + qApp->setAttribute(Qt::AA_DontShowShortcutsInContextMenus, value == 0); // 0 = unchecked +} + +/** + * Updates the settings for cardViewInitialRowsMax. + * Forces expanded rows max to always be >= initial rows max + * @param value The new value + */ +void AppearanceSettingsPage::cardViewInitialRowsMaxChanged(int value) +{ + SettingsCache::instance().setCardViewInitialRowsMax(value); + if (cardViewExpandedRowsMaxBox.value() < value) { + cardViewExpandedRowsMaxBox.setValue(value); + } +} + +/** + * Updates the settings for cardViewExpandedRowsMax. + * Forces initial rows max to always be <= expanded rows max + * @param value The new value + */ +void AppearanceSettingsPage::cardViewExpandedRowsMaxChanged(int value) +{ + SettingsCache::instance().setCardViewExpandedRowsMax(value); + if (cardViewInitialRowsMaxBox.value() > value) { + cardViewInitialRowsMaxBox.setValue(value); + } +} + void AppearanceSettingsPage::retranslateUi() { themeGroupBox->setTitle(tr("Theme settings")); themeLabel.setText(tr("Current theme:")); openThemeButton.setText(tr("Open themes folder")); + menuGroupBox->setTitle(tr("Menu settings")); + showShortcutsCheckBox.setText(tr("Show keyboard shortcuts in right-click menus")); + cardsGroupBox->setTitle(tr("Card rendering")); displayCardNamesCheckBox.setText(tr("Display card names on cards having a picture")); + autoRotateSidewaysLayoutCardsCheckBox.setText(tr("Auto-Rotate cards with sideways layout")); + overrideAllCardArtWithPersonalPreferenceCheckBox.setText( + tr("Override all card art with personal set preference (Pre-ProviderID change behavior) [Requires Client " + "restart]")); + bumpSetsWithCardsInDeckToTopCheckBox.setText( + tr("Bump sets that the deck contains cards from to the top in the printing selector")); cardScalingCheckBox.setText(tr("Scale cards on mouse over")); + roundCardCornersCheckBox.setText(tr("Use rounded card corners")); verticalCardOverlapPercentLabel.setText( tr("Minimum overlap percentage of cards on the stack and in vertical hand")); + cardViewInitialRowsMaxLabel.setText(tr("Maximum initial height for card view window:")); + cardViewInitialRowsMaxBox.setSuffix(tr(" rows")); + cardViewExpandedRowsMaxLabel.setText(tr("Maximum expanded height for card view window:")); + cardViewExpandedRowsMaxBox.setSuffix(tr(" rows")); handGroupBox->setTitle(tr("Hand layout")); horizontalHandCheckBox.setText(tr("Display hand horizontally (wastes space)")); @@ -442,43 +567,53 @@ UserInterfaceSettingsPage::UserInterfaceSettingsPage() { // general settings and notification settings notificationsEnabledCheckBox.setChecked(SettingsCache::instance().getNotificationsEnabled()); - connect(¬ificationsEnabledCheckBox, SIGNAL(QT_STATE_CHANGED(QT_STATE_CHANGED_T)), &SettingsCache::instance(), - SLOT(setNotificationsEnabled(QT_STATE_CHANGED_T))); - connect(¬ificationsEnabledCheckBox, SIGNAL(QT_STATE_CHANGED(QT_STATE_CHANGED_T)), this, - SLOT(setNotificationEnabled(QT_STATE_CHANGED_T))); + connect(¬ificationsEnabledCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), + &SettingsCache::setNotificationsEnabled); + connect(¬ificationsEnabledCheckBox, &QCheckBox::QT_STATE_CHANGED, this, + &UserInterfaceSettingsPage::setNotificationEnabled); specNotificationsEnabledCheckBox.setChecked(SettingsCache::instance().getSpectatorNotificationsEnabled()); specNotificationsEnabledCheckBox.setEnabled(SettingsCache::instance().getNotificationsEnabled()); - connect(&specNotificationsEnabledCheckBox, SIGNAL(QT_STATE_CHANGED(QT_STATE_CHANGED_T)), &SettingsCache::instance(), - SLOT(setSpectatorNotificationsEnabled(QT_STATE_CHANGED_T))); + connect(&specNotificationsEnabledCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), + &SettingsCache::setSpectatorNotificationsEnabled); buddyConnectNotificationsEnabledCheckBox.setChecked( SettingsCache::instance().getBuddyConnectNotificationsEnabled()); buddyConnectNotificationsEnabledCheckBox.setEnabled(SettingsCache::instance().getNotificationsEnabled()); - connect(&buddyConnectNotificationsEnabledCheckBox, SIGNAL(QT_STATE_CHANGED(QT_STATE_CHANGED_T)), - &SettingsCache::instance(), SLOT(setBuddyConnectNotificationsEnabled(QT_STATE_CHANGED_T))); + connect(&buddyConnectNotificationsEnabledCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), + &SettingsCache::setBuddyConnectNotificationsEnabled); doubleClickToPlayCheckBox.setChecked(SettingsCache::instance().getDoubleClickToPlay()); - connect(&doubleClickToPlayCheckBox, SIGNAL(QT_STATE_CHANGED(QT_STATE_CHANGED_T)), &SettingsCache::instance(), - SLOT(setDoubleClickToPlay(QT_STATE_CHANGED_T))); + connect(&doubleClickToPlayCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), + &SettingsCache::setDoubleClickToPlay); + + clickPlaysAllSelectedCheckBox.setChecked(SettingsCache::instance().getClickPlaysAllSelected()); + connect(&clickPlaysAllSelectedCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), + &SettingsCache::setClickPlaysAllSelected); playToStackCheckBox.setChecked(SettingsCache::instance().getPlayToStack()); - connect(&playToStackCheckBox, SIGNAL(QT_STATE_CHANGED(QT_STATE_CHANGED_T)), &SettingsCache::instance(), - SLOT(setPlayToStack(QT_STATE_CHANGED_T))); + connect(&playToStackCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), + &SettingsCache::setPlayToStack); + + closeEmptyCardViewCheckBox.setChecked(SettingsCache::instance().getCloseEmptyCardView()); + connect(&closeEmptyCardViewCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), + &SettingsCache::setCloseEmptyCardView); annotateTokensCheckBox.setChecked(SettingsCache::instance().getAnnotateTokens()); - connect(&annotateTokensCheckBox, SIGNAL(QT_STATE_CHANGED(QT_STATE_CHANGED_T)), &SettingsCache::instance(), - SLOT(setAnnotateTokens(QT_STATE_CHANGED_T))); + connect(&annotateTokensCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), + &SettingsCache::setAnnotateTokens); useTearOffMenusCheckBox.setChecked(SettingsCache::instance().getUseTearOffMenus()); connect(&useTearOffMenusCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), - [](QT_STATE_CHANGED_T state) { SettingsCache::instance().setUseTearOffMenus(state == Qt::Checked); }); + [](const QT_STATE_CHANGED_T state) { SettingsCache::instance().setUseTearOffMenus(state == Qt::Checked); }); auto *generalGrid = new QGridLayout; generalGrid->addWidget(&doubleClickToPlayCheckBox, 0, 0); - generalGrid->addWidget(&playToStackCheckBox, 1, 0); - generalGrid->addWidget(&annotateTokensCheckBox, 2, 0); - generalGrid->addWidget(&useTearOffMenusCheckBox, 3, 0); + generalGrid->addWidget(&clickPlaysAllSelectedCheckBox, 1, 0); + generalGrid->addWidget(&playToStackCheckBox, 2, 0); + generalGrid->addWidget(&closeEmptyCardViewCheckBox, 3, 0); + generalGrid->addWidget(&annotateTokensCheckBox, 4, 0); + generalGrid->addWidget(&useTearOffMenusCheckBox, 5, 0); generalGroupBox = new QGroupBox; generalGroupBox->setLayout(generalGrid); @@ -493,8 +628,8 @@ UserInterfaceSettingsPage::UserInterfaceSettingsPage() // animation settings tapAnimationCheckBox.setChecked(SettingsCache::instance().getTapAnimation()); - connect(&tapAnimationCheckBox, SIGNAL(QT_STATE_CHANGED(QT_STATE_CHANGED_T)), &SettingsCache::instance(), - SLOT(setTapAnimation(QT_STATE_CHANGED_T))); + connect(&tapAnimationCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), + &SettingsCache::setTapAnimation); auto *animationGrid = new QGridLayout; animationGrid->addWidget(&tapAnimationCheckBox, 0, 0); @@ -504,27 +639,60 @@ UserInterfaceSettingsPage::UserInterfaceSettingsPage() // deck editor settings openDeckInNewTabCheckBox.setChecked(SettingsCache::instance().getOpenDeckInNewTab()); - connect(&openDeckInNewTabCheckBox, SIGNAL(QT_STATE_CHANGED(QT_STATE_CHANGED_T)), &SettingsCache::instance(), - SLOT(setOpenDeckInNewTab(QT_STATE_CHANGED_T))); + connect(&openDeckInNewTabCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), + &SettingsCache::setOpenDeckInNewTab); + + visualDeckStoragePromptForConversionCheckBox.setChecked( + SettingsCache::instance().getVisualDeckStoragePromptForConversion()); + connect(&visualDeckStoragePromptForConversionCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), + &SettingsCache::setVisualDeckStoragePromptForConversion); + + visualDeckStorageAlwaysConvertCheckBox.setChecked(SettingsCache::instance().getVisualDeckStorageAlwaysConvert()); + connect(&visualDeckStorageAlwaysConvertCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), + &SettingsCache::setVisualDeckStorageAlwaysConvert); + + visualDeckStorageInGameCheckBox.setChecked(SettingsCache::instance().getVisualDeckStorageInGame()); + connect(&visualDeckStorageInGameCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), + &SettingsCache::setVisualDeckStorageInGame); auto *deckEditorGrid = new QGridLayout; deckEditorGrid->addWidget(&openDeckInNewTabCheckBox, 0, 0); + deckEditorGrid->addWidget(&visualDeckStoragePromptForConversionCheckBox, 1, 0); + deckEditorGrid->addWidget(&visualDeckStorageAlwaysConvertCheckBox, 2, 0); + deckEditorGrid->addWidget(&visualDeckStorageInGameCheckBox, 3, 0); deckEditorGroupBox = new QGroupBox; deckEditorGroupBox->setLayout(deckEditorGrid); + // replay settings + rewindBufferingMsBox.setRange(0, 9999); + rewindBufferingMsBox.setValue(SettingsCache::instance().getRewindBufferingMs()); + connect(&rewindBufferingMsBox, qOverload(&QSpinBox::valueChanged), &SettingsCache::instance(), + &SettingsCache::setRewindBufferingMs); + + auto *replayGrid = new QGridLayout; + replayGrid->addWidget(&rewindBufferingMsLabel, 0, 0, 1, 1); + replayGrid->addWidget(&rewindBufferingMsBox, 0, 1, 1, 1); + + replayGroupBox = new QGroupBox; + replayGroupBox->setLayout(replayGrid); + // putting it all together auto *mainLayout = new QVBoxLayout; mainLayout->addWidget(generalGroupBox); mainLayout->addWidget(notificationsGroupBox); mainLayout->addWidget(animationGroupBox); mainLayout->addWidget(deckEditorGroupBox); + mainLayout->addWidget(replayGroupBox); mainLayout->addStretch(); setLayout(mainLayout); + + connect(&SettingsCache::instance(), &SettingsCache::langChanged, this, &UserInterfaceSettingsPage::retranslateUi); + retranslateUi(); } -void UserInterfaceSettingsPage::setNotificationEnabled(int i) +void UserInterfaceSettingsPage::setNotificationEnabled(QT_STATE_CHANGED_T i) { specNotificationsEnabledCheckBox.setEnabled(i != 0); buddyConnectNotificationsEnabledCheckBox.setEnabled(i != 0); @@ -538,7 +706,9 @@ void UserInterfaceSettingsPage::retranslateUi() { generalGroupBox->setTitle(tr("General interface settings")); doubleClickToPlayCheckBox.setText(tr("&Double-click cards to play them (instead of single-click)")); + clickPlaysAllSelectedCheckBox.setText(tr("&Clicking plays all selected cards (instead of just the clicked card)")); playToStackCheckBox.setText(tr("&Play all nonlands onto the stack (not the battlefield) by default")); + closeEmptyCardViewCheckBox.setText(tr("Close card view window when last card is removed")); annotateTokensCheckBox.setText(tr("Annotate card text on tokens")); useTearOffMenusCheckBox.setText(tr("Use tear-off menus, allowing right click menus to persist on screen")); notificationsGroupBox->setTitle(tr("Notifications settings")); @@ -547,15 +717,21 @@ void UserInterfaceSettingsPage::retranslateUi() buddyConnectNotificationsEnabledCheckBox.setText(tr("Notify in the taskbar when users in your buddy list connect")); animationGroupBox->setTitle(tr("Animation settings")); tapAnimationCheckBox.setText(tr("&Tap/untap animation")); - deckEditorGroupBox->setTitle(tr("Deck editor settings")); - openDeckInNewTabCheckBox.setText(tr("Always open deck in new tab")); + deckEditorGroupBox->setTitle(tr("Deck editor/storage settings")); + openDeckInNewTabCheckBox.setText(tr("Open deck in new tab by default")); + visualDeckStoragePromptForConversionCheckBox.setText(tr("Prompt before converting .txt decks to .cod format")); + visualDeckStorageAlwaysConvertCheckBox.setText(tr("Always convert if not prompted")); + visualDeckStorageInGameCheckBox.setText(tr("Use visual deck storage in game lobby")); + replayGroupBox->setTitle(tr("Replay settings")); + rewindBufferingMsLabel.setText(tr("Buffer time for backwards skip via shortcut:")); + rewindBufferingMsBox.setSuffix(" ms"); } DeckEditorSettingsPage::DeckEditorSettingsPage() { picDownloadCheckBox.setChecked(SettingsCache::instance().getPicDownload()); - connect(&picDownloadCheckBox, SIGNAL(QT_STATE_CHANGED(QT_STATE_CHANGED_T)), &SettingsCache::instance(), - SLOT(setPicDownload(QT_STATE_CHANGED_T))); + connect(&picDownloadCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), + &SettingsCache::setPicDownload); urlLinkLabel.setTextInteractionFlags(Qt::LinksAccessibleByMouse); urlLinkLabel.setOpenExternalLinks(true); @@ -573,7 +749,7 @@ DeckEditorSettingsPage::DeckEditorSettingsPage() mpSpoilerPathButton = new QPushButton("..."); connect(mpSpoilerPathButton, SIGNAL(clicked()), this, SLOT(spoilerPathButtonClicked())); - updateNowButton = new QPushButton(tr("Update Spoilers")); + updateNowButton = new QPushButton; updateNowButton->setFixedWidth(150); connect(updateNowButton, SIGNAL(clicked()), this, SLOT(updateSpoilers())); @@ -588,32 +764,30 @@ DeckEditorSettingsPage::DeckEditorSettingsPage() connect(urlList->model(), SIGNAL(rowsMoved(const QModelIndex, int, int, const QModelIndex, int)), this, SLOT(urlListChanged(const QModelIndex, int, int, const QModelIndex, int))); - for (int i = 0; i < SettingsCache::instance().downloads().getCount(); i++) - urlList->addItem(SettingsCache::instance().downloads().getDownloadUrlAt(i)); + urlList->addItems(SettingsCache::instance().downloads().getAllURLs()); - auto aAdd = new QAction(this); + aAdd = new QAction(this); aAdd->setIcon(QPixmap("theme:icons/increment")); - aAdd->setToolTip(tr("Add New URL")); connect(aAdd, SIGNAL(triggered()), this, SLOT(actAddURL())); - auto aEdit = new QAction(this); + + aEdit = new QAction(this); aEdit->setIcon(QPixmap("theme:icons/pencil")); - aEdit->setToolTip(tr("Edit URL")); connect(aEdit, SIGNAL(triggered()), this, SLOT(actEditURL())); - auto aRemove = new QAction(this); + + aRemove = new QAction(this); aRemove->setIcon(QPixmap("theme:icons/decrement")); - aRemove->setToolTip(tr("Remove URL")); connect(aRemove, SIGNAL(triggered()), this, SLOT(actRemoveURL())); - auto *messageToolBar = new QToolBar; - messageToolBar->setOrientation(Qt::Vertical); - messageToolBar->addAction(aAdd); - messageToolBar->addAction(aRemove); - messageToolBar->addAction(aEdit); - messageToolBar->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::MinimumExpanding); + auto *urlToolBar = new QToolBar; + urlToolBar->setOrientation(Qt::Vertical); + urlToolBar->addAction(aAdd); + urlToolBar->addAction(aRemove); + urlToolBar->addAction(aEdit); + urlToolBar->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::MinimumExpanding); - auto *messageListLayout = new QHBoxLayout; - messageListLayout->addWidget(messageToolBar); - messageListLayout->addWidget(urlList); + auto *urlListLayout = new QHBoxLayout; + urlListLayout->addWidget(urlToolBar); + urlListLayout->addWidget(urlList); // pixmap cache pixmapCacheEdit.setMinimum(PIXMAPCACHE_SIZE_MIN); @@ -633,7 +807,6 @@ DeckEditorSettingsPage::DeckEditorSettingsPage() networkRedirectCacheTtlEdit.setMaximum(NETWORK_REDIRECT_CACHE_TTL_MAX); networkRedirectCacheTtlEdit.setSingleStep(1); networkRedirectCacheTtlEdit.setValue(SettingsCache::instance().getRedirectCacheTtl()); - networkRedirectCacheTtlEdit.setSuffix(" " + tr("Day(s)")); auto networkCacheLayout = new QHBoxLayout; networkCacheLayout->addStretch(); @@ -653,10 +826,10 @@ DeckEditorSettingsPage::DeckEditorSettingsPage() // Top Layout lpGeneralGrid->addWidget(&picDownloadCheckBox, 0, 0); lpGeneralGrid->addWidget(&resetDownloadURLs, 0, 1); - lpGeneralGrid->addLayout(messageListLayout, 1, 0, 1, 2); - lpGeneralGrid->addLayout(networkCacheLayout, 2, 0, 1, 2); - lpGeneralGrid->addLayout(pixmapCacheLayout, 3, 0, 1, 2); - lpGeneralGrid->addLayout(networkRedirectCacheLayout, 4, 0, 1, 2); + lpGeneralGrid->addLayout(urlListLayout, 1, 0, 1, 2); + lpGeneralGrid->addLayout(networkCacheLayout, 2, 1); + lpGeneralGrid->addLayout(networkRedirectCacheLayout, 3, 0); + lpGeneralGrid->addLayout(pixmapCacheLayout, 3, 1); lpGeneralGrid->addWidget(&urlLinkLabel, 5, 0); lpGeneralGrid->addWidget(&clearDownloadedPicsButton, 5, 1); @@ -690,11 +863,14 @@ DeckEditorSettingsPage::DeckEditorSettingsPage() lpMainLayout->addWidget(mpSpoilerGroupBox); setLayout(lpMainLayout); + + connect(&SettingsCache::instance(), &SettingsCache::langChanged, this, &DeckEditorSettingsPage::retranslateUi); + retranslateUi(); } void DeckEditorSettingsPage::resetDownloadedURLsButtonClicked() { - SettingsCache::instance().downloads().clear(); + SettingsCache::instance().downloads().resetToDefaultURLs(); urlList->clear(); urlList->addItems(SettingsCache::instance().downloads().getAllURLs()); QMessageBox::information(this, tr("Success"), tr("Download URLs have been reset.")); @@ -774,11 +950,13 @@ void DeckEditorSettingsPage::actEditURL() void DeckEditorSettingsPage::storeSettings() { qInfo() << "URL Priority Reset"; - SettingsCache::instance().downloads().clear(); + + QStringList downloadUrls; for (int i = 0; i < urlList->count(); i++) { qInfo() << "Priority" << i << ":" << urlList->item(i)->text(); - SettingsCache::instance().downloads().setDownloadUrlAt(i, urlList->item(i)->text()); + downloadUrls << urlList->item(i)->text(); } + SettingsCache::instance().downloads().setDownloadUrls(downloadUrls); } void DeckEditorSettingsPage::urlListChanged(const QModelIndex &, int, int, const QModelIndex &, int) @@ -864,35 +1042,39 @@ void DeckEditorSettingsPage::retranslateUi() networkRedirectCacheTtlEdit.setToolTip(tr("How long cached redirects for urls are valid for.")); pixmapCacheLabel.setText(tr("Picture Cache Size:")); pixmapCacheEdit.setToolTip(tr("In-memory cache for pictures not currently on screen")); + updateNowButton->setText(tr("Update Spoilers")); + aAdd->setText(tr("Add New URL")); + aEdit->setText(tr("Edit URL")); + aRemove->setText(tr("Remove URL")); + networkRedirectCacheTtlEdit.setSuffix(" " + tr("Day(s)")); } MessagesSettingsPage::MessagesSettingsPage() { chatMentionCheckBox.setChecked(SettingsCache::instance().getChatMention()); - connect(&chatMentionCheckBox, SIGNAL(QT_STATE_CHANGED(QT_STATE_CHANGED_T)), &SettingsCache::instance(), - SLOT(setChatMention(QT_STATE_CHANGED_T))); + connect(&chatMentionCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), + &SettingsCache::setChatMention); chatMentionCompleterCheckbox.setChecked(SettingsCache::instance().getChatMentionCompleter()); - connect(&chatMentionCompleterCheckbox, SIGNAL(QT_STATE_CHANGED(QT_STATE_CHANGED_T)), &SettingsCache::instance(), - SLOT(setChatMentionCompleter(QT_STATE_CHANGED_T))); + connect(&chatMentionCompleterCheckbox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), + &SettingsCache::setChatMentionCompleter); explainMessagesLabel.setTextInteractionFlags(Qt::LinksAccessibleByMouse); explainMessagesLabel.setOpenExternalLinks(true); ignoreUnregUsersMainChat.setChecked(SettingsCache::instance().getIgnoreUnregisteredUsers()); ignoreUnregUserMessages.setChecked(SettingsCache::instance().getIgnoreUnregisteredUserMessages()); - connect(&ignoreUnregUsersMainChat, SIGNAL(QT_STATE_CHANGED(QT_STATE_CHANGED_T)), &SettingsCache::instance(), - SLOT(setIgnoreUnregisteredUsers(QT_STATE_CHANGED_T))); - connect(&ignoreUnregUserMessages, SIGNAL(QT_STATE_CHANGED(QT_STATE_CHANGED_T)), &SettingsCache::instance(), - SLOT(setIgnoreUnregisteredUserMessages(QT_STATE_CHANGED_T))); + connect(&ignoreUnregUsersMainChat, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), + &SettingsCache::setIgnoreUnregisteredUsers); + connect(&ignoreUnregUserMessages, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), + &SettingsCache::setIgnoreUnregisteredUserMessages); invertMentionForeground.setChecked(SettingsCache::instance().getChatMentionForeground()); - connect(&invertMentionForeground, SIGNAL(QT_STATE_CHANGED(QT_STATE_CHANGED_T)), this, - SLOT(updateTextColor(QT_STATE_CHANGED_T))); + connect(&invertMentionForeground, &QCheckBox::QT_STATE_CHANGED, this, &MessagesSettingsPage::updateTextColor); invertHighlightForeground.setChecked(SettingsCache::instance().getChatHighlightForeground()); - connect(&invertHighlightForeground, SIGNAL(QT_STATE_CHANGED(QT_STATE_CHANGED_T)), this, - SLOT(updateTextHighlightColor(QT_STATE_CHANGED_T))); + connect(&invertHighlightForeground, &QCheckBox::QT_STATE_CHANGED, this, + &MessagesSettingsPage::updateTextHighlightColor); mentionColor = new QLineEdit(); mentionColor->setText(SettingsCache::instance().getChatMentionColor()); @@ -900,19 +1082,17 @@ MessagesSettingsPage::MessagesSettingsPage() connect(mentionColor, SIGNAL(textChanged(QString)), this, SLOT(updateColor(QString))); messagePopups.setChecked(SettingsCache::instance().getShowMessagePopup()); - connect(&messagePopups, SIGNAL(QT_STATE_CHANGED(QT_STATE_CHANGED_T)), &SettingsCache::instance(), - SLOT(setShowMessagePopups(QT_STATE_CHANGED_T))); + connect(&messagePopups, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), + &SettingsCache::setShowMessagePopups); mentionPopups.setChecked(SettingsCache::instance().getShowMentionPopup()); - connect(&mentionPopups, SIGNAL(QT_STATE_CHANGED(QT_STATE_CHANGED_T)), &SettingsCache::instance(), - SLOT(setShowMentionPopups(QT_STATE_CHANGED_T))); + connect(&mentionPopups, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), + &SettingsCache::setShowMentionPopups); roomHistory.setChecked(SettingsCache::instance().getRoomHistory()); - connect(&roomHistory, SIGNAL(QT_STATE_CHANGED(QT_STATE_CHANGED_T)), &SettingsCache::instance(), - SLOT(setRoomHistory(QT_STATE_CHANGED_T))); + connect(&roomHistory, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), &SettingsCache::setRoomHistory); customAlertString = new QLineEdit(); - customAlertString->setPlaceholderText(tr("Word1 Word2 Word3")); customAlertString->setText(SettingsCache::instance().getHighlightWords()); connect(customAlertString, SIGNAL(textChanged(QString)), &SettingsCache::instance(), SLOT(setHighlightWords(QString))); @@ -953,15 +1133,14 @@ MessagesSettingsPage::MessagesSettingsPage() aAdd = new QAction(this); aAdd->setIcon(QPixmap("theme:icons/increment")); - aAdd->setToolTip(tr("Add New Message")); connect(aAdd, SIGNAL(triggered()), this, SLOT(actAdd())); + aEdit = new QAction(this); aEdit->setIcon(QPixmap("theme:icons/pencil")); - aEdit->setToolTip(tr("Edit Message")); connect(aEdit, SIGNAL(triggered()), this, SLOT(actEdit())); + aRemove = new QAction(this); aRemove->setIcon(QPixmap("theme:icons/decrement")); - aRemove->setToolTip(tr("Remove Message")); connect(aRemove, SIGNAL(triggered()), this, SLOT(actRemove())); auto *messageToolBar = new QToolBar; @@ -989,6 +1168,7 @@ MessagesSettingsPage::MessagesSettingsPage() setLayout(mainLayout); + connect(&SettingsCache::instance(), &SettingsCache::langChanged, this, &MessagesSettingsPage::retranslateUi); retranslateUi(); } @@ -1106,13 +1286,17 @@ void MessagesSettingsPage::retranslateUi() hexLabel.setText(tr("(Color is hexadecimal)")); hexHighlightLabel.setText(tr("(Color is hexadecimal)")); customAlertStringLabel.setText(tr("Separate words with a space, alphanumeric characters only")); + customAlertString->setPlaceholderText(tr("Word1 Word2 Word3")); + aAdd->setText(tr("Add New Message")); + aEdit->setText(tr("Edit Message")); + aRemove->setText(tr("Remove Message")); } SoundSettingsPage::SoundSettingsPage() { soundEnabledCheckBox.setChecked(SettingsCache::instance().getSoundEnabled()); - connect(&soundEnabledCheckBox, SIGNAL(QT_STATE_CHANGED(QT_STATE_CHANGED_T)), &SettingsCache::instance(), - SLOT(setSoundEnabled(QT_STATE_CHANGED_T))); + connect(&soundEnabledCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), + &SettingsCache::setSoundEnabled); QString themeName = SettingsCache::instance().getSoundThemeName(); @@ -1159,6 +1343,9 @@ SoundSettingsPage::SoundSettingsPage() mainLayout->addStretch(); setLayout(mainLayout); + + connect(&SettingsCache::instance(), &SettingsCache::langChanged, this, &SoundSettingsPage::retranslateUi); + retranslateUi(); } void SoundSettingsPage::themeBoxChanged(int index) @@ -1184,13 +1371,23 @@ void SoundSettingsPage::retranslateUi() ShortcutSettingsPage::ShortcutSettingsPage() { + // search bar + searchEdit = new SearchLineEdit; + searchEdit->setObjectName("searchEdit"); + searchEdit->setClearButtonEnabled(true); + + setFocusProxy(searchEdit); + setFocusPolicy(Qt::ClickFocus); + // table - shortcutsTable = new QTreeWidget(); - shortcutsTable->setColumnCount(2); + shortcutsTable = new ShortcutTreeView(this); + shortcutsTable->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); - shortcutsTable->setUniformRowHeights(true); - shortcutsTable->setAlternatingRowColors(true); - shortcutsTable->header()->resizeSection(0, shortcutsTable->width() / 3 * 2); + shortcutsTable->setEditTriggers(QAbstractItemView::NoEditTriggers); + shortcutsTable->setColumnWidth(0, width() / 3 * 2); + searchEdit->setTreeView(shortcutsTable); + + connect(searchEdit, &SearchLineEdit::textChanged, shortcutsTable, &ShortcutTreeView::updateSearchString); // edit widget currentActionGroupLabel = new QLabel(this); @@ -1230,6 +1427,7 @@ ShortcutSettingsPage::ShortcutSettingsPage() _buttonsLayout->addWidget(btnClearAll); auto *_mainLayout = new QVBoxLayout; + _mainLayout->addWidget(searchEdit); _mainLayout->addWidget(shortcutsTable); _mainLayout->addWidget(editShortcutGroupBox); _mainLayout->addLayout(_buttonsLayout); @@ -1238,21 +1436,20 @@ ShortcutSettingsPage::ShortcutSettingsPage() connect(btnResetAll, SIGNAL(clicked()), this, SLOT(resetShortcuts())); connect(btnClearAll, SIGNAL(clicked()), this, SLOT(clearShortcuts())); - connect(shortcutsTable, SIGNAL(currentItemChanged(QTreeWidgetItem *, QTreeWidgetItem *)), this, - SLOT(currentItemChanged(QTreeWidgetItem *, QTreeWidgetItem *))); - connect(&SettingsCache::instance().shortcuts(), SIGNAL(shortCutChanged()), this, SLOT(refreshShortcuts())); - createShortcuts(); + connect(shortcutsTable, &ShortcutTreeView::currentItemChanged, this, &ShortcutSettingsPage::currentItemChanged); + + connect(&SettingsCache::instance(), &SettingsCache::langChanged, this, &ShortcutSettingsPage::retranslateUi); + retranslateUi(); } -void ShortcutSettingsPage::currentItemChanged(QTreeWidgetItem *current, QTreeWidgetItem * /*previous */) +void ShortcutSettingsPage::currentItemChanged(const QString &key) { - if (current == nullptr) { + if (key.isEmpty()) { currentActionGroupName->setText(""); currentActionName->setText(""); editTextBox->setShortcutName(""); } else { - QString key = current->data(2, Qt::DisplayRole).toString(); QString group = SettingsCache::instance().shortcuts().getShortcut(key).getGroupName(); QString action = SettingsCache::instance().shortcuts().getShortcut(key).getName(); currentActionGroupName->setText(group); @@ -1269,59 +1466,6 @@ void ShortcutSettingsPage::resetShortcuts() } } -void ShortcutSettingsPage::createShortcuts() -{ - QHash parentItems; - QTreeWidgetItem *curParent = nullptr; - for (const auto &key : SettingsCache::instance().shortcuts().getAllShortcutKeys()) { - QString name = SettingsCache::instance().shortcuts().getShortcut(key).getName(); - QString group = SettingsCache::instance().shortcuts().getShortcut(key).getGroupName(); - QString shortcut = SettingsCache::instance().shortcuts().getShortcutString(key); - - if (parentItems.contains(group)) { - curParent = parentItems.value(group); - } else { - curParent = new QTreeWidgetItem((QTreeWidget *)nullptr, QStringList({group, "", ""})); - static QFont font = curParent->font(0); - font.setBold(true); - curParent->setFont(0, font); - parentItems.insert(group, curParent); - } - - new QTreeWidgetItem(curParent, QStringList({name, shortcut, key})); - } - shortcutsTable->clear(); - shortcutsTable->insertTopLevelItems(0, parentItems.values()); - shortcutsTable->setCurrentItem(nullptr); - shortcutsTable->expandAll(); - shortcutsTable->sortItems(0, Qt::AscendingOrder); -} - -void ShortcutSettingsPage::refreshShortcuts() -{ - QTreeWidgetItem *curParent = nullptr; - QTreeWidgetItem *curChild = nullptr; - for (int i = 0; i < shortcutsTable->topLevelItemCount(); ++i) { - curParent = shortcutsTable->topLevelItem(i); - for (int j = 0; j < curParent->childCount(); ++j) { - curChild = curParent->child(j); - QString key = curChild->data(2, Qt::DisplayRole).toString(); - QString name = SettingsCache::instance().shortcuts().getShortcut(key).getName(); - QString shortcut = SettingsCache::instance().shortcuts().getShortcutString(key); - curChild->setText(0, name); - curChild->setText(1, shortcut); - - if (j == 0) { - // the first child also updates the parent's group name - QString group = SettingsCache::instance().shortcuts().getShortcut(key).getGroupName(); - curParent->setText(0, group); - } - } - } - shortcutsTable->sortItems(0, Qt::AscendingOrder); - currentItemChanged(shortcutsTable->currentItem(), nullptr); -} - void ShortcutSettingsPage::clearShortcuts() { if (QMessageBox::question(this, tr("Clear all default shortcuts"), @@ -1332,8 +1476,7 @@ void ShortcutSettingsPage::clearShortcuts() void ShortcutSettingsPage::retranslateUi() { - shortcutsTable->setHeaderLabels(QStringList() << tr("Action") << tr("Shortcut")); - refreshShortcuts(); + shortcutsTable->retranslateUi(); currentActionGroupLabel->setText(tr("Section:")); currentActionLabel->setText(tr("Action:")); @@ -1342,6 +1485,20 @@ void ShortcutSettingsPage::retranslateUi() faqLabel->setText(QString("%2").arg(WIKI_CUSTOM_SHORTCUTS).arg(tr("How to set custom shortcuts"))); btnResetAll->setText(tr("Restore all default shortcuts")); btnClearAll->setText(tr("Clear all shortcuts")); + searchEdit->setPlaceholderText(tr("Search by shortcut name")); +} + +static QScrollArea *makeScrollable(QWidget *widget) +{ + widget->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Maximum); + + auto *scrollArea = new QScrollArea; + scrollArea->setWidgetResizable(true); + scrollArea->setContentsMargins(0, 0, 0, 0); + scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + scrollArea->horizontalScrollBar()->setEnabled(false); + scrollArea->setWidget(widget); + return scrollArea; } DlgSettings::DlgSettings(QWidget *parent) : QDialog(parent) @@ -1361,8 +1518,8 @@ DlgSettings::DlgSettings(QWidget *parent) : QDialog(parent) pagesWidget = new QStackedWidget; pagesWidget->addWidget(new GeneralSettingsPage); - pagesWidget->addWidget(new AppearanceSettingsPage); - pagesWidget->addWidget(new UserInterfaceSettingsPage); + pagesWidget->addWidget(makeScrollable(new AppearanceSettingsPage)); + pagesWidget->addWidget(makeScrollable(new UserInterfaceSettingsPage)); pagesWidget->addWidget(new DeckEditorSettingsPage); pagesWidget->addWidget(new MessagesSettingsPage); pagesWidget->addWidget(new SoundSettingsPage); @@ -1384,6 +1541,7 @@ DlgSettings::DlgSettings(QWidget *parent) : QDialog(parent) mainLayout->addWidget(buttonBox); setLayout(mainLayout); + connect(&SettingsCache::instance(), &SettingsCache::langChanged, this, &DlgSettings::retranslateUi); retranslateUi(); adjustSize(); @@ -1452,19 +1610,12 @@ void DlgSettings::updateLanguage() installNewTranslator(); } -void DlgSettings::changeEvent(QEvent *event) -{ - if (event->type() == QEvent::LanguageChange) - retranslateUi(); - QDialog::changeEvent(event); -} - void DlgSettings::closeEvent(QCloseEvent *event) { bool showLoadError = true; QString loadErrorMessage = tr("Unknown Error loading card database"); LoadStatus loadStatus = CardDatabaseManager::getInstance()->getLoadStatus(); - qDebug() << "Card Database load status: " << loadStatus; + qCInfo(DlgSettingsLog) << "Card Database load status: " << loadStatus; switch (loadStatus) { case Ok: showLoadError = false; @@ -1547,8 +1698,5 @@ void DlgSettings::retranslateUi() soundButton->setText(tr("Sound")); shortcutsButton->setText(tr("Shortcuts")); - for (int i = 0; i < pagesWidget->count(); i++) - dynamic_cast(pagesWidget->widget(i))->retranslateUi(); - contentsWidget->reset(); } diff --git a/cockatrice/src/dialogs/dlg_settings.h b/cockatrice/src/dialogs/dlg_settings.h index 09c5ded13..95ae17979 100644 --- a/cockatrice/src/dialogs/dlg_settings.h +++ b/cockatrice/src/dialogs/dlg_settings.h @@ -8,9 +8,16 @@ #include #include #include +#include #include #include +inline Q_LOGGING_CATEGORY(DlgSettingsLog, "dlg_settings"); + +class ShortcutTreeView; +class SearchLineEdit; +class QTreeView; +class QStandardItemModel; class CardDatabase; class QCloseEvent; class QGridLayout; @@ -21,8 +28,6 @@ class QListWidgetItem; class QRadioButton; class QSlider; class QStackedWidget; -class QTreeWidget; -class QTreeWidgetItem; class QVBoxLayout; class SequenceEdit; @@ -63,6 +68,7 @@ private: QGroupBox *personalGroupBox; QGroupBox *pathsGroupBox; QComboBox languageBox; + QCheckBox startupUpdateCheckCheckBox; QCheckBox updateNotificationCheckBox; QCheckBox newVersionOracleCheckBox; QComboBox updateReleaseChannelBox; @@ -74,6 +80,7 @@ private: QLabel customCardDatabasePathLabel; QLabel tokenDatabasePathLabel; QLabel updateReleaseChannelLabel; + QLabel advertiseTranslationPageLabel; QCheckBox showTipsOnStartup; }; @@ -83,6 +90,10 @@ class AppearanceSettingsPage : public AbstractSettingsPage private slots: void themeBoxChanged(int index); void openThemeLocation(); + void showShortcutsChanged(QT_STATE_CHANGED_T enabled); + + void cardViewInitialRowsMaxChanged(int value); + void cardViewExpandedRowsMaxChanged(int value); private: QLabel themeLabel; @@ -90,14 +101,24 @@ private: QPushButton openThemeButton; QLabel minPlayersForMultiColumnLayoutLabel; QLabel maxFontSizeForCardsLabel; + QCheckBox showShortcutsCheckBox; QCheckBox displayCardNamesCheckBox; + QCheckBox autoRotateSidewaysLayoutCardsCheckBox; + QCheckBox overrideAllCardArtWithPersonalPreferenceCheckBox; + QCheckBox bumpSetsWithCardsInDeckToTopCheckBox; QCheckBox cardScalingCheckBox; + QCheckBox roundCardCornersCheckBox; QLabel verticalCardOverlapPercentLabel; QSpinBox verticalCardOverlapPercentBox; + QLabel cardViewInitialRowsMaxLabel; + QSpinBox cardViewInitialRowsMaxBox; + QLabel cardViewExpandedRowsMaxLabel; + QSpinBox cardViewExpandedRowsMaxBox; QCheckBox horizontalHandCheckBox; QCheckBox leftJustifiedHandCheckBox; QCheckBox invertVerticalCoordinateCheckBox; QGroupBox *themeGroupBox; + QGroupBox *menuGroupBox; QGroupBox *cardsGroupBox; QGroupBox *handGroupBox; QGroupBox *tableGroupBox; @@ -113,22 +134,30 @@ class UserInterfaceSettingsPage : public AbstractSettingsPage { Q_OBJECT private slots: - void setNotificationEnabled(int); + void setNotificationEnabled(QT_STATE_CHANGED_T); private: QCheckBox notificationsEnabledCheckBox; QCheckBox specNotificationsEnabledCheckBox; QCheckBox buddyConnectNotificationsEnabledCheckBox; QCheckBox doubleClickToPlayCheckBox; + QCheckBox clickPlaysAllSelectedCheckBox; QCheckBox playToStackCheckBox; + QCheckBox closeEmptyCardViewCheckBox; QCheckBox annotateTokensCheckBox; QCheckBox useTearOffMenusCheckBox; QCheckBox tapAnimationCheckBox; QCheckBox openDeckInNewTabCheckBox; + QCheckBox visualDeckStoragePromptForConversionCheckBox; + QCheckBox visualDeckStorageAlwaysConvertCheckBox; + QCheckBox visualDeckStorageInGameCheckBox; + QLabel rewindBufferingMsLabel; + QSpinBox rewindBufferingMsBox; QGroupBox *generalGroupBox; QGroupBox *notificationsGroupBox; QGroupBox *animationGroupBox; QGroupBox *deckEditorGroupBox; + QGroupBox *replayGroupBox; public: UserInterfaceSettingsPage(); @@ -162,6 +191,7 @@ private: QLabel urlLinkLabel; QCheckBox picDownloadCheckBox; QListWidget *urlList; + QAction *aAdd, *aEdit, *aRemove; QCheckBox mcDownloadSpoilersCheckBox; QLabel msDownloadSpoilersLabel; QGroupBox *mpGeneralGroupBox; @@ -256,7 +286,8 @@ public: void retranslateUi() override; private: - QTreeWidget *shortcutsTable; + SearchLineEdit *searchEdit; + ShortcutTreeView *shortcutsTable; QVBoxLayout *mainLayout; QHBoxLayout *buttonsLayout; QGroupBox *editShortcutGroupBox; @@ -273,10 +304,8 @@ private: private slots: void resetShortcuts(); - void refreshShortcuts(); - void createShortcuts(); void clearShortcuts(); - void currentItemChanged(QTreeWidgetItem *current, QTreeWidgetItem *previous); + void currentItemChanged(const QString &key); }; class DlgSettings : public QDialog @@ -299,7 +328,6 @@ private: void retranslateUi(); protected: - void changeEvent(QEvent *event) override; void closeEvent(QCloseEvent *event) override; }; diff --git a/cockatrice/src/dialogs/dlg_tip_of_the_day.cpp b/cockatrice/src/dialogs/dlg_tip_of_the_day.cpp index c4b75ccd0..53225f50c 100644 --- a/cockatrice/src/dialogs/dlg_tip_of_the_day.cpp +++ b/cockatrice/src/dialogs/dlg_tip_of_the_day.cpp @@ -88,8 +88,8 @@ DlgTipOfTheDay::DlgTipOfTheDay(QWidget *parent) : QDialog(parent) setLayout(mainLayout); setWindowTitle(tr("Tip of the Day")); - setMinimumWidth(500); - setMinimumHeight(300); + setMinimumWidth(700); + setMinimumHeight(500); successfulInit = true; } @@ -144,9 +144,10 @@ void DlgTipOfTheDay::updateTip(int tipId) title->setText("

" + titleText + "

"); tipTextContent->setText(contentText); + tipTextContent->setTextFormat(Qt::RichText); if (!image->load(imagePath)) { - qDebug() << "Image failed to load from" << imagePath; + qCWarning(DlgTipOfTheDayLog) << "Image failed to load from" << imagePath; imageLabel->clear(); } else { int h = std::min(std::max(imageLabel->height(), MIN_TIP_IMAGE_HEIGHT), MAX_TIP_IMAGE_HEIGHT); @@ -159,6 +160,8 @@ void DlgTipOfTheDay::updateTip(int tipId) tipNumber->setText("Tip " + QString::number(tipId + 1) + " / " + QString::number(tipDatabase->rowCount())); currentTip = static_cast(tipId); + + adjustSize(); } void DlgTipOfTheDay::resizeEvent(QResizeEvent *event) diff --git a/cockatrice/src/dialogs/dlg_tip_of_the_day.h b/cockatrice/src/dialogs/dlg_tip_of_the_day.h index 79d01f5c6..4a7d86354 100644 --- a/cockatrice/src/dialogs/dlg_tip_of_the_day.h +++ b/cockatrice/src/dialogs/dlg_tip_of_the_day.h @@ -6,9 +6,12 @@ #include #include #include +#include #include #include +inline Q_LOGGING_CATEGORY(DlgTipOfTheDayLog, "dlg_tip_of_the_day"); + class QLabel; class QPushButton; class QCheckBox; diff --git a/cockatrice/src/dialogs/dlg_update.cpp b/cockatrice/src/dialogs/dlg_update.cpp index 21f08f827..6144406af 100644 --- a/cockatrice/src/dialogs/dlg_update.cpp +++ b/cockatrice/src/dialogs/dlg_update.cpp @@ -1,5 +1,6 @@ #include "dlg_update.h" +#include "../client/network/client_update_checker.h" #include "../client/network/release_channel.h" #include "../client/ui/window_main.h" #include "../settings/cache_settings.h" @@ -71,11 +72,6 @@ DlgUpdate::DlgUpdate(QWidget *parent) : QDialog(parent) connect(uDownloader, SIGNAL(progressMade(qint64, qint64)), this, SLOT(downloadProgressMade(qint64, qint64))); connect(uDownloader, SIGNAL(error(QString)), this, SLOT(downloadError(QString))); - ReleaseChannel *channel = SettingsCache::instance().getUpdateReleaseChannel(); - connect(channel, SIGNAL(finishedCheck(bool, bool, Release *)), this, - SLOT(finishedUpdateCheck(bool, bool, Release *))); - connect(channel, SIGNAL(error(QString)), this, SLOT(updateCheckError(QString))); - // Check for updates beginUpdateCheck(); } @@ -90,9 +86,9 @@ void DlgUpdate::gotoDownloadPage() QDesktopServices::openUrl(SettingsCache::instance().getUpdateReleaseChannel()->getManualDownloadUrl()); } -void DlgUpdate::downloadUpdate() +void DlgUpdate::downloadUpdate(const QString &releaseName) { - setLabel(tr("Downloading update...")); + setLabel(tr("Downloading update: %1").arg(releaseName)); addStopDownloadAndRemoveOthers(true); // Will remove all other buttons uDownloader->beginDownload(updateUrl); } @@ -110,7 +106,12 @@ void DlgUpdate::beginUpdateCheck() progress->setMinimum(0); progress->setMaximum(0); setLabel(tr("Checking for updates...")); - SettingsCache::instance().getUpdateReleaseChannel()->checkForUpdates(); + + auto checker = new ClientUpdateChecker(this); + connect(checker, SIGNAL(finishedCheck(bool, bool, Release *)), this, + SLOT(finishedUpdateCheck(bool, bool, Release *))); + connect(checker, SIGNAL(error(QString)), this, SLOT(updateCheckError(QString))); + checker->check(); } void DlgUpdate::finishedUpdateCheck(bool needToUpdate, bool isCompatible, Release *release) @@ -156,7 +157,7 @@ void DlgUpdate::finishedUpdateCheck(bool needToUpdate, bool isCompatible, Releas QMessageBox::Yes | QMessageBox::No); if (reply == QMessageBox::Yes) - downloadUpdate(); + downloadUpdate(release->getName()); } else { QMessageBox::information( this, tr("Update Available"), @@ -164,10 +165,12 @@ void DlgUpdate::finishedUpdateCheck(bool needToUpdate, bool isCompatible, Releas QString(": %1
").arg(release->getName()) + "" + tr("Released") + QString(": %1 (").arg(publishDate, release->getDescriptionUrl()) + tr("Changelog") + ")

" + - tr("Unfortunately there are no download packages available for your operating system. \nYou may have " - "to build from source yourself.") + + tr("Unfortunately, the automatic updater failed to find a compatible download. \nYou may have to " + "manually download the new version.") + "

" + - tr("Please check the download page manually and visit the wiki for instructions on compiling.")); + tr("Please check the releases page on our Github and download the build for your " + "system.") + .arg(release->getDescriptionUrl())); } } @@ -220,7 +223,7 @@ void DlgUpdate::downloadSuccessful(const QUrl &filepath) // Try to open the installer. If it opens, quit Cockatrice if (QDesktopServices::openUrl(filepath)) { QMetaObject::invokeMethod(static_cast(parent()), "close", Qt::QueuedConnection); - qDebug() << "Opened downloaded update file successfully - closing Cockatrice"; + qCInfo(DlgUpdateLog) << "Opened downloaded update file successfully - closing Cockatrice"; close(); } else { setLabel(tr("Error")); diff --git a/cockatrice/src/dialogs/dlg_update.h b/cockatrice/src/dialogs/dlg_update.h index 0eaa3ba76..3ac86dee1 100644 --- a/cockatrice/src/dialogs/dlg_update.h +++ b/cockatrice/src/dialogs/dlg_update.h @@ -4,20 +4,24 @@ #include "../client/update_downloader.h" #include +#include #include #include + +inline Q_LOGGING_CATEGORY(DlgUpdateLog, "dlg_update"); + class Release; class DlgUpdate : public QDialog { Q_OBJECT public: - DlgUpdate(QWidget *parent); + explicit DlgUpdate(QWidget *parent); private slots: void finishedUpdateCheck(bool needToUpdate, bool isCompatible, Release *release); void gotoDownloadPage(); - void downloadUpdate(); + void downloadUpdate(const QString &releaseName); void cancelDownload(); void updateCheckError(const QString &errorString); void downloadSuccessful(const QUrl &filepath); diff --git a/cockatrice/src/dialogs/dlg_view_log.cpp b/cockatrice/src/dialogs/dlg_view_log.cpp index 545da6ad4..1e79b8282 100644 --- a/cockatrice/src/dialogs/dlg_view_log.cpp +++ b/cockatrice/src/dialogs/dlg_view_log.cpp @@ -38,7 +38,7 @@ void DlgViewLog::actCheckBoxChanged(bool abNewValue) void DlgViewLog::loadInitialLogBuffer() { QList logBuffer = Logger::getInstance().getLogBuffer(); - foreach (QString message, logBuffer) + for (const QString &message : logBuffer) logEntryAdded(message); } diff --git a/cockatrice/src/game/board/abstract_counter.cpp b/cockatrice/src/game/board/abstract_counter.cpp index 10c51a638..b98647cdf 100644 --- a/cockatrice/src/game/board/abstract_counter.cpp +++ b/cockatrice/src/game/board/abstract_counter.cpp @@ -36,7 +36,7 @@ AbstractCounter::AbstractCounter(Player *_player, QString displayName = TranslateCounterName::getDisplayName(_name); menu = new TearOffMenu(displayName); aSet = new QAction(this); - connect(aSet, SIGNAL(triggered()), this, SLOT(setCounter())); + connect(aSet, &QAction::triggered, this, &AbstractCounter::setCounter); menu->addAction(aSet); menu->addSeparator(); for (int i = 10; i >= -10; --i) { @@ -49,7 +49,7 @@ AbstractCounter::AbstractCounter(Player *_player, else if (i == 1) aInc = aIncrement; aIncrement->setData(i); - connect(aIncrement, SIGNAL(triggered()), this, SLOT(incrementCounter())); + connect(aIncrement, &QAction::triggered, this, &AbstractCounter::incrementCounter); menu->addAction(aIncrement); } } @@ -57,7 +57,8 @@ AbstractCounter::AbstractCounter(Player *_player, menu = nullptr; } - connect(&SettingsCache::instance().shortcuts(), SIGNAL(shortCutChanged()), this, SLOT(refreshShortcuts())); + connect(&SettingsCache::instance().shortcuts(), &ShortcutsSettings::shortCutChanged, this, + &AbstractCounter::refreshShortcuts); refreshShortcuts(); retranslateUi(); } diff --git a/cockatrice/src/game/board/abstract_counter.h b/cockatrice/src/game/board/abstract_counter.h index 24d2f1243..8f2ee2691 100644 --- a/cockatrice/src/game/board/abstract_counter.h +++ b/cockatrice/src/game/board/abstract_counter.h @@ -88,7 +88,7 @@ public: AbstractCounterDialog(const QString &name, const QString &value, QWidget *parent = nullptr); protected: - bool eventFilter(QObject *obj, QEvent *event); + bool eventFilter(QObject *obj, QEvent *event) override; void changeValue(int diff); }; diff --git a/cockatrice/src/game/board/abstract_graphics_item.h b/cockatrice/src/game/board/abstract_graphics_item.h index 8fef7bb79..738be17d8 100644 --- a/cockatrice/src/game/board/abstract_graphics_item.h +++ b/cockatrice/src/game/board/abstract_graphics_item.h @@ -13,6 +13,9 @@ enum GraphicsItemType typeOther = QGraphicsItem::UserType + 6 }; +/** + * Parent class of all objects that appear in a game. + */ class AbstractGraphicsItem : public QObject, public QGraphicsItem { Q_OBJECT @@ -21,7 +24,7 @@ protected: void paintNumberEllipse(int number, int radius, const QColor &color, int position, int count, QPainter *painter); public: - AbstractGraphicsItem(QGraphicsItem *parent = nullptr) : QObject(), QGraphicsItem(parent) + explicit AbstractGraphicsItem(QGraphicsItem *parent = nullptr) : QGraphicsItem(parent) { } }; diff --git a/cockatrice/src/game/board/arrow_item.cpp b/cockatrice/src/game/board/arrow_item.cpp index 430ea6adc..512d1ab72 100644 --- a/cockatrice/src/game/board/arrow_item.cpp +++ b/cockatrice/src/game/board/arrow_item.cpp @@ -19,10 +19,9 @@ #include ArrowItem::ArrowItem(Player *_player, int _id, ArrowTarget *_startItem, ArrowTarget *_targetItem, const QColor &_color) - : QGraphicsItem(), player(_player), id(_id), startItem(_startItem), targetItem(_targetItem), color(_color), - fullColor(true) + : QGraphicsItem(), player(_player), id(_id), startItem(_startItem), targetItem(_targetItem), targetLocked(false), + color(_color), fullColor(true) { - qDebug() << "ArrowItem constructor: startItem=" << static_cast(startItem); setZValue(2000000005); if (startItem) @@ -36,7 +35,6 @@ ArrowItem::ArrowItem(Player *_player, int _id, ArrowTarget *_startItem, ArrowTar ArrowItem::~ArrowItem() { - qDebug() << "ArrowItem destructor"; } void ArrowItem::delArrow() @@ -167,7 +165,7 @@ void ArrowDragItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event) { // This ensures that if a mouse move event happens after a call to delArrow(), // the event will be discarded as it would create some stray pointers. - if (!startItem) + if (targetLocked || !startItem) return; QPointF endPos = event->scenePos(); @@ -268,7 +266,7 @@ void ArrowAttachItem::addChildArrow(ArrowAttachItem *childArrow) void ArrowAttachItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event) { - if (!startItem) + if (targetLocked || !startItem) return; QPointF endPos = event->scenePos(); @@ -307,25 +305,48 @@ void ArrowAttachItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event) } } +void ArrowAttachItem::attachCards(CardItem *startCard, const CardItem *targetCard) +{ + // do nothing if target is already attached to another card or is not in play + if (targetCard->getAttachedTo() || targetCard->getZone()->getName() != "table") { + return; + } + + CardZone *startZone = startCard->getZone(); + CardZone *targetZone = targetCard->getZone(); + + // move card onto table first if attaching from some other zone + if (startZone->getName() != "table") { + player->playCardToTable(startCard, false); + } + + Command_AttachCard cmd; + cmd.set_start_zone("table"); + cmd.set_card_id(startCard->getId()); + cmd.set_target_player_id(targetZone->getPlayer()->getId()); + cmd.set_target_zone(targetZone->getName().toStdString()); + cmd.set_target_card_id(targetCard->getId()); + + player->sendGameCommand(cmd); +} + void ArrowAttachItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) { if (!startItem) return; + // Attaching could move startItem under the current cursor position, causing all children to retarget to it right + // before they are processed. Prevent that. + for (ArrowAttachItem *child : childArrows) { + child->setTargetLocked(true); + } + if (targetItem && (targetItem != startItem)) { - CardItem *startCard = qgraphicsitem_cast(startItem); - CardZone *startZone = startCard->getZone(); - CardItem *targetCard = qgraphicsitem_cast(targetItem); - CardZone *targetZone = targetCard->getZone(); - - Command_AttachCard cmd; - cmd.set_start_zone(startZone->getName().toStdString()); - cmd.set_card_id(startCard->getId()); - cmd.set_target_player_id(targetZone->getPlayer()->getId()); - cmd.set_target_zone(targetZone->getName().toStdString()); - cmd.set_target_card_id(targetCard->getId()); - - player->sendGameCommand(cmd); + auto startCard = qgraphicsitem_cast(startItem); + auto targetCard = qgraphicsitem_cast(targetItem); + if (startCard && targetCard) { + attachCards(startCard, targetCard); + } } delArrow(); diff --git a/cockatrice/src/game/board/arrow_item.h b/cockatrice/src/game/board/arrow_item.h index 04d4bc207..fe5669eb3 100644 --- a/cockatrice/src/game/board/arrow_item.h +++ b/cockatrice/src/game/board/arrow_item.h @@ -21,19 +21,20 @@ protected: Player *player; int id; ArrowTarget *startItem, *targetItem; + bool targetLocked; QColor color; bool fullColor; - void mousePressEvent(QGraphicsSceneMouseEvent *event); + void mousePressEvent(QGraphicsSceneMouseEvent *event) override; public: ArrowItem(Player *_player, int _id, ArrowTarget *_startItem, ArrowTarget *_targetItem, const QColor &color); - ~ArrowItem(); - void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget); - QRectF boundingRect() const + ~ArrowItem() override; + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override; + QRectF boundingRect() const override { return path.boundingRect(); } - QPainterPath shape() const + QPainterPath shape() const override { return path; } @@ -64,6 +65,10 @@ public: { return targetItem; } + void setTargetLocked(bool _targetLocked) + { + targetLocked = _targetLocked; + } void delArrow(); }; @@ -78,8 +83,8 @@ public: void addChildArrow(ArrowDragItem *childArrow); protected: - void mouseMoveEvent(QGraphicsSceneMouseEvent *event); - void mouseReleaseEvent(QGraphicsSceneMouseEvent *event); + void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override; + void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override; }; class ArrowAttachItem : public ArrowItem @@ -88,13 +93,15 @@ class ArrowAttachItem : public ArrowItem private: QList childArrows; + void attachCards(CardItem *startCard, const CardItem *targetCard); + public: - ArrowAttachItem(ArrowTarget *_startItem); + explicit ArrowAttachItem(ArrowTarget *_startItem); void addChildArrow(ArrowAttachItem *childArrow); protected: - void mouseMoveEvent(QGraphicsSceneMouseEvent *event); - void mouseReleaseEvent(QGraphicsSceneMouseEvent *event); + void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override; + void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override; }; #endif // ARROWITEM_H diff --git a/cockatrice/src/game/board/arrow_target.cpp b/cockatrice/src/game/board/arrow_target.cpp index e844757dd..2dbd913fa 100644 --- a/cockatrice/src/game/board/arrow_target.cpp +++ b/cockatrice/src/game/board/arrow_target.cpp @@ -6,6 +6,7 @@ ArrowTarget::ArrowTarget(Player *_owner, QGraphicsItem *parent) : AbstractGraphicsItem(parent), owner(_owner), beingPointedAt(false) { + setFlag(ItemSendsScenePositionChanges); } ArrowTarget::~ArrowTarget() @@ -25,3 +26,16 @@ void ArrowTarget::setBeingPointedAt(bool _beingPointedAt) beingPointedAt = _beingPointedAt; update(); } + +QVariant ArrowTarget::itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value) +{ + if (change == ItemScenePositionHasChanged && scene()) { + for (auto *arrow : arrowsFrom) + arrow->updatePath(); + + for (auto *arrow : arrowsTo) + arrow->updatePath(); + } + + return QGraphicsItem::itemChange(change, value); +} diff --git a/cockatrice/src/game/board/arrow_target.h b/cockatrice/src/game/board/arrow_target.h index 3f8f2135f..a4ef6895d 100644 --- a/cockatrice/src/game/board/arrow_target.h +++ b/cockatrice/src/game/board/arrow_target.h @@ -19,8 +19,8 @@ private: QList arrowsFrom, arrowsTo; public: - ArrowTarget(Player *_owner, QGraphicsItem *parent = nullptr); - ~ArrowTarget(); + explicit ArrowTarget(Player *_owner, QGraphicsItem *parent = nullptr); + ~ArrowTarget() override; Player *getOwner() const { @@ -57,5 +57,8 @@ public: { arrowsTo.removeOne(arrow); } + +protected: + QVariant itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value) override; }; #endif diff --git a/cockatrice/src/game/board/counter_general.h b/cockatrice/src/game/board/counter_general.h index 150ce2659..0bee9c824 100644 --- a/cockatrice/src/game/board/counter_general.h +++ b/cockatrice/src/game/board/counter_general.h @@ -20,8 +20,8 @@ public: bool useNameForShortcut = false, QGraphicsItem *parent = nullptr, QWidget *game = nullptr); - QRectF boundingRect() const; - void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget); + QRectF boundingRect() const override; + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override; }; #endif diff --git a/cockatrice/src/game/cards/abstract_card_drag_item.cpp b/cockatrice/src/game/cards/abstract_card_drag_item.cpp index e9fe30c50..0c7acd917 100644 --- a/cockatrice/src/game/cards/abstract_card_drag_item.cpp +++ b/cockatrice/src/game/cards/abstract_card_drag_item.cpp @@ -1,6 +1,6 @@ #include "abstract_card_drag_item.h" -#include "card_database.h" +#include "../../settings/cache_settings.h" #include #include @@ -32,6 +32,13 @@ AbstractCardDragItem::AbstractCardDragItem(AbstractCardItem *_item, .translate(-CARD_WIDTH_HALF, -CARD_HEIGHT_HALF)); setCacheMode(DeviceCoordinateCache); + + connect(&SettingsCache::instance(), &SettingsCache::roundCardCornersChanged, this, [this](bool _roundCardCorners) { + Q_UNUSED(_roundCardCorners); + + prepareGeometryChange(); + update(); + }); } AbstractCardDragItem::~AbstractCardDragItem() @@ -43,7 +50,8 @@ AbstractCardDragItem::~AbstractCardDragItem() QPainterPath AbstractCardDragItem::shape() const { QPainterPath shape; - shape.addRoundedRect(boundingRect(), 0.05 * CARD_WIDTH, 0.05 * CARD_WIDTH); + qreal cardCornerRadius = SettingsCache::instance().getRoundCardCorners() ? 0.05 * CARD_WIDTH : 0.0; + shape.addRoundedRect(boundingRect(), cardCornerRadius, cardCornerRadius); return shape; } diff --git a/cockatrice/src/game/cards/abstract_card_drag_item.h b/cockatrice/src/game/cards/abstract_card_drag_item.h index 491a7043c..85ce38b15 100644 --- a/cockatrice/src/game/cards/abstract_card_drag_item.h +++ b/cockatrice/src/game/cards/abstract_card_drag_item.h @@ -26,7 +26,7 @@ public: return Type; } AbstractCardDragItem(AbstractCardItem *_item, const QPointF &_hotSpot, AbstractCardDragItem *parentDrag = 0); - ~AbstractCardDragItem(); + ~AbstractCardDragItem() override; QRectF boundingRect() const override { return QRectF(0, 0, CARD_WIDTH, CARD_HEIGHT); diff --git a/cockatrice/src/game/cards/abstract_card_item.cpp b/cockatrice/src/game/cards/abstract_card_item.cpp index 80da703a7..dc00aae66 100644 --- a/cockatrice/src/game/cards/abstract_card_item.cpp +++ b/cockatrice/src/game/cards/abstract_card_item.cpp @@ -1,6 +1,6 @@ #include "abstract_card_item.h" -#include "../../client/ui/picture_loader.h" +#include "../../client/ui/picture_loader/picture_loader.h" #include "../../settings/cache_settings.h" #include "../game_scene.h" #include "card_database.h" @@ -24,8 +24,15 @@ AbstractCardItem::AbstractCardItem(QGraphicsItem *parent, setFlag(ItemIsSelectable); setCacheMode(DeviceCoordinateCache); - connect(&SettingsCache::instance(), SIGNAL(displayCardNamesChanged()), this, SLOT(callUpdate())); - cardInfoUpdated(); + connect(&SettingsCache::instance(), &SettingsCache::displayCardNamesChanged, this, [this] { update(); }); + refreshCardInfo(); + + connect(&SettingsCache::instance(), &SettingsCache::roundCardCornersChanged, this, [this](bool _roundCardCorners) { + Q_UNUSED(_roundCardCorners); + + prepareGeometryChange(); + update(); + }); } AbstractCardItem::~AbstractCardItem() @@ -41,7 +48,8 @@ QRectF AbstractCardItem::boundingRect() const QPainterPath AbstractCardItem::shape() const { QPainterPath shape; - shape.addRoundedRect(boundingRect(), 0.05 * CARD_WIDTH, 0.05 * CARD_WIDTH); + qreal cardCornerRadius = SettingsCache::instance().getRoundCardCorners() ? 0.05 * CARD_WIDTH : 0.0; + shape.addRoundedRect(boundingRect(), cardCornerRadius, cardCornerRadius); return shape; } @@ -51,7 +59,7 @@ void AbstractCardItem::pixmapUpdated() emit sigPixmapUpdated(); } -void AbstractCardItem::cardInfoUpdated() +void AbstractCardItem::refreshCardInfo() { info = CardDatabaseManager::getInstance()->getCardByNameAndProviderId(name, providerId); @@ -59,10 +67,10 @@ void AbstractCardItem::cardInfoUpdated() QVariantHash properties = QVariantHash(); info = CardInfo::newInstance(name, "", true, QVariantHash(), QList(), QList(), - CardInfoPerSetMap(), false, -1, false); + CardInfoPerSetMap(), false, false, -1, false); } if (info.data()) { - connect(info.data(), SIGNAL(pixmapUpdated()), this, SLOT(pixmapUpdated())); + connect(info.data(), &CardInfo::pixmapUpdated, this, &AbstractCardItem::pixmapUpdated); } cacheBgColor(); @@ -141,8 +149,13 @@ void AbstractCardItem::paintPicture(QPainter *painter, const QSizeF &translatedS QString nameStr; if (facedown) nameStr = "# " + QString::number(id); - else - nameStr = name; + else { + QString prefix = ""; + if (SettingsCache::instance().debug().getShowCardId()) { + prefix = "#" + QString::number(id) + " "; + } + nameStr = prefix + name; + } painter->drawText(QRectF(3 * scaleFactor, 3 * scaleFactor, translatedSize.width() - 6 * scaleFactor, translatedSize.height() - 6 * scaleFactor), Qt::AlignTop | Qt::AlignLeft | Qt::TextWrapAnywhere, nameStr); @@ -185,7 +198,7 @@ void AbstractCardItem::setName(const QString &_name) disconnect(info.data(), nullptr, this, nullptr); name = _name; - cardInfoUpdated(); + refreshCardInfo(); } void AbstractCardItem::setProviderId(const QString &_providerId) @@ -200,7 +213,7 @@ void AbstractCardItem::setProviderId(const QString &_providerId) } providerId = _providerId; - cardInfoUpdated(); + refreshCardInfo(); } void AbstractCardItem::setHovered(bool _hovered) @@ -296,7 +309,7 @@ void AbstractCardItem::mousePressEvent(QGraphicsSceneMouseEvent *event) if (event->button() == Qt::LeftButton) setCursor(Qt::ClosedHandCursor); else if (event->button() == Qt::MiddleButton) - emit showCardInfoPopup(event->screenPos(), name); + emit showCardInfoPopup(event->screenPos(), name, providerId); event->accept(); } @@ -320,5 +333,5 @@ QVariant AbstractCardItem::itemChange(QGraphicsItem::GraphicsItemChange change, update(); return value; } else - return QGraphicsItem::itemChange(change, value); + return ArrowTarget::itemChange(change, value); } diff --git a/cockatrice/src/game/cards/abstract_card_item.h b/cockatrice/src/game/cards/abstract_card_item.h index c7ecd0615..51b345267 100644 --- a/cockatrice/src/game/cards/abstract_card_item.h +++ b/cockatrice/src/game/cards/abstract_card_item.h @@ -28,14 +28,13 @@ private: qreal realZValue; private slots: void pixmapUpdated(); - void cardInfoUpdated(); - void callUpdate() - { - update(); - } + +public slots: + void refreshCardInfo(); + signals: void hovered(AbstractCardItem *card); - void showCardInfoPopup(QPoint pos, QString cardName); + void showCardInfoPopup(const QPoint &pos, const QString &cardName, const QString &providerId); void deleteCardInfoPopup(QString cardName); void sigPixmapUpdated(); void cardShiftClicked(QString cardName); @@ -49,12 +48,12 @@ public: { return Type; } - AbstractCardItem(QGraphicsItem *parent = nullptr, - const QString &_name = QString(), - const QString &_providerId = QString(), - Player *_owner = nullptr, - int _id = -1); - ~AbstractCardItem(); + explicit AbstractCardItem(QGraphicsItem *parent = nullptr, + const QString &_name = QString(), + const QString &_providerId = QString(), + Player *_owner = nullptr, + int _id = -1); + ~AbstractCardItem() override; QRectF boundingRect() const override; QPainterPath shape() const override; QSizeF getTranslatedSize(QPainter *painter) const; diff --git a/cockatrice/src/game/cards/card_database.cpp b/cockatrice/src/game/cards/card_database.cpp index cd101d37f..044f2c623 100644 --- a/cockatrice/src/game/cards/card_database.cpp +++ b/cockatrice/src/game/cards/card_database.cpp @@ -1,10 +1,8 @@ #include "card_database.h" -#include "../../client/network/spoiler_background_updater.h" -#include "../../client/ui/picture_loader.h" +#include "../../client/ui/picture_loader/picture_loader.h" #include "../../settings/cache_settings.h" #include "../../utility/card_set_comparator.h" -#include "../game_specific_terms.h" #include "./card_database_parser/cockatrice_xml_3.h" #include "./card_database_parser/cockatrice_xml_4.h" @@ -20,318 +18,6 @@ const char *CardDatabase::TOKENS_SETNAME = "TK"; -CardSet::CardSet(const QString &_shortName, - const QString &_longName, - const QString &_setType, - const QDate &_releaseDate) - : shortName(_shortName), longName(_longName), releaseDate(_releaseDate), setType(_setType) -{ - loadSetOptions(); -} - -CardSetPtr CardSet::newInstance(const QString &_shortName, - const QString &_longName, - const QString &_setType, - const QDate &_releaseDate) -{ - CardSetPtr ptr(new CardSet(_shortName, _longName, _setType, _releaseDate)); - // ptr->setSmartPointer(ptr); - return ptr; -} - -QString CardSet::getCorrectedShortName() const -{ - // For Windows machines. - QSet invalidFileNames; - invalidFileNames << "CON" - << "PRN" - << "AUX" - << "NUL" - << "COM1" - << "COM2" - << "COM3" - << "COM4" - << "COM5" - << "COM6" - << "COM7" - << "COM8" - << "COM9" - << "LPT1" - << "LPT2" - << "LPT3" - << "LPT4" - << "LPT5" - << "LPT6" - << "LPT7" - << "LPT8" - << "LPT9"; - - return invalidFileNames.contains(shortName) ? shortName + "_" : shortName; -} - -void CardSet::loadSetOptions() -{ - sortKey = SettingsCache::instance().cardDatabase().getSortKey(shortName); - enabled = SettingsCache::instance().cardDatabase().isEnabled(shortName); - isknown = SettingsCache::instance().cardDatabase().isKnown(shortName); -} - -void CardSet::setSortKey(unsigned int _sortKey) -{ - sortKey = _sortKey; - SettingsCache::instance().cardDatabase().setSortKey(shortName, _sortKey); -} - -void CardSet::setEnabled(bool _enabled) -{ - enabled = _enabled; - SettingsCache::instance().cardDatabase().setEnabled(shortName, _enabled); -} - -void CardSet::setIsKnown(bool _isknown) -{ - isknown = _isknown; - SettingsCache::instance().cardDatabase().setIsKnown(shortName, _isknown); -} - -class SetList::KeyCompareFunctor -{ -public: - inline bool operator()(const CardSetPtr &a, const CardSetPtr &b) const - { - if (a.isNull() || b.isNull()) { - qDebug() << "SetList::KeyCompareFunctor a or b is null"; - return false; - } - - return a->getSortKey() < b->getSortKey(); - } -}; - -void SetList::sortByKey() -{ - std::sort(begin(), end(), KeyCompareFunctor()); -} - -int SetList::getEnabledSetsNum() -{ - int num = 0; - for (int i = 0; i < size(); ++i) { - CardSetPtr set = at(i); - if (set && set->getEnabled()) { - ++num; - } - } - return num; -} - -int SetList::getUnknownSetsNum() -{ - int num = 0; - for (int i = 0; i < size(); ++i) { - CardSetPtr set = at(i); - if (set && !set->getIsKnown() && !set->getIsKnownIgnored()) { - ++num; - } - } - return num; -} - -QStringList SetList::getUnknownSetsNames() -{ - QStringList sets = QStringList(); - for (int i = 0; i < size(); ++i) { - CardSetPtr set = at(i); - if (set && !set->getIsKnown() && !set->getIsKnownIgnored()) { - sets << set->getShortName(); - } - } - return sets; -} - -void SetList::enableAllUnknown() -{ - for (int i = 0; i < size(); ++i) { - CardSetPtr set = at(i); - if (set && !set->getIsKnown() && !set->getIsKnownIgnored()) { - set->setIsKnown(true); - set->setEnabled(true); - } else if (set && set->getIsKnownIgnored() && !set->getEnabled()) { - set->setEnabled(true); - } - } -} - -void SetList::enableAll() -{ - for (int i = 0; i < size(); ++i) { - CardSetPtr set = at(i); - - if (set == nullptr) { - qDebug() << "enabledAll has null"; - continue; - } - - if (!set->getIsKnownIgnored()) { - set->setIsKnown(true); - } - - set->setEnabled(true); - } -} - -void SetList::markAllAsKnown() -{ - for (int i = 0; i < size(); ++i) { - CardSetPtr set = at(i); - if (set && !set->getIsKnown() && !set->getIsKnownIgnored()) { - set->setIsKnown(true); - set->setEnabled(false); - } else if (set && set->getIsKnownIgnored() && !set->getEnabled()) { - set->setEnabled(true); - } - } -} - -void SetList::guessSortKeys() -{ - // sort by release date DESC; invalid dates to the bottom. - QDate distantFuture(2050, 1, 1); - int aHundredYears = 36500; - for (int i = 0; i < size(); ++i) { - CardSetPtr set = at(i); - if (set.isNull()) { - qDebug() << "guessSortKeys set is null"; - continue; - } - - QDate date = set->getReleaseDate(); - if (date.isNull()) { - set->setSortKey(static_cast(aHundredYears)); - } else { - set->setSortKey(static_cast(date.daysTo(distantFuture))); - } - } -} - -CardInfoPerSet::CardInfoPerSet(const CardSetPtr &_set) : set(_set) -{ -} - -CardInfo::CardInfo(const QString &_name, - const QString &_text, - bool _isToken, - QVariantHash _properties, - const QList &_relatedCards, - const QList &_reverseRelatedCards, - CardInfoPerSetMap _sets, - bool _cipt, - int _tableRow, - bool _upsideDownArt) - : name(_name), text(_text), isToken(_isToken), properties(std::move(_properties)), relatedCards(_relatedCards), - reverseRelatedCards(_reverseRelatedCards), sets(std::move(_sets)), cipt(_cipt), tableRow(_tableRow), - upsideDownArt(_upsideDownArt) -{ - pixmapCacheKey = QLatin1String("card_") + name; - simpleName = CardInfo::simplifyName(name); - - refreshCachedSetNames(); -} - -CardInfo::~CardInfo() -{ - PictureLoader::clearPixmapCache(smartThis); -} - -CardInfoPtr CardInfo::newInstance(const QString &_name, - const QString &_text, - bool _isToken, - QVariantHash _properties, - const QList &_relatedCards, - const QList &_reverseRelatedCards, - CardInfoPerSetMap _sets, - bool _cipt, - int _tableRow, - bool _upsideDownArt) -{ - CardInfoPtr ptr(new CardInfo(_name, _text, _isToken, std::move(_properties), _relatedCards, _reverseRelatedCards, - _sets, _cipt, _tableRow, _upsideDownArt)); - ptr->setSmartPointer(ptr); - - for (const CardInfoPerSet &set : _sets) { - set.getPtr()->append(ptr); - } - - return ptr; -} - -QString CardInfo::getCorrectedName() const -{ - // remove all the characters reserved in windows file paths, - // other oses only disallow a subset of these so it covers all - static const QRegularExpression rmrx(R"(( // |[*<>:"\\?\x00-\x08\x10-\x1f]))"); - static const QRegularExpression spacerx(R"([/\x09-\x0f])"); - static const QString space(' '); - QString result = name; - // Fire // Ice, Circle of Protection: Red, "Ach! Hans, Run!", Who/What/When/Where/Why, Question Elemental? - return result.remove(rmrx).replace(spacerx, space); -} - -void CardInfo::addToSet(const CardSetPtr &_set, const CardInfoPerSet _info) -{ - _set->append(smartThis); - sets.insert(_set->getShortName(), _info); - - refreshCachedSetNames(); -} - -void CardInfo::refreshCachedSetNames() -{ - QStringList setList; - // update the cached list of set names - for (const auto &set : sets) { - if (set.getPtr()->getEnabled()) { - setList << set.getPtr()->getShortName(); - } - } - setsNames = setList.join(", "); -} - -QString CardInfo::simplifyName(const QString &name) -{ - static const QRegularExpression spaceOrSplit("(\\s+|\\/\\/.*)"); - static const QRegularExpression nonAlnum("[^a-z0-9]"); - - QString simpleName = name.toLower(); - - // remove spaces and right halves of split cards - simpleName.remove(spaceOrSplit); - - // So Aetherling would work, but not Ætherling since 'Æ' would get replaced - // with nothing. - simpleName.replace("æ", "ae"); - - // Replace Jötun Grunt with Jotun Grunt. - simpleName = simpleName.normalized(QString::NormalizationForm_KD); - - // remove all non alphanumeric characters from the name - simpleName.remove(nonAlnum); - return simpleName; -} - -const QChar CardInfo::getColorChar() const -{ - QString colors = getColors(); - switch (colors.size()) { - case 0: - return QChar(); - case 1: - return colors.at(0); - default: - return QChar('m'); - } -} - CardDatabase::CardDatabase(QObject *parent) : QObject(parent), loadStatus(NotLoaded) { qRegisterMetaType("CardInfoPtr"); @@ -342,11 +28,12 @@ CardDatabase::CardDatabase(QObject *parent) : QObject(parent), loadStatus(NotLoa availableParsers << new CockatriceXml3Parser; for (auto &parser : availableParsers) { - connect(parser, SIGNAL(addCard(CardInfoPtr)), this, SLOT(addCard(CardInfoPtr)), Qt::DirectConnection); - connect(parser, SIGNAL(addSet(CardSetPtr)), this, SLOT(addSet(CardSetPtr)), Qt::DirectConnection); + connect(parser, &ICardDatabaseParser::addCard, this, &CardDatabase::addCard, Qt::DirectConnection); + connect(parser, &ICardDatabaseParser::addSet, this, &CardDatabase::addSet, Qt::DirectConnection); } - connect(&SettingsCache::instance(), SIGNAL(cardDatabasePathChanged()), this, SLOT(loadCardDatabases())); + connect(&SettingsCache::instance(), &SettingsCache::cardDatabasePathChanged, this, + &CardDatabase::loadCardDatabases); } CardDatabase::~CardDatabase() @@ -381,15 +68,17 @@ void CardDatabase::clear() void CardDatabase::addCard(CardInfoPtr card) { if (card == nullptr) { - qDebug() << "addCard(nullptr)"; + qCWarning(CardDatabaseLog) << "CardDatabase::addCard(nullptr)"; return; } // if card already exists just add the new set property if (cards.contains(card->getName())) { CardInfoPtr sameCard = cards[card->getName()]; - for (const CardInfoPerSet &set : card->getSets()) { - sameCard->addToSet(set.getPtr(), set); + for (const auto &cardInfoPerSetList : card->getSets()) { + for (const CardInfoPerSet &set : cardInfoPerSetList) { + sameCard->addToSet(set.getPtr(), set); + } } return; } @@ -404,7 +93,7 @@ void CardDatabase::addCard(CardInfoPtr card) void CardDatabase::removeCard(CardInfoPtr card) { if (card.isNull()) { - qDebug() << "removeCard(nullptr)"; + qCWarning(CardDatabaseLog) << "CardDatabase::removeCard(nullptr)"; return; } @@ -432,7 +121,7 @@ CardInfoPtr CardDatabase::getCard(const QString &cardName) const QList CardDatabase::getCards(const QStringList &cardNames) const { QList cardInfos; - foreach (QString cardName, cardNames) { + for (const QString &cardName : cardNames) { CardInfoPtr ptr = getCardFromMap(cards, cardName); if (ptr) cardInfos.append(ptr); @@ -441,6 +130,18 @@ QList CardDatabase::getCards(const QStringList &cardNames) const return cardInfos; } +QList CardDatabase::getCardsByNameAndProviderId(const QMap &cardNames) const +{ + QList cardInfos; + for (const QString &cardName : cardNames) { + CardInfoPtr ptr = getCardByNameAndProviderId(cardName, cardNames[cardName]); + if (ptr) + cardInfos.append(ptr); + } + + return cardInfos; +} + CardInfoPtr CardDatabase::getCardByNameAndProviderId(const QString &cardName, const QString &providerId) const { auto info = getCard(cardName); @@ -448,12 +149,14 @@ CardInfoPtr CardDatabase::getCardByNameAndProviderId(const QString &cardName, co return info; } - for (const auto &set : info->getSets()) { - if (set.getProperty("uuid") == providerId) { - CardInfoPtr cardFromSpecificSet = info->clone(); - cardFromSpecificSet->setPixmapCacheKey(QLatin1String("card_") + QString(info->getName()) + QString("_") + - QString(set.getProperty("uuid"))); - return cardFromSpecificSet; + for (const auto &cardInfoPerSetList : info->getSets()) { + for (const auto &set : cardInfoPerSetList) { + if (set.getProperty("uuid") == providerId) { + CardInfoPtr cardFromSpecificSet = info->clone(); + cardFromSpecificSet->setPixmapCacheKey(QLatin1String("card_") + QString(info->getName()) + + QString("_") + QString(set.getProperty("uuid"))); + return cardFromSpecificSet; + } } } return {}; @@ -464,13 +167,14 @@ CardInfoPtr CardDatabase::getCardBySimpleName(const QString &cardName) const return getCardFromMap(simpleNameCards, CardInfo::simplifyName(cardName)); } -CardInfoPtr CardDatabase::guessCard(const QString &cardName) const +CardInfoPtr CardDatabase::guessCard(const QString &cardName, const QString &providerId) const { - CardInfoPtr temp = getCard(cardName); + CardInfoPtr temp = providerId.isEmpty() ? getCard(cardName) : getCardByNameAndProviderId(cardName, providerId); + if (temp == nullptr) { // get card by simple name instead temp = getCardBySimpleName(cardName); if (temp == nullptr) { // still could not find the card, so simplify the cardName too - QString simpleCardName = CardInfo::simplifyName(cardName); + const auto &simpleCardName = CardInfo::simplifyName(cardName); temp = getCardBySimpleName(simpleCardName); } } @@ -543,8 +247,8 @@ LoadStatus CardDatabase::loadCardDatabase(const QString &path) } int msecs = startTime.msecsTo(QTime::currentTime()); - qDebug() << "[CardDatabase] loadCardDatabase(): Path =" << path << "Status =" << tempLoadStatus - << "Cards =" << cards.size() << "Sets =" << sets.size() << QString("%1ms").arg(msecs); + qCInfo(CardDatabaseLoadingLog) << "Path =" << path << "Status =" << tempLoadStatus << "Cards =" << cards.size() + << "Sets =" << sets.size() << QString("%1ms").arg(msecs); return tempLoadStatus; } @@ -553,7 +257,7 @@ LoadStatus CardDatabase::loadCardDatabases() { reloadDatabaseMutex->lock(); - qDebug() << "CardDatabase::loadCardDatabases start"; + qCInfo(CardDatabaseLoadingLog) << "Card Database Loading Started"; clear(); // remove old db @@ -574,7 +278,7 @@ LoadStatus CardDatabase::loadCardDatabases() for (auto i = 0; i < databasePaths.size(); ++i) { const auto &databasePath = databasePaths.at(i); - qDebug() << "Loading Custom Set" << i << "(" << databasePath << ")"; + qCInfo(CardDatabaseLoadingLog) << "Loading Custom Set" << i << "(" << databasePath << ")"; loadCardDatabase(databasePath); } @@ -587,10 +291,10 @@ LoadStatus CardDatabase::loadCardDatabases() if (loadStatus == Ok) { checkUnknownSets(); // update deck editors, etc - qDebug() << "CardDatabase::loadCardDatabases success"; + qCInfo(CardDatabaseLoadingSuccessOrFailureLog) << "Card Database Loading Success"; emit cardDatabaseLoadingFinished(); } else { - qDebug() << "CardDatabase::loadCardDatabases failed"; + qCInfo(CardDatabaseLoadingSuccessOrFailureLog) << "Card Database Loading Failed"; emit cardDatabaseLoadingFailed(); // bring up the settings dialog } @@ -606,7 +310,7 @@ void CardDatabase::refreshPreferredPrintings() } } -CardInfoPerSet CardDatabase::getPreferredSetForCard(const QString &cardName) +CardInfoPerSet CardDatabase::getPreferredSetForCard(const QString &cardName) const { CardInfoPtr cardInfo = getCard(cardName); if (!cardInfo) { @@ -622,11 +326,13 @@ CardInfoPerSet CardDatabase::getPreferredSetForCard(const QString &cardName) CardInfoPerSet preferredCard; SetPriorityComparator comparator; - for (auto &cardInfoForSet : setMap) { - CardSetPtr currentSet = cardInfoForSet.getPtr(); - if (!preferredSet || comparator(currentSet, preferredSet)) { - preferredSet = currentSet; - preferredCard = cardInfoForSet; + for (const auto &cardInfoPerSetList : setMap) { + for (auto &cardInfoForSet : cardInfoPerSetList) { + CardSetPtr currentSet = cardInfoForSet.getPtr(); + if (!preferredSet || comparator(currentSet, preferredSet)) { + preferredSet = currentSet; + preferredCard = cardInfoForSet; + } } } @@ -649,9 +355,41 @@ CardInfoPerSet CardDatabase::getSpecificSetForCard(const QString &cardName, cons return CardInfoPerSet(nullptr); } - for (auto &cardInfoForSet : setMap) { - if (cardInfoForSet.getProperty("uuid") == providerId) { - return cardInfoForSet; + for (const auto &cardInfoPerSetList : setMap) { + for (auto &cardInfoForSet : cardInfoPerSetList) { + if (cardInfoForSet.getProperty("uuid") == providerId) { + return cardInfoForSet; + } + } + } + + if (providerId.isNull()) { + return getPreferredSetForCard(cardName); + } + + return CardInfoPerSet(nullptr); +} + +CardInfoPerSet CardDatabase::getSpecificSetForCard(const QString &cardName, + const QString &setShortName, + const QString &collectorNumber) const +{ + CardInfoPtr cardInfo = getCard(cardName); + if (!cardInfo) { + return CardInfoPerSet(nullptr); + } + + CardInfoPerSetMap setMap = cardInfo->getSets(); + if (setMap.empty()) { + return CardInfoPerSet(nullptr); + } + + for (const auto &cardInfoPerSetList : setMap) { + for (auto &cardInfoForSet : cardInfoPerSetList) { + if (cardInfoForSet.getPtr()->getShortName() == setShortName && + cardInfoForSet.getProperty("num") == collectorNumber) { + return cardInfoForSet; + } } } @@ -681,6 +419,25 @@ bool CardDatabase::isProviderIdForPreferredPrinting(const QString &cardName, con return providerId == getPreferredPrintingProviderIdForCard(cardName); } +CardInfoPerSet CardDatabase::getSetInfoForCard(const CardInfoPtr &_card) +{ + const CardInfoPerSetMap &setMap = _card->getSets(); + if (setMap.empty()) { + return CardInfoPerSet(nullptr); + } + + for (const auto &cardInfoPerSetList : setMap) { + for (const auto &cardInfoForSet : cardInfoPerSetList) { + if (QLatin1String("card_") + _card->getName() + QString("_") + cardInfoForSet.getProperty("uuid") == + _card->getPixmapCacheKey()) { + return cardInfoForSet; + } + } + } + + return CardInfoPerSet(nullptr); +} + void CardDatabase::refreshCachedReverseRelatedCards() { for (const CardInfoPtr &card : cards) @@ -691,7 +448,7 @@ void CardDatabase::refreshCachedReverseRelatedCards() continue; } - foreach (CardRelation *cardRelation, card->getReverseRelatedCards()) { + for (CardRelation *cardRelation : card->getReverseRelatedCards()) { const QString &targetCard = cardRelation->getName(); if (!cards.contains(targetCard)) { continue; @@ -780,64 +537,3 @@ bool CardDatabase::saveCustomTokensToFile() availableParsers.first()->saveToFile(tmpSets, tmpCards, fileName); return true; } - -CardRelation::CardRelation(const QString &_name, - AttachType _attachType, - bool _isCreateAllExclusion, - bool _isVariableCount, - int _defaultCount, - bool _isPersistent) - : name(_name), attachType(_attachType), isCreateAllExclusion(_isCreateAllExclusion), - isVariableCount(_isVariableCount), defaultCount(_defaultCount), isPersistent(_isPersistent) -{ -} - -void CardInfo::resetReverseRelatedCards2Me() -{ - foreach (CardRelation *cardRelation, this->getReverseRelatedCards2Me()) { - cardRelation->deleteLater(); - } - reverseRelatedCardsToMe = QList(); -} - -// Back-compatibility methods. Remove ASAP -const QString CardInfo::getCardType() const -{ - return getProperty(Mtg::CardType); -} -void CardInfo::setCardType(const QString &value) -{ - setProperty(Mtg::CardType, value); -} -const QString CardInfo::getCmc() const -{ - return getProperty(Mtg::ConvertedManaCost); -} -const QString CardInfo::getColors() const -{ - return getProperty(Mtg::Colors); -} -void CardInfo::setColors(const QString &value) -{ - setProperty(Mtg::Colors, value); -} -const QString CardInfo::getLoyalty() const -{ - return getProperty(Mtg::Loyalty); -} -const QString CardInfo::getMainCardType() const -{ - return getProperty(Mtg::MainCardType); -} -const QString CardInfo::getManaCost() const -{ - return getProperty(Mtg::ManaCost); -} -const QString CardInfo::getPowTough() const -{ - return getProperty(Mtg::PowTough); -} -void CardInfo::setPowTough(const QString &value) -{ - setProperty(Mtg::PowTough, value); -} diff --git a/cockatrice/src/game/cards/card_database.h b/cockatrice/src/game/cards/card_database.h index 3322a5bb6..236278b30 100644 --- a/cockatrice/src/game/cards/card_database.h +++ b/cockatrice/src/game/cards/card_database.h @@ -1,380 +1,23 @@ #ifndef CARDDATABASE_H #define CARDDATABASE_H +#include "card_info.h" + #include #include #include #include -#include -#include -#include +#include #include -#include #include #include -class CardDatabase; -class CardInfo; -class CardInfoPerSet; -class CardSet; -class CardRelation; +inline Q_LOGGING_CATEGORY(CardDatabaseLog, "card_database"); +inline Q_LOGGING_CATEGORY(CardDatabaseLoadingLog, "card_database.loading"); +inline Q_LOGGING_CATEGORY(CardDatabaseLoadingSuccessOrFailureLog, "card_database.loading.success_or_failure"); + class ICardDatabaseParser; -typedef QMap QStringMap; -typedef QSharedPointer CardInfoPtr; -typedef QSharedPointer CardSetPtr; -typedef QMap CardInfoPerSetMap; - -Q_DECLARE_METATYPE(CardInfoPtr) - -class CardSet : public QList -{ -private: - QString shortName, longName; - unsigned int sortKey; - QDate releaseDate; - QString setType; - bool enabled, isknown; - -public: - explicit CardSet(const QString &_shortName = QString(), - const QString &_longName = QString(), - const QString &_setType = QString(), - const QDate &_releaseDate = QDate()); - static CardSetPtr newInstance(const QString &_shortName = QString(), - const QString &_longName = QString(), - const QString &_setType = QString(), - const QDate &_releaseDate = QDate()); - QString getCorrectedShortName() const; - QString getShortName() const - { - return shortName; - } - QString getLongName() const - { - return longName; - } - QString getSetType() const - { - return setType; - } - QDate getReleaseDate() const - { - return releaseDate; - } - void setLongName(const QString &_longName) - { - longName = _longName; - } - void setSetType(const QString &_setType) - { - setType = _setType; - } - void setReleaseDate(const QDate &_releaseDate) - { - releaseDate = _releaseDate; - } - - void loadSetOptions(); - int getSortKey() const - { - return sortKey; - } - void setSortKey(unsigned int _sortKey); - bool getEnabled() const - { - return enabled; - } - void setEnabled(bool _enabled); - bool getIsKnown() const - { - return isknown; - } - void setIsKnown(bool _isknown); - - // Determine incomplete sets. - bool getIsKnownIgnored() const - { - return longName.length() + setType.length() + releaseDate.toString().length() == 0; - } -}; - -class SetList : public QList -{ -private: - class KeyCompareFunctor; - -public: - void sortByKey(); - void guessSortKeys(); - void enableAllUnknown(); - void enableAll(); - void markAllAsKnown(); - int getEnabledSetsNum(); - int getUnknownSetsNum(); - QStringList getUnknownSetsNames(); -}; - -class CardInfoPerSet -{ -public: - explicit CardInfoPerSet(const CardSetPtr &_set = QSharedPointer(nullptr)); - ~CardInfoPerSet() = default; - -private: - CardSetPtr set; - // per-set card properties; - QVariantHash properties; - -public: - const CardSetPtr getPtr() const - { - return set; - } - const QStringList getProperties() const - { - return properties.keys(); - } - const QString getProperty(const QString &propertyName) const - { - return properties.value(propertyName).toString(); - } - void setProperty(const QString &_name, const QString &_value) - { - properties.insert(_name, _value); - } -}; - -class CardInfo : public QObject -{ - Q_OBJECT -private: - CardInfoPtr smartThis; - // The card name - QString name; - // The name without punctuation or capitalization, for better card name recognition. - QString simpleName; - // The key used to identify this card in the cache - QString pixmapCacheKey; - // card text - QString text; - // whether this is not a "real" card but a token - bool isToken; - // basic card properties; common for all the sets - QVariantHash properties; - // the cards i'm related to - QList relatedCards; - // the card i'm reverse-related to - QList reverseRelatedCards; - // the cards thare are reverse-related to me - QList reverseRelatedCardsToMe; - // card sets - CardInfoPerSetMap sets; - // cached set names - QString setsNames; - // positioning properties; used by UI - bool cipt; - int tableRow; - bool upsideDownArt; - -public: - explicit CardInfo(const QString &_name = QString(), - const QString &_text = QString(), - bool _isToken = false, - QVariantHash _properties = QVariantHash(), - const QList &_relatedCards = QList(), - const QList &_reverseRelatedCards = QList(), - CardInfoPerSetMap _sets = CardInfoPerSetMap(), - bool _cipt = false, - int _tableRow = 0, - bool _upsideDownArt = false); - CardInfo(const CardInfo &other) - : QObject(other.parent()), name(other.name), simpleName(other.simpleName), pixmapCacheKey(other.pixmapCacheKey), - text(other.text), isToken(other.isToken), properties(other.properties), relatedCards(other.relatedCards), - reverseRelatedCards(other.reverseRelatedCards), reverseRelatedCardsToMe(other.reverseRelatedCardsToMe), - sets(other.sets), setsNames(other.setsNames), cipt(other.cipt), tableRow(other.tableRow), - upsideDownArt(other.upsideDownArt) - { - } - ~CardInfo() override; - - static CardInfoPtr newInstance(const QString &_name = QString(), - const QString &_text = QString(), - bool _isToken = false, - QVariantHash _properties = QVariantHash(), - const QList &_relatedCards = QList(), - const QList &_reverseRelatedCards = QList(), - CardInfoPerSetMap _sets = CardInfoPerSetMap(), - bool _cipt = false, - int _tableRow = 0, - bool _upsideDownArt = false); - - CardInfoPtr clone() const - { - // Use the copy constructor to create a new instance - CardInfoPtr newCardInfo = CardInfoPtr(new CardInfo(*this)); - newCardInfo->setSmartPointer(newCardInfo); // Set the smart pointer for the new instance - return newCardInfo; - } - - void setSmartPointer(CardInfoPtr _ptr) - { - smartThis = std::move(_ptr); - } - - // basic properties - inline const QString &getName() const - { - return name; - } - const QString &getSimpleName() const - { - return simpleName; - } - void setPixmapCacheKey(QString _pixmapCacheKey) - { - pixmapCacheKey = _pixmapCacheKey; - } - const QString &getPixmapCacheKey() const - { - return pixmapCacheKey; - } - - const QString &getText() const - { - return text; - } - void setText(const QString &_text) - { - text = _text; - emit cardInfoChanged(smartThis); - } - - bool getIsToken() const - { - return isToken; - } - const QStringList getProperties() const - { - return properties.keys(); - } - const QString getProperty(const QString &propertyName) const - { - return properties.value(propertyName).toString(); - } - void setProperty(const QString &_name, const QString &_value) - { - properties.insert(_name, _value); - emit cardInfoChanged(smartThis); - } - bool hasProperty(const QString &propertyName) const - { - return properties.contains(propertyName); - } - const CardInfoPerSetMap &getSets() const - { - return sets; - } - const QString &getSetsNames() const - { - return setsNames; - } - const QString getSetProperty(const QString &setName, const QString &propertyName) const - { - if (!sets.contains(setName)) - return ""; - return sets[setName].getProperty(propertyName); - } - void setSetProperty(const QString &setName, const QString &_name, const QString &_value) - { - if (!sets.contains(setName)) - return; - - sets[setName].setProperty(_name, _value); - emit cardInfoChanged(smartThis); - } - - // related cards - const QList &getRelatedCards() const - { - return relatedCards; - } - const QList &getReverseRelatedCards() const - { - return reverseRelatedCards; - } - const QList &getReverseRelatedCards2Me() const - { - return reverseRelatedCardsToMe; - } - const QList getAllRelatedCards() const - { - QList result; - result.append(getRelatedCards()); - result.append(getReverseRelatedCards2Me()); - return result; - } - void resetReverseRelatedCards2Me(); - void addReverseRelatedCards2Me(CardRelation *cardRelation) - { - reverseRelatedCardsToMe.append(cardRelation); - } - - // positioning - bool getCipt() const - { - return cipt; - } - int getTableRow() const - { - return tableRow; - } - void setTableRow(int _tableRow) - { - tableRow = _tableRow; - } - bool getUpsideDownArt() const - { - return upsideDownArt; - } - const QChar getColorChar() const; - - // Back-compatibility methods. Remove ASAP - const QString getCardType() const; - void setCardType(const QString &value); - const QString getCmc() const; - const QString getColors() const; - void setColors(const QString &value); - const QString getLoyalty() const; - const QString getMainCardType() const; - const QString getManaCost() const; - const QString getPowTough() const; - void setPowTough(const QString &value); - - // methods using per-set properties - QString getCustomPicURL(const QString &set) const - { - return getSetProperty(set, "picurl"); - } - QString getCorrectedName() const; - void addToSet(const CardSetPtr &_set, CardInfoPerSet _info = CardInfoPerSet()); - void emitPixmapUpdated() - { - emit pixmapUpdated(); - } - void refreshCachedSetNames(); - - /** - * Simplify a name to have no punctuation and lowercase all letters, for - * less strict name-matching. - */ - static QString simplifyName(const QString &name); - -signals: - void pixmapUpdated(); - void cardInfoChanged(CardInfoPtr card); -}; - enum LoadStatus { Ok, @@ -427,25 +70,29 @@ public: ~CardDatabase() override; void clear(); void removeCard(CardInfoPtr card); - CardInfoPtr getCard(const QString &cardName) const; - QList getCards(const QStringList &cardNames) const; - CardInfoPtr getCardByNameAndProviderId(const QString &cardName, const QString &providerId) const; - CardInfoPerSet getPreferredSetForCard(const QString &cardName); - CardInfoPerSet getSpecificSetForCard(const QString &cardName, const QString &providerId) const; + [[nodiscard]] CardInfoPtr getCard(const QString &cardName) const; + [[nodiscard]] QList getCards(const QStringList &cardNames) const; + QList getCardsByNameAndProviderId(const QMap &cardNames) const; + [[nodiscard]] CardInfoPtr getCardByNameAndProviderId(const QString &cardName, const QString &providerId) const; + [[nodiscard]] CardInfoPerSet getPreferredSetForCard(const QString &cardName) const; + [[nodiscard]] CardInfoPerSet getSpecificSetForCard(const QString &cardName, const QString &providerId) const; + CardInfoPerSet + getSpecificSetForCard(const QString &cardName, const QString &setShortName, const QString &collectorNumber) const; QString getPreferredPrintingProviderIdForCard(const QString &cardName); - CardInfoPtr guessCard(const QString &cardName) const; + [[nodiscard]] CardInfoPtr guessCard(const QString &cardName, const QString &providerId = QString()) const; /* * Get a card by its simple name. The name will be simplified in this * function, so you don't need to simplify it beforehand. */ - CardInfoPtr getCardBySimpleName(const QString &cardName) const; + [[nodiscard]] CardInfoPtr getCardBySimpleName(const QString &cardName) const; CardSetPtr getSet(const QString &setName); bool isProviderIdForPreferredPrinting(const QString &cardName, const QString &providerId); - QList getCardList() const + static CardInfoPerSet getSetInfoForCard(const CardInfoPtr &_card); + const CardNameMap &getCardList() const { - return cards.values(); + return cards; } SetList getSetList() const; LoadStatus loadFromFile(const QString &fileName); @@ -476,79 +123,4 @@ signals: void cardRemoved(CardInfoPtr card); }; -class CardRelation : public QObject -{ - Q_OBJECT -public: - enum AttachType - { - DoesNotAttach = 0, - AttachTo = 1, - TransformInto = 2, - }; - -private: - QString name; - AttachType attachType; - bool isCreateAllExclusion; - bool isVariableCount; - int defaultCount; - bool isPersistent; - -public: - explicit CardRelation(const QString &_name = QString(), - AttachType _attachType = DoesNotAttach, - bool _isCreateAllExclusion = false, - bool _isVariableCount = false, - int _defaultCount = 1, - bool _isPersistent = false); - - inline const QString &getName() const - { - return name; - } - AttachType getAttachType() const - { - return attachType; - } - bool getDoesAttach() const - { - return attachType != DoesNotAttach; - } - bool getDoesTransform() const - { - return attachType == TransformInto; - } - QString getAttachTypeAsString() const - { - switch (attachType) { - case AttachTo: - return "attach"; - case TransformInto: - return "transform"; - default: - return ""; - } - } - bool getCanCreateAnother() const - { - return !getDoesAttach(); - } - bool getIsCreateAllExclusion() const - { - return isCreateAllExclusion; - } - bool getIsVariable() const - { - return isVariableCount; - } - int getDefaultCount() const - { - return defaultCount; - } - bool getIsPersistent() const - { - return isPersistent; - } -}; #endif diff --git a/cockatrice/src/game/cards/card_database_model.cpp b/cockatrice/src/game/cards/card_database_model.cpp index 81af0f2ae..31ff98263 100644 --- a/cockatrice/src/game/cards/card_database_model.cpp +++ b/cockatrice/src/game/cards/card_database_model.cpp @@ -9,9 +9,10 @@ CardDatabaseModel::CardDatabaseModel(CardDatabase *_db, bool _showOnlyCardsFromEnabledSets, QObject *parent) : QAbstractListModel(parent), db(_db), showOnlyCardsFromEnabledSets(_showOnlyCardsFromEnabledSets) { - connect(db, SIGNAL(cardAdded(CardInfoPtr)), this, SLOT(cardAdded(CardInfoPtr))); - connect(db, SIGNAL(cardRemoved(CardInfoPtr)), this, SLOT(cardRemoved(CardInfoPtr))); - connect(db, SIGNAL(cardDatabaseEnabledSetsChanged()), this, SLOT(cardDatabaseEnabledSetsChanged())); + connect(db, &CardDatabase::cardAdded, this, &CardDatabaseModel::cardAdded); + connect(db, &CardDatabase::cardRemoved, this, &CardDatabaseModel::cardRemoved); + connect(db, &CardDatabase::cardDatabaseEnabledSetsChanged, this, + &CardDatabaseModel::cardDatabaseEnabledSetsChanged); cardDatabaseEnabledSetsChanged(); } @@ -97,9 +98,11 @@ bool CardDatabaseModel::checkCardHasAtLeastOneEnabledSet(CardInfoPtr card) if (!showOnlyCardsFromEnabledSets) return true; - for (const auto &set : card->getSets()) { - if (set.getPtr()->getEnabled()) - return true; + for (const auto &cardInfoPerSetList : card->getSets()) { + for (const auto &set : cardInfoPerSetList) { + if (set.getPtr()->getEnabled()) + return true; + } } return false; @@ -108,15 +111,17 @@ bool CardDatabaseModel::checkCardHasAtLeastOneEnabledSet(CardInfoPtr card) void CardDatabaseModel::cardDatabaseEnabledSetsChanged() { // remove all the cards no more present in at least one enabled set - foreach (CardInfoPtr card, cardList) { - if (!checkCardHasAtLeastOneEnabledSet(card)) + for (const CardInfoPtr &card : cardList) { + if (!checkCardHasAtLeastOneEnabledSet(card)) { cardRemoved(card); + } } // re-check all the card currently not shown, maybe their part of a newly-enabled set - foreach (CardInfoPtr card, db->getCardList()) { - if (!cardList.contains(card)) + for (const CardInfoPtr &card : db->getCardList()) { + if (!cardListSet.contains(card)) { cardAdded(card); + } } } @@ -126,7 +131,8 @@ void CardDatabaseModel::cardAdded(CardInfoPtr card) // add the card if it's present in at least one enabled set beginInsertRows(QModelIndex(), cardList.size(), cardList.size()); cardList.append(card); - connect(card.data(), SIGNAL(cardInfoChanged(CardInfoPtr)), this, SLOT(cardInfoChanged(CardInfoPtr))); + cardListSet.insert(card); + connect(card.data(), &CardInfo::cardInfoChanged, this, &CardDatabaseModel::cardInfoChanged); endInsertRows(); } } @@ -140,6 +146,7 @@ void CardDatabaseModel::cardRemoved(CardInfoPtr card) beginRemoveRows(QModelIndex(), row, row); disconnect(card.data(), nullptr, this, nullptr); + cardListSet.remove(card); card.clear(); cardList.removeAt(row); endRemoveRows(); @@ -168,7 +175,12 @@ void CardDatabaseDisplayModel::fetchMore(const QModelIndex &index) int remainder = sourceModel()->rowCount(index) - loadedRowCount; int itemsToFetch = qMin(100, remainder); - beginInsertRows(QModelIndex(), loadedRowCount, loadedRowCount + itemsToFetch - 1); + if (itemsToFetch == 0) { + return; + } + + const auto startIndex = qMin(rowCount(QModelIndex()), loadedRowCount); + beginInsertRows(QModelIndex(), startIndex, startIndex + itemsToFetch - 1); loadedRowCount += itemsToFetch; endInsertRows(); @@ -176,7 +188,7 @@ void CardDatabaseDisplayModel::fetchMore(const QModelIndex &index) int CardDatabaseDisplayModel::rowCount(const QModelIndex &parent) const { - return qMin(QSortFilterProxyModel::rowCount(parent), loadedRowCount); + return QSortFilterProxyModel::rowCount(parent); } bool CardDatabaseDisplayModel::lessThan(const QModelIndex &left, const QModelIndex &right) const @@ -332,7 +344,7 @@ void CardDatabaseDisplayModel::setFilterTree(FilterTree *_filterTree) disconnect(this->filterTree, nullptr, this, nullptr); this->filterTree = _filterTree; - connect(this->filterTree, SIGNAL(changed()), this, SLOT(filterTreeChanged())); + connect(this->filterTree, &FilterTree::changed, this, &CardDatabaseDisplayModel::filterTreeChanged); invalidate(); } diff --git a/cockatrice/src/game/cards/card_database_model.h b/cockatrice/src/game/cards/card_database_model.h index 403713c98..5a6b8feec 100644 --- a/cockatrice/src/game/cards/card_database_model.h +++ b/cockatrice/src/game/cards/card_database_model.h @@ -46,6 +46,7 @@ public: private: QList cardList; + QSet cardListSet; // Supports faster lookups in cardDatabaseEnabledSetsChanged() CardDatabase *db; bool showOnlyCardsFromEnabledSets; @@ -116,14 +117,15 @@ public: } void clearFilterAll(); int rowCount(const QModelIndex &parent = QModelIndex()) const override; + bool canFetchMore(const QModelIndex &parent) const override; + void fetchMore(const QModelIndex &parent) override; protected: bool lessThan(const QModelIndex &left, const QModelIndex &right) const override; static int lessThanNumerically(const QString &left, const QString &right); bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override; bool rowMatchesCardName(CardInfoPtr info) const; - bool canFetchMore(const QModelIndex &parent) const override; - void fetchMore(const QModelIndex &parent) override; + private slots: void filterTreeChanged(); /** Will translate all undesirable characters in DIRTYNAME according to the TABLE. */ diff --git a/cockatrice/src/game/cards/card_database_parser/card_database_parser.cpp b/cockatrice/src/game/cards/card_database_parser/card_database_parser.cpp index 67dcce748..ac1372f7f 100644 --- a/cockatrice/src/game/cards/card_database_parser/card_database_parser.cpp +++ b/cockatrice/src/game/cards/card_database_parser/card_database_parser.cpp @@ -10,7 +10,8 @@ void ICardDatabaseParser::clearSetlist() CardSetPtr ICardDatabaseParser::internalAddSet(const QString &setName, const QString &longName, const QString &setType, - const QDate &releaseDate) + const QDate &releaseDate, + const CardSet::Priority priority) { if (sets.contains(setName)) { return sets.value(setName); @@ -20,8 +21,9 @@ CardSetPtr ICardDatabaseParser::internalAddSet(const QString &setName, newSet->setLongName(longName); newSet->setSetType(setType); newSet->setReleaseDate(releaseDate); + newSet->setPriority(priority); sets.insert(setName, newSet); emit addSet(newSet); return newSet; -} \ No newline at end of file +} diff --git a/cockatrice/src/game/cards/card_database_parser/card_database_parser.h b/cockatrice/src/game/cards/card_database_parser/card_database_parser.h index 0bc2b9d98..12da8efbb 100644 --- a/cockatrice/src/game/cards/card_database_parser/card_database_parser.h +++ b/cockatrice/src/game/cards/card_database_parser/card_database_parser.h @@ -10,6 +10,7 @@ class ICardDatabaseParser : public QObject { + Q_OBJECT public: ~ICardDatabaseParser() override = default; @@ -32,12 +33,13 @@ protected: CardSetPtr internalAddSet(const QString &setName, const QString &longName = "", const QString &setType = "", - const QDate &releaseDate = QDate()); + const QDate &releaseDate = QDate(), + const CardSet::Priority priority = CardSet::PriorityFallback); signals: - virtual void addCard(CardInfoPtr card) = 0; - virtual void addSet(CardSetPtr set) = 0; + void addCard(CardInfoPtr card); + void addSet(CardSetPtr set); }; Q_DECLARE_INTERFACE(ICardDatabaseParser, "ICardDatabaseParser") -#endif \ No newline at end of file +#endif diff --git a/cockatrice/src/game/cards/card_database_parser/cockatrice_xml_3.cpp b/cockatrice/src/game/cards/card_database_parser/cockatrice_xml_3.cpp index 24106a3f4..1c0e2c758 100644 --- a/cockatrice/src/game/cards/card_database_parser/cockatrice_xml_3.cpp +++ b/cockatrice/src/game/cards/card_database_parser/cockatrice_xml_3.cpp @@ -13,10 +13,10 @@ bool CockatriceXml3Parser::getCanParseFile(const QString &fileName, QIODevice &device) { - qDebug() << "[CockatriceXml3Parser] Trying to parse: " << fileName; + qCInfo(CockatriceXml3Log) << "Trying to parse: " << fileName; if (!fileName.endsWith(".xml", Qt::CaseInsensitive)) { - qDebug() << "[CockatriceXml3Parser] Parsing failed: wrong extension"; + qCInfo(CockatriceXml3Log) << "Parsing failed: wrong extension"; return false; } @@ -28,12 +28,12 @@ bool CockatriceXml3Parser::getCanParseFile(const QString &fileName, QIODevice &d if (version == COCKATRICE_XML3_TAGVER) { return true; } else { - qDebug() << "[CockatriceXml3Parser] Parsing failed: wrong version" << version; + qCInfo(CockatriceXml3Log) << "Parsing failed: wrong version" << version; return false; } } else { - qDebug() << "[CockatriceXml3Parser] Parsing failed: wrong element tag" << xml.name(); + qCInfo(CockatriceXml3Log) << "Parsing failed: wrong element tag" << xml.name(); return false; } } @@ -58,7 +58,7 @@ void CockatriceXml3Parser::parseFile(QIODevice &device) } else if (name == "cards") { loadCardsFromXml(xml); } else if (!name.isEmpty()) { - qDebug() << "[CockatriceXml3Parser] Unknown item" << name << ", trying to continue anyway"; + qCInfo(CockatriceXml3Log) << "Unknown item" << name << ", trying to continue anyway"; xml.skipCurrentElement(); } } @@ -93,7 +93,7 @@ void CockatriceXml3Parser::loadSetsFromXml(QXmlStreamReader &xml) releaseDate = QDate::fromString(xml.readElementText(QXmlStreamReader::IncludeChildElements), Qt::ISODate); } else if (!name.isEmpty()) { - qDebug() << "[CockatriceXml3Parser] Unknown set property" << name << ", trying to continue anyway"; + qCInfo(CockatriceXml3Log) << "Unknown set property" << name << ", trying to continue anyway"; xml.skipCurrentElement(); } } @@ -158,6 +158,7 @@ void CockatriceXml3Parser::loadCardsFromXml(QXmlStreamReader &xml) auto _sets = CardInfoPerSetMap(); int tableRow = 0; bool cipt = false; + bool landscapeOrientation = false; bool isToken = false; bool upsideDown = false; @@ -172,7 +173,7 @@ void CockatriceXml3Parser::loadCardsFromXml(QXmlStreamReader &xml) name = xml.readElementText(QXmlStreamReader::IncludeChildElements); } else if (xmlName == "text") { text = xml.readElementText(QXmlStreamReader::IncludeChildElements); - } else if (xmlName == "color") { + } else if (xmlName == "color" || xmlName == "colors") { colors.append(xml.readElementText(QXmlStreamReader::IncludeChildElements)); } else if (xmlName == "token") { isToken = static_cast(xml.readElementText(QXmlStreamReader::IncludeChildElements).toInt()); @@ -194,6 +195,8 @@ void CockatriceXml3Parser::loadCardsFromXml(QXmlStreamReader &xml) tableRow = xml.readElementText(QXmlStreamReader::IncludeChildElements).toInt(); } else if (xmlName == "cipt") { cipt = (xml.readElementText(QXmlStreamReader::IncludeChildElements) == "1"); + } else if (xmlName == "landscapeOrientation") { + landscapeOrientation = (xml.readElementText(QXmlStreamReader::IncludeChildElements) == "1"); } else if (xmlName == "upsidedown") { upsideDown = (xml.readElementText(QXmlStreamReader::IncludeChildElements) == "1"); // sets @@ -221,7 +224,7 @@ void CockatriceXml3Parser::loadCardsFromXml(QXmlStreamReader &xml) if (attrs.hasAttribute("rarity")) { setInfo.setProperty("rarity", attrs.value("rarity").toString()); } - _sets.insert(setName, setInfo); + _sets[setName].append(setInfo); // related cards } else if (xmlName == "related" || xmlName == "reverse-related") { CardRelation::AttachType attach = CardRelation::DoesNotAttach; @@ -260,15 +263,15 @@ void CockatriceXml3Parser::loadCardsFromXml(QXmlStreamReader &xml) relatedCards << relation; } } else if (!xmlName.isEmpty()) { - qDebug() << "[CockatriceXml3Parser] Unknown card property" << xmlName - << ", trying to continue anyway"; + qCInfo(CockatriceXml3Log) << "Unknown card property" << xmlName << ", trying to continue anyway"; xml.skipCurrentElement(); } } properties.insert("colors", colors); - CardInfoPtr newCard = CardInfo::newInstance(name, text, isToken, properties, relatedCards, - reverseRelatedCards, _sets, cipt, tableRow, upsideDown); + CardInfoPtr newCard = + CardInfo::newInstance(name, text, isToken, properties, relatedCards, reverseRelatedCards, _sets, cipt, + landscapeOrientation, tableRow, upsideDown); emit addCard(newCard); } } @@ -277,7 +280,7 @@ void CockatriceXml3Parser::loadCardsFromXml(QXmlStreamReader &xml) static QXmlStreamWriter &operator<<(QXmlStreamWriter &xml, const CardSetPtr &set) { if (set.isNull()) { - qDebug() << "&operator<< set is nullptr"; + qCWarning(CockatriceXml3Log) << "&operator<< set is nullptr"; return xml; } @@ -294,7 +297,7 @@ static QXmlStreamWriter &operator<<(QXmlStreamWriter &xml, const CardSetPtr &set static QXmlStreamWriter &operator<<(QXmlStreamWriter &xml, const CardInfoPtr &info) { if (info.isNull()) { - qDebug() << "operator<< info is nullptr"; + qCWarning(CockatriceXml3Log) << "operator<< info is nullptr"; return xml; } @@ -331,24 +334,26 @@ static QXmlStreamWriter &operator<<(QXmlStreamWriter &xml, const CardInfoPtr &in // sets const CardInfoPerSetMap sets = info->getSets(); - for (CardInfoPerSet set : sets) { - xml.writeStartElement("set"); - xml.writeAttribute("rarity", set.getProperty("rarity")); - xml.writeAttribute("muId", set.getProperty("muid")); - xml.writeAttribute("uuId", set.getProperty("uuid")); + for (const auto &cardInfoPerSetList : sets) { + for (const CardInfoPerSet &set : cardInfoPerSetList) { + xml.writeStartElement("set"); + xml.writeAttribute("rarity", set.getProperty("rarity")); + xml.writeAttribute("muId", set.getProperty("muid")); + xml.writeAttribute("uuId", set.getProperty("uuid")); - tmpString = set.getProperty("num"); - if (!tmpString.isEmpty()) { - xml.writeAttribute("num", tmpString); + tmpString = set.getProperty("num"); + if (!tmpString.isEmpty()) { + xml.writeAttribute("num", tmpString); + } + + tmpString = set.getProperty("picurl"); + if (!tmpString.isEmpty()) { + xml.writeAttribute("picURL", tmpString); + } + + xml.writeCharacters(set.getPtr()->getShortName()); + xml.writeEndElement(); } - - tmpString = set.getProperty("picurl"); - if (!tmpString.isEmpty()) { - xml.writeAttribute("picURL", tmpString); - } - - xml.writeCharacters(set.getPtr()->getShortName()); - xml.writeEndElement(); } // related cards @@ -403,6 +408,9 @@ static QXmlStreamWriter &operator<<(QXmlStreamWriter &xml, const CardInfoPtr &in if (info->getCipt()) { xml.writeTextElement("cipt", "1"); } + if (info->getLandscapeOrientation()) { + xml.writeTextElement("landscapeOrientation", "1"); + } if (info->getUpsideDownArt()) { xml.writeTextElement("upsidedown", "1"); } diff --git a/cockatrice/src/game/cards/card_database_parser/cockatrice_xml_3.h b/cockatrice/src/game/cards/card_database_parser/cockatrice_xml_3.h index 2006d2203..f8c060774 100644 --- a/cockatrice/src/game/cards/card_database_parser/cockatrice_xml_3.h +++ b/cockatrice/src/game/cards/card_database_parser/cockatrice_xml_3.h @@ -3,8 +3,11 @@ #include "card_database_parser.h" +#include #include +inline Q_LOGGING_CATEGORY(CockatriceXml3Log, "cockatrice_xml.xml_3_parser"); + class CockatriceXml3Parser : public ICardDatabaseParser { Q_OBJECT @@ -24,9 +27,6 @@ private: void loadCardsFromXml(QXmlStreamReader &xml); void loadSetsFromXml(QXmlStreamReader &xml); QString getMainCardType(QString &type); -signals: - void addCard(CardInfoPtr card) override; - void addSet(CardSetPtr set) override; }; #endif \ No newline at end of file diff --git a/cockatrice/src/game/cards/card_database_parser/cockatrice_xml_4.cpp b/cockatrice/src/game/cards/card_database_parser/cockatrice_xml_4.cpp index 7b6fb75e7..b84f37366 100644 --- a/cockatrice/src/game/cards/card_database_parser/cockatrice_xml_4.cpp +++ b/cockatrice/src/game/cards/card_database_parser/cockatrice_xml_4.cpp @@ -1,5 +1,7 @@ #include "cockatrice_xml_4.h" +#include "../../../settings/cache_settings.h" + #include #include #include @@ -13,10 +15,10 @@ bool CockatriceXml4Parser::getCanParseFile(const QString &fileName, QIODevice &device) { - qDebug() << "[CockatriceXml4Parser] Trying to parse: " << fileName; + qCInfo(CockatriceXml4Log) << "Trying to parse: " << fileName; if (!fileName.endsWith(".xml", Qt::CaseInsensitive)) { - qDebug() << "[CockatriceXml4Parser] Parsing failed: wrong extension"; + qCInfo(CockatriceXml4Log) << "Parsing failed: wrong extension"; return false; } @@ -28,12 +30,12 @@ bool CockatriceXml4Parser::getCanParseFile(const QString &fileName, QIODevice &d if (version == COCKATRICE_XML4_TAGVER) { return true; } else { - qDebug() << "[CockatriceXml4Parser] Parsing failed: wrong version" << version; + qCInfo(CockatriceXml4Log) << "Parsing failed: wrong version" << version; return false; } } else { - qDebug() << "[CockatriceXml4Parser] Parsing failed: wrong element tag" << xml.name(); + qCInfo(CockatriceXml4Log) << "Parsing failed: wrong element tag" << xml.name(); return false; } } @@ -58,7 +60,7 @@ void CockatriceXml4Parser::parseFile(QIODevice &device) } else if (xmlName == "cards") { loadCardsFromXml(xml); } else if (!xmlName.isEmpty()) { - qDebug() << "[CockatriceXml4Parser] Unknown item" << xmlName << ", trying to continue anyway"; + qCInfo(CockatriceXml4Log) << "Unknown item" << xmlName << ", trying to continue anyway"; xml.skipCurrentElement(); } } @@ -77,6 +79,7 @@ void CockatriceXml4Parser::loadSetsFromXml(QXmlStreamReader &xml) if (xmlName == "set") { QString shortName, longName, setType; QDate releaseDate; + short priority; while (!xml.atEnd()) { if (xml.readNext() == QXmlStreamReader::EndElement) { break; @@ -92,14 +95,15 @@ void CockatriceXml4Parser::loadSetsFromXml(QXmlStreamReader &xml) } else if (xmlName == "releasedate") { releaseDate = QDate::fromString(xml.readElementText(QXmlStreamReader::IncludeChildElements), Qt::ISODate); + } else if (xmlName == "priority") { + priority = xml.readElementText(QXmlStreamReader::IncludeChildElements).toShort(); } else if (!xmlName.isEmpty()) { - qDebug() << "[CockatriceXml4Parser] Unknown set property" << xmlName - << ", trying to continue anyway"; + qCInfo(CockatriceXml4Log) << "Unknown set property" << xmlName << ", trying to continue anyway"; xml.skipCurrentElement(); } } - internalAddSet(shortName, longName, setType, releaseDate); + internalAddSet(shortName, longName, setType, releaseDate, static_cast(priority)); } } } @@ -122,6 +126,7 @@ QVariantHash CockatriceXml4Parser::loadCardPropertiesFromXml(QXmlStreamReader &x void CockatriceXml4Parser::loadCardsFromXml(QXmlStreamReader &xml) { + bool includeRebalancedCards = SettingsCache::instance().getIncludeRebalancedCards(); while (!xml.atEnd()) { if (xml.readNext() == QXmlStreamReader::EndElement) { break; @@ -137,6 +142,7 @@ void CockatriceXml4Parser::loadCardsFromXml(QXmlStreamReader &xml) auto _sets = CardInfoPerSetMap(); int tableRow = 0; bool cipt = false; + bool landscapeOrientation = false; bool isToken = false; bool upsideDown = false; @@ -162,6 +168,8 @@ void CockatriceXml4Parser::loadCardsFromXml(QXmlStreamReader &xml) tableRow = xml.readElementText(QXmlStreamReader::IncludeChildElements).toInt(); } else if (xmlName == "cipt") { cipt = (xml.readElementText(QXmlStreamReader::IncludeChildElements) == "1"); + } else if (xmlName == "landscapeOrientation") { + landscapeOrientation = (xml.readElementText(QXmlStreamReader::IncludeChildElements) == "1"); } else if (xmlName == "upsidedown") { upsideDown = (xml.readElementText(QXmlStreamReader::IncludeChildElements) == "1"); // sets @@ -178,7 +186,17 @@ void CockatriceXml4Parser::loadCardsFromXml(QXmlStreamReader &xml) attrName = "picurl"; setInfo.setProperty(attrName, attr.value().toString()); } - _sets.insert(setName, setInfo); + + // This is very much a hack and not the right place to + // put this check, as it requires a reload of Cockatrice + // to be apply. + // + // However, this is also true of the `set->getEnabled()` + // check above (which is currently bugged as well), so + // we'll fix both at the same time. + if (includeRebalancedCards || setInfo.getProperty("isRebalanced") != "true") { + _sets[setName].append(setInfo); + } } // related cards } else if (xmlName == "related" || xmlName == "reverse-related") { @@ -224,14 +242,14 @@ void CockatriceXml4Parser::loadCardsFromXml(QXmlStreamReader &xml) relatedCards << relation; } } else if (!xmlName.isEmpty()) { - qDebug() << "[CockatriceXml4Parser] Unknown card property" << xmlName - << ", trying to continue anyway"; + qCInfo(CockatriceXml4Log) << "Unknown card property" << xmlName << ", trying to continue anyway"; xml.skipCurrentElement(); } } - CardInfoPtr newCard = CardInfo::newInstance(name, text, isToken, properties, relatedCards, - reverseRelatedCards, _sets, cipt, tableRow, upsideDown); + CardInfoPtr newCard = + CardInfo::newInstance(name, text, isToken, properties, relatedCards, reverseRelatedCards, _sets, cipt, + landscapeOrientation, tableRow, upsideDown); emit addCard(newCard); } } @@ -240,7 +258,7 @@ void CockatriceXml4Parser::loadCardsFromXml(QXmlStreamReader &xml) static QXmlStreamWriter &operator<<(QXmlStreamWriter &xml, const CardSetPtr &set) { if (set.isNull()) { - qDebug() << "&operator<< set is nullptr"; + qCWarning(CockatriceXml4Log) << "&operator<< set is nullptr"; return xml; } @@ -249,6 +267,7 @@ static QXmlStreamWriter &operator<<(QXmlStreamWriter &xml, const CardSetPtr &set xml.writeTextElement("longname", set->getLongName()); xml.writeTextElement("settype", set->getSetType()); xml.writeTextElement("releasedate", set->getReleaseDate().toString(Qt::ISODate)); + xml.writeTextElement("priority", QString::number(set->getPriority())); xml.writeEndElement(); return xml; @@ -257,7 +276,7 @@ static QXmlStreamWriter &operator<<(QXmlStreamWriter &xml, const CardSetPtr &set static QXmlStreamWriter &operator<<(QXmlStreamWriter &xml, const CardInfoPtr &info) { if (info.isNull()) { - qDebug() << "operator<< info is nullptr"; + qCWarning(CockatriceXml4Log) << "operator<< info is nullptr"; return xml; } @@ -280,14 +299,16 @@ static QXmlStreamWriter &operator<<(QXmlStreamWriter &xml, const CardInfoPtr &in xml.writeEndElement(); // sets - for (CardInfoPerSet set : info->getSets()) { - xml.writeStartElement("set"); - for (QString propName : set.getProperties()) { - xml.writeAttribute(propName, set.getProperty(propName)); - } + for (const auto &cardInfoPerSetList : info->getSets()) { + for (const CardInfoPerSet &set : cardInfoPerSetList) { + xml.writeStartElement("set"); + for (const QString &propName : set.getProperties()) { + xml.writeAttribute(propName, set.getProperty(propName)); + } - xml.writeCharacters(set.getPtr()->getShortName()); - xml.writeEndElement(); + xml.writeCharacters(set.getPtr()->getShortName()); + xml.writeEndElement(); + } } // related cards @@ -347,6 +368,9 @@ static QXmlStreamWriter &operator<<(QXmlStreamWriter &xml, const CardInfoPtr &in if (info->getCipt()) { xml.writeTextElement("cipt", "1"); } + if (info->getLandscapeOrientation()) { + xml.writeTextElement("landscapeOrientation", "1"); + } if (info->getUpsideDownArt()) { xml.writeTextElement("upsidedown", "1"); } diff --git a/cockatrice/src/game/cards/card_database_parser/cockatrice_xml_4.h b/cockatrice/src/game/cards/card_database_parser/cockatrice_xml_4.h index 59de582a3..9cd200441 100644 --- a/cockatrice/src/game/cards/card_database_parser/cockatrice_xml_4.h +++ b/cockatrice/src/game/cards/card_database_parser/cockatrice_xml_4.h @@ -3,8 +3,11 @@ #include "card_database_parser.h" +#include #include +inline Q_LOGGING_CATEGORY(CockatriceXml4Log, "cockatrice_xml.xml_4_parser"); + class CockatriceXml4Parser : public ICardDatabaseParser { Q_OBJECT @@ -24,9 +27,6 @@ private: QVariantHash loadCardPropertiesFromXml(QXmlStreamReader &xml); void loadCardsFromXml(QXmlStreamReader &xml); void loadSetsFromXml(QXmlStreamReader &xml); -signals: - void addCard(CardInfoPtr card) override; - void addSet(CardSetPtr set) override; }; #endif \ No newline at end of file diff --git a/cockatrice/src/game/cards/card_drag_item.cpp b/cockatrice/src/game/cards/card_drag_item.cpp index bf7db01dd..d21e9168b 100644 --- a/cockatrice/src/game/cards/card_drag_item.cpp +++ b/cockatrice/src/game/cards/card_drag_item.cpp @@ -95,18 +95,24 @@ void CardDragItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) QList dragItemList; CardZone *startZone = static_cast(item)->getZone(); - if (currentZone && !(static_cast(item)->getAttachedTo() && (startZone == currentZone)) && !occupied) { - dragItemList.append(this); + if (currentZone && !(static_cast(item)->getAttachedTo() && (startZone == currentZone))) { + if (!occupied) { + dragItemList.append(this); + } + for (int i = 0; i < childDrags.size(); i++) { CardDragItem *c = static_cast(childDrags[i]); - if (!(static_cast(c->item)->getAttachedTo() && (startZone == currentZone)) && !c->occupied) + if (!occupied && !(static_cast(c->item)->getAttachedTo() && (startZone == currentZone)) && + !c->occupied) { dragItemList.append(c); + } sc->removeItem(c); } } - if (currentZone) + if (currentZone) { currentZone->handleDropEvent(dragItemList, startZone, (sp - currentZone->scenePos()).toPoint()); + } event->accept(); } diff --git a/cockatrice/src/game/cards/card_drag_item.h b/cockatrice/src/game/cards/card_drag_item.h index 96c8eca50..794fd6ed3 100644 --- a/cockatrice/src/game/cards/card_drag_item.h +++ b/cockatrice/src/game/cards/card_drag_item.h @@ -28,11 +28,11 @@ public: { return faceDown; } - void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget); - void updatePosition(const QPointF &cursorScenePos); + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override; + void updatePosition(const QPointF &cursorScenePos) override; protected: - void mouseReleaseEvent(QGraphicsSceneMouseEvent *event); + void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override; }; #endif diff --git a/cockatrice/src/game/cards/card_info.cpp b/cockatrice/src/game/cards/card_info.cpp new file mode 100644 index 000000000..ae55ad7af --- /dev/null +++ b/cockatrice/src/game/cards/card_info.cpp @@ -0,0 +1,418 @@ +#include "card_info.h" + +#include "../../client/ui/picture_loader/picture_loader.h" +#include "../../settings/cache_settings.h" +#include "../game_specific_terms.h" + +#include +#include +#include +#include +#include +#include + +CardSet::CardSet(const QString &_shortName, + const QString &_longName, + const QString &_setType, + const QDate &_releaseDate, + const CardSet::Priority _priority) + : shortName(_shortName), longName(_longName), releaseDate(_releaseDate), setType(_setType), priority(_priority) +{ + loadSetOptions(); +} + +CardSetPtr CardSet::newInstance(const QString &_shortName, + const QString &_longName, + const QString &_setType, + const QDate &_releaseDate, + const Priority _priority) +{ + CardSetPtr ptr(new CardSet(_shortName, _longName, _setType, _releaseDate, _priority)); + // ptr->setSmartPointer(ptr); + return ptr; +} + +QString CardSet::getCorrectedShortName() const +{ + // For Windows machines. + QSet invalidFileNames; + invalidFileNames << "CON" + << "PRN" + << "AUX" + << "NUL" + << "COM1" + << "COM2" + << "COM3" + << "COM4" + << "COM5" + << "COM6" + << "COM7" + << "COM8" + << "COM9" + << "LPT1" + << "LPT2" + << "LPT3" + << "LPT4" + << "LPT5" + << "LPT6" + << "LPT7" + << "LPT8" + << "LPT9"; + + return invalidFileNames.contains(shortName) ? shortName + "_" : shortName; +} + +void CardSet::loadSetOptions() +{ + sortKey = SettingsCache::instance().cardDatabase().getSortKey(shortName); + enabled = SettingsCache::instance().cardDatabase().isEnabled(shortName); + isknown = SettingsCache::instance().cardDatabase().isKnown(shortName); +} + +void CardSet::setSortKey(unsigned int _sortKey) +{ + sortKey = _sortKey; + SettingsCache::instance().cardDatabase().setSortKey(shortName, _sortKey); +} + +void CardSet::setEnabled(bool _enabled) +{ + enabled = _enabled; + SettingsCache::instance().cardDatabase().setEnabled(shortName, _enabled); +} + +void CardSet::setIsKnown(bool _isknown) +{ + isknown = _isknown; + SettingsCache::instance().cardDatabase().setIsKnown(shortName, _isknown); +} + +class SetList::KeyCompareFunctor +{ +public: + inline bool operator()(const CardSetPtr &a, const CardSetPtr &b) const + { + if (a.isNull() || b.isNull()) { + qCWarning(CardInfoLog) << "SetList::KeyCompareFunctor a or b is null"; + return false; + } + + return a->getSortKey() < b->getSortKey(); + } +}; + +void SetList::sortByKey() +{ + std::sort(begin(), end(), KeyCompareFunctor()); +} + +int SetList::getEnabledSetsNum() +{ + int num = 0; + for (int i = 0; i < size(); ++i) { + CardSetPtr set = at(i); + if (set && set->getEnabled()) { + ++num; + } + } + return num; +} + +int SetList::getUnknownSetsNum() +{ + int num = 0; + for (int i = 0; i < size(); ++i) { + CardSetPtr set = at(i); + if (set && !set->getIsKnown() && !set->getIsKnownIgnored()) { + ++num; + } + } + return num; +} + +QStringList SetList::getUnknownSetsNames() +{ + QStringList sets = QStringList(); + for (int i = 0; i < size(); ++i) { + CardSetPtr set = at(i); + if (set && !set->getIsKnown() && !set->getIsKnownIgnored()) { + sets << set->getShortName(); + } + } + return sets; +} + +void SetList::enableAllUnknown() +{ + for (int i = 0; i < size(); ++i) { + CardSetPtr set = at(i); + if (set && !set->getIsKnown() && !set->getIsKnownIgnored()) { + set->setIsKnown(true); + set->setEnabled(true); + } else if (set && set->getIsKnownIgnored() && !set->getEnabled()) { + set->setEnabled(true); + } + } +} + +void SetList::enableAll() +{ + for (int i = 0; i < size(); ++i) { + CardSetPtr set = at(i); + + if (set == nullptr) { + qCWarning(CardInfoLog) << "enabledAll has null"; + continue; + } + + if (!set->getIsKnownIgnored()) { + set->setIsKnown(true); + } + + set->setEnabled(true); + } +} + +void SetList::markAllAsKnown() +{ + for (int i = 0; i < size(); ++i) { + CardSetPtr set = at(i); + if (set && !set->getIsKnown() && !set->getIsKnownIgnored()) { + set->setIsKnown(true); + set->setEnabled(false); + } else if (set && set->getIsKnownIgnored() && !set->getEnabled()) { + set->setEnabled(true); + } + } +} + +void SetList::guessSortKeys() +{ + defaultSort(); + for (int i = 0; i < size(); ++i) { + CardSetPtr set = at(i); + if (set.isNull()) { + qCWarning(CardInfoLog) << "guessSortKeys set is null"; + continue; + } + set->setSortKey(i); + } +} + +void SetList::defaultSort() +{ + std::sort(begin(), end(), [](const CardSetPtr &a, const CardSetPtr &b) { + // Sort by priority, then by release date, then by short name + if (a->getPriority() != b->getPriority()) { + return a->getPriority() < b->getPriority(); // lowest first + } else if (a->getReleaseDate() != b->getReleaseDate()) { + return a->getReleaseDate() > b->getReleaseDate(); // most recent first + } else { + return a->getShortName() < b->getShortName(); // alphabetically + } + }); +} + +CardInfoPerSet::CardInfoPerSet(const CardSetPtr &_set) : set(_set) +{ +} + +CardInfo::CardInfo(const QString &_name, + const QString &_text, + bool _isToken, + QVariantHash _properties, + const QList &_relatedCards, + const QList &_reverseRelatedCards, + CardInfoPerSetMap _sets, + bool _cipt, + bool _landscapeOrientation, + int _tableRow, + bool _upsideDownArt) + : name(_name), text(_text), isToken(_isToken), properties(std::move(_properties)), relatedCards(_relatedCards), + reverseRelatedCards(_reverseRelatedCards), sets(std::move(_sets)), cipt(_cipt), + landscapeOrientation(_landscapeOrientation), tableRow(_tableRow), upsideDownArt(_upsideDownArt) +{ + pixmapCacheKey = QLatin1String("card_") + name; + simpleName = CardInfo::simplifyName(name); + + refreshCachedSetNames(); +} + +CardInfo::~CardInfo() +{ + PictureLoader::clearPixmapCache(smartThis); +} + +CardInfoPtr CardInfo::newInstance(const QString &_name) +{ + return newInstance(_name, QString(), false, QVariantHash(), QList(), QList(), + CardInfoPerSetMap(), false, false, 0, false); +} + +CardInfoPtr CardInfo::newInstance(const QString &_name, + const QString &_text, + bool _isToken, + QVariantHash _properties, + const QList &_relatedCards, + const QList &_reverseRelatedCards, + CardInfoPerSetMap _sets, + bool _cipt, + bool _landscapeOrientation, + int _tableRow, + bool _upsideDownArt) +{ + CardInfoPtr ptr(new CardInfo(_name, _text, _isToken, std::move(_properties), _relatedCards, _reverseRelatedCards, + _sets, _cipt, _landscapeOrientation, _tableRow, _upsideDownArt)); + ptr->setSmartPointer(ptr); + + for (const auto &cardInfoPerSetList : _sets) { + for (const CardInfoPerSet &set : cardInfoPerSetList) { + set.getPtr()->append(ptr); + break; + } + } + + return ptr; +} + +QString CardInfo::getCorrectedName() const +{ + // remove all the characters reserved in windows file paths, + // other oses only disallow a subset of these so it covers all + static const QRegularExpression rmrx(R"(( // |[*<>:"\\?\x00-\x08\x10-\x1f]))"); + static const QRegularExpression spacerx(R"([/\x09-\x0f])"); + static const QString space(' '); + QString result = name; + // Fire // Ice, Circle of Protection: Red, "Ach! Hans, Run!", Who/What/When/Where/Why, Question Elemental? + return result.remove(rmrx).replace(spacerx, space); +} + +void CardInfo::addToSet(const CardSetPtr &_set, const CardInfoPerSet _info) +{ + _set->append(smartThis); + sets[_set->getShortName()].append(_info); + + refreshCachedSetNames(); +} + +void CardInfo::combineLegalities(const QVariantHash &props) +{ + QHashIterator it(props); + while (it.hasNext()) { + it.next(); + if (it.key().startsWith("format-")) { + smartThis->setProperty(it.key(), it.value().toString()); + } + } +} + +void CardInfo::refreshCachedSetNames() +{ + QStringList setList; + // update the cached list of set names + for (const auto &cardInfoPerSetList : sets) { + for (const auto &set : cardInfoPerSetList) { + if (set.getPtr()->getEnabled()) { + setList << set.getPtr()->getShortName(); + } + break; + } + } + setsNames = setList.join(", "); +} + +QString CardInfo::simplifyName(const QString &name) +{ + static const QRegularExpression spaceOrSplit("(\\s+|\\/\\/.*)"); + static const QRegularExpression nonAlnum("[^a-z0-9]"); + + QString simpleName = name.toLower(); + + // remove spaces and right halves of split cards + simpleName.remove(spaceOrSplit); + + // So Aetherling would work, but not Ætherling since 'Æ' would get replaced + // with nothing. + simpleName.replace("æ", "ae"); + + // Replace Jötun Grunt with Jotun Grunt. + simpleName = simpleName.normalized(QString::NormalizationForm_KD); + + // remove all non alphanumeric characters from the name + simpleName.remove(nonAlnum); + return simpleName; +} + +const QChar CardInfo::getColorChar() const +{ + QString colors = getColors(); + switch (colors.size()) { + case 0: + return QChar(); + case 1: + return colors.at(0); + default: + return QChar('m'); + } +} + +CardRelation::CardRelation(const QString &_name, + AttachType _attachType, + bool _isCreateAllExclusion, + bool _isVariableCount, + int _defaultCount, + bool _isPersistent) + : name(_name), attachType(_attachType), isCreateAllExclusion(_isCreateAllExclusion), + isVariableCount(_isVariableCount), defaultCount(_defaultCount), isPersistent(_isPersistent) +{ +} + +void CardInfo::resetReverseRelatedCards2Me() +{ + for (CardRelation *cardRelation : this->getReverseRelatedCards2Me()) { + cardRelation->deleteLater(); + } + reverseRelatedCardsToMe = QList(); +} + +// Back-compatibility methods. Remove ASAP +const QString CardInfo::getCardType() const +{ + return getProperty(Mtg::CardType); +} +void CardInfo::setCardType(const QString &value) +{ + setProperty(Mtg::CardType, value); +} +const QString CardInfo::getCmc() const +{ + return getProperty(Mtg::ConvertedManaCost); +} +const QString CardInfo::getColors() const +{ + return getProperty(Mtg::Colors); +} +void CardInfo::setColors(const QString &value) +{ + setProperty(Mtg::Colors, value); +} +const QString CardInfo::getLoyalty() const +{ + return getProperty(Mtg::Loyalty); +} +const QString CardInfo::getMainCardType() const +{ + return getProperty(Mtg::MainCardType); +} +const QString CardInfo::getManaCost() const +{ + return getProperty(Mtg::ManaCost); +} +const QString CardInfo::getPowTough() const +{ + return getProperty(Mtg::PowTough); +} +void CardInfo::setPowTough(const QString &value) +{ + setProperty(Mtg::PowTough, value); +} diff --git a/cockatrice/src/game/cards/card_info.h b/cockatrice/src/game/cards/card_info.h new file mode 100644 index 000000000..9d06bc5f6 --- /dev/null +++ b/cockatrice/src/game/cards/card_info.h @@ -0,0 +1,491 @@ +#ifndef CARD_INFO_H +#define CARD_INFO_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +inline Q_LOGGING_CATEGORY(CardInfoLog, "card_info"); + +class CardInfo; +class CardInfoPerSet; +class CardSet; +class CardRelation; +class ICardDatabaseParser; + +typedef QMap QStringMap; +typedef QSharedPointer CardInfoPtr; +typedef QSharedPointer CardSetPtr; +typedef QMap> CardInfoPerSetMap; + +Q_DECLARE_METATYPE(CardInfoPtr) + +class CardSet : public QList +{ +public: + enum Priority + { + PriorityFallback = 0, + PriorityPrimary = 10, + PrioritySecondary = 20, + PriorityReprint = 30, + PriorityOther = 40, + PriorityLowest = 100, + }; + +private: + QString shortName, longName; + unsigned int sortKey; + QDate releaseDate; + QString setType; + Priority priority; + bool enabled, isknown; + +public: + explicit CardSet(const QString &_shortName = QString(), + const QString &_longName = QString(), + const QString &_setType = QString(), + const QDate &_releaseDate = QDate(), + const Priority _priority = PriorityFallback); + static CardSetPtr newInstance(const QString &_shortName = QString(), + const QString &_longName = QString(), + const QString &_setType = QString(), + const QDate &_releaseDate = QDate(), + const Priority _priority = PriorityFallback); + QString getCorrectedShortName() const; + QString getShortName() const + { + return shortName; + } + QString getLongName() const + { + return longName; + } + QString getSetType() const + { + return setType; + } + QDate getReleaseDate() const + { + return releaseDate; + } + Priority getPriority() const + { + return priority; + } + void setLongName(const QString &_longName) + { + longName = _longName; + } + void setSetType(const QString &_setType) + { + setType = _setType; + } + void setReleaseDate(const QDate &_releaseDate) + { + releaseDate = _releaseDate; + } + void setPriority(const Priority _priority) + { + priority = _priority; + } + + void loadSetOptions(); + int getSortKey() const + { + return sortKey; + } + void setSortKey(unsigned int _sortKey); + bool getEnabled() const + { + return enabled; + } + void setEnabled(bool _enabled); + bool getIsKnown() const + { + return isknown; + } + void setIsKnown(bool _isknown); + + // Determine incomplete sets. + bool getIsKnownIgnored() const + { + return longName.length() + setType.length() + releaseDate.toString().length() == 0; + } +}; + +class SetList : public QList +{ +private: + class KeyCompareFunctor; + +public: + void sortByKey(); + void guessSortKeys(); + void enableAllUnknown(); + void enableAll(); + void markAllAsKnown(); + int getEnabledSetsNum(); + int getUnknownSetsNum(); + QStringList getUnknownSetsNames(); + void defaultSort(); +}; + +class CardInfoPerSet +{ +public: + explicit CardInfoPerSet(const CardSetPtr &_set = QSharedPointer(nullptr)); + ~CardInfoPerSet() = default; + + bool operator==(const CardInfoPerSet &other) const + { + return this->set == other.set && this->properties == other.properties; + } + +private: + CardSetPtr set; + // per-set card properties; + QVariantHash properties; + +public: + const CardSetPtr getPtr() const + { + return set; + } + const QStringList getProperties() const + { + return properties.keys(); + } + const QString getProperty(const QString &propertyName) const + { + return properties.value(propertyName).toString(); + } + void setProperty(const QString &_name, const QString &_value) + { + properties.insert(_name, _value); + } +}; + +class CardInfo : public QObject +{ + Q_OBJECT +private: + CardInfoPtr smartThis; + // The card name + QString name; + // The name without punctuation or capitalization, for better card name recognition. + QString simpleName; + // The key used to identify this card in the cache + QString pixmapCacheKey; + // card text + QString text; + // whether this is not a "real" card but a token + bool isToken; + // basic card properties; common for all the sets + QVariantHash properties; + // the cards i'm related to + QList relatedCards; + // the card i'm reverse-related to + QList reverseRelatedCards; + // the cards thare are reverse-related to me + QList reverseRelatedCardsToMe; + // card sets + CardInfoPerSetMap sets; + // cached set names + QString setsNames; + // positioning properties; used by UI + bool cipt; + bool landscapeOrientation; + int tableRow; + bool upsideDownArt; + +public: + explicit CardInfo(const QString &_name, + const QString &_text, + bool _isToken, + QVariantHash _properties, + const QList &_relatedCards, + const QList &_reverseRelatedCards, + CardInfoPerSetMap _sets, + bool _cipt, + bool _landscapeOrientation, + int _tableRow, + bool _upsideDownArt); + CardInfo(const CardInfo &other) + : QObject(other.parent()), name(other.name), simpleName(other.simpleName), pixmapCacheKey(other.pixmapCacheKey), + text(other.text), isToken(other.isToken), properties(other.properties), relatedCards(other.relatedCards), + reverseRelatedCards(other.reverseRelatedCards), reverseRelatedCardsToMe(other.reverseRelatedCardsToMe), + sets(other.sets), setsNames(other.setsNames), cipt(other.cipt), + landscapeOrientation(other.landscapeOrientation), tableRow(other.tableRow), upsideDownArt(other.upsideDownArt) + { + } + ~CardInfo() override; + + static CardInfoPtr newInstance(const QString &_name); + + static CardInfoPtr newInstance(const QString &_name, + const QString &_text, + bool _isToken, + QVariantHash _properties, + const QList &_relatedCards, + const QList &_reverseRelatedCards, + CardInfoPerSetMap _sets, + bool _cipt, + bool _landscapeOrientation, + int _tableRow, + bool _upsideDownArt); + + CardInfoPtr clone() const + { + // Use the copy constructor to create a new instance + CardInfoPtr newCardInfo = CardInfoPtr(new CardInfo(*this)); + newCardInfo->setSmartPointer(newCardInfo); // Set the smart pointer for the new instance + return newCardInfo; + } + + void setSmartPointer(CardInfoPtr _ptr) + { + smartThis = std::move(_ptr); + } + + // basic properties + inline const QString &getName() const + { + return name; + } + const QString &getSimpleName() const + { + return simpleName; + } + void setPixmapCacheKey(QString _pixmapCacheKey) + { + pixmapCacheKey = _pixmapCacheKey; + } + const QString &getPixmapCacheKey() const + { + return pixmapCacheKey; + } + + const QString &getText() const + { + return text; + } + void setText(const QString &_text) + { + text = _text; + emit cardInfoChanged(smartThis); + } + + bool getIsToken() const + { + return isToken; + } + const QStringList getProperties() const + { + return properties.keys(); + } + const QString getProperty(const QString &propertyName) const + { + return properties.value(propertyName).toString(); + } + void setProperty(const QString &_name, const QString &_value) + { + properties.insert(_name, _value); + emit cardInfoChanged(smartThis); + } + bool hasProperty(const QString &propertyName) const + { + return properties.contains(propertyName); + } + const CardInfoPerSetMap &getSets() const + { + return sets; + } + const QString &getSetsNames() const + { + return setsNames; + } + const QString getSetProperty(const QString &setName, const QString &propertyName) const + { + if (!sets.contains(setName)) + return ""; + + for (const auto &set : sets[setName]) { + if (QLatin1String("card_") + this->getName() + QString("_") + QString(set.getProperty("uuid")) == + this->getPixmapCacheKey()) { + return set.getProperty(propertyName); + } + } + + return sets[setName][0].getProperty(propertyName); + } + + // related cards + const QList &getRelatedCards() const + { + return relatedCards; + } + const QList &getReverseRelatedCards() const + { + return reverseRelatedCards; + } + const QList &getReverseRelatedCards2Me() const + { + return reverseRelatedCardsToMe; + } + const QList getAllRelatedCards() const + { + QList result; + result.append(getRelatedCards()); + result.append(getReverseRelatedCards2Me()); + return result; + } + void resetReverseRelatedCards2Me(); + void addReverseRelatedCards2Me(CardRelation *cardRelation) + { + reverseRelatedCardsToMe.append(cardRelation); + } + + // positioning + bool getCipt() const + { + return cipt; + } + bool getLandscapeOrientation() const + { + return landscapeOrientation; + } + int getTableRow() const + { + return tableRow; + } + void setTableRow(int _tableRow) + { + tableRow = _tableRow; + } + bool getUpsideDownArt() const + { + return upsideDownArt; + } + const QChar getColorChar() const; + + // Back-compatibility methods. Remove ASAP + const QString getCardType() const; + void setCardType(const QString &value); + const QString getCmc() const; + const QString getColors() const; + void setColors(const QString &value); + const QString getLoyalty() const; + const QString getMainCardType() const; + const QString getManaCost() const; + const QString getPowTough() const; + void setPowTough(const QString &value); + + // methods using per-set properties + QString getCustomPicURL(const QString &set) const + { + return getSetProperty(set, "picurl"); + } + QString getCorrectedName() const; + void addToSet(const CardSetPtr &_set, CardInfoPerSet _info = CardInfoPerSet()); + void combineLegalities(const QVariantHash &props); + void emitPixmapUpdated() + { + emit pixmapUpdated(); + } + void refreshCachedSetNames(); + + /** + * Simplify a name to have no punctuation and lowercase all letters, for + * less strict name-matching. + */ + static QString simplifyName(const QString &name); + +signals: + void pixmapUpdated(); + void cardInfoChanged(CardInfoPtr card); +}; + +class CardRelation : public QObject +{ + Q_OBJECT +public: + enum AttachType + { + DoesNotAttach = 0, + AttachTo = 1, + TransformInto = 2, + }; + +private: + QString name; + AttachType attachType; + bool isCreateAllExclusion; + bool isVariableCount; + int defaultCount; + bool isPersistent; + +public: + explicit CardRelation(const QString &_name = QString(), + AttachType _attachType = DoesNotAttach, + bool _isCreateAllExclusion = false, + bool _isVariableCount = false, + int _defaultCount = 1, + bool _isPersistent = false); + + inline const QString &getName() const + { + return name; + } + AttachType getAttachType() const + { + return attachType; + } + bool getDoesAttach() const + { + return attachType != DoesNotAttach; + } + bool getDoesTransform() const + { + return attachType == TransformInto; + } + QString getAttachTypeAsString() const + { + switch (attachType) { + case AttachTo: + return "attach"; + case TransformInto: + return "transform"; + default: + return ""; + } + } + bool getCanCreateAnother() const + { + return !getDoesAttach(); + } + bool getIsCreateAllExclusion() const + { + return isCreateAllExclusion; + } + bool getIsVariable() const + { + return isVariableCount; + } + int getDefaultCount() const + { + return defaultCount; + } + bool getIsPersistent() const + { + return isPersistent; + } +}; +#endif diff --git a/cockatrice/src/game/cards/card_item.cpp b/cockatrice/src/game/cards/card_item.cpp index 0378c1909..c088a4ed1 100644 --- a/cockatrice/src/game/cards/card_item.cpp +++ b/cockatrice/src/game/cards/card_item.cpp @@ -22,10 +22,9 @@ CardItem::CardItem(Player *_owner, const QString &_name, const QString &_providerId, int _cardid, - bool _revealedCard, CardZone *_zone) - : AbstractCardItem(parent, _name, _providerId, _owner, _cardid), zone(_zone), revealedCard(_revealedCard), - attacking(false), destroyOnZoneChange(false), doesntUntap(false), dragItem(nullptr), attachedTo(nullptr) + : AbstractCardItem(parent, _name, _providerId, _owner, _cardid), zone(_zone), attacking(false), + destroyOnZoneChange(false), doesntUntap(false), dragItem(nullptr), attachedTo(nullptr) { owner->addCard(this); @@ -38,16 +37,9 @@ CardItem::CardItem(Player *_owner, CardItem::~CardItem() { - prepareDelete(); - - if (scene()) - static_cast(scene())->unregisterAnimationItem(this); - delete cardMenu; delete ptMenu; delete moveMenu; - - deleteDragItem(); } void CardItem::prepareDelete() @@ -74,6 +66,8 @@ void CardItem::prepareDelete() void CardItem::deleteLater() { prepareDelete(); + if (scene()) + static_cast(scene())->unregisterAnimationItem(this); AbstractCardItem::deleteLater(); } @@ -217,13 +211,15 @@ void CardItem::setAttachedTo(CardItem *_attachedTo) } } -void CardItem::resetState() +void CardItem::resetState(bool keepAnnotations) { attacking = false; facedown = false; counters.clear(); pt.clear(); - annotation.clear(); + if (!keepAnnotations) { + annotation.clear(); + } attachedTo = 0; attachedCards.clear(); setTapped(false, false); @@ -383,8 +379,46 @@ void CardItem::playCard(bool faceDown) TableZone *tz = qobject_cast(zone); if (tz) tz->toggleTapped(); - else - zone->getPlayer()->playCard(this, faceDown, info ? info->getCipt() : false); + else { + if (SettingsCache::instance().getClickPlaysAllSelected()) { + faceDown ? zone->getPlayer()->actPlayFacedown() : zone->getPlayer()->actPlay(); + } else { + zone->getPlayer()->playCard(this, faceDown); + } + } +} + +/** + * @brief returns true if the zone is a unwritable reveal zone view (eg a card reveal window). Will return false if zone + * is nullptr. + */ +static bool isUnwritableRevealZone(CardZone *zone) +{ + if (zone && zone->getIsView()) { + if (auto *view = static_cast(zone)) { + return view->getRevealZone() && !view->getWriteableRevealZone(); + } + } + return false; +} + +/** + * This method is called when a "click to play" is done on the card. + * This is either triggered by a single click or double click, depending on the settings. + * + * @param shiftHeld if the shift key was held during the click + */ +void CardItem::handleClickedToPlay(bool shiftHeld) +{ + if (isUnwritableRevealZone(zone)) { + if (SettingsCache::instance().getClickPlaysAllSelected()) { + zone->getPlayer()->actHide(); + } else { + zone->removeCard(this); + } + } else { + playCard(shiftHeld); + } } void CardItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) @@ -396,17 +430,7 @@ void CardItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) } } else if ((event->modifiers() != Qt::AltModifier) && (event->button() == Qt::LeftButton) && (!SettingsCache::instance().getDoubleClickToPlay())) { - bool hideCard = false; - if (zone && zone->getIsView()) { - auto *view = static_cast(zone); - if (view->getRevealZone() && !view->getWriteableRevealZone()) - hideCard = true; - } - if (zone && hideCard) { - zone->removeCard(this); - } else { - playCard(event->modifiers().testFlag(Qt::ShiftModifier)); - } + handleClickedToPlay(event->modifiers().testFlag(Qt::ShiftModifier)); } if (owner != nullptr) { // cards without owner will be deleted @@ -417,12 +441,9 @@ void CardItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) void CardItem::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) { - if ((event->modifiers() != Qt::AltModifier) && (SettingsCache::instance().getDoubleClickToPlay()) && - (event->buttons() == Qt::LeftButton)) { - if (revealedCard) - zone->removeCard(this); - else - playCard(event->modifiers().testFlag(Qt::ShiftModifier)); + if ((event->modifiers() != Qt::AltModifier) && (event->buttons() == Qt::LeftButton) && + (SettingsCache::instance().getDoubleClickToPlay())) { + handleClickedToPlay(event->modifiers().testFlag(Qt::ShiftModifier)); } event->accept(); } @@ -465,5 +486,5 @@ QVariant CardItem::itemChange(GraphicsItemChange change, const QVariant &value) owner->getGame()->setActiveCard(nullptr); } } - return QGraphicsItem::itemChange(change, value); + return AbstractCardItem::itemChange(change, value); } diff --git a/cockatrice/src/game/cards/card_item.h b/cockatrice/src/game/cards/card_item.h index bb037ead9..5b4b325ba 100644 --- a/cockatrice/src/game/cards/card_item.h +++ b/cockatrice/src/game/cards/card_item.h @@ -22,7 +22,6 @@ class CardItem : public AbstractCardItem Q_OBJECT private: CardZone *zone; - bool revealedCard; bool attacking; QMap counters; QString annotation; @@ -37,6 +36,7 @@ private: QMenu *cardMenu, *ptMenu, *moveMenu; void prepareDelete(); + void handleClickedToPlay(bool shiftHeld); public slots: void deleteLater(); @@ -45,25 +45,24 @@ public: { Type = typeCard }; - int type() const + int type() const override { return Type; } - CardItem(Player *_owner, - QGraphicsItem *parent = nullptr, - const QString &_name = QString(), - const QString &_providerId = QString(), - int _cardid = -1, - bool revealedCard = false, - CardZone *_zone = nullptr); - ~CardItem(); + explicit CardItem(Player *_owner, + QGraphicsItem *parent = nullptr, + const QString &_name = QString(), + const QString &_providerId = QString(), + int _cardid = -1, + CardZone *_zone = nullptr); + ~CardItem() override; void retranslateUi(); CardZone *getZone() const { return zone; } void setZone(CardZone *_zone); - void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget); + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override; QPoint getGridPoint() const { return gridPoint; @@ -84,10 +83,6 @@ public: { owner = _owner; } - bool getRevealedCard() const - { - return revealedCard; - } bool getAttacking() const { return attacking; @@ -138,7 +133,7 @@ public: { return attachedCards; } - void resetState(); + void resetState(bool keepAnnotations = false); void processCardInfo(const ServerInfo_Card &_info); QMenu *getCardMenu() const @@ -162,10 +157,10 @@ public: void playCard(bool faceDown); protected: - void mouseMoveEvent(QGraphicsSceneMouseEvent *event); - void mouseReleaseEvent(QGraphicsSceneMouseEvent *event); - void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event); - QVariant itemChange(GraphicsItemChange change, const QVariant &value); + void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override; + void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override; + void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) override; + QVariant itemChange(GraphicsItemChange change, const QVariant &value) override; }; #endif diff --git a/cockatrice/src/game/cards/card_list.cpp b/cockatrice/src/game/cards/card_list.cpp index e2866e5d8..3a353ce3f 100644 --- a/cockatrice/src/game/cards/card_list.cpp +++ b/cockatrice/src/game/cards/card_list.cpp @@ -3,61 +3,154 @@ #include "card_database.h" #include "card_item.h" +#include #include CardList::CardList(bool _contentsKnown) : QList(), contentsKnown(_contentsKnown) { } -CardItem *CardList::findCard(const int id, const bool remove, int *position) +/** + * @brief Finds the CardItem with the given id in the list. + * If contentsKnown is false, then this just returns the first element of the list. + * + * @param cardId The id of the card to find. + * + * @returns A pointer to the CardItem, or a nullptr if not found. + */ +CardItem *CardList::findCard(const int cardId) const { - if (!contentsKnown) { - if (empty()) - return 0; - CardItem *temp = at(0); - if (remove) - removeAt(0); - if (position) - *position = id; - return temp; - } else - for (int i = 0; i < size(); i++) { - CardItem *temp = at(i); - if (temp->getId() == id) { - if (remove) - removeAt(i); - if (position) - *position = i; - return temp; + if (!contentsKnown && !empty()) { + return at(0); + } else { + for (auto *cardItem : *this) { + if (cardItem->getId() == cardId) { + return cardItem; } } - return 0; + } + return nullptr; } -class CardList::compareFunctor +/** + * @brief sorts the list by using string comparison on properties extracted from the CardItem + * The cards are compared using each property in order. + * If two cards have the same value for a property, then the next property in the list is used. + * + * @param option the option to compare the cards by, in order of usage. + */ +void CardList::sortBy(const QList &option) { -private: - int flags; - -public: - explicit compareFunctor(int _flags) : flags(_flags) - { + // early return if we know we won't be sorting + if (option.isEmpty()) { + return; } - inline bool operator()(CardItem *a, CardItem *b) const - { - if (flags & SortByType) { - QString t1 = a->getInfo() ? a->getInfo()->getMainCardType() : QString(); - QString t2 = b->getInfo() ? b->getInfo()->getMainCardType() : QString(); - if ((t1 == t2) && (flags & SortByName)) - return a->getName() < b->getName(); - return t1 < t2; - } else - return a->getName() < b->getName(); - } -}; -void CardList::sort(int flags) -{ - compareFunctor cf(flags); - std::sort(begin(), end(), cf); + auto comparator = [&option](CardItem *a, CardItem *b) { + for (auto prop : option) { + auto extractor = getExtractorFor(prop); + QString t1 = extractor(a); + QString t2 = extractor(b); + if (t1 != t2) { + return t1 < t2; + } + } + return false; + }; + + std::sort(begin(), end(), comparator); } + +/** + * Creates a String for the card such that when sorting cards using that string, it will result in the + * following sort order: + * - Unrecognized colors + * - Land cards + * - Colorless cards + * - Monocolor cards, in wubrg order + * - Monocolor cards of any custom colors + * - 2C cards (no internal order) + * - 3C cards (no internal order) + * - 4C cards (no internal order) + * - 5C cards (no internal order) + * + * @param c The card info + * @param appendAtEnd For multicolor cards, whether to also append the entire color string at the end. + */ +static QString getColorSortString(CardInfoPtr c, bool appendAtEnd) +{ + QString colors = c->getColors(); + switch (colors.size()) { + case 0: { + if (c->getCardType().contains("Land")) { + return "a_land"; + } else { + return "b_colorless"; + } + } + case 1: + // force wubrg order + switch (colors.at(0).toLatin1()) { + case 'W': + return "c_W"; + case 'U': + return "d_U"; + case 'B': + return "e_B"; + case 'R': + return "f_R"; + case 'G': + return "g_G"; + default: + // handle any custom colors + return QString("h_%1").arg(colors.at(0)); + } + default: + return QString("i%1_%2").arg(colors.size()).arg(appendAtEnd ? colors : ""); + } +} + +/** + * @brief returns the function that extracts the given property from the CardItem. + */ +std::function CardList::getExtractorFor(SortOption option) +{ + switch (option) { + case NoSort: + return [](CardItem *) { return ""; }; + case SortByMainType: + return [](CardItem *c) { return c->getInfo() ? c->getInfo()->getMainCardType() : ""; }; + case SortByManaValue: + // getCmc returns the int as a string. We pad with 0's so that string comp also works on it + return [](CardItem *c) { return c->getInfo() ? c->getInfo()->getCmc().rightJustified(4, '0') : ""; }; + case SortByColorGrouping: + return [](CardItem *c) { return c->getInfo() ? getColorSortString(c->getInfo(), false) : ""; }; + case SortByName: + return [](CardItem *c) { return c->getName(); }; + case SortByType: + return [](CardItem *c) { return c->getInfo() ? c->getInfo()->getCardType() : ""; }; + case SortByManaCost: + return [](CardItem *c) { + auto info = c->getInfo(); + if (!info) + return QString(""); + + // calculation copied from CardDatabaseModel. + // we pad the cmc and also append the mana cost to the end so same cmc cards still have a sort order + return QString("%1%2").arg(info->getCmc(), 4, QChar('0')).arg(info->getManaCost()); + }; + case SortByColors: + return [](CardItem *c) { return c->getInfo() ? getColorSortString(c->getInfo(), true) : ""; }; + case SortByPt: + // do the same padding trick as above + return [](CardItem *c) { return c->getInfo() ? c->getInfo()->getPowTough().rightJustified(10, '0') : ""; }; + case SortBySet: + return [](CardItem *c) { return c->getInfo() ? c->getInfo()->getSetsNames() : ""; }; + case SortByPrinting: + return [](CardItem *c) { return c->getProviderId(); }; + } + + // this line should never be reached + qCWarning(CardListLog) << "cardlist.cpp: Could not find extractor for SortOption" << option; + return [](CardItem *) { return ""; }; +} \ No newline at end of file diff --git a/cockatrice/src/game/cards/card_list.h b/cockatrice/src/game/cards/card_list.h index 1835ca96e..d719b9a7e 100644 --- a/cockatrice/src/game/cards/card_list.h +++ b/cockatrice/src/game/cards/card_list.h @@ -2,30 +2,48 @@ #define CARDLIST_H #include +#include + +inline Q_LOGGING_CATEGORY(CardListLog, "card_list"); class CardItem; class CardList : public QList { -private: - class compareFunctor; - protected: bool contentsKnown; public: - enum SortFlags + enum SortOption { - SortByName = 1, - SortByType = 2 + NoSort, + + // Options that are used by groupBy + // Should partition all cards into a reasonable number of buckets + SortByMainType, + SortByManaValue, + SortByColorGrouping, + + // Options that are used by sortBy + // We don't care about buckets; we want as many distinct values as possible. + SortByName, + SortByType, + SortByManaCost, + SortByColors, + SortByPt, + SortBySet, + SortByPrinting }; - CardList(bool _contentsKnown); - CardItem *findCard(const int id, const bool remove, int *position = NULL); + explicit CardList(bool _contentsKnown); + CardItem *findCard(const int cardId) const; bool getContentsKnown() const { return contentsKnown; } - void sort(int flags = SortByName); + + void sortBy(const QList &options); + + static std::function getExtractorFor(SortOption option); }; #endif diff --git a/cockatrice/src/deck/deck_view.cpp b/cockatrice/src/game/deckview/deck_view.cpp similarity index 95% rename from cockatrice/src/deck/deck_view.cpp rename to cockatrice/src/game/deckview/deck_view.cpp index 9b7d77c5c..41c6ed45a 100644 --- a/cockatrice/src/deck/deck_view.cpp +++ b/cockatrice/src/game/deckview/deck_view.cpp @@ -1,9 +1,8 @@ #include "deck_view.h" -#include "../client/ui/theme_manager.h" -#include "../game/cards/card_database.h" -#include "../main.h" -#include "../settings/cache_settings.h" +#include "../../client/ui/theme_manager.h" +#include "../../game/cards/card_database.h" +#include "../../settings/cache_settings.h" #include "decklist.h" #include @@ -75,6 +74,12 @@ DeckViewCard::DeckViewCard(QGraphicsItem *parent, : AbstractCardItem(parent, _name, _providerId, 0, -1), originZone(_originZone), dragItem(0) { setAcceptHoverEvents(true); + + connect(&SettingsCache::instance(), &SettingsCache::roundCardCornersChanged, this, [this](bool _roundCardCorners) { + Q_UNUSED(_roundCardCorners); + + update(); + }); } DeckViewCard::~DeckViewCard() @@ -92,7 +97,7 @@ void DeckViewCard::paint(QPainter *painter, const QStyleOptionGraphicsItem *opti pen.setJoinStyle(Qt::MiterJoin); pen.setColor(originZone == DECK_ZONE_MAIN ? Qt::green : Qt::red); painter->setPen(pen); - qreal cardRadius = 0.05 * (CARD_WIDTH - 3); + qreal cardRadius = SettingsCache::instance().getRoundCardCorners() ? 0.05 * (CARD_WIDTH - 3) : 0.0; painter->drawRoundedRect(QRectF(1.5, 1.5, CARD_WIDTH - 3., CARD_HEIGHT - 3.), cardRadius, cardRadius); painter->restore(); } @@ -179,7 +184,7 @@ void DeckViewCardContainer::paint(QPainter *painter, const QStyleOptionGraphicsI { qreal totalTextWidth = getCardTypeTextWidth(); - painter->fillRect(boundingRect(), themeManager->getTableBgBrush()); + painter->fillRect(boundingRect(), themeManager->getBgBrush(ThemeManager::Table)); painter->setPen(QColor(255, 255, 255, 100)); painter->drawLine(QPointF(0, separatorY), QPointF(width, separatorY)); @@ -496,9 +501,9 @@ DeckView::DeckView(QWidget *parent) : QGraphicsView(parent) setRenderHints(QPainter::TextAntialiasing | QPainter::Antialiasing /* | QPainter::SmoothPixmapTransform*/); setScene(deckViewScene); - connect(deckViewScene, SIGNAL(sceneRectChanged(const QRectF &)), this, SLOT(updateSceneRect(const QRectF &))); - connect(deckViewScene, SIGNAL(newCardAdded(AbstractCardItem *)), this, SIGNAL(newCardAdded(AbstractCardItem *))); - connect(deckViewScene, SIGNAL(sideboardPlanChanged()), this, SIGNAL(sideboardPlanChanged())); + connect(deckViewScene, &DeckViewScene::sceneRectChanged, this, &DeckView::updateSceneRect); + connect(deckViewScene, &DeckViewScene::newCardAdded, this, &DeckView::newCardAdded); + connect(deckViewScene, &DeckViewScene::sideboardPlanChanged, this, &DeckView::sideboardPlanChanged); } void DeckView::resizeEvent(QResizeEvent *event) @@ -518,7 +523,12 @@ void DeckView::setDeck(const DeckList &_deck) deckViewScene->setDeck(_deck); } +void DeckView::clearDeck() +{ + deckViewScene->clearContents(); +} + void DeckView::resetSideboardPlan() { deckViewScene->resetSideboardPlan(); -} \ No newline at end of file +} diff --git a/cockatrice/src/deck/deck_view.h b/cockatrice/src/game/deckview/deck_view.h similarity index 75% rename from cockatrice/src/deck/deck_view.h rename to cockatrice/src/game/deckview/deck_view.h index 88bbfdf39..777298714 100644 --- a/cockatrice/src/deck/deck_view.h +++ b/cockatrice/src/game/deckview/deck_view.h @@ -1,7 +1,7 @@ #ifndef DECKVIEW_H #define DECKVIEW_H -#include "../game/cards/abstract_card_drag_item.h" +#include "../../game/cards/abstract_card_drag_item.h" #include "pb/move_card_to_zone.pb.h" #include @@ -24,20 +24,20 @@ private: DeckViewCardDragItem *dragItem; public: - DeckViewCard(QGraphicsItem *parent = nullptr, - const QString &_name = QString(), - const QString &_providerId = QString(), - const QString &_originZone = QString()); - ~DeckViewCard(); - void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget); + explicit DeckViewCard(QGraphicsItem *parent = nullptr, + const QString &_name = QString(), + const QString &_providerId = QString(), + const QString &_originZone = QString()); + ~DeckViewCard() override; + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override; const QString &getOriginZone() const { return originZone; } protected: - void mouseMoveEvent(QGraphicsSceneMouseEvent *event); - void hoverEnterEvent(QGraphicsSceneHoverEvent *event); + void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override; + void hoverEnterEvent(QGraphicsSceneHoverEvent *event) override; }; class DeckViewCardDragItem : public AbstractCardDragItem @@ -48,10 +48,10 @@ private: public: DeckViewCardDragItem(DeckViewCard *_item, const QPointF &_hotSpot, AbstractCardDragItem *parentDrag = 0); - void updatePosition(const QPointF &cursorScenePos); + void updatePosition(const QPointF &cursorScenePos) override; protected: - void mouseReleaseEvent(QGraphicsSceneMouseEvent *event); + void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override; }; class DeckViewCardContainer : public QGraphicsItem @@ -73,13 +73,13 @@ public: { Type = typeDeckViewCardContainer }; - int type() const + int type() const override { return Type; } - DeckViewCardContainer(const QString &_name); - QRectF boundingRect() const; - void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget); + explicit DeckViewCardContainer(const QString &_name); + QRectF boundingRect() const override; + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override; void addCard(DeckViewCard *card); void removeCard(DeckViewCard *card); const QList &getCards() const @@ -109,12 +109,11 @@ private: DeckList *deck; QMap cardContainers; qreal optimalAspectRatio; - void clearContents(); void rebuildTree(); public: - DeckViewScene(QObject *parent = nullptr); - ~DeckViewScene(); + explicit DeckViewScene(QObject *parent = nullptr); + ~DeckViewScene() override; void setLocked(bool _locked) { locked = _locked; @@ -123,6 +122,7 @@ public: { return locked; } + void clearContents(); void setDeck(const DeckList &_deck); void setOptimalAspectRatio(qreal _optimalAspectRatio) { @@ -142,7 +142,7 @@ private: DeckViewScene *deckViewScene; protected: - void resizeEvent(QResizeEvent *event); + void resizeEvent(QResizeEvent *event) override; public slots: void updateSceneRect(const QRectF &rect); signals: @@ -150,8 +150,9 @@ signals: void sideboardPlanChanged(); public: - DeckView(QWidget *parent = nullptr); + explicit DeckView(QWidget *parent = nullptr); void setDeck(const DeckList &_deck); + void clearDeck(); void setLocked(bool _locked) { deckViewScene->setLocked(_locked); @@ -160,7 +161,7 @@ public: { return deckViewScene->getSideboardPlan(); } - void mouseDoubleClickEvent(QMouseEvent *event); + void mouseDoubleClickEvent(QMouseEvent *event) override; void resetSideboardPlan(); }; diff --git a/cockatrice/src/game/deckview/deck_view_container.cpp b/cockatrice/src/game/deckview/deck_view_container.cpp new file mode 100644 index 000000000..f7ec617ee --- /dev/null +++ b/cockatrice/src/game/deckview/deck_view_container.cpp @@ -0,0 +1,379 @@ +#include "deck_view_container.h" + +#include "../../client/tabs/tab_game.h" +#include "../../client/ui/picture_loader/picture_loader.h" +#include "../../deck/deck_loader.h" +#include "../../dialogs/dlg_load_deck.h" +#include "../../dialogs/dlg_load_remote_deck.h" +#include "../../server/pending_command.h" +#include "../../settings/cache_settings.h" +#include "../cards/card_database.h" +#include "../cards/card_database_manager.h" +#include "../game_scene.h" +#include "deck_view.h" +#include "pb/command_deck_select.pb.h" +#include "pb/command_ready_start.pb.h" +#include "pb/command_set_sideboard_lock.pb.h" +#include "pb/command_set_sideboard_plan.pb.h" +#include "pb/response_deck_download.pb.h" +#include "trice_limits.h" + +#include +#include +#include +#include + +ToggleButton::ToggleButton(QWidget *parent) : QPushButton(parent), state(false) +{ +} + +void ToggleButton::paintEvent(QPaintEvent *event) +{ + QPushButton::paintEvent(event); + + QPainter painter(this); + QPen pen; + pen.setWidth(3); + pen.setJoinStyle(Qt::MiterJoin); + pen.setColor(state ? Qt::green : Qt::red); + painter.setPen(pen); + painter.drawRect(QRect(1, 1, width() - 3, height() - 3)); +} + +void ToggleButton::setState(bool _state) +{ + state = _state; + emit stateChanged(); + update(); +} + +DeckViewContainer::DeckViewContainer(int _playerId, TabGame *parent) + : QWidget(nullptr), visualDeckStorageWidget(nullptr), parentGame(parent), playerId(_playerId) +{ + loadLocalButton = new QPushButton; + loadRemoteButton = new QPushButton; + unloadDeckButton = new QPushButton; + readyStartButton = new ToggleButton; + forceStartGameButton = new QPushButton; + sideboardLockButton = new ToggleButton; + + connect(loadLocalButton, SIGNAL(clicked()), this, SLOT(loadLocalDeck())); + connect(readyStartButton, SIGNAL(clicked()), this, SLOT(readyStart())); + connect(unloadDeckButton, &QPushButton::clicked, this, &DeckViewContainer::unloadDeck); + connect(forceStartGameButton, &QPushButton::clicked, this, &DeckViewContainer::forceStart); + connect(sideboardLockButton, SIGNAL(clicked()), this, SLOT(sideboardLockButtonClicked())); + connect(sideboardLockButton, SIGNAL(stateChanged()), this, SLOT(updateSideboardLockButtonText())); + + if (parentGame->getIsLocalGame()) { + loadRemoteButton->setVisible(false); + loadRemoteButton->setEnabled(false); + } else { + connect(loadRemoteButton, SIGNAL(clicked()), this, SLOT(loadRemoteDeck())); + } + + auto *buttonHBox = new QHBoxLayout; + buttonHBox->addWidget(loadLocalButton); + buttonHBox->addWidget(loadRemoteButton); + buttonHBox->addWidget(unloadDeckButton); + buttonHBox->addWidget(readyStartButton); + buttonHBox->addWidget(sideboardLockButton); + buttonHBox->addWidget(forceStartGameButton); + + buttonHBox->setContentsMargins(11, 0, 11, 0); + buttonHBox->addStretch(); + + deckView = new DeckView; + connect(deckView, SIGNAL(newCardAdded(AbstractCardItem *)), this, SIGNAL(newCardAdded(AbstractCardItem *))); + connect(deckView, SIGNAL(sideboardPlanChanged()), this, SLOT(sideboardPlanChanged())); + + deckViewLayout = new QVBoxLayout; + deckViewLayout->addLayout(buttonHBox); + deckViewLayout->addWidget(deckView); + deckViewLayout->setContentsMargins(0, 0, 0, 0); + setLayout(deckViewLayout); + + retranslateUi(); + connect(&SettingsCache::instance().shortcuts(), SIGNAL(shortCutChanged()), this, SLOT(refreshShortcuts())); + refreshShortcuts(); + + connect(&SettingsCache::instance(), &SettingsCache::visualDeckStorageInGameChanged, this, + &DeckViewContainer::setVisualDeckStorageExists); + + switchToDeckSelectView(); +} + +/** + * Creates the VDS widget and inserts it into the layout. No-ops if the widget already exists + */ +void DeckViewContainer::tryCreateVisualDeckStorageWidget() +{ + if (visualDeckStorageWidget) { + return; + } + + visualDeckStorageWidget = new VisualDeckStorageWidget(this); + connect(visualDeckStorageWidget, &VisualDeckStorageWidget::deckLoadRequested, this, + &DeckViewContainer::loadDeckFromFile); + connect(visualDeckStorageWidget, &VisualDeckStorageWidget::openDeckEditor, parentGame, &TabGame::openDeckEditor); + + deckViewLayout->addWidget(visualDeckStorageWidget); +} + +void DeckViewContainer::retranslateUi() +{ + loadLocalButton->setText(tr("Load deck...")); + loadRemoteButton->setText(tr("Load remote deck...")); + unloadDeckButton->setText(tr("Unload deck")); + readyStartButton->setText(tr("Ready to start")); + forceStartGameButton->setText(tr("Force start")); + updateSideboardLockButtonText(); +} + +static void setVisibility(QPushButton *button, bool visible) +{ + button->setHidden(!visible); + button->setEnabled(visible); +} + +void DeckViewContainer::switchToDeckSelectView() +{ + if (SettingsCache::instance().getVisualDeckStorageInGame()) { + deckView->setHidden(true); + + tryCreateVisualDeckStorageWidget(); + visualDeckStorageWidget->setHidden(false); + } else { + deckView->setHidden(false); + if (visualDeckStorageWidget) { + visualDeckStorageWidget->setHidden(true); + } + } + + deckViewLayout->update(); + + setVisibility(loadLocalButton, true); + setVisibility(loadRemoteButton, !parentGame->getIsLocalGame()); + setVisibility(unloadDeckButton, false); + setVisibility(readyStartButton, false); + setVisibility(sideboardLockButton, false); + setVisibility(forceStartGameButton, false); + + readyStartButton->setState(false); + sideboardLockButton->setState(false); + + deckView->setLocked(true); + + sendReadyStartCommand(false); +} + +void DeckViewContainer::switchToDeckLoadedView() +{ + deckView->setHidden(false); + if (visualDeckStorageWidget) { + visualDeckStorageWidget->setHidden(true); + } + + deckViewLayout->update(); + + setVisibility(loadLocalButton, false); + setVisibility(loadRemoteButton, false); + setVisibility(unloadDeckButton, true); + setVisibility(readyStartButton, true); + setVisibility(sideboardLockButton, true); + + if (parentGame->isHost()) { + setVisibility(forceStartGameButton, true); + } +} + +void DeckViewContainer::updateSideboardLockButtonText() +{ + if (sideboardLockButton->getState()) { + sideboardLockButton->setText(tr("Sideboard unlocked")); + } else { + sideboardLockButton->setText(tr("Sideboard locked")); + } + // setting text on a button removes its shortcut + ShortcutsSettings &shortcuts = SettingsCache::instance().shortcuts(); + sideboardLockButton->setShortcut(shortcuts.getSingleShortcut("DeckViewContainer/sideboardLockButton")); +} + +void DeckViewContainer::refreshShortcuts() +{ + ShortcutsSettings &shortcuts = SettingsCache::instance().shortcuts(); + loadLocalButton->setShortcut(shortcuts.getSingleShortcut("DeckViewContainer/loadLocalButton")); + loadRemoteButton->setShortcut(shortcuts.getSingleShortcut("DeckViewContainer/loadRemoteButton")); + readyStartButton->setShortcut(shortcuts.getSingleShortcut("DeckViewContainer/readyStartButton")); + sideboardLockButton->setShortcut(shortcuts.getSingleShortcut("DeckViewContainer/sideboardLockButton")); +} + +/** + * Updates the existence of the embedded Visual Deck Storage, destroying or creating it if needed. + * Note that this change is temporary; the VDS may get recreated when the view transitions to the deck select state, + * depending on current settings. + */ +void DeckViewContainer::setVisualDeckStorageExists(bool exists) +{ + if (exists) { + // view mode state isn't stored in a field, so we determine state by checking the button + if (loadLocalButton->isEnabled()) { + // We only need to handle the setting changing while in deck select state; tryCreate already gets called + // when switching from deck loaded to deck select state + tryCreateVisualDeckStorageWidget(); + visualDeckStorageWidget->setHidden(false); + deckView->setHidden(true); + } + } else { + if (visualDeckStorageWidget) { + visualDeckStorageWidget->deleteLater(); + visualDeckStorageWidget = nullptr; + } + deckView->setHidden(false); + } + + deckViewLayout->update(); +} + +void DeckViewContainer::unloadDeck() +{ + deckView->clearDeck(); + switchToDeckSelectView(); +} + +void DeckViewContainer::loadLocalDeck() +{ + DlgLoadDeck dialog(this); + if (!dialog.exec()) + return; + + loadDeckFromFile(dialog.selectedFiles().at(0)); +} + +void DeckViewContainer::loadDeckFromFile(const QString &filePath) +{ + DeckLoader::FileFormat fmt = DeckLoader::getFormatFromName(filePath); + QString deckString; + DeckLoader deck; + + bool error = !deck.loadFromFile(filePath, fmt, true); + if (!error) { + deckString = deck.writeToString_Native(); + error = deckString.length() > MAX_FILE_LENGTH; + } + if (error) { + QMessageBox::critical(this, tr("Error"), tr("The selected file could not be loaded.")); + return; + } + + Command_DeckSelect cmd; + cmd.set_deck(deckString.toStdString()); + PendingCommand *pend = parentGame->prepareGameCommand(cmd); + connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this, + SLOT(deckSelectFinished(const Response &))); + parentGame->sendGameCommand(pend, playerId); +} + +void DeckViewContainer::loadRemoteDeck() +{ + DlgLoadRemoteDeck dlg(parentGame->getClientForPlayer(playerId), this); + if (dlg.exec()) { + Command_DeckSelect cmd; + cmd.set_deck_id(dlg.getDeckId()); + PendingCommand *pend = parentGame->prepareGameCommand(cmd); + connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this, + SLOT(deckSelectFinished(const Response &))); + parentGame->sendGameCommand(pend, playerId); + } +} + +void DeckViewContainer::deckSelectFinished(const Response &r) +{ + const Response_DeckDownload &resp = r.GetExtension(Response_DeckDownload::ext); + DeckLoader newDeck(QString::fromStdString(resp.deck())); + PictureLoader::cacheCardPixmaps( + CardDatabaseManager::getInstance()->getCardsByNameAndProviderId(newDeck.getCardListWithProviderId())); + setDeck(newDeck); + switchToDeckLoadedView(); +} + +void DeckViewContainer::readyStart() +{ + sendReadyStartCommand(!readyStartButton->getState()); +} + +void DeckViewContainer::forceStart() +{ + const auto msg = tr("Are you sure you want to force start?\nThis will kick all non-ready players from the game."); + const auto res = + QMessageBox::warning(this, tr("Cockatrice"), msg, QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes); + if (res == QMessageBox::No) { + return; + } + + Command_ReadyStart cmd; + cmd.set_force_start(true); + cmd.set_ready(true); + parentGame->sendGameCommand(cmd, playerId); +} + +void DeckViewContainer::sideboardLockButtonClicked() +{ + Command_SetSideboardLock cmd; + cmd.set_locked(sideboardLockButton->getState()); + + parentGame->sendGameCommand(cmd, playerId); +} + +void DeckViewContainer::sideboardPlanChanged() +{ + Command_SetSideboardPlan cmd; + const QList &newPlan = deckView->getSideboardPlan(); + for (const auto &i : newPlan) + cmd.add_move_list()->CopyFrom(i); + parentGame->sendGameCommand(cmd, playerId); +} + +/** + * Sends the basic ReadyStart command. + */ +void DeckViewContainer::sendReadyStartCommand(bool ready) +{ + Command_ReadyStart cmd; + cmd.set_ready(ready); + parentGame->sendGameCommand(cmd, playerId); +} + +/** + * Updates the buttons to make the client-side ready state match the given state. + * + * Notably, this method only updates the client and *does not* send a ReadyStart command to the server. + * This method is intended to be called upon receiving the response from a ReadyStart command. + */ +void DeckViewContainer::setReadyStart(bool ready) +{ + readyStartButton->setState(ready); + deckView->setLocked(ready || !sideboardLockButton->getState()); + sideboardLockButton->setEnabled(!readyStartButton->getState() && readyStartButton->isEnabled()); +} + +/** + * Sends a ReadyStart command with ready=true to the server + */ +void DeckViewContainer::readyAndUpdate() +{ + sendReadyStartCommand(true); +} + +void DeckViewContainer::setSideboardLocked(bool locked) +{ + sideboardLockButton->setState(!locked); + deckView->setLocked(readyStartButton->getState() || !sideboardLockButton->getState()); + if (locked) + deckView->resetSideboardPlan(); +} + +void DeckViewContainer::setDeck(const DeckLoader &deck) +{ + deckView->setDeck(deck); + switchToDeckLoadedView(); +} \ No newline at end of file diff --git a/cockatrice/src/game/deckview/deck_view_container.h b/cockatrice/src/game/deckview/deck_view_container.h new file mode 100644 index 000000000..c841c5934 --- /dev/null +++ b/cockatrice/src/game/deckview/deck_view_container.h @@ -0,0 +1,86 @@ +#ifndef DECK_VIEW_CONTAINER_H +#define DECK_VIEW_CONTAINER_H + +#include "../../deck/deck_loader.h" + +#include + +class QVBoxLayout; +class AbstractCardItem; +class VisualDeckStorageWidget; +class DeckPreviewWidget; +class Response; +class TabGame; +class DeckView; + +/** + * Custom QButton implementation in order to have the red/green toggling square around the button + */ +class ToggleButton : public QPushButton +{ + Q_OBJECT +private: + bool state; +signals: + void stateChanged(); + +public: + explicit ToggleButton(QWidget *parent = nullptr); + bool getState() const + { + return state; + } + void setState(bool _state); + +protected: + void paintEvent(QPaintEvent *event) override; +}; + +/** + * This widget contains the deck selection view that is used before a game begins. + */ +class DeckViewContainer : public QWidget +{ + Q_OBJECT +private: + QVBoxLayout *deckViewLayout; + QPushButton *loadLocalButton, *loadRemoteButton, *unloadDeckButton, *forceStartGameButton; + ToggleButton *readyStartButton, *sideboardLockButton; + DeckView *deckView; + VisualDeckStorageWidget *visualDeckStorageWidget; + TabGame *parentGame; + int playerId; + + void tryCreateVisualDeckStorageWidget(); + void sendReadyStartCommand(bool ready); +private slots: + void switchToDeckSelectView(); + void switchToDeckLoadedView(); + void loadLocalDeck(); + void loadRemoteDeck(); + void unloadDeck(); + void readyStart(); + void forceStart(); + void deckSelectFinished(const Response &r); + void sideboardPlanChanged(); + void sideboardLockButtonClicked(); + void updateSideboardLockButtonText(); + void refreshShortcuts(); +signals: + void newCardAdded(AbstractCardItem *card); + void notIdle(); + +public: + DeckViewContainer(int _playerId, TabGame *parent); + void retranslateUi(); + void setReadyStart(bool ready); + void readyAndUpdate(); + void setSideboardLocked(bool locked); + void setDeck(const DeckLoader &deck); + void setVisualDeckStorageExists(bool exists); + +public slots: + void loadDeckFromFile(const QString &filePath); +}; + +#endif // DECK_VIEW_CONTAINER_H diff --git a/cockatrice/src/game/filters/filter_builder.cpp b/cockatrice/src/game/filters/filter_builder.cpp index fafb8a910..461da6841 100644 --- a/cockatrice/src/game/filters/filter_builder.cpp +++ b/cockatrice/src/game/filters/filter_builder.cpp @@ -40,9 +40,9 @@ FilterBuilder::FilterBuilder(QWidget *parent) : QWidget(parent) setLayout(layout); - connect(filterCombo, SIGNAL(activated(int)), edit, SLOT(setFocus())); - connect(edit, SIGNAL(returnPressed()), this, SLOT(emit_add())); - connect(ok, SIGNAL(released()), this, SLOT(emit_add())); + connect(filterCombo, qOverload(&QComboBox::activated), edit, [this](int) { setFocus(); }); + connect(edit, &LineEditUnfocusable::returnPressed, this, &FilterBuilder::emit_add); + connect(ok, &QPushButton::released, this, &FilterBuilder::emit_add); fltr = NULL; } diff --git a/cockatrice/src/game/filters/filter_builder.h b/cockatrice/src/game/filters/filter_builder.h index be048836d..45ae24839 100644 --- a/cockatrice/src/game/filters/filter_builder.h +++ b/cockatrice/src/game/filters/filter_builder.h @@ -21,8 +21,8 @@ private: void destroyFilter(); public: - FilterBuilder(QWidget *parent = nullptr); - ~FilterBuilder(); + explicit FilterBuilder(QWidget *parent = nullptr); + ~FilterBuilder() override; signals: void add(const CardFilter *f); diff --git a/cockatrice/src/game/filters/filter_string.cpp b/cockatrice/src/game/filters/filter_string.cpp index 7705ef8b0..7edfb0d41 100644 --- a/cockatrice/src/game/filters/filter_string.cpp +++ b/cockatrice/src/game/filters/filter_string.cpp @@ -26,7 +26,9 @@ OracleQuery <- 'o' [:] RegexString CMCQuery <- ('cmc'/'mv') ws? NumericExpression PowerQuery <- [Pp] 'ow' 'er'? ws? NumericExpression ToughnessQuery <- [Tt] 'ou' 'ghness'? ws? NumericExpression -RarityQuery <- [rR] ':' RegexString + +RarityQuery <- [rR] ':' Rarity +Rarity <- [Cc] 'ommon'? / [Uu] 'ncommon'? / [Rr] 'are'? / [Mm] 'ythic'? / [Ss] 'pecial'? / [a-zA-Z] [a-z]* FormatQuery <- 'f' ':' Format / Legality ':' Format Format <- [a-zA-Z] [a-z]* @@ -42,10 +44,11 @@ ColorQuery <- [cC] 'olor'? <[iI]?> <[:!]> ColorEx* FieldQuery <- String [:] RegexString / String ws? NumericExpression -NonDoubleQuoteUnlessEscaped <- !["]. / '\"'. -NonSingleQuoteUnlessEscaped <- ![']. / "\'". +NonDoubleQuoteUnlessEscaped <- '\\\"'. / !["]. +NonSingleQuoteUnlessEscaped <- "\\\'". / ![']. UnescapedStringListPart <- !['":<>=! ]. -String <- UnescapedStringListPart+ / ["] ["] / ['] ['] +SingleApostropheString <- (UnescapedStringListPart+ ws*)* ['] (UnescapedStringListPart+ ws*)* +String <- SingleApostropheString / UnescapedStringListPart+ / ["] ["] / ['] ['] StringValue <- String / [(] StringList [)] StringList <- StringListString (ws? [,] ws? StringListString)* StringListString <- UnescapedStringListPart+ @@ -58,76 +61,92 @@ CompactStringSet <- StringListString ([,+] StringListString)+ NumericExpression <- NumericOperator ws? NumericValue NumericOperator <- [=:] / <[> NumericValue <- [0-9]+ - )"); std::once_flag init; static void setupParserRules() { - auto passthru = [](const peg::SemanticValues &sv) -> Filter { return !sv.empty() ? sv[0].get() : nullptr; }; + auto passthru = [](const peg::SemanticValues &sv) -> Filter { + return !sv.empty() ? std::any_cast(sv[0]) : nullptr; + }; search["Start"] = passthru; search["QueryPartList"] = [](const peg::SemanticValues &sv) -> Filter { - return [=](CardData x) { - for (int i = 0; i < static_cast(sv.size()); ++i) { - if (!sv[i].get()(x)) - return false; - } - return true; + return [=](const CardData &x) { + auto matchesFilter = [&x](const std::any &query) { return std::any_cast(query)(x); }; + return std::all_of(sv.begin(), sv.end(), matchesFilter); }; }; search["ComplexQueryPart"] = [](const peg::SemanticValues &sv) -> Filter { - return [=](CardData x) { - for (int i = 0; i < static_cast(sv.size()); ++i) { - if (sv[i].get()(x)) - return true; - } - return false; + return [=](const CardData &x) { + auto matchesFilter = [&x](const std::any &query) { return std::any_cast(query)(x); }; + return std::any_of(sv.begin(), sv.end(), matchesFilter); }; }; search["SomewhatComplexQueryPart"] = passthru; search["QueryPart"] = passthru; search["NotQuery"] = [](const peg::SemanticValues &sv) -> Filter { - Filter dependent = sv[0].get(); - return [=](CardData x) -> bool { return !dependent(x); }; + const auto dependent = std::any_cast(sv[0]); + return [=](const CardData &x) -> bool { return !dependent(x); }; }; search["TypeQuery"] = [](const peg::SemanticValues &sv) -> Filter { - StringMatcher matcher = sv[0].get(); - return [=](CardData x) -> bool { return matcher(x->getCardType()); }; + const auto matcher = std::any_cast(sv[0]); + return [=](const CardData &x) -> bool { return matcher(x->getCardType()); }; }; search["SetQuery"] = [](const peg::SemanticValues &sv) -> Filter { - StringMatcher matcher = sv[0].get(); - return [=](CardData x) -> bool { - for (const auto &set : x->getSets().keys()) { - if (matcher(set)) - return true; - } - return false; + auto matcher = std::any_cast(sv[0]); + return [=](const CardData &x) -> bool { + QList sets = x->getSets().keys(); + + auto matchesSet = [&matcher](const QString &set) { return matcher(set); }; + return std::any_of(sets.begin(), sets.end(), matchesSet); }; }; - search["RarityQuery"] = [](const peg::SemanticValues &sv) -> Filter { - StringMatcher matcher = sv[0].get(); - return [=](CardData x) -> bool { - for (const auto &set : x->getSets().values()) { - if (matcher(set.getProperty("rarity"))) - return true; - } - return false; - }; - }; - search["FormatQuery"] = [](const peg::SemanticValues &sv) -> Filter { - if (sv.choice() == 0) { - QString format = sv[0].get(); - return [=](CardData x) -> bool { return x->getProperty(QString("format-%1").arg(format)) == "legal"; }; - } else { - QString format = sv[1].get(); - QString legality = sv[0].get(); - return [=](CardData x) -> bool { return x->getProperty(QString("format-%1").arg(format)) == legality; }; + search["Rarity"] = [](const peg::SemanticValues &sv) -> QString { + switch (tolower(std::string(sv.sv())[0])) { + case 'c': + return "common"; + case 'u': + return "uncommon"; + case 'r': + return "rare"; + case 'm': + return "mythic"; + case 's': + return "special"; + default: + return QString::fromStdString(std::string(sv.sv())); } }; + search["RarityQuery"] = [](const peg::SemanticValues &sv) -> Filter { + const auto rarity = std::any_cast(sv[0]); + return [=](const CardData &x) -> bool { + QList infos; + for (const auto &setsValue : x->getSets().values()) { + for (const auto &cardInfoPerSet : setsValue) { + infos.append(cardInfoPerSet); + } + } + + auto matchesRarity = [&rarity](const CardInfoPerSet &info) { return rarity == info.getProperty("rarity"); }; + return std::any_of(infos.begin(), infos.end(), matchesRarity); + }; + }; + + search["FormatQuery"] = [](const peg::SemanticValues &sv) -> Filter { + if (sv.choice() == 0) { + const auto format = std::any_cast(sv[0]); + return + [=](const CardData &x) -> bool { return x->getProperty(QString("format-%1").arg(format)) == "legal"; }; + } + + const auto format = std::any_cast(sv[1]); + const auto legality = std::any_cast(sv[0]); + return [=](const CardData &x) -> bool { return x->getProperty(QString("format-%1").arg(format)) == legality; }; + }; search["Legality"] = [](const peg::SemanticValues &sv) -> QString { - switch (tolower(sv.str()[0])) { + switch (tolower(std::string(sv.sv())[0])) { case 'l': return "legal"; case 'b': @@ -140,8 +159,8 @@ static void setupParserRules() }; search["Format"] = [](const peg::SemanticValues &sv) -> QString { - if (sv.length() == 1) { - switch (tolower(sv.str()[0])) { + if (sv.size() == 1) { + switch (tolower(std::string(sv.sv())[0])) { case 'm': return "modern"; case 's': @@ -157,193 +176,191 @@ static void setupParserRules() default: return ""; } - } else { - return QString::fromStdString(sv.str()).toLower(); } + + return QString::fromStdString(std::string(sv.sv())).toLower(); }; search["StringValue"] = [](const peg::SemanticValues &sv) -> StringMatcher { if (sv.choice() == 0) { - auto target = sv[0].get(); + const auto target = std::any_cast(sv[0]); return [=](const QString &s) { return s.split(" ").contains(target, Qt::CaseInsensitive); }; - } else { - auto target = sv[0].get(); - return [=](const QString &s) { - for (const QString &str : target) { - if (s.split(" ").contains(str, Qt::CaseInsensitive)) { - return true; - } - } - return false; - }; } + + const auto target = std::any_cast(sv[0]); + return [=](const QString &s) { + auto containsString = [&s](const QString &str) { return s.split(" ").contains(str, Qt::CaseInsensitive); }; + return std::any_of(target.begin(), target.end(), containsString); + }; }; search["String"] = [](const peg::SemanticValues &sv) -> QString { if (sv.choice() == 0) { - return QString::fromStdString(sv.str()); - } else { - return QString::fromStdString(sv.token(0)); + return QString::fromStdString(std::string(sv.sv())); } + + return QString::fromStdString(std::string(sv.token(0))); }; search["FlexStringValue"] = [](const peg::SemanticValues &sv) -> StringMatcher { if (sv.choice() != 1) { - auto target = sv[0].get(); + const auto target = std::any_cast(sv[0]); return [=](const QString &s) { - for (const QString &str : target) { - if (s.split(" ").contains(str, Qt::CaseInsensitive)) { - return true; - } - } - return false; + auto containsString = [&s](const QString &str) { + return s.split(" ").contains(str, Qt::CaseInsensitive); + }; + return std::any_of(target.begin(), target.end(), containsString); }; - } else { - auto target = sv[0].get(); - return [=](const QString &s) { return s.split(" ").contains(target, Qt::CaseInsensitive); }; } + + const auto target = std::any_cast(sv[0]); + return [=](const QString &s) { return s.split(" ").contains(target, Qt::CaseInsensitive); }; }; - search["CompactStringSet"] = search["StringList"] = [](const peg::SemanticValues &sv) -> QStringList { + search["CompactStringSet"] = [](const peg::SemanticValues &sv) -> QStringList { QStringList result; - for (int i = 0; i < static_cast(sv.size()); ++i) { - result.append(sv[i].get()); + for (const auto &i : sv) { + result.append(std::any_cast(i)); + } + return result; + }; + search["StringList"] = [](const peg::SemanticValues &sv) -> QStringList { + QStringList result; + for (const auto &i : sv) { + result.append(std::any_cast(i)); } return result; }; search["StringListString"] = [](const peg::SemanticValues &sv) -> QString { - return QString::fromStdString(sv.str()); + return QString::fromStdString(std::string(sv.sv())); }; search["NumericExpression"] = [](const peg::SemanticValues &sv) -> NumberMatcher { - auto arg = sv[1].get(); - auto op = sv[0].get(); + const auto arg = std::any_cast(sv[1]); + const auto op = std::any_cast(sv[0]); if (op == ">") - return [=](int s) { return s > arg; }; + return [=](const int s) { return s > arg; }; if (op == ">=") - return [=](int s) { return s >= arg; }; + return [=](const int s) { return s >= arg; }; if (op == "<") - return [=](int s) { return s < arg; }; + return [=](const int s) { return s < arg; }; if (op == "<=") - return [=](int s) { return s <= arg; }; + return [=](const int s) { return s <= arg; }; if (op == "=") - return [=](int s) { return s == arg; }; + return [=](const int s) { return s == arg; }; if (op == ":") - return [=](int s) { return s == arg; }; + return [=](const int s) { return s == arg; }; if (op == "!=") - return [=](int s) { return s != arg; }; + return [=](const int s) { return s != arg; }; return [](int) { return false; }; }; search["NumericValue"] = [](const peg::SemanticValues &sv) -> int { - return QString::fromStdString(sv.str()).toInt(); + return QString::fromStdString(std::string(sv.sv())).toInt(); }; search["NumericOperator"] = [](const peg::SemanticValues &sv) -> QString { - return QString::fromStdString(sv.str()); + return QString::fromStdString(std::string(sv.sv())); }; search["RegexString"] = [](const peg::SemanticValues &sv) -> StringMatcher { - auto target = sv[0].get(); + auto target = std::any_cast(sv[0]); return [=](const QString &s) { auto sanitizedTarget = QString(target); sanitizedTarget.replace("\\\"", "\""); sanitizedTarget.replace("\\'", "'"); - return s.QString::contains(sanitizedTarget, Qt::CaseInsensitive); + return s.contains(sanitizedTarget, Qt::CaseInsensitive); }; }; search["OracleQuery"] = [](const peg::SemanticValues &sv) -> Filter { - StringMatcher matcher = sv[0].get(); - return [=](CardData x) { return matcher(x->getText()); }; + const auto matcher = std::any_cast(sv[0]); + return [=](const CardData &x) { return matcher(x->getText()); }; }; search["ColorQuery"] = [](const peg::SemanticValues &sv) -> Filter { QString parts; - for (int i = 0; i < static_cast(sv.size()); ++i) { - parts += sv[i].get(); + for (const auto &i : sv) { + parts += std::any_cast(i); } - bool idenity = sv.tokens[0].first[0] != 'i'; - if (sv.tokens[1].first[0] == ':') { - return [=](CardData x) { - QString match = idenity ? x->getColors() : x->getProperty("coloridentity"); + const bool identity = sv.tokens[0].empty() || sv.tokens[0][0] != 'i'; + if (sv.tokens[1][0] == ':') { + return [=](const CardData &x) { + QString match = identity ? x->getColors() : x->getProperty("coloridentity"); if (parts.contains("m") && match.length() < 2) { return false; - } else if (parts == "m") { + } + if (parts == "m") { return true; } if (parts.contains("c") && match.length() == 0) return true; - for (const auto &i : match) { - if (parts.contains(i)) - return true; - } - return false; - }; - } else { - return [=](CardData x) { - QString match = idenity ? x->getColors() : x->getProperty("colorIdentity"); - if (parts.contains("m") && match.length() < 2) - return false; - - if (parts.contains("c") && match.length() != 0) - return false; - - for (const auto &part : parts) { - if (!match.contains(part)) - return false; - } - - for (const auto &i : match) { - if (!parts.contains(i)) - return false; - } - return true; + auto containsColor = [&parts](const QString &s) { return parts.contains(s); }; + return std::any_of(match.begin(), match.end(), containsColor); }; } + + return [=](const CardData &x) { + QString match = identity ? x->getColors() : x->getProperty("colorIdentity"); + if (parts.contains("m") && match.length() < 2) { + return false; + } + + if (parts.contains("c") && match.length() != 0) { + return false; + } + + for (const auto &part : parts) { + if (!match.contains(part)) { + return false; + } + } + + auto containsColor = [&parts](const QString &s) { return parts.contains(s); }; + return std::all_of(match.begin(), match.end(), containsColor); + }; }; search["CMCQuery"] = [](const peg::SemanticValues &sv) -> Filter { - NumberMatcher matcher = sv[0].get(); - return [=](CardData x) -> bool { return matcher(x->getProperty("cmc").toInt()); }; + const auto matcher = std::any_cast(sv[0]); + return [=](const CardData &x) -> bool { return matcher(x->getProperty("cmc").toInt()); }; }; search["PowerQuery"] = [](const peg::SemanticValues &sv) -> Filter { - NumberMatcher matcher = sv[0].get(); - return [=](CardData x) -> bool { return matcher(x->getPowTough().split("/")[0].toInt()); }; + const auto matcher = std::any_cast(sv[0]); + return [=](const CardData &x) -> bool { return matcher(x->getPowTough().split("/")[0].toInt()); }; }; search["ToughnessQuery"] = [](const peg::SemanticValues &sv) -> Filter { - NumberMatcher matcher = sv[0].get(); - return [=](CardData x) -> bool { + const auto matcher = std::any_cast(sv[0]); + return [=](const CardData &x) -> bool { auto parts = x->getPowTough().split("/"); return matcher(parts.length() == 2 ? parts[1].toInt() : 0); }; }; search["FieldQuery"] = [](const peg::SemanticValues &sv) -> Filter { - QString field = sv[0].get(); + const auto field = std::any_cast(sv[0]); if (sv.choice() == 0) { - StringMatcher matcher = sv[1].get(); - return [=](CardData x) -> bool { return x->hasProperty(field) ? matcher(x->getProperty(field)) : false; }; - } else { - NumberMatcher matcher = sv[1].get(); - return [=](CardData x) -> bool { - return x->hasProperty(field) ? matcher(x->getProperty(field).toInt()) : false; - }; + const auto matcher = std::any_cast(sv[1]); + return [=](const CardData &x) -> bool { return x->hasProperty(field) && matcher(x->getProperty(field)); }; } + + const auto matcher = std::any_cast(sv[1]); + return + [=](const CardData &x) -> bool { return x->hasProperty(field) && matcher(x->getProperty(field).toInt()); }; }; search["GenericQuery"] = [](const peg::SemanticValues &sv) -> Filter { - StringMatcher matcher = sv[0].get(); - return [=](CardData x) { return matcher(x->getName()); }; + const auto matcher = std::any_cast(sv[0]); + return [=](const CardData &x) { return matcher(x->getName()); }; }; search["Color"] = [](const peg::SemanticValues &sv) -> char { return "WUBRGU"[sv.choice()]; }; search["ColorEx"] = [](const peg::SemanticValues &sv) -> char { - return sv.choice() == 0 ? sv[0].get() : *sv.c_str(); + return sv.choice() == 0 ? std::any_cast(sv[0]) : *std::string(sv.sv()).c_str(); }; } FilterString::FilterString() { - result = [](CardData) -> bool { return false; }; + result = [](const CardData &) -> bool { return false; }; _error = "Not initialized"; } @@ -356,16 +373,16 @@ FilterString::FilterString(const QString &expr) _error = QString(); if (ba.isEmpty()) { - result = [](CardData) -> bool { return true; }; + result = [](const CardData &) -> bool { return true; }; return; } - search.log = [&](size_t ln, size_t col, const std::string &msg) { - _error = QString("%1:%2: %3").arg(ln).arg(col).arg(QString::fromStdString(msg)); - }; + search.set_logger([&](size_t /*ln*/, size_t col, const std::string &msg) { + _error = QString("Error at position %1: %2").arg(col).arg(QString::fromStdString(msg)); + }); if (!search.parse(ba.data(), result)) { - qDebug() << "Filter string error" << _error; - result = [](CardData) -> bool { return false; }; + qCInfo(FilterStringLog).nospace() << "FilterString error for " << expr << "; " << qPrintable(_error); + result = [](const CardData &) -> bool { return false; }; } } diff --git a/cockatrice/src/game/filters/filter_string.h b/cockatrice/src/game/filters/filter_string.h index 4ef9f3ae0..a633a235b 100644 --- a/cockatrice/src/game/filters/filter_string.h +++ b/cockatrice/src/game/filters/filter_string.h @@ -4,11 +4,14 @@ #include "../cards/card_database.h" #include "filter_tree.h" +#include #include #include #include #include +inline Q_LOGGING_CATEGORY(FilterStringLog, "filter_string"); + typedef CardInfoPtr CardData; typedef std::function Filter; typedef std::function StringMatcher; @@ -26,7 +29,7 @@ class FilterString public: FilterString(); explicit FilterString(const QString &exp); - bool check(const CardData &card) + bool check(const CardData &card) const { return result(card); } diff --git a/cockatrice/src/game/filters/filter_tree.cpp b/cockatrice/src/game/filters/filter_tree.cpp index 1ed61f1e3..d36a1d253 100644 --- a/cockatrice/src/game/filters/filter_tree.cpp +++ b/cockatrice/src/game/filters/filter_tree.cpp @@ -203,11 +203,13 @@ bool FilterItem::acceptText(const CardInfoPtr info) const bool FilterItem::acceptSet(const CardInfoPtr info) const { bool status = false; - for (const auto &set : info->getSets()) { - if (set.getPtr()->getShortName().compare(term, Qt::CaseInsensitive) == 0 || - set.getPtr()->getLongName().compare(term, Qt::CaseInsensitive) == 0) { - status = true; - break; + for (const auto &cardInfoPerSetList : info->getSets()) { + for (const auto &set : cardInfoPerSetList) { + if (set.getPtr()->getShortName().compare(term, Qt::CaseInsensitive) == 0 || + set.getPtr()->getLongName().compare(term, Qt::CaseInsensitive) == 0) { + status = true; + break; + } } } @@ -336,9 +338,11 @@ bool FilterItem::acceptRarity(const CardInfoPtr info) const } } - for (const auto &set : info->getSets()) { - if (set.getProperty("rarity").compare(converted_term, Qt::CaseInsensitive) == 0) { - return true; + for (const auto &cardInfoPerSetList : info->getSets()) { + for (const auto &set : cardInfoPerSetList) { + if (set.getProperty("rarity").compare(converted_term, Qt::CaseInsensitive) == 0) { + return true; + } } } return false; diff --git a/cockatrice/src/game/filters/filter_tree_model.cpp b/cockatrice/src/game/filters/filter_tree_model.cpp index e3dcaec2f..7541fc243 100644 --- a/cockatrice/src/game/filters/filter_tree_model.cpp +++ b/cockatrice/src/game/filters/filter_tree_model.cpp @@ -8,14 +8,10 @@ FilterTreeModel::FilterTreeModel(QObject *parent) : QAbstractItemModel(parent) { fTree = new FilterTree; - connect(fTree, SIGNAL(preInsertRow(const FilterTreeNode *, int)), this, - SLOT(proxyBeginInsertRow(const FilterTreeNode *, int))); - connect(fTree, SIGNAL(postInsertRow(const FilterTreeNode *, int)), this, - SLOT(proxyEndInsertRow(const FilterTreeNode *, int))); - connect(fTree, SIGNAL(preRemoveRow(const FilterTreeNode *, int)), this, - SLOT(proxyBeginRemoveRow(const FilterTreeNode *, int))); - connect(fTree, SIGNAL(postRemoveRow(const FilterTreeNode *, int)), this, - SLOT(proxyEndRemoveRow(const FilterTreeNode *, int))); + connect(fTree, &FilterTree::preInsertRow, this, &FilterTreeModel::proxyBeginInsertRow); + connect(fTree, &FilterTree::postInsertRow, this, &FilterTreeModel::proxyEndInsertRow); + connect(fTree, &FilterTree::preRemoveRow, this, &FilterTreeModel::proxyBeginRemoveRow); + connect(fTree, &FilterTree::postRemoveRow, this, &FilterTreeModel::proxyEndRemoveRow); } FilterTreeModel::~FilterTreeModel() diff --git a/cockatrice/src/game/filters/filter_tree_model.h b/cockatrice/src/game/filters/filter_tree_model.h index 83271f49b..3ea2c4013 100644 --- a/cockatrice/src/game/filters/filter_tree_model.h +++ b/cockatrice/src/game/filters/filter_tree_model.h @@ -28,19 +28,19 @@ private: public: FilterTreeModel(QObject *parent = nullptr); - ~FilterTreeModel(); + ~FilterTreeModel() override; FilterTree *filterTree() const { return fTree; } - int rowCount(const QModelIndex &parent = QModelIndex()) const; - int columnCount(const QModelIndex & /*parent*/ = QModelIndex()) const; - QVariant data(const QModelIndex &index, int role) const; - bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole); - Qt::ItemFlags flags(const QModelIndex &index) const; - QModelIndex parent(const QModelIndex &ind) const; - QModelIndex index(int row, int column, const QModelIndex &parent) const; - bool removeRows(int row, int count, const QModelIndex &parent); + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + int columnCount(const QModelIndex & /*parent*/ = QModelIndex()) const override; + QVariant data(const QModelIndex &index, int role) const override; + bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; + Qt::ItemFlags flags(const QModelIndex &index) const override; + QModelIndex parent(const QModelIndex &ind) const override; + QModelIndex index(int row, int column, const QModelIndex &parent) const override; + bool removeRows(int row, int count, const QModelIndex &parent) override; }; #endif diff --git a/cockatrice/src/game/game_scene.cpp b/cockatrice/src/game/game_scene.cpp index f8217305e..afd6cc815 100644 --- a/cockatrice/src/game/game_scene.cpp +++ b/cockatrice/src/game/game_scene.cpp @@ -20,7 +20,8 @@ GameScene::GameScene(PhasesToolbar *_phasesToolbar, QObject *parent) { animationTimer = new QBasicTimer; addItem(phasesToolbar); - connect(&SettingsCache::instance(), SIGNAL(minPlayersForMultiColumnLayoutChanged()), this, SLOT(rearrange())); + connect(&SettingsCache::instance(), &SettingsCache::minPlayersForMultiColumnLayoutChanged, this, + &GameScene::rearrange); rearrange(); } @@ -28,6 +29,13 @@ GameScene::GameScene(PhasesToolbar *_phasesToolbar, QObject *parent) GameScene::~GameScene() { delete animationTimer; + + // DO NOT call clearViews() here + // clearViews calls close() on the zoneViews, which sends signals; sending signals in destructors leads to segfaults + // deleteLater() deletes the zoneView without allowing it to send signals + for (const auto &zoneView : zoneViews) { + zoneView->deleteLater(); + } } void GameScene::retranslateUi() @@ -38,16 +46,16 @@ void GameScene::retranslateUi() void GameScene::addPlayer(Player *player) { - qDebug() << "GameScene::addPlayer name=" << player->getName(); + qCInfo(GameScenePlayerAdditionRemovalLog) << "GameScene::addPlayer name=" << player->getName(); players << player; addItem(player); - connect(player, SIGNAL(sizeChanged()), this, SLOT(rearrange())); - connect(player, SIGNAL(playerCountChanged()), this, SLOT(rearrange())); + connect(player, &Player::sizeChanged, this, &GameScene::rearrange); + connect(player, &Player::playerCountChanged, this, &GameScene::rearrange); } void GameScene::removePlayer(Player *player) { - qDebug() << "GameScene::removePlayer name=" << player->getName(); + qCInfo(GameScenePlayerAdditionRemovalLog) << "GameScene::removePlayer name=" << player->getName(); for (ZoneViewWidget *zone : zoneViews) { if (zone->getPlayer() == player) { zone->close(); @@ -75,7 +83,7 @@ void GameScene::rearrange() QListIterator playersIter(players); while (playersIter.hasNext()) { Player *p = playersIter.next(); - if (!p->getConceded()) { + if (p && !p->getConceded()) { playersPlaying.append(p); if (!firstPlayerFound && (p->getLocal())) { firstPlayerIndex = playersPlaying.size() - 1; @@ -142,7 +150,7 @@ void GameScene::rearrange() processViewSizeChange(viewSize); } -void GameScene::toggleZoneView(Player *player, const QString &zoneName, int numberCards) +void GameScene::toggleZoneView(Player *player, const QString &zoneName, int numberCards, bool isReversed) { for (auto &view : zoneViews) { ZoneViewZone *temp = view->getZone(); @@ -151,9 +159,10 @@ void GameScene::toggleZoneView(Player *player, const QString &zoneName, int numb } } - ZoneViewWidget *item = new ZoneViewWidget(player, player->getZones().value(zoneName), numberCards, false); + ZoneViewWidget *item = + new ZoneViewWidget(player, player->getZones().value(zoneName), numberCards, false, false, {}, isReversed); zoneViews.append(item); - connect(item, SIGNAL(closePressed(ZoneViewWidget *)), this, SLOT(removeZoneView(ZoneViewWidget *))); + connect(item, &ZoneViewWidget::closePressed, this, &GameScene::removeZoneView); addItem(item); if (zoneName == "grave") { item->setPos(360, 100); @@ -171,7 +180,7 @@ void GameScene::addRevealedZoneView(Player *player, { ZoneViewWidget *item = new ZoneViewWidget(player, zone, -2, true, withWritePermission, cardList); zoneViews.append(item); - connect(item, SIGNAL(closePressed(ZoneViewWidget *)), this, SLOT(removeZoneView(ZoneViewWidget *))); + connect(item, &ZoneViewWidget::closePressed, this, &GameScene::removeZoneView); addItem(item); item->setPos(600, 80); } diff --git a/cockatrice/src/game/game_scene.h b/cockatrice/src/game/game_scene.h index 7b80b8b6f..801acacee 100644 --- a/cockatrice/src/game/game_scene.h +++ b/cockatrice/src/game/game_scene.h @@ -3,9 +3,13 @@ #include #include +#include #include #include +inline Q_LOGGING_CATEGORY(GameSceneLog, "game_scene"); +inline Q_LOGGING_CATEGORY(GameScenePlayerAdditionRemovalLog, "game_scene.player_addition_removal"); + class Player; class ZoneViewWidget; class CardZone; @@ -33,8 +37,8 @@ private: void updateHover(const QPointF &scenePos); public: - GameScene(PhasesToolbar *_phasesToolbar, QObject *parent = nullptr); - ~GameScene(); + explicit GameScene(PhasesToolbar *_phasesToolbar, QObject *parent = nullptr); + ~GameScene() override; void retranslateUi(); void processViewSizeChange(const QSize &newSize); QTransform getViewTransform() const; @@ -47,7 +51,7 @@ public: void registerAnimationItem(AbstractCardItem *item); void unregisterAnimationItem(AbstractCardItem *card); public slots: - void toggleZoneView(Player *player, const QString &zoneName, int numberCards); + void toggleZoneView(Player *player, const QString &zoneName, int numberCards, bool isReversed = false); void addRevealedZoneView(Player *player, CardZone *zone, const QList &cardList, @@ -61,8 +65,8 @@ public slots: void rearrange(); protected: - bool event(QEvent *event); - void timerEvent(QTimerEvent *event); + bool event(QEvent *event) override; + void timerEvent(QTimerEvent *event) override; signals: void sigStartRubberBand(const QPointF &selectionOrigin); void sigResizeRubberBand(const QPointF &cursorPoint); diff --git a/cockatrice/src/game/game_selector.cpp b/cockatrice/src/game/game_selector.cpp index fe534cf12..694e79ec9 100644 --- a/cockatrice/src/game/game_selector.cpp +++ b/cockatrice/src/game/game_selector.cpp @@ -9,6 +9,7 @@ #include "../dialogs/dlg_create_game.h" #include "../dialogs/dlg_filter_games.h" #include "../server/pending_command.h" +#include "../server/user/user_list_manager.h" #include "games_model.h" #include "pb/response.pb.h" #include "pb/room_commands.pb.h" @@ -37,15 +38,19 @@ GameSelector::GameSelector(AbstractClient *_client, : QGroupBox(parent), client(_client), tabSupervisor(_tabSupervisor), room(_room), showFilters(_showfilters) { gameListView = new QTreeView; + gameListView->setContextMenuPolicy(Qt::CustomContextMenu); + connect(gameListView, &QTreeView::customContextMenuRequested, this, &GameSelector::customContextMenu); + gameListModel = new GamesModel(_rooms, _gameTypes, this); if (showFilters) { - gameListProxyModel = new GamesProxyModel(this, tabSupervisor); + gameListProxyModel = new GamesProxyModel(this, tabSupervisor->getUserListManager()); gameListProxyModel->setSourceModel(gameListModel); gameListProxyModel->setSortCaseSensitivity(Qt::CaseInsensitive); gameListView->setModel(gameListProxyModel); } else { gameListView->setModel(gameListModel); } + gameListView->setIconSize(QSize(13, 13)); gameListView->setSortingEnabled(true); gameListView->sortByColumn(gameListModel->startTimeColIndex(), Qt::AscendingOrder); gameListView->setAlternatingRowColors(true); @@ -73,16 +78,16 @@ GameSelector::GameSelector(AbstractClient *_client, filterButton = new QPushButton; filterButton->setIcon(QPixmap("theme:icons/search")); - connect(filterButton, SIGNAL(clicked()), this, SLOT(actSetFilter())); + connect(filterButton, &QPushButton::clicked, this, &GameSelector::actSetFilter); clearFilterButton = new QPushButton; clearFilterButton->setIcon(QPixmap("theme:icons/clearsearch")); bool filtersSetToDefault = showFilters && gameListProxyModel->areFilterParametersSetToDefaults(); clearFilterButton->setEnabled(!filtersSetToDefault); - connect(clearFilterButton, SIGNAL(clicked()), this, SLOT(actClearFilter())); + connect(clearFilterButton, &QPushButton::clicked, this, &GameSelector::actClearFilter); if (room) { createButton = new QPushButton; - connect(createButton, SIGNAL(clicked()), this, SLOT(actCreate())); + connect(createButton, &QPushButton::clicked, this, &GameSelector::actCreate); } else { createButton = nullptr; } @@ -111,18 +116,15 @@ GameSelector::GameSelector(AbstractClient *_client, setMinimumWidth((qreal)(gameListView->columnWidth(0) * gameListModel->columnCount()) / 1.5); setMinimumHeight(200); - connect(joinButton, SIGNAL(clicked()), this, SLOT(actJoin())); - connect(spectateButton, SIGNAL(clicked()), this, SLOT(actJoin())); - connect(gameListView->selectionModel(), SIGNAL(currentRowChanged(const QModelIndex &, const QModelIndex &)), this, - SLOT(actSelectedGameChanged(const QModelIndex &, const QModelIndex &))); - connect(gameListView, SIGNAL(activated(const QModelIndex &)), this, SLOT(actJoin())); + connect(joinButton, &QPushButton::clicked, this, &GameSelector::actJoin); + connect(spectateButton, &QPushButton::clicked, this, &GameSelector::actSpectate); + connect(gameListView->selectionModel(), &QItemSelectionModel::currentRowChanged, this, + &GameSelector::actSelectedGameChanged); + connect(gameListView, &QTreeView::activated, this, &GameSelector::actJoin); - connect(client, SIGNAL(ignoreListReceived(const QList &)), this, - SLOT(ignoreListReceived(const QList &))); - connect(client, SIGNAL(addToListEventReceived(const Event_AddToList &)), this, - SLOT(processAddToListEvent(const Event_AddToList &))); - connect(client, SIGNAL(removeFromListEventReceived(const Event_RemoveFromList &)), this, - SLOT(processRemoveFromListEvent(const Event_RemoveFromList &))); + connect(client, &AbstractClient::ignoreListReceived, this, &GameSelector::ignoreListReceived); + connect(client, &AbstractClient::addToListEventReceived, this, &GameSelector::processAddToListEvent); + connect(client, &AbstractClient::removeFromListEventReceived, this, &GameSelector::processRemoveFromListEvent); } void GameSelector::ignoreListReceived(const QList &) @@ -153,10 +155,10 @@ void GameSelector::actSetFilter() if (!dlg.exec()) return; - gameListProxyModel->setShowBuddiesOnlyGames(dlg.getShowBuddiesOnlyGames()); - gameListProxyModel->setShowFullGames(dlg.getShowFullGames()); - gameListProxyModel->setShowGamesThatStarted(dlg.getShowGamesThatStarted()); - gameListProxyModel->setShowPasswordProtectedGames(dlg.getShowPasswordProtectedGames()); + gameListProxyModel->setHideBuddiesOnlyGames(dlg.getHideBuddiesOnlyGames()); + gameListProxyModel->setHideFullGames(dlg.getHideFullGames()); + gameListProxyModel->setHideGamesThatStarted(dlg.getHideGamesThatStarted()); + gameListProxyModel->setHidePasswordProtectedGames(dlg.getHidePasswordProtectedGames()); gameListProxyModel->setHideIgnoredUserGames(dlg.getHideIgnoredUserGames()); gameListProxyModel->setGameNameFilter(dlg.getGameNameFilter()); gameListProxyModel->setCreatorNameFilter(dlg.getCreatorNameFilter()); @@ -243,21 +245,65 @@ void GameSelector::checkResponse(const Response &response) void GameSelector::actJoin() { - QModelIndex ind = gameListView->currentIndex(); - if (!ind.isValid()) + return joinGame(false); +} + +void GameSelector::actSpectate() +{ + return joinGame(true); +} + +void GameSelector::customContextMenu(const QPoint &point) +{ + const auto &index = gameListView->indexAt(point); + if (!index.isValid()) { return; + } + + QAction joinGame(tr("Join Game")); + connect(&joinGame, &QAction::triggered, this, &GameSelector::actJoin); + + QAction spectateGame(tr("Spectate Game")); + connect(&spectateGame, &QAction::triggered, this, &GameSelector::actSpectate); + + QAction getGameInfo(tr("Game Information")); + connect(&getGameInfo, &QAction::triggered, this, [=, this]() { + const ServerInfo_Game &gameInfo = gameListModel->getGame(index.data(Qt::UserRole).toInt()); + const QMap &gameTypes = gameListModel->getGameTypes().value(gameInfo.room_id()); + + DlgCreateGame dlg(gameInfo, gameTypes, this); + dlg.exec(); + }); + + QMenu menu; + menu.addAction(&joinGame); + menu.addAction(&spectateGame); + menu.addAction(&getGameInfo); + menu.exec(gameListView->mapToGlobal(point)); +} + +void GameSelector::joinGame(const bool isSpectator) +{ + QModelIndex ind = gameListView->currentIndex(); + if (!ind.isValid()) { + return; + } + const ServerInfo_Game &game = gameListModel->getGame(ind.data(Qt::UserRole).toInt()); if (tabSupervisor->switchToGameTabIfAlreadyExists(game.game_id())) { return; } - bool spectator = sender() == spectateButton || game.player_count() == game.max_players(); + + bool spectator = isSpectator || game.player_count() == game.max_players(); + bool overrideRestrictions = !tabSupervisor->getAdminLocked(); QString password; if (game.with_password() && !(spectator && !game.spectators_need_password()) && !overrideRestrictions) { bool ok; password = getTextWithMax(this, tr("Join game"), tr("Password:"), QLineEdit::Password, QString(), &ok); - if (!ok) + if (!ok) { return; + } } Command_JoinGame cmd; @@ -274,7 +320,7 @@ void GameSelector::actJoin() } PendingCommand *pend = r->prepareRoomCommand(cmd); - connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this, SLOT(checkResponse(Response))); + connect(pend, &PendingCommand::finished, this, &GameSelector::checkResponse); r->sendRoomCommand(pend); disableButtons(); diff --git a/cockatrice/src/game/game_selector.h b/cockatrice/src/game/game_selector.h index 57e204a01..a4b8a68e2 100644 --- a/cockatrice/src/game/game_selector.h +++ b/cockatrice/src/game/game_selector.h @@ -6,6 +6,7 @@ #include #include #include +#include class QTreeView; class GamesModel; @@ -26,7 +27,11 @@ private slots: void actSetFilter(); void actClearFilter(); void actCreate(); + void actJoin(); + void actSpectate(); + void customContextMenu(const QPoint &point); + void actSelectedGameChanged(const QModelIndex ¤t, const QModelIndex &previous); void checkResponse(const Response &response); @@ -52,6 +57,7 @@ private: void disableButtons(); void enableButtons(); void enableButtonsForIndex(const QModelIndex ¤t); + void joinGame(const bool isSpectator); public: GameSelector(AbstractClient *_client, diff --git a/cockatrice/src/game/game_view.cpp b/cockatrice/src/game/game_view.cpp index a17a903a4..7ce9dda3e 100644 --- a/cockatrice/src/game/game_view.cpp +++ b/cockatrice/src/game/game_view.cpp @@ -7,24 +7,25 @@ #include #include -GameView::GameView(QGraphicsScene *scene, QWidget *parent) : QGraphicsView(scene, parent), rubberBand(0) +GameView::GameView(GameScene *scene, QWidget *parent) : QGraphicsView(scene, parent), rubberBand(0) { setBackgroundBrush(QBrush(QColor(0, 0, 0))); setRenderHints(QPainter::TextAntialiasing | QPainter::Antialiasing); setFocusPolicy(Qt::NoFocus); setViewportUpdateMode(BoundingRectViewportUpdate); - connect(scene, SIGNAL(sceneRectChanged(const QRectF &)), this, SLOT(updateSceneRect(const QRectF &))); + connect(scene, &GameScene::sceneRectChanged, this, &GameView::updateSceneRect); - connect(scene, SIGNAL(sigStartRubberBand(const QPointF &)), this, SLOT(startRubberBand(const QPointF &))); - connect(scene, SIGNAL(sigResizeRubberBand(const QPointF &)), this, SLOT(resizeRubberBand(const QPointF &))); - connect(scene, SIGNAL(sigStopRubberBand()), this, SLOT(stopRubberBand())); + connect(scene, &GameScene::sigStartRubberBand, this, &GameView::startRubberBand); + connect(scene, &GameScene::sigResizeRubberBand, this, &GameView::resizeRubberBand); + connect(scene, &GameScene::sigStopRubberBand, this, &GameView::stopRubberBand); aCloseMostRecentZoneView = new QAction(this); - connect(aCloseMostRecentZoneView, SIGNAL(triggered()), scene, SLOT(closeMostRecentZoneView())); + connect(aCloseMostRecentZoneView, &QAction::triggered, scene, &GameScene::closeMostRecentZoneView); addAction(aCloseMostRecentZoneView); - connect(&SettingsCache::instance().shortcuts(), SIGNAL(shortCutChanged()), this, SLOT(refreshShortcuts())); + connect(&SettingsCache::instance().shortcuts(), &ShortcutsSettings::shortCutChanged, this, + &GameView::refreshShortcuts); refreshShortcuts(); rubberBand = new QRubberBand(QRubberBand::Rectangle, this); } diff --git a/cockatrice/src/game/game_view.h b/cockatrice/src/game/game_view.h index e1ca517b3..19771298d 100644 --- a/cockatrice/src/game/game_view.h +++ b/cockatrice/src/game/game_view.h @@ -3,6 +3,7 @@ #include +class GameScene; class QRubberBand; class GameView : public QGraphicsView @@ -14,7 +15,7 @@ private: QPointF selectionOrigin; protected: - void resizeEvent(QResizeEvent *event); + void resizeEvent(QResizeEvent *event) override; private slots: void startRubberBand(const QPointF &selectionOrigin); void resizeRubberBand(const QPointF &cursorPoint); @@ -24,7 +25,7 @@ public slots: void updateSceneRect(const QRectF &rect); public: - GameView(QGraphicsScene *scene, QWidget *parent = nullptr); + explicit GameView(GameScene *scene, QWidget *parent = nullptr); }; #endif diff --git a/cockatrice/src/game/games_model.cpp b/cockatrice/src/game/games_model.cpp index dd3332e84..93dbb7f20 100644 --- a/cockatrice/src/game/games_model.cpp +++ b/cockatrice/src/game/games_model.cpp @@ -2,7 +2,8 @@ #include "../client/tabs/tab_account.h" #include "../client/ui/pixel_map_generator.h" -#include "../server/user/user_list.h" +#include "../server/user/user_list_manager.h" +#include "../server/user/user_list_widget.h" #include "../settings/cache_settings.h" #include "pb/serverinfo_game.pb.h" @@ -25,15 +26,6 @@ enum GameListColumn SPECTATORS }; -const bool DEFAULT_SHOW_FULL_GAMES = false; -const bool DEFAULT_SHOW_GAMES_THAT_STARTED = false; -const bool DEFAULT_SHOW_PASSWORD_PROTECTED_GAMES = true; -const bool DEFAULT_SHOW_BUDDIES_ONLY_GAMES = true; -const bool DEFAULT_HIDE_IGNORED_USER_GAMES = false; -const bool DEFAULT_SHOW_ONLY_IF_SPECTATORS_CAN_WATCH = false; -const bool DEFAULT_SHOW_SPECTATOR_PASSWORD_PROTECTED = true; -const bool DEFAULT_SHOW_ONLY_IF_SPECTATORS_CAN_CHAT = false; -const bool DEFAULT_SHOW_ONLY_IF_SPECTATORS_CAN_SEE_HANDS = false; const int DEFAULT_MAX_PLAYERS_MIN = 1; const int DEFAULT_MAX_PLAYERS_MAX = 99; constexpr QTime DEFAULT_MAX_GAME_AGE = QTime(); @@ -126,10 +118,10 @@ QVariant GamesModel::data(const QModelIndex &index, int role) const case Qt::DisplayRole: return QString::fromStdString(gameentry.creator_info().name()); case Qt::DecorationRole: { - QPixmap avatarPixmap = UserLevelPixmapGenerator::generatePixmap( - 13, (UserLevelFlags)gameentry.creator_info().user_level(), false, + return UserLevelPixmapGenerator::generateIcon( + 13, UserLevelFlags(gameentry.creator_info().user_level()), + gameentry.creator_info().pawn_colors(), false, QString::fromStdString(gameentry.creator_info().privlevel())); - return QIcon(avatarPixmap); } default: return QVariant(); @@ -284,18 +276,17 @@ void GamesModel::updateGameList(const ServerInfo_Game &game) endInsertRows(); } -GamesProxyModel::GamesProxyModel(QObject *parent, const TabSupervisor *_tabSupervisor) - : QSortFilterProxyModel(parent), ownUserIsRegistered(_tabSupervisor->isOwnUserRegistered()), - tabSupervisor(_tabSupervisor) +GamesProxyModel::GamesProxyModel(QObject *parent, const UserListProxy *_userListProxy) + : QSortFilterProxyModel(parent), userListProxy(_userListProxy) { resetFilterParameters(); setSortRole(GamesModel::SORT_ROLE); setDynamicSortFilter(true); } -void GamesProxyModel::setShowBuddiesOnlyGames(bool _showBuddiesOnlyGames) +void GamesProxyModel::setHideBuddiesOnlyGames(bool _showBuddiesOnlyGames) { - showBuddiesOnlyGames = _showBuddiesOnlyGames; + hideBuddiesOnlyGames = _showBuddiesOnlyGames; invalidateFilter(); } @@ -305,21 +296,21 @@ void GamesProxyModel::setHideIgnoredUserGames(bool _hideIgnoredUserGames) invalidateFilter(); } -void GamesProxyModel::setShowFullGames(bool _showFullGames) +void GamesProxyModel::setHideFullGames(bool _showFullGames) { - showFullGames = _showFullGames; + hideFullGames = _showFullGames; invalidateFilter(); } -void GamesProxyModel::setShowGamesThatStarted(bool _showGamesThatStarted) +void GamesProxyModel::setHideGamesThatStarted(bool _showGamesThatStarted) { - showGamesThatStarted = _showGamesThatStarted; + hideGamesThatStarted = _showGamesThatStarted; invalidateFilter(); } -void GamesProxyModel::setShowPasswordProtectedGames(bool _showPasswordProtectedGames) +void GamesProxyModel::setHidePasswordProtectedGames(bool _showPasswordProtectedGames) { - showPasswordProtectedGames = _showPasswordProtectedGames; + hidePasswordProtectedGames = _showPasswordProtectedGames; invalidateFilter(); } @@ -395,47 +386,43 @@ int GamesProxyModel::getNumFilteredGames() const void GamesProxyModel::resetFilterParameters() { - showFullGames = DEFAULT_SHOW_FULL_GAMES; - showGamesThatStarted = DEFAULT_SHOW_GAMES_THAT_STARTED; - showPasswordProtectedGames = DEFAULT_SHOW_PASSWORD_PROTECTED_GAMES; - showBuddiesOnlyGames = DEFAULT_SHOW_BUDDIES_ONLY_GAMES; - hideIgnoredUserGames = DEFAULT_HIDE_IGNORED_USER_GAMES; + hideFullGames = false; + hideGamesThatStarted = false; + hidePasswordProtectedGames = false; + hideBuddiesOnlyGames = false; + hideIgnoredUserGames = false; gameNameFilter = QString(); creatorNameFilter = QString(); gameTypeFilter.clear(); maxPlayersFilterMin = DEFAULT_MAX_PLAYERS_MIN; maxPlayersFilterMax = DEFAULT_MAX_PLAYERS_MAX; maxGameAge = DEFAULT_MAX_GAME_AGE; - showOnlyIfSpectatorsCanWatch = DEFAULT_SHOW_ONLY_IF_SPECTATORS_CAN_WATCH; - showSpectatorPasswordProtected = DEFAULT_SHOW_SPECTATOR_PASSWORD_PROTECTED; - showOnlyIfSpectatorsCanChat = DEFAULT_SHOW_ONLY_IF_SPECTATORS_CAN_CHAT; - showOnlyIfSpectatorsCanSeeHands = DEFAULT_SHOW_ONLY_IF_SPECTATORS_CAN_SEE_HANDS; + showOnlyIfSpectatorsCanWatch = false; + showSpectatorPasswordProtected = false; + showOnlyIfSpectatorsCanChat = false; + showOnlyIfSpectatorsCanSeeHands = false; invalidateFilter(); } bool GamesProxyModel::areFilterParametersSetToDefaults() const { - return showFullGames == DEFAULT_SHOW_FULL_GAMES && showGamesThatStarted == DEFAULT_SHOW_GAMES_THAT_STARTED && - showPasswordProtectedGames == DEFAULT_SHOW_PASSWORD_PROTECTED_GAMES && - showBuddiesOnlyGames == DEFAULT_SHOW_BUDDIES_ONLY_GAMES && - hideIgnoredUserGames == DEFAULT_HIDE_IGNORED_USER_GAMES && gameNameFilter.isEmpty() && - creatorNameFilter.isEmpty() && gameTypeFilter.isEmpty() && maxPlayersFilterMin == DEFAULT_MAX_PLAYERS_MIN && + return !hideFullGames && !hideGamesThatStarted && !hidePasswordProtectedGames && !hideBuddiesOnlyGames && + !hideIgnoredUserGames && gameNameFilter.isEmpty() && creatorNameFilter.isEmpty() && + gameTypeFilter.isEmpty() && maxPlayersFilterMin == DEFAULT_MAX_PLAYERS_MIN && maxPlayersFilterMax == DEFAULT_MAX_PLAYERS_MAX && maxGameAge == DEFAULT_MAX_GAME_AGE && - showOnlyIfSpectatorsCanWatch == DEFAULT_SHOW_ONLY_IF_SPECTATORS_CAN_WATCH && - showSpectatorPasswordProtected == DEFAULT_SHOW_SPECTATOR_PASSWORD_PROTECTED && - showOnlyIfSpectatorsCanChat == DEFAULT_SHOW_ONLY_IF_SPECTATORS_CAN_CHAT && - showOnlyIfSpectatorsCanSeeHands == DEFAULT_SHOW_ONLY_IF_SPECTATORS_CAN_SEE_HANDS; + !showOnlyIfSpectatorsCanWatch && !showSpectatorPasswordProtected && !showOnlyIfSpectatorsCanChat && + !showOnlyIfSpectatorsCanSeeHands; } void GamesProxyModel::loadFilterParameters(const QMap &allGameTypes) { GameFiltersSettings &gameFilters = SettingsCache::instance().gameFilters(); - showFullGames = gameFilters.isShowFullGames(); - showGamesThatStarted = gameFilters.isShowGamesThatStarted(); - showPasswordProtectedGames = gameFilters.isShowPasswordProtectedGames(); + hideFullGames = gameFilters.isHideFullGames(); + hideGamesThatStarted = gameFilters.isHideGamesThatStarted(); + hidePasswordProtectedGames = gameFilters.isHidePasswordProtectedGames(); hideIgnoredUserGames = gameFilters.isHideIgnoredUserGames(); - showBuddiesOnlyGames = gameFilters.isShowBuddiesOnlyGames(); + hideBuddiesOnlyGames = gameFilters.isHideBuddiesOnlyGames(); gameNameFilter = gameFilters.getGameNameFilter(); creatorNameFilter = gameFilters.getCreatorNameFilter(); maxPlayersFilterMin = gameFilters.getMinPlayers(); @@ -460,10 +447,10 @@ void GamesProxyModel::loadFilterParameters(const QMap &allGameType void GamesProxyModel::saveFilterParameters(const QMap &allGameTypes) { GameFiltersSettings &gameFilters = SettingsCache::instance().gameFilters(); - gameFilters.setShowBuddiesOnlyGames(showBuddiesOnlyGames); - gameFilters.setShowFullGames(showFullGames); - gameFilters.setShowGamesThatStarted(showGamesThatStarted); - gameFilters.setShowPasswordProtectedGames(showPasswordProtectedGames); + gameFilters.setHideBuddiesOnlyGames(hideBuddiesOnlyGames); + gameFilters.setHideFullGames(hideFullGames); + gameFilters.setHideGamesThatStarted(hideGamesThatStarted); + gameFilters.setHidePasswordProtectedGames(hidePasswordProtectedGames); gameFilters.setHideIgnoredUserGames(hideIgnoredUserGames); gameFilters.setGameNameFilter(gameNameFilter); gameFilters.setCreatorNameFilter(creatorNameFilter); @@ -505,21 +492,20 @@ bool GamesProxyModel::filterAcceptsRow(int sourceRow) const const ServerInfo_Game &game = model->getGame(sourceRow); - if (!showBuddiesOnlyGames && game.only_buddies()) { + if (hideBuddiesOnlyGames && game.only_buddies()) { return false; } - if (hideIgnoredUserGames && tabSupervisor->getUserListsTab()->getIgnoreList()->getUsers().contains( - QString::fromStdString(game.creator_info().name()))) { + if (hideIgnoredUserGames && userListProxy->isUserIgnored(QString::fromStdString(game.creator_info().name()))) { return false; } - if (!showFullGames && game.player_count() == game.max_players()) + if (hideFullGames && game.player_count() == game.max_players()) return false; - if (!showGamesThatStarted && game.started()) + if (hideGamesThatStarted && game.started()) return false; - if (!ownUserIsRegistered) + if (!userListProxy->isOwnUserRegistered()) if (game.only_registered()) return false; - if (!showPasswordProtectedGames && game.with_password()) + if (hidePasswordProtectedGames && game.with_password()) return false; if (!gameNameFilter.isEmpty()) if (!QString::fromStdString(game.description()).contains(gameNameFilter, Qt::CaseInsensitive)) diff --git a/cockatrice/src/game/games_model.h b/cockatrice/src/game/games_model.h index 655608d26..6f590ee47 100644 --- a/cockatrice/src/game/games_model.h +++ b/cockatrice/src/game/games_model.h @@ -1,7 +1,6 @@ #ifndef GAMESMODEL_H #define GAMESMODEL_H -#include "../client/tabs/tab_supervisor.h" #include "game_type_map.h" #include "pb/serverinfo_game.pb.h" @@ -12,6 +11,8 @@ #include #include +class UserListProxy; + class GamesModel : public QAbstractTableModel { Q_OBJECT @@ -26,16 +27,16 @@ public: static const int SORT_ROLE = Qt::UserRole + 1; GamesModel(const QMap &_rooms, const QMap &_gameTypes, QObject *parent = nullptr); - int rowCount(const QModelIndex &parent = QModelIndex()) const + int rowCount(const QModelIndex &parent = QModelIndex()) const override { return parent.isValid() ? 0 : gameList.size(); } - int columnCount(const QModelIndex & /*parent*/ = QModelIndex()) const + int columnCount(const QModelIndex & /*parent*/ = QModelIndex()) const override { return NUM_COLS; } - QVariant data(const QModelIndex &index, int role) const; - QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; + QVariant data(const QModelIndex &index, int role) const override; + QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; static const QString getGameCreatedString(const int secs); const ServerInfo_Game &getGame(int row); @@ -65,8 +66,7 @@ class GamesProxyModel : public QSortFilterProxyModel { Q_OBJECT private: - bool ownUserIsRegistered; - const TabSupervisor *tabSupervisor; + const UserListProxy *userListProxy; // If adding any additional filters, make sure to update: // - GamesProxyModel() @@ -75,11 +75,11 @@ private: // - loadFilterParameters() // - saveFilterParameters() // - filterAcceptsRow() - bool showBuddiesOnlyGames; + bool hideBuddiesOnlyGames; bool hideIgnoredUserGames; - bool showFullGames; - bool showGamesThatStarted; - bool showPasswordProtectedGames; + bool hideFullGames; + bool hideGamesThatStarted; + bool hidePasswordProtectedGames; QString gameNameFilter, creatorNameFilter; QSet gameTypeFilter; quint32 maxPlayersFilterMin, maxPlayersFilterMax; @@ -88,33 +88,33 @@ private: showOnlyIfSpectatorsCanSeeHands; public: - GamesProxyModel(QObject *parent = nullptr, const TabSupervisor *_tabSupervisor = nullptr); + explicit GamesProxyModel(QObject *parent = nullptr, const UserListProxy *_userListProxy = nullptr); - bool getShowBuddiesOnlyGames() const + bool getHideBuddiesOnlyGames() const { - return showBuddiesOnlyGames; + return hideBuddiesOnlyGames; } - void setShowBuddiesOnlyGames(bool _showBuddiesOnlyGames); + void setHideBuddiesOnlyGames(bool _showBuddiesOnlyGames); bool getHideIgnoredUserGames() const { return hideIgnoredUserGames; } void setHideIgnoredUserGames(bool _hideIgnoredUserGames); - bool getShowFullGames() const + bool getHideFullGames() const { - return showFullGames; + return hideFullGames; } - void setShowFullGames(bool _showFullGames); - bool getShowGamesThatStarted() const + void setHideFullGames(bool _showFullGames); + bool getHideGamesThatStarted() const { - return showGamesThatStarted; + return hideGamesThatStarted; } - void setShowGamesThatStarted(bool _showGamesThatStarted); - bool getShowPasswordProtectedGames() const + void setHideGamesThatStarted(bool _showGamesThatStarted); + bool getHidePasswordProtectedGames() const { - return showPasswordProtectedGames; + return hidePasswordProtectedGames; } - void setShowPasswordProtectedGames(bool _showPasswordProtectedGames); + void setHidePasswordProtectedGames(bool _showPasswordProtectedGames); QString getGameNameFilter() const { return gameNameFilter; @@ -173,7 +173,7 @@ public: void refresh(); protected: - bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const; + bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override; bool filterAcceptsRow(int sourceRow) const; }; diff --git a/cockatrice/src/game/hand_counter.h b/cockatrice/src/game/hand_counter.h index 71b1a95a4..2a6633b63 100644 --- a/cockatrice/src/game/hand_counter.h +++ b/cockatrice/src/game/hand_counter.h @@ -15,7 +15,7 @@ private: int number; protected: - void mousePressEvent(QGraphicsSceneMouseEvent *event); + void mousePressEvent(QGraphicsSceneMouseEvent *event) override; public slots: void updateNumber(); signals: @@ -26,14 +26,14 @@ public: { Type = typeOther }; - int type() const + int type() const override { return Type; } - HandCounter(QGraphicsItem *parent = nullptr); - ~HandCounter(); - QRectF boundingRect() const; - void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget); + explicit HandCounter(QGraphicsItem *parent = nullptr); + ~HandCounter() override; + QRectF boundingRect() const override; + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override; }; #endif diff --git a/cockatrice/src/game/player/player.cpp b/cockatrice/src/game/player/player.cpp index dd98305ff..d82a6ee6e 100644 --- a/cockatrice/src/game/player/player.cpp +++ b/cockatrice/src/game/player/player.cpp @@ -5,6 +5,7 @@ #include "../../client/ui/theme_manager.h" #include "../../deck/deck_loader.h" #include "../../dialogs/dlg_create_token.h" +#include "../../dialogs/dlg_move_top_cards_until.h" #include "../../dialogs/dlg_roll_dice.h" #include "../../main.h" #include "../../settings/cache_settings.h" @@ -73,6 +74,7 @@ #include #include #include +#include // milliseconds in between triggers of the move top cards until action static constexpr int MOVE_TOP_CARD_UNTIL_INTERVAL = 100; @@ -80,7 +82,7 @@ static constexpr int MOVE_TOP_CARD_UNTIL_INTERVAL = 100; PlayerArea::PlayerArea(QGraphicsItem *parentItem) : QObject(), QGraphicsItem(parentItem) { setCacheMode(DeviceCoordinateCache); - connect(themeManager, SIGNAL(themeChanged()), this, SLOT(updateBg())); + connect(themeManager, &ThemeManager::themeChanged, this, &PlayerArea::updateBg); updateBg(); } @@ -91,12 +93,7 @@ void PlayerArea::updateBg() void PlayerArea::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*option*/, QWidget * /*widget*/) { - QBrush brush = themeManager->getPlayerBgBrush(); - - if (playerZoneId > 0) { - // If the extra image is not found, load the default one - brush = themeManager->getExtraPlayerBgBrush(QString::number(playerZoneId), brush); - } + QBrush brush = themeManager->getExtraBgBrush(ThemeManager::Player, playerZoneId); painter->fillRect(boundingRect(), brush); } @@ -119,8 +116,8 @@ Player::Player(const ServerInfo_User &info, int _id, bool _local, bool _judge, T userInfo = new ServerInfo_User; userInfo->CopyFrom(info); - connect(&SettingsCache::instance(), SIGNAL(horizontalHandChanged()), this, SLOT(rearrangeZones())); - connect(&SettingsCache::instance(), SIGNAL(handJustificationChanged()), this, SLOT(rearrangeZones())); + connect(&SettingsCache::instance(), &SettingsCache::horizontalHandChanged, this, &Player::rearrangeZones); + connect(&SettingsCache::instance(), &SettingsCache::handJustificationChanged, this, &Player::rearrangeZones); playerArea = new PlayerArea(this); @@ -149,20 +146,20 @@ Player::Player(const ServerInfo_User &info, int _id, bool _local, bool _judge, T sb->setVisible(false); table = new TableZone(this, this); - connect(table, SIGNAL(sizeChanged()), this, SLOT(updateBoundingRect())); + connect(table, &TableZone::sizeChanged, this, &Player::updateBoundingRect); stack = new StackZone(this, (int)table->boundingRect().height(), this); hand = new HandZone(this, _local || _judge || (_parent->getSpectator() && _parent->getSpectatorsSeeEverything()), (int)table->boundingRect().height(), this); - connect(hand, SIGNAL(cardCountChanged()), handCounter, SLOT(updateNumber())); - connect(handCounter, SIGNAL(showContextMenu(const QPoint &)), hand, SLOT(showContextMenu(const QPoint &))); + connect(hand, &HandZone::cardCountChanged, handCounter, &HandCounter::updateNumber); + connect(handCounter, &HandCounter::showContextMenu, hand, &HandZone::showContextMenu); updateBoundingRect(); if (local || judge) { - connect(_parent, SIGNAL(playerAdded(Player *)), this, SLOT(addPlayer(Player *))); - connect(_parent, SIGNAL(playerRemoved(Player *)), this, SLOT(removePlayer(Player *))); + connect(_parent, &TabGame::playerAdded, this, &Player::addPlayer); + connect(_parent, &TabGame::playerRemoved, this, &Player::removePlayer); } if (local || judge) { @@ -175,10 +172,10 @@ Player::Player(const ServerInfo_User &info, int _id, bool _local, bool _judge, T aMoveHandToRfg = new QAction(this); aMoveHandToRfg->setData(QList() << "rfg" << 0); - connect(aMoveHandToTopLibrary, SIGNAL(triggered()), hand, SLOT(moveAllToZone())); - connect(aMoveHandToBottomLibrary, SIGNAL(triggered()), hand, SLOT(moveAllToZone())); - connect(aMoveHandToGrave, SIGNAL(triggered()), hand, SLOT(moveAllToZone())); - connect(aMoveHandToRfg, SIGNAL(triggered()), hand, SLOT(moveAllToZone())); + connect(aMoveHandToTopLibrary, &QAction::triggered, hand, &HandZone::moveAllToZone); + connect(aMoveHandToBottomLibrary, &QAction::triggered, hand, &HandZone::moveAllToZone); + connect(aMoveHandToGrave, &QAction::triggered, hand, &HandZone::moveAllToZone); + connect(aMoveHandToRfg, &QAction::triggered, hand, &HandZone::moveAllToZone); aMoveGraveToTopLibrary = new QAction(this); aMoveGraveToTopLibrary->setData(QList() << "deck" << 0); @@ -189,10 +186,10 @@ Player::Player(const ServerInfo_User &info, int _id, bool _local, bool _judge, T aMoveGraveToRfg = new QAction(this); aMoveGraveToRfg->setData(QList() << "rfg" << 0); - connect(aMoveGraveToTopLibrary, SIGNAL(triggered()), grave, SLOT(moveAllToZone())); - connect(aMoveGraveToBottomLibrary, SIGNAL(triggered()), grave, SLOT(moveAllToZone())); - connect(aMoveGraveToHand, SIGNAL(triggered()), grave, SLOT(moveAllToZone())); - connect(aMoveGraveToRfg, SIGNAL(triggered()), grave, SLOT(moveAllToZone())); + connect(aMoveGraveToTopLibrary, &QAction::triggered, grave, &PileZone::moveAllToZone); + connect(aMoveGraveToBottomLibrary, &QAction::triggered, grave, &PileZone::moveAllToZone); + connect(aMoveGraveToHand, &QAction::triggered, grave, &PileZone::moveAllToZone); + connect(aMoveGraveToRfg, &QAction::triggered, grave, &PileZone::moveAllToZone); aMoveRfgToTopLibrary = new QAction(this); aMoveRfgToTopLibrary->setData(QList() << "deck" << 0); @@ -203,85 +200,95 @@ Player::Player(const ServerInfo_User &info, int _id, bool _local, bool _judge, T aMoveRfgToGrave = new QAction(this); aMoveRfgToGrave->setData(QList() << "grave" << 0); - connect(aMoveRfgToTopLibrary, SIGNAL(triggered()), rfg, SLOT(moveAllToZone())); - connect(aMoveRfgToBottomLibrary, SIGNAL(triggered()), rfg, SLOT(moveAllToZone())); - connect(aMoveRfgToHand, SIGNAL(triggered()), rfg, SLOT(moveAllToZone())); - connect(aMoveRfgToGrave, SIGNAL(triggered()), rfg, SLOT(moveAllToZone())); + connect(aMoveRfgToTopLibrary, &QAction::triggered, rfg, &PileZone::moveAllToZone); + connect(aMoveRfgToBottomLibrary, &QAction::triggered, rfg, &PileZone::moveAllToZone); + connect(aMoveRfgToHand, &QAction::triggered, rfg, &PileZone::moveAllToZone); + connect(aMoveRfgToGrave, &QAction::triggered, rfg, &PileZone::moveAllToZone); aViewLibrary = new QAction(this); - connect(aViewLibrary, SIGNAL(triggered()), this, SLOT(actViewLibrary())); + connect(aViewLibrary, &QAction::triggered, this, &Player::actViewLibrary); aViewHand = new QAction(this); - connect(aViewHand, SIGNAL(triggered()), this, SLOT(actViewHand())); + connect(aViewHand, &QAction::triggered, this, &Player::actViewHand); aViewTopCards = new QAction(this); - connect(aViewTopCards, SIGNAL(triggered()), this, SLOT(actViewTopCards())); + connect(aViewTopCards, &QAction::triggered, this, &Player::actViewTopCards); + aViewBottomCards = new QAction(this); + connect(aViewBottomCards, &QAction::triggered, this, &Player::actViewBottomCards); aAlwaysRevealTopCard = new QAction(this); aAlwaysRevealTopCard->setCheckable(true); - connect(aAlwaysRevealTopCard, SIGNAL(triggered()), this, SLOT(actAlwaysRevealTopCard())); + connect(aAlwaysRevealTopCard, &QAction::triggered, this, &Player::actAlwaysRevealTopCard); aAlwaysLookAtTopCard = new QAction(this); aAlwaysLookAtTopCard->setCheckable(true); - connect(aAlwaysLookAtTopCard, SIGNAL(triggered()), this, SLOT(actAlwaysLookAtTopCard())); + connect(aAlwaysLookAtTopCard, &QAction::triggered, this, &Player::actAlwaysLookAtTopCard); aOpenDeckInDeckEditor = new QAction(this); aOpenDeckInDeckEditor->setEnabled(false); - connect(aOpenDeckInDeckEditor, SIGNAL(triggered()), this, SLOT(actOpenDeckInDeckEditor())); + connect(aOpenDeckInDeckEditor, &QAction::triggered, this, &Player::actOpenDeckInDeckEditor); } aViewGraveyard = new QAction(this); - connect(aViewGraveyard, SIGNAL(triggered()), this, SLOT(actViewGraveyard())); + connect(aViewGraveyard, &QAction::triggered, this, &Player::actViewGraveyard); aViewRfg = new QAction(this); - connect(aViewRfg, SIGNAL(triggered()), this, SLOT(actViewRfg())); + connect(aViewRfg, &QAction::triggered, this, &Player::actViewRfg); if (local || judge) { aViewSideboard = new QAction(this); - connect(aViewSideboard, SIGNAL(triggered()), this, SLOT(actViewSideboard())); + connect(aViewSideboard, &QAction::triggered, this, &Player::actViewSideboard); aDrawCard = new QAction(this); - connect(aDrawCard, SIGNAL(triggered()), this, SLOT(actDrawCard())); + connect(aDrawCard, &QAction::triggered, this, &Player::actDrawCard); aDrawCards = new QAction(this); - connect(aDrawCards, SIGNAL(triggered()), this, SLOT(actDrawCards())); + connect(aDrawCards, &QAction::triggered, this, &Player::actDrawCards); aUndoDraw = new QAction(this); - connect(aUndoDraw, SIGNAL(triggered()), this, SLOT(actUndoDraw())); + connect(aUndoDraw, &QAction::triggered, this, &Player::actUndoDraw); + aShuffle = new QAction(this); - connect(aShuffle, SIGNAL(triggered()), this, SLOT(actShuffle())); + connect(aShuffle, &QAction::triggered, this, &Player::actShuffle); + aMulligan = new QAction(this); - connect(aMulligan, SIGNAL(triggered()), this, SLOT(actMulligan())); + connect(aMulligan, &QAction::triggered, this, &Player::actMulligan); aMoveTopToPlay = new QAction(this); - connect(aMoveTopToPlay, SIGNAL(triggered()), this, SLOT(actMoveTopCardToPlay())); + connect(aMoveTopToPlay, &QAction::triggered, this, &Player::actMoveTopCardToPlay); aMoveTopToPlayFaceDown = new QAction(this); - connect(aMoveTopToPlayFaceDown, SIGNAL(triggered()), this, SLOT(actMoveTopCardToPlayFaceDown())); + connect(aMoveTopToPlayFaceDown, &QAction::triggered, this, &Player::actMoveTopCardToPlayFaceDown); aMoveTopCardToGraveyard = new QAction(this); - connect(aMoveTopCardToGraveyard, SIGNAL(triggered()), this, SLOT(actMoveTopCardToGrave())); + connect(aMoveTopCardToGraveyard, &QAction::triggered, this, &Player::actMoveTopCardToGrave); aMoveTopCardToExile = new QAction(this); - connect(aMoveTopCardToExile, SIGNAL(triggered()), this, SLOT(actMoveTopCardToExile())); + connect(aMoveTopCardToExile, &QAction::triggered, this, &Player::actMoveTopCardToExile); aMoveTopCardsToGraveyard = new QAction(this); - connect(aMoveTopCardsToGraveyard, SIGNAL(triggered()), this, SLOT(actMoveTopCardsToGrave())); + connect(aMoveTopCardsToGraveyard, &QAction::triggered, this, &Player::actMoveTopCardsToGrave); aMoveTopCardsToExile = new QAction(this); - connect(aMoveTopCardsToExile, SIGNAL(triggered()), this, SLOT(actMoveTopCardsToExile())); + connect(aMoveTopCardsToExile, &QAction::triggered, this, &Player::actMoveTopCardsToExile); aMoveTopCardsUntil = new QAction(this); - connect(aMoveTopCardsUntil, SIGNAL(triggered()), this, SLOT(actMoveTopCardsUntil())); + connect(aMoveTopCardsUntil, &QAction::triggered, this, &Player::actMoveTopCardsUntil); aMoveTopCardToBottom = new QAction(this); - connect(aMoveTopCardToBottom, SIGNAL(triggered()), this, SLOT(actMoveTopCardToBottom())); + connect(aMoveTopCardToBottom, &QAction::triggered, this, &Player::actMoveTopCardToBottom); + + aShuffleTopCards = new QAction(this); + connect(aShuffleTopCards, &QAction::triggered, this, &Player::actShuffleTop); aDrawBottomCard = new QAction(this); - connect(aDrawBottomCard, SIGNAL(triggered()), this, SLOT(actDrawBottomCard())); + connect(aDrawBottomCard, &QAction::triggered, this, &Player::actDrawBottomCard); aDrawBottomCards = new QAction(this); - connect(aDrawBottomCards, SIGNAL(triggered()), this, SLOT(actDrawBottomCards())); + connect(aDrawBottomCards, &QAction::triggered, this, &Player::actDrawBottomCards); aMoveBottomToPlay = new QAction(this); - connect(aMoveBottomToPlay, SIGNAL(triggered()), this, SLOT(actMoveBottomCardToPlay())); + connect(aMoveBottomToPlay, &QAction::triggered, this, &Player::actMoveBottomCardToPlay); aMoveBottomToPlayFaceDown = new QAction(this); - connect(aMoveBottomToPlayFaceDown, SIGNAL(triggered()), this, SLOT(actMoveBottomCardToPlayFaceDown())); + connect(aMoveBottomToPlayFaceDown, &QAction::triggered, this, &Player::actMoveBottomCardToPlayFaceDown); aMoveBottomCardToGraveyard = new QAction(this); - connect(aMoveBottomCardToGraveyard, SIGNAL(triggered()), this, SLOT(actMoveBottomCardToGrave())); + connect(aMoveBottomCardToGraveyard, &QAction::triggered, this, &Player::actMoveBottomCardToGrave); aMoveBottomCardToExile = new QAction(this); - connect(aMoveBottomCardToExile, SIGNAL(triggered()), this, SLOT(actMoveBottomCardToExile())); + connect(aMoveBottomCardToExile, &QAction::triggered, this, &Player::actMoveBottomCardToExile); aMoveBottomCardsToGraveyard = new QAction(this); - connect(aMoveBottomCardsToGraveyard, SIGNAL(triggered()), this, SLOT(actMoveBottomCardsToGrave())); + connect(aMoveBottomCardsToGraveyard, &QAction::triggered, this, &Player::actMoveBottomCardsToGrave); aMoveBottomCardsToExile = new QAction(this); - connect(aMoveBottomCardsToExile, SIGNAL(triggered()), this, SLOT(actMoveBottomCardsToExile())); + connect(aMoveBottomCardsToExile, &QAction::triggered, this, &Player::actMoveBottomCardsToExile); aMoveBottomCardToTop = new QAction(this); - connect(aMoveBottomCardToTop, SIGNAL(triggered()), this, SLOT(actMoveBottomCardToTop())); + connect(aMoveBottomCardToTop, &QAction::triggered, this, &Player::actMoveBottomCardToTop); + + aShuffleBottomCards = new QAction(this); + connect(aShuffleBottomCards, &QAction::triggered, this, &Player::actShuffleBottom); } playerMenu = new TearOffMenu(); @@ -313,6 +320,7 @@ Player::Player(const ServerInfo_User &info, int _id, bool _local, bool _judge, T libraryMenu->addSeparator(); libraryMenu->addAction(aViewLibrary); libraryMenu->addAction(aViewTopCards); + libraryMenu->addAction(aViewBottomCards); libraryMenu->addSeparator(); playerLists.append(mRevealLibrary = libraryMenu->addMenu(QString())); singlePlayerLists.append(mLendLibrary = libraryMenu->addMenu(QString())); @@ -325,7 +333,6 @@ Player::Player(const ServerInfo_User &info, int _id, bool _local, bool _judge, T libraryMenu->addSeparator(); libraryMenu->addAction(aOpenDeckInDeckEditor); _deck->setMenu(libraryMenu, aDrawCard); - topLibraryMenu->addAction(aMoveTopToPlay); topLibraryMenu->addAction(aMoveTopToPlayFaceDown); topLibraryMenu->addAction(aMoveTopCardToBottom); @@ -335,6 +342,8 @@ Player::Player(const ServerInfo_User &info, int _id, bool _local, bool _judge, T topLibraryMenu->addAction(aMoveTopCardToExile); topLibraryMenu->addAction(aMoveTopCardsToExile); topLibraryMenu->addAction(aMoveTopCardsUntil); + topLibraryMenu->addSeparator(); + topLibraryMenu->addAction(aShuffleTopCards); bottomLibraryMenu->addAction(aDrawBottomCard); bottomLibraryMenu->addAction(aDrawBottomCards); @@ -347,6 +356,8 @@ Player::Player(const ServerInfo_User &info, int _id, bool _local, bool _judge, T bottomLibraryMenu->addAction(aMoveBottomCardsToGraveyard); bottomLibraryMenu->addAction(aMoveBottomCardToExile); bottomLibraryMenu->addAction(aMoveBottomCardsToExile); + bottomLibraryMenu->addSeparator(); + bottomLibraryMenu->addAction(aShuffleBottomCards); } graveMenu = playerMenu->addTearOffMenu(QString()); @@ -356,7 +367,7 @@ Player::Player(const ServerInfo_User &info, int _id, bool _local, bool _judge, T mRevealRandomGraveyardCard = graveMenu->addMenu(QString()); QAction *newAction = mRevealRandomGraveyardCard->addAction(QString()); newAction->setData(-1); - connect(newAction, SIGNAL(triggered()), this, SLOT(actRevealRandomGraveyardCard())); + connect(newAction, &QAction::triggered, this, &Player::actRevealRandomGraveyardCard); allPlayersActions.append(newAction); mRevealRandomGraveyardCard->addSeparator(); } @@ -390,16 +401,16 @@ Player::Player(const ServerInfo_User &info, int _id, bool _local, bool _judge, T sb->setMenu(sbMenu, aViewSideboard); aUntapAll = new QAction(this); - connect(aUntapAll, SIGNAL(triggered()), this, SLOT(actUntapAll())); + connect(aUntapAll, &QAction::triggered, this, &Player::actUntapAll); aRollDie = new QAction(this); - connect(aRollDie, SIGNAL(triggered()), this, SLOT(actRollDie())); + connect(aRollDie, &QAction::triggered, this, &Player::actRollDie); aCreateToken = new QAction(this); - connect(aCreateToken, SIGNAL(triggered()), this, SLOT(actCreateToken())); + connect(aCreateToken, &QAction::triggered, this, &Player::actCreateToken); aCreateAnotherToken = new QAction(this); - connect(aCreateAnotherToken, SIGNAL(triggered()), this, SLOT(actCreateAnotherToken())); + connect(aCreateAnotherToken, &QAction::triggered, this, &Player::actCreateAnotherToken); aCreateAnotherToken->setEnabled(false); createPredefinedTokenMenu = new QMenu(QString()); @@ -420,25 +431,22 @@ Player::Player(const ServerInfo_User &info, int _id, bool _local, bool _judge, T if (local) { sayMenu = playerMenu->addMenu(QString()); - connect(&SettingsCache::instance().messages(), SIGNAL(messageMacrosChanged()), this, SLOT(initSayMenu())); + connect(&SettingsCache::instance().messages(), &MessageSettings::messageMacrosChanged, this, + &Player::initSayMenu); initSayMenu(); } - if (local || judge) { - aCardMenu = new QAction(this); - aCardMenu->setEnabled(false); - playerMenu->addSeparator(); - playerMenu->addAction(aCardMenu); - } else { - aCardMenu = nullptr; - } + aCardMenu = new QAction(this); + aCardMenu->setEnabled(false); + playerMenu->addSeparator(); + playerMenu->addAction(aCardMenu); if (local || judge) { for (auto &playerList : playerLists) { QAction *newAction = playerList->addAction(QString()); newAction->setData(-1); - connect(newAction, SIGNAL(triggered()), this, SLOT(playerListActionTriggered())); + connect(newAction, &QAction::triggered, this, &Player::playerListActionTriggered); allPlayersActions.append(newAction); playerList->addSeparator(); } @@ -453,47 +461,47 @@ Player::Player(const ServerInfo_User &info, int _id, bool _local, bool _judge, T aTap = new QAction(this); aTap->setData(cmTap); - connect(aTap, SIGNAL(triggered()), this, SLOT(cardMenuAction())); + connect(aTap, &QAction::triggered, this, &Player::cardMenuAction); aDoesntUntap = new QAction(this); aDoesntUntap->setData(cmDoesntUntap); - connect(aDoesntUntap, SIGNAL(triggered()), this, SLOT(cardMenuAction())); + connect(aDoesntUntap, &QAction::triggered, this, &Player::cardMenuAction); aAttach = new QAction(this); - connect(aAttach, SIGNAL(triggered()), this, SLOT(actAttach())); + connect(aAttach, &QAction::triggered, this, &Player::actAttach); aUnattach = new QAction(this); - connect(aUnattach, SIGNAL(triggered()), this, SLOT(actUnattach())); + connect(aUnattach, &QAction::triggered, this, &Player::actUnattach); aDrawArrow = new QAction(this); - connect(aDrawArrow, SIGNAL(triggered()), this, SLOT(actDrawArrow())); + connect(aDrawArrow, &QAction::triggered, this, &Player::actDrawArrow); aIncP = new QAction(this); - connect(aIncP, SIGNAL(triggered()), this, SLOT(actIncP())); + connect(aIncP, &QAction::triggered, this, &Player::actIncP); aDecP = new QAction(this); - connect(aDecP, SIGNAL(triggered()), this, SLOT(actDecP())); + connect(aDecP, &QAction::triggered, this, &Player::actDecP); aIncT = new QAction(this); - connect(aIncT, SIGNAL(triggered()), this, SLOT(actIncT())); + connect(aIncT, &QAction::triggered, this, &Player::actIncT); aDecT = new QAction(this); - connect(aDecT, SIGNAL(triggered()), this, SLOT(actDecT())); + connect(aDecT, &QAction::triggered, this, &Player::actDecT); aIncPT = new QAction(this); - connect(aIncPT, SIGNAL(triggered()), this, SLOT(actIncPT())); + connect(aIncPT, &QAction::triggered, this, [this] { actIncPT(); }); aDecPT = new QAction(this); - connect(aDecPT, SIGNAL(triggered()), this, SLOT(actDecPT())); + connect(aDecPT, &QAction::triggered, this, &Player::actDecPT); aFlowP = new QAction(this); - connect(aFlowP, SIGNAL(triggered()), this, SLOT(actFlowP())); + connect(aFlowP, &QAction::triggered, this, &Player::actFlowP); aFlowT = new QAction(this); - connect(aFlowT, SIGNAL(triggered()), this, SLOT(actFlowT())); + connect(aFlowT, &QAction::triggered, this, &Player::actFlowT); aSetPT = new QAction(this); - connect(aSetPT, SIGNAL(triggered()), this, SLOT(actSetPT())); + connect(aSetPT, &QAction::triggered, this, &Player::actSetPT); aResetPT = new QAction(this); - connect(aResetPT, SIGNAL(triggered()), this, SLOT(actResetPT())); + connect(aResetPT, &QAction::triggered, this, &Player::actResetPT); aSetAnnotation = new QAction(this); - connect(aSetAnnotation, SIGNAL(triggered()), this, SLOT(actSetAnnotation())); + connect(aSetAnnotation, &QAction::triggered, this, &Player::actSetAnnotation); aFlip = new QAction(this); aFlip->setData(cmFlip); - connect(aFlip, SIGNAL(triggered()), this, SLOT(cardMenuAction())); + connect(aFlip, &QAction::triggered, this, &Player::cardMenuAction); aPeek = new QAction(this); aPeek->setData(cmPeek); - connect(aPeek, SIGNAL(triggered()), this, SLOT(cardMenuAction())); + connect(aPeek, &QAction::triggered, this, &Player::cardMenuAction); aClone = new QAction(this); aClone->setData(cmClone); - connect(aClone, SIGNAL(triggered()), this, SLOT(cardMenuAction())); + connect(aClone, &QAction::triggered, this, &Player::cardMenuAction); aMoveToTopLibrary = new QAction(this); aMoveToTopLibrary->setData(cmMoveToTopLibrary); aMoveToBottomLibrary = new QAction(this); @@ -505,19 +513,26 @@ Player::Player(const ServerInfo_User &info, int _id, bool _local, bool _judge, T aMoveToGraveyard->setData(cmMoveToGraveyard); aMoveToExile = new QAction(this); aMoveToExile->setData(cmMoveToExile); - connect(aMoveToTopLibrary, SIGNAL(triggered()), this, SLOT(cardMenuAction())); - connect(aMoveToBottomLibrary, SIGNAL(triggered()), this, SLOT(cardMenuAction())); - connect(aMoveToXfromTopOfLibrary, SIGNAL(triggered()), this, SLOT(actMoveCardXCardsFromTop())); - connect(aMoveToHand, SIGNAL(triggered()), this, SLOT(cardMenuAction())); - connect(aMoveToGraveyard, SIGNAL(triggered()), this, SLOT(cardMenuAction())); - connect(aMoveToExile, SIGNAL(triggered()), this, SLOT(cardMenuAction())); + connect(aMoveToTopLibrary, &QAction::triggered, this, &Player::cardMenuAction); + connect(aMoveToBottomLibrary, &QAction::triggered, this, &Player::cardMenuAction); + connect(aMoveToXfromTopOfLibrary, &QAction::triggered, this, &Player::actMoveCardXCardsFromTop); + connect(aMoveToHand, &QAction::triggered, this, &Player::cardMenuAction); + connect(aMoveToGraveyard, &QAction::triggered, this, &Player::cardMenuAction); + connect(aMoveToExile, &QAction::triggered, this, &Player::cardMenuAction); + + aSelectAll = new QAction(this); + connect(aSelectAll, &QAction::triggered, this, &Player::actSelectAll); + aSelectRow = new QAction(this); + connect(aSelectRow, &QAction::triggered, this, &Player::actSelectRow); + aSelectColumn = new QAction(this); + connect(aSelectColumn, &QAction::triggered, this, &Player::actSelectColumn); aPlay = new QAction(this); - connect(aPlay, SIGNAL(triggered()), this, SLOT(actPlay())); + connect(aPlay, &QAction::triggered, this, &Player::actPlay); aHide = new QAction(this); - connect(aHide, SIGNAL(triggered()), this, SLOT(actHide())); + connect(aHide, &QAction::triggered, this, &Player::actHide); aPlayFacedown = new QAction(this); - connect(aPlayFacedown, SIGNAL(triggered()), this, SLOT(actPlayFacedown())); + connect(aPlayFacedown, &QAction::triggered, this, &Player::actPlayFacedown); for (int i = 0; i < 3; ++i) { auto *tempAddCounter = new QAction(this); @@ -529,9 +544,9 @@ Player::Player(const ServerInfo_User &info, int _id, bool _local, bool _judge, T aAddCounter.append(tempAddCounter); aRemoveCounter.append(tempRemoveCounter); aSetCounter.append(tempSetCounter); - connect(tempAddCounter, SIGNAL(triggered()), this, SLOT(actCardCounterTrigger())); - connect(tempRemoveCounter, SIGNAL(triggered()), this, SLOT(actCardCounterTrigger())); - connect(tempSetCounter, SIGNAL(triggered()), this, SLOT(actCardCounterTrigger())); + connect(tempAddCounter, &QAction::triggered, this, &Player::actCardCounterTrigger); + connect(tempRemoveCounter, &QAction::triggered, this, &Player::actCardCounterTrigger); + connect(tempSetCounter, &QAction::triggered, this, &Player::actCardCounterTrigger); } const QList &players = game->getPlayers().values(); @@ -546,17 +561,15 @@ Player::Player(const ServerInfo_User &info, int _id, bool _local, bool _judge, T rearrangeZones(); retranslateUi(); - connect(&SettingsCache::instance().shortcuts(), SIGNAL(shortCutChanged()), this, SLOT(refreshShortcuts())); + connect(&SettingsCache::instance().shortcuts(), &ShortcutsSettings::shortCutChanged, this, + &Player::refreshShortcuts); refreshShortcuts(); } Player::~Player() { - qDebug() << "Player destructor:" << getName(); + qCInfo(PlayerLog) << "Player destructor:" << getName(); - static_cast(scene())->removePlayer(this); - - clear(); QMapIterator i(zones); while (i.hasNext()) delete i.next().value(); @@ -598,7 +611,7 @@ void Player::addPlayerToList(QMenu *playerList, Player *player) { QAction *newAction = playerList->addAction(player->getName()); newAction->setData(player->getId()); - connect(newAction, SIGNAL(triggered()), this, SLOT(playerListActionTriggered())); + connect(newAction, &QAction::triggered, this, &Player::playerListActionTriggered); } void Player::removePlayer(Player *player) @@ -658,8 +671,8 @@ void Player::playerListActionTriggered() cmd.set_top_cards(number); // backward compatibility: servers before #1051 only permits to reveal the first card cmd.add_card_id(0); + defaultNumberTopCards = number; } - } else if (menu == mRevealHand) { cmd.set_zone_name("hand"); } else if (menu == mRevealRandomHandCard) { @@ -771,6 +784,7 @@ void Player::retranslateUi() aViewLibrary->setText(tr("&View library")); aViewHand->setText(tr("&View hand")); aViewTopCards->setText(tr("View &top cards of library...")); + aViewBottomCards->setText(tr("View bottom cards of library...")); mRevealLibrary->setTitle(tr("Reveal &library to...")); mLendLibrary->setTitle(tr("Lend library to...")); mRevealTopCard->setTitle(tr("Reveal &top cards to...")); @@ -784,7 +798,8 @@ void Player::retranslateUi() aDrawCards->setText(tr("D&raw cards...")); aUndoDraw->setText(tr("&Undo last draw")); aMulligan->setText(tr("Take &mulligan")); - aShuffle->setText(tr("&Shuffle")); + + aShuffle->setText(tr("Shuffle")); aMoveTopToPlay->setText(tr("&Play top card")); aMoveTopToPlayFaceDown->setText(tr("Play top card &face down")); @@ -794,6 +809,7 @@ void Player::retranslateUi() aMoveTopCardsToGraveyard->setText(tr("Move top cards to &graveyard...")); aMoveTopCardsToExile->setText(tr("Move top cards to &exile...")); aMoveTopCardsUntil->setText(tr("Put top cards on stack &until...")); + aShuffleTopCards->setText(tr("Shuffle top cards...")); aDrawBottomCard->setText(tr("&Draw bottom card")); aDrawBottomCards->setText(tr("D&raw bottom cards...")); @@ -804,6 +820,7 @@ void Player::retranslateUi() aMoveBottomCardsToGraveyard->setText(tr("Move bottom cards to &graveyard...")); aMoveBottomCardsToExile->setText(tr("Move bottom cards to &exile...")); aMoveBottomCardToTop->setText(tr("Put bottom card on &top")); + aShuffleBottomCards->setText(tr("Shuffle bottom cards...")); handMenu->setTitle(tr("&Hand")); mRevealHand->setTitle(tr("&Reveal hand to...")); @@ -824,17 +841,21 @@ void Player::retranslateUi() counterIterator.next().value()->retranslateUi(); } - aCardMenu->setText(tr("Selec&ted cards")); - for (auto &allPlayersAction : allPlayersActions) { allPlayersAction->setText(tr("&All players")); } } + aCardMenu->setText(tr("Selec&ted cards")); + if (local) { sayMenu->setTitle(tr("S&ay")); } + aSelectAll->setText(tr("&Select All")); + aSelectRow->setText(tr("S&elect Row")); + aSelectColumn->setText(tr("S&elect Column")); + aPlay->setText(tr("&Play")); aHide->setText(tr("&Hide")); aPlayFacedown->setText(tr("Play &Face Down")); @@ -895,6 +916,7 @@ void Player::setShortcutsActive() shortcutsActive = true; ShortcutsSettings &shortcuts = SettingsCache::instance().shortcuts(); + aHide->setShortcuts(shortcuts.getShortcut("Player/aHide")); aPlay->setShortcuts(shortcuts.getShortcut("Player/aPlay")); aTap->setShortcuts(shortcuts.getShortcut("Player/aTap")); aDoesntUntap->setShortcuts(shortcuts.getShortcut("Player/aDoesntUntap")); @@ -921,6 +943,10 @@ void Player::setShortcutsActive() aMoveToGraveyard->setShortcuts(shortcuts.getShortcut("Player/aMoveToGraveyard")); aMoveToExile->setShortcuts(shortcuts.getShortcut("Player/aMoveToExile")); + aSelectAll->setShortcuts(shortcuts.getShortcut("Player/aSelectAll")); + aSelectRow->setShortcuts(shortcuts.getShortcut("Player/aSelectRow")); + aSelectColumn->setShortcuts(shortcuts.getShortcut("Player/aSelectColumn")); + QList addCCShortCuts; addCCShortCuts.append(shortcuts.getSingleShortcut("Player/aCCRed")); addCCShortCuts.append(shortcuts.getSingleShortcut("Player/aCCYellow")); @@ -955,12 +981,15 @@ void Player::setShortcutsActive() aViewLibrary->setShortcut(shortcuts.getSingleShortcut("Player/aViewLibrary")); aViewHand->setShortcut(shortcuts.getSingleShortcut("Player/aViewHand")); aViewTopCards->setShortcut(shortcuts.getSingleShortcut("Player/aViewTopCards")); + aViewBottomCards->setShortcut(shortcuts.getSingleShortcut("Player/aViewBottomCards")); aViewGraveyard->setShortcut(shortcuts.getSingleShortcut("Player/aViewGraveyard")); aDrawCard->setShortcut(shortcuts.getSingleShortcut("Player/aDrawCard")); aDrawCards->setShortcut(shortcuts.getSingleShortcut("Player/aDrawCards")); aUndoDraw->setShortcut(shortcuts.getSingleShortcut("Player/aUndoDraw")); aMulligan->setShortcut(shortcuts.getSingleShortcut("Player/aMulligan")); aShuffle->setShortcut(shortcuts.getSingleShortcut("Player/aShuffle")); + aShuffleTopCards->setShortcut(shortcuts.getSingleShortcut("Player/aShuffleTopCards")); + aShuffleBottomCards->setShortcut(shortcuts.getSingleShortcut("Player/aShuffleBottomCards")); aUntapAll->setShortcut(shortcuts.getSingleShortcut("Player/aUntapAll")); aRollDie->setShortcut(shortcuts.getSingleShortcut("Player/aRollDie")); aCreateToken->setShortcut(shortcuts.getSingleShortcut("Player/aCreateToken")); @@ -986,6 +1015,14 @@ void Player::setShortcutsActive() aMoveBottomCardToTop->setShortcut(shortcuts.getSingleShortcut("Player/aMoveBottomCardToTop")); aPlayFacedown->setShortcut(shortcuts.getSingleShortcut("Player/aPlayFacedown")); aPlay->setShortcut(shortcuts.getSingleShortcut("Player/aPlay")); + + // Don't enable always-active shortcuts in local games, since it causes keyboard shortcuts to work inconsistently + // when there are more than 1 player. + if (!game->getIsLocalGame()) { + // unattach action is only active in card menu if the active card is attached. + // make unattach shortcut always active so that it consistently works when multiple cards are selected. + game->addAction(aUnattach); + } } void Player::setShortcutsInactive() @@ -996,12 +1033,15 @@ void Player::setShortcutsInactive() aViewLibrary->setShortcut(QKeySequence()); aViewHand->setShortcut(QKeySequence()); aViewTopCards->setShortcut(QKeySequence()); + aViewBottomCards->setShortcut(QKeySequence()); aViewGraveyard->setShortcut(QKeySequence()); aDrawCard->setShortcut(QKeySequence()); aDrawCards->setShortcut(QKeySequence()); aUndoDraw->setShortcut(QKeySequence()); aMulligan->setShortcut(QKeySequence()); aShuffle->setShortcut(QKeySequence()); + aShuffleTopCards->setShortcut(QKeySequence()); + aShuffleBottomCards->setShortcut(QKeySequence()); aUntapAll->setShortcut(QKeySequence()); aRollDie->setShortcut(QKeySequence()); aCreateToken->setShortcut(QKeySequence()); @@ -1042,7 +1082,7 @@ void Player::initSayMenu() if (i < 10) { newAction->setShortcut(QKeySequence("Ctrl+" + QString::number((i + 1) % 10))); } - connect(newAction, SIGNAL(triggered()), this, SLOT(actSayMessage())); + connect(newAction, &QAction::triggered, this, &Player::actSayMessage); sayMenu->addAction(newAction); } } @@ -1078,7 +1118,7 @@ void Player::setDeck(const DeckLoader &_deck) if (i < 10) { a->setShortcut(QKeySequence("Alt+" + QString::number((i + 1) % 10))); } - connect(a, SIGNAL(triggered()), this, SLOT(actCreatePredefinedToken())); + connect(a, &QAction::triggered, this, &Player::actCreatePredefinedToken); } } } @@ -1106,6 +1146,19 @@ void Player::actViewTopCards() } } +void Player::actViewBottomCards() +{ + int deckSize = zones.value("deck")->getCards().size(); + bool ok; + int number = + QInputDialog::getInt(game, tr("View bottom cards of library"), tr("Number of cards: (max. %1)").arg(deckSize), + defaultNumberBottomCards, 1, deckSize, 1, &ok); + if (ok) { + defaultNumberBottomCards = number; + static_cast(scene())->toggleZoneView(this, "deck", number, true); + } +} + void Player::actAlwaysRevealTopCard() { Command_ChangeZoneProperties cmd; @@ -1162,6 +1215,64 @@ void Player::actShuffle() sendGameCommand(Command_Shuffle()); } +void Player::actShuffleTop() +{ + const int maxCards = zones.value("deck")->getCards().size(); + if (maxCards == 0) { + return; + } + + bool ok; + int number = + QInputDialog::getInt(game, tr("Shuffle top cards of library"), tr("Number of cards: (max. %1)").arg(maxCards), + defaultNumberTopCards, 1, maxCards, 1, &ok); + if (!ok) { + return; + } + + if (number > maxCards) { + number = maxCards; + } + + defaultNumberTopCards = number; + + Command_Shuffle cmd; + cmd.set_zone_name("deck"); + cmd.set_start(0); + cmd.set_end(number - 1); // inclusive, the indexed card at end will be shuffled + + sendGameCommand(cmd); +} + +void Player::actShuffleBottom() +{ + const int maxCards = zones.value("deck")->getCards().size(); + if (maxCards == 0) { + return; + } + + bool ok; + int number = QInputDialog::getInt(game, tr("Shuffle bottom cards of library"), + tr("Number of cards: (max. %1)").arg(maxCards), defaultNumberBottomCards, 1, + maxCards, 1, &ok); + if (!ok) { + return; + } + + if (number > maxCards) { + number = maxCards; + } + + defaultNumberBottomCards = number; + + Command_Shuffle cmd; + cmd.set_zone_name("deck"); + cmd.set_start(-number); + cmd.set_end(-1); + + sendGameCommand(cmd); +} + void Player::actDrawCard() { Command_DrawCards cmd; @@ -1330,45 +1441,64 @@ void Player::actMoveTopCardsToExile() void Player::actMoveTopCardsUntil() { - moveTopCardTimer->stop(); - movingCardsUntil = false; - QString expr = previousMovingCardsUntilExpr; - for (;;) { - bool ok; - expr = QInputDialog::getText(game, "Put top cards on stack until", "Card name (or search expressions)", {}, - expr, &ok); - if (!ok) { - return; - } - movingCardsUntilFilter = FilterString(expr); - if (movingCardsUntilFilter.valid()) { - break; - } else { - auto button = QMessageBox::warning(game, "Invalid filter", movingCardsUntilFilter.error()); - if (button != QMessageBox::Ok) { - return; - } - } + stopMoveTopCardsUntil(); + + DlgMoveTopCardsUntil dlg(game, movingCardsUntilExprs, movingCardsUntilNumberOfHits, movingCardsUntilAutoPlay); + if (!dlg.exec()) { + return; } - previousMovingCardsUntilExpr = expr; + + auto expr = dlg.getExpr(); + movingCardsUntilExprs = dlg.getExprs(); + movingCardsUntilNumberOfHits = dlg.getNumberOfHits(); + movingCardsUntilAutoPlay = dlg.isAutoPlay(); + if (zones.value("deck")->getCards().empty()) { - movingCardsUntil = false; + stopMoveTopCardsUntil(); } else { + movingCardsUntilFilter = FilterString(expr); + movingCardsUntilCounter = movingCardsUntilNumberOfHits; movingCardsUntil = true; actMoveTopCardToPlay(); } } -void Player::moveOneCardUntil(const CardInfoPtr card) +void Player::moveOneCardUntil(CardItem *card) { moveTopCardTimer->stop(); - if (zones.value("deck")->getCards().empty() || card.isNull() || movingCardsUntilFilter.check(card)) { - movingCardsUntil = false; + + const bool isMatch = card && movingCardsUntilFilter.check(card->getInfo()); + + if (isMatch && movingCardsUntilAutoPlay) { + // Directly calling playCard will deadlock, since we are already in the middle of processing an event. + // Use QTimer::singleShot to queue up the playCard on the event loop. + QTimer::singleShot(0, this, [card, this] { playCard(card, false); }); + } + + if (zones.value("deck")->getCards().empty() || !card) { + stopMoveTopCardsUntil(); + } else if (isMatch) { + --movingCardsUntilCounter; + if (movingCardsUntilCounter > 0) { + moveTopCardTimer->start(); + } else { + stopMoveTopCardsUntil(); + } } else { moveTopCardTimer->start(); } } +/** + * @brief Immediately stops any ongoing `play top card to stack until...` process, resetting all variables involved. + */ +void Player::stopMoveTopCardsUntil() +{ + moveTopCardTimer->stop(); + movingCardsUntilCounter = 0; + movingCardsUntil = false; +} + void Player::actMoveTopCardToBottom() { if (zones.value("deck")->getCards().empty()) { @@ -1527,6 +1657,61 @@ void Player::actMoveBottomCardToTop() sendGameCommand(cmd); } +/** + * Selects all cards in the given zone. + * + * @param zone The zone to select from + * @param filter A predicate to filter which cards are selected. Defaults to always returning true. + */ +static void selectCardsInZone( + const CardZone *zone, + std::function filter = [](const CardItem *) { return true; }) +{ + if (!zone) { + return; + } + + for (auto &cardItem : zone->getCards()) { + if (cardItem && filter(cardItem)) { + cardItem->setSelected(true); + } + } +} + +void Player::actSelectAll() +{ + const CardItem *card = game->getActiveCard(); + if (!card) { + return; + } + + selectCardsInZone(card->getZone()); +} + +void Player::actSelectRow() +{ + const CardItem *card = game->getActiveCard(); + if (!card) { + return; + } + + auto isSameRow = [card](const CardItem *cardItem) { + return qAbs(card->scenePos().y() - cardItem->scenePos().y()) < 50; + }; + selectCardsInZone(card->getZone(), isSameRow); +} + +void Player::actSelectColumn() +{ + const CardItem *card = game->getActiveCard(); + if (!card) { + return; + } + + auto isSameColumn = [card](const CardItem *cardItem) { return cardItem->x() == card->x(); }; + selectCardsInZone(card->getZone(), isSameColumn); +} + void Player::actDrawBottomCard() { if (zones.value("deck")->getCards().empty()) { @@ -1693,7 +1878,7 @@ void Player::actCreatePredefinedToken() void Player::actCreateRelatedCard() { - CardItem *sourceCard = game->getActiveCard(); + const CardItem *sourceCard = game->getActiveCard(); if (!sourceCard) { return; } @@ -1707,14 +1892,15 @@ void Player::actCreateRelatedCard() * then let's allow it to be created via "create another token" */ if (createRelatedFromRelation(sourceCard, cardRelation) && cardRelation->getCanCreateAnother()) { - CardInfoPtr cardInfo = CardDatabaseManager::getInstance()->getCard(cardRelation->getName()); + CardInfoPtr cardInfo = CardDatabaseManager::getInstance()->getCardByNameAndProviderId( + cardRelation->getName(), sourceCard->getProviderId()); setLastToken(cardInfo); } } void Player::actCreateAllRelatedCards() { - CardItem *sourceCard = game->getActiveCard(); + const CardItem *sourceCard = game->getActiveCard(); if (!sourceCard) { return; } @@ -1816,7 +2002,15 @@ bool Player::createRelatedFromRelation(const CardItem *sourceCard, const CardRel createCard(sourceCard, dbName, CardRelation::DoesNotAttach, persistent); } } else { - createCard(sourceCard, dbName, cardRelation->getAttachType(), persistent); + auto attachType = cardRelation->getAttachType(); + + // move card onto table first if attaching from some other zone + // we only do this for AttachTo because cross-zone TransformInto is already handled server-side + if (attachType == CardRelation::AttachTo && sourceCard->getZone()->getName() != "table") { + playCardToTable(sourceCard, false); + } + + createCard(sourceCard, dbName, attachType, persistent); } return true; } @@ -1859,22 +2053,26 @@ void Player::createCard(const CardItem *sourceCard, cmd.set_annotation(""); } cmd.set_destroy_on_zone_change(!persistent); - cmd.set_target_zone(sourceCard->getZone()->getName().toStdString()); cmd.set_x(gridPoint.x()); cmd.set_y(gridPoint.y()); switch (attachType) { case CardRelation::DoesNotAttach: + cmd.set_target_zone("table"); break; case CardRelation::AttachTo: + cmd.set_target_zone("table"); // We currently only support creating tokens on the table cmd.set_target_card_id(sourceCard->getId()); cmd.set_target_mode(Command_CreateToken::ATTACH_TO); break; case CardRelation::TransformInto: + // Transform card zone changes are handled server-side + cmd.set_target_zone(sourceCard->getZone()->getName().toStdString()); cmd.set_target_card_id(sourceCard->getId()); cmd.set_target_mode(Command_CreateToken::TRANSFORM_INTO); + cmd.set_card_provider_id(sourceCard->getProviderId().toStdString()); break; } @@ -1893,7 +2091,8 @@ void Player::setCardAttrHelper(const GameEventContext &context, CardItem *card, CardAttribute attribute, const QString &avalue, - bool allCards) + bool allCards, + EventProcessingOptions options) { if (card == nullptr) { return; @@ -1907,7 +2106,8 @@ void Player::setCardAttrHelper(const GameEventContext &context, if (!allCards) { emit logSetTapped(this, card, tapped); } - card->setTapped(tapped, !moveCardContext); + bool canAnimate = !options.testFlag(SKIP_TAP_ANIMATION) && !moveCardContext; + card->setTapped(tapped, canAnimate); } break; } @@ -1965,7 +2165,7 @@ void Player::eventShuffle(const Event_Shuffle &event) int length = view->getCards().length(); // we want to close empty views as well if (length == 0 || length > absStart) { // note this assumes views always start at the top of the library - view->deleteLater(); + view->close(); break; } } else { @@ -2031,7 +2231,8 @@ void Player::eventCreateToken(const Event_CreateToken &event) return; } - CardItem *card = new CardItem(this, nullptr, QString::fromStdString(event.card_name()), QString(), event.card_id()); + CardItem *card = new CardItem(this, nullptr, QString::fromStdString(event.card_name()), + QString::fromStdString(event.card_provider_id()), event.card_id()); // use db PT if not provided in event if (!QString::fromStdString(event.pt()).isEmpty()) { card->setPT(QString::fromStdString(event.pt())); @@ -2049,7 +2250,9 @@ void Player::eventCreateToken(const Event_CreateToken &event) zone->addCard(card, true, event.x(), event.y()); } -void Player::eventSetCardAttr(const Event_SetCardAttr &event, const GameEventContext &context) +void Player::eventSetCardAttr(const Event_SetCardAttr &event, + const GameEventContext &context, + EventProcessingOptions options) { CardZone *zone = zones.value(QString::fromStdString(event.zone_name()), 0); if (!zone) { @@ -2059,8 +2262,8 @@ void Player::eventSetCardAttr(const Event_SetCardAttr &event, const GameEventCon if (!event.has_card_id()) { const CardList &cards = zone->getCards(); for (int i = 0; i < cards.size(); ++i) { - setCardAttrHelper(context, cards.at(i), event.attribute(), QString::fromStdString(event.attr_value()), - true); + setCardAttrHelper(context, cards.at(i), event.attribute(), QString::fromStdString(event.attr_value()), true, + options); } if (event.attribute() == AttrTapped) { emit logSetTapped(this, nullptr, event.attr_value() == "1"); @@ -2071,7 +2274,7 @@ void Player::eventSetCardAttr(const Event_SetCardAttr &event, const GameEventCon qWarning() << "Player::eventSetCardAttr: card id=" << event.card_id() << "not found"; return; } - setCardAttrHelper(context, card, event.attribute(), QString::fromStdString(event.attr_value()), false); + setCardAttrHelper(context, card, event.attribute(), QString::fromStdString(event.attr_value()), false, options); } } @@ -2124,7 +2327,7 @@ void Player::eventDumpZone(const Event_DumpZone &event) if (!zone) { return; } - emit logDumpZone(this, zone, event.number_cards()); + emit logDumpZone(this, zone, event.number_cards(), event.is_reversed()); } void Player::eventMoveCard(const Event_MoveCard &event, const GameEventContext &context) @@ -2231,7 +2434,7 @@ void Player::eventMoveCard(const Event_MoveCard &event, const GameEventContext & updateCardMenu(card); if (movingCardsUntil && startZoneString == "deck" && targetZone->getName() == "stack") { - moveOneCardUntil(card->getInfo()); + moveOneCardUntil(card); } } @@ -2386,8 +2589,12 @@ void Player::eventRevealCards(const Event_RevealCards &event, EventProcessingOpt auto cardId = event.card_id_size() == 0 ? -1 : event.card_id(0); if (cardList.size() == 1) { cardName = QString::fromStdString(cardList.first()->name()); - if ((cardId == 0) && dynamic_cast(zone)) { - zone->getCards().first()->setName(cardName); + + // Handle case of revealing top card of library in-place + if (cardId == 0 && dynamic_cast(zone)) { + auto card = zone->getCards().first(); + card->setName(cardName); + card->setProviderId(QString::fromStdString(cardList.first()->provider_id())); zone->update(); showZoneView = false; } @@ -2444,7 +2651,7 @@ void Player::processGameEvent(GameEvent::GameEventType type, eventCreateToken(event.GetExtension(Event_CreateToken::ext)); break; case GameEvent::SET_CARD_ATTR: - eventSetCardAttr(event.GetExtension(Event_SetCardAttr::ext), context); + eventSetCardAttr(event.GetExtension(Event_SetCardAttr::ext), context, options); break; case GameEvent::SET_CARD_COUNTER: eventSetCardCounter(event.GetExtension(Event_SetCardCounter::ext)); @@ -2583,7 +2790,7 @@ void Player::processCardAttachment(const ServerInfo_Player &info) } } -void Player::playCard(CardItem *card, bool faceDown, bool tapped) +void Player::playCard(CardItem *card, bool faceDown) { if (card == nullptr) { return; @@ -2620,7 +2827,7 @@ void Player::playCard(CardItem *card, bool faceDown, bool tapped) if (!faceDown) { cardToMove->set_pt(info->getPowTough().toStdString()); } - cardToMove->set_tapped(faceDown ? false : tapped); + cardToMove->set_tapped(!faceDown && info->getCipt()); if (tableRow != 3) cmd.set_target_zone("table"); cmd.set_x(gridPoint.x()); @@ -2629,6 +2836,46 @@ void Player::playCard(CardItem *card, bool faceDown, bool tapped) sendGameCommand(cmd); } +/** + * Like {@link Player::playCard}, but forces the card to be played to the table zone. + * Cards with tablerow 3 (the stack) will be played to tablerow 1 (the noncreatures row). + */ +void Player::playCardToTable(const CardItem *card, bool faceDown) +{ + if (card == nullptr) { + return; + } + + Command_MoveCard cmd; + cmd.set_start_player_id(card->getZone()->getPlayer()->getId()); + cmd.set_start_zone(card->getZone()->getName().toStdString()); + cmd.set_target_player_id(getId()); + CardToMove *cardToMove = cmd.mutable_cards_to_move()->add_card(); + cardToMove->set_card_id(card->getId()); + + CardInfoPtr info = card->getInfo(); + if (!info) { + return; + } + + int tableRow = faceDown ? 2 : info->getTableRow(); + // default instant/sorcery cards to the noncreatures row + if (tableRow > 2) { + tableRow = 1; + } + + QPoint gridPoint = QPoint(-1, TableZone::clampValidTableRow(2 - tableRow)); + cardToMove->set_face_down(faceDown); + if (!faceDown) { + cardToMove->set_pt(info->getPowTough().toStdString()); + } + cardToMove->set_tapped(!faceDown && info->getCipt()); + cmd.set_target_zone("table"); + cmd.set_x(gridPoint.x()); + cmd.set_y(gridPoint.y()); + sendGameCommand(cmd); +} + void Player::addCard(CardItem *card) { emit newCardAdded(card); @@ -2966,6 +3213,7 @@ void Player::cardMenuAction() auto *cmd = new Command_CreateToken; cmd->set_zone("table"); cmd->set_card_name(card->getName().toStdString()); + cmd->set_card_provider_id(card->getProviderId().toStdString()); cmd->set_color(card->getColor().toStdString()); cmd->set_pt(card->getPT().toStdString()); cmd->set_annotation(card->getAnnotation().toStdString()); @@ -2980,12 +3228,23 @@ void Player::cardMenuAction() } } } else { + CardZone *zone = cardList[0]->getZone(); + if (!zone) { + return; + } + + Player *startPlayer = zone->getPlayer(); + if (!startPlayer) { + return; + } + + int startPlayerId = startPlayer->getId(); + QString startZone = zone->getName(); + ListOfCardsToMove idList; for (const auto &i : cardList) { idList.add_card()->set_card_id(i->getId()); } - int startPlayerId = cardList[0]->getZone()->getPlayer()->getId(); - QString startZone = cardList[0]->getZone()->getName(); switch (static_cast(a->data().toInt())) { case cmMoveToTopLibrary: { @@ -3002,7 +3261,7 @@ void Player::cardMenuAction() auto *scmd = new Command_Shuffle; scmd->set_zone_name("deck"); scmd->set_start(0); - scmd->set_end(idList.card_size()); + scmd->set_end(idList.card_size() - 1); // inclusive, the indexed card at end will be shuffled // Server process events backwards, so... commandList.append(scmd); } @@ -3243,11 +3502,10 @@ void Player::actSetPT() void Player::actDrawArrow() { - if (!game->getActiveCard()) { - return; + auto *card = game->getActiveCard(); + if (card) { + card->drawArrow(Qt::red); } - - game->getActiveCard()->drawArrow(Qt::red); } void Player::actIncP() @@ -3349,13 +3607,14 @@ void Player::actAttach() void Player::actUnattach() { - if (!game->getActiveCard()) { - return; - } - QList commandList; for (QGraphicsItem *item : scene()->selectedItems()) { auto *card = static_cast(item); + + if (!card->getAttachedTo()) { + continue; + } + auto *cmd = new Command_AttachCard; cmd->set_start_zone(card->getZone()->getName().toStdString()); cmd->set_card_id(card->getId()); @@ -3430,32 +3689,58 @@ void Player::actCardCounterTrigger() sendGameCommand(prepareGameCommand(commandList)); } -void Player::actPlay() +/** + * @brief returns true if the zone is a unwritable reveal zone view (eg a card reveal window). Will return false if zone + * is nullptr. + */ +static bool isUnwritableRevealZone(CardZone *zone) { - if (!game->getActiveCard()) { - return; + if (zone && zone->getIsView()) { + auto *view = static_cast(zone); + return view && view->getRevealZone() && !view->getWriteableRevealZone(); } - - bool cipt = game->getActiveCard()->getInfo() ? game->getActiveCard()->getInfo()->getCipt() : false; - playCard(game->getActiveCard(), false, cipt); + return false; } -void Player::actHide() +void Player::playSelectedCards(const bool faceDown) { - if (!game->getActiveCard()) { - return; + QList selectedCards; + for (const auto &item : scene()->selectedItems()) { + auto *card = static_cast(item); + selectedCards.append(card); } - game->getActiveCard()->getZone()->removeCard(game->getActiveCard()); + // CardIds will get shuffled downwards when cards leave the deck. + // We need to iterate through the cards in reverse order so cardIds don't get changed out from under us as we play + // out the cards one-by-one. + std::sort(selectedCards.begin(), selectedCards.end(), + [](const auto &card1, const auto &card2) { return card1->getId() > card2->getId(); }); + + for (auto &card : selectedCards) { + if (card && !isUnwritableRevealZone(card->getZone()) && card->getZone()->getName() != "table") { + playCard(card, faceDown); + } + } +} + +void Player::actPlay() +{ + playSelectedCards(false); } void Player::actPlayFacedown() { - if (!game->getActiveCard()) { - return; - } + playSelectedCards(true); +} - playCard(game->getActiveCard(), true, false); +void Player::actHide() +{ + for (const auto &item : scene()->selectedItems()) { + auto *card = static_cast(item); + if (card && isUnwritableRevealZone(card->getZone())) { + card->getZone()->removeCard(card); + } + } } void Player::actReveal(QAction *action) @@ -3515,9 +3800,15 @@ void Player::updateCardMenu(const CardItem *card) if (revealedCard) { cardMenu->addAction(aHide); + cardMenu->addAction(aClone); + cardMenu->addSeparator(); + cardMenu->addAction(aSelectAll); + cardMenu->addAction(aSelectColumn); addRelatedCardView(card, cardMenu); } else if (writeableCard) { - if (moveMenu->isEmpty()) { + bool canModifyCard = judge || card->getOwner() == this; + + if (moveMenu->isEmpty() && canModifyCard) { moveMenu->addAction(aMoveToTopLibrary); moveMenu->addAction(aMoveToXfromTopOfLibrary); moveMenu->addAction(aMoveToBottomLibrary); @@ -3533,6 +3824,20 @@ void Player::updateCardMenu(const CardItem *card) if (card->getZone()->getName() == "table") { // Card is on the battlefield + if (!canModifyCard) { + addRelatedCardView(card, cardMenu); + addRelatedCardActions(card, cardMenu); + + cardMenu->addSeparator(); + cardMenu->addAction(aDrawArrow); + cardMenu->addSeparator(); + cardMenu->addAction(aClone); + cardMenu->addSeparator(); + cardMenu->addAction(aSelectAll); + cardMenu->addAction(aSelectRow); + return; + } + if (ptMenu->isEmpty()) { ptMenu->addAction(aIncP); ptMenu->addAction(aDecP); @@ -3571,6 +3876,9 @@ void Player::updateCardMenu(const CardItem *card) cardMenu->addSeparator(); cardMenu->addAction(aClone); cardMenu->addMenu(moveMenu); + cardMenu->addSeparator(); + cardMenu->addAction(aSelectAll); + cardMenu->addAction(aSelectRow); for (int i = 0; i < aAddCounter.size(); ++i) { cardMenu->addSeparator(); @@ -3583,20 +3891,48 @@ void Player::updateCardMenu(const CardItem *card) cardMenu->addSeparator(); } else if (card->getZone()->getName() == "stack") { // Card is on the stack - cardMenu->addAction(aDrawArrow); - cardMenu->addSeparator(); - cardMenu->addAction(aClone); - cardMenu->addMenu(moveMenu); + if (canModifyCard) { + cardMenu->addAction(aAttach); + cardMenu->addAction(aDrawArrow); + cardMenu->addSeparator(); + cardMenu->addAction(aClone); + cardMenu->addMenu(moveMenu); + cardMenu->addSeparator(); + cardMenu->addAction(aSelectAll); + } else { + cardMenu->addAction(aDrawArrow); + cardMenu->addSeparator(); + cardMenu->addAction(aClone); + cardMenu->addSeparator(); + cardMenu->addAction(aSelectAll); + } addRelatedCardView(card, cardMenu); addRelatedCardActions(card, cardMenu); } else if (card->getZone()->getName() == "rfg" || card->getZone()->getName() == "grave") { // Card is in the graveyard or exile - cardMenu->addAction(aPlay); - cardMenu->addAction(aPlayFacedown); - cardMenu->addSeparator(); - cardMenu->addAction(aClone); - cardMenu->addMenu(moveMenu); + if (canModifyCard) { + cardMenu->addAction(aPlay); + cardMenu->addAction(aPlayFacedown); + + cardMenu->addSeparator(); + cardMenu->addAction(aClone); + cardMenu->addMenu(moveMenu); + cardMenu->addSeparator(); + cardMenu->addAction(aSelectAll); + cardMenu->addAction(aSelectColumn); + + cardMenu->addSeparator(); + cardMenu->addAction(aAttach); + cardMenu->addAction(aDrawArrow); + } else { + cardMenu->addAction(aClone); + cardMenu->addSeparator(); + cardMenu->addAction(aSelectAll); + cardMenu->addAction(aSelectColumn); + cardMenu->addSeparator(); + cardMenu->addAction(aDrawArrow); + } addRelatedCardView(card, cardMenu); addRelatedCardActions(card, cardMenu); @@ -3609,8 +3945,27 @@ void Player::updateCardMenu(const CardItem *card) initContextualPlayersMenu(revealMenu); connect(revealMenu, &QMenu::triggered, this, &Player::actReveal); + cardMenu->addSeparator(); + cardMenu->addAction(aClone); cardMenu->addMenu(moveMenu); + + // actions that are really wonky when done from deck or sideboard + if (card->getZone()->getName() == "hand") { + cardMenu->addSeparator(); + cardMenu->addAction(aAttach); + cardMenu->addAction(aDrawArrow); + } + + cardMenu->addSeparator(); + cardMenu->addAction(aSelectAll); + if (card->getZone()->getIsView()) { + cardMenu->addAction(aSelectColumn); + } + addRelatedCardView(card, cardMenu); + if (card->getZone()->getName() == "hand") { + addRelatedCardActions(card, cardMenu); + } } } else { cardMenu->addMenu(moveMenu); @@ -3623,6 +3978,8 @@ void Player::updateCardMenu(const CardItem *card) addRelatedCardActions(card, cardMenu); cardMenu->addSeparator(); cardMenu->addAction(aClone); + cardMenu->addSeparator(); + cardMenu->addAction(aSelectAll); } } } @@ -3651,13 +4008,17 @@ void Player::addRelatedCardView(const CardItem *card, QMenu *cardMenu) return; } + const auto ¤tCardSet = CardDatabase::getSetInfoForCard(cardInfo); + cardMenu->addSeparator(); auto viewRelatedCards = new QMenu(tr("View related cards")); cardMenu->addMenu(viewRelatedCards); for (const CardRelation *relatedCard : relatedCards) { QString relatedCardName = relatedCard->getName(); QAction *viewCard = viewRelatedCards->addAction(relatedCardName); - connect(viewCard, &QAction::triggered, game, [this, relatedCardName] { game->viewCardInfo(relatedCardName); }); + connect(viewCard, &QAction::triggered, game, [this, relatedCardName, currentCardSet] { + game->viewCardInfo(relatedCardName, currentCardSet.getProperty("uuid")); + }); } } @@ -3676,13 +4037,20 @@ void Player::addRelatedCardActions(const CardItem *card, QMenu *cardMenu) return; } + const auto ¤tCardSet = CardDatabase::getSetInfoForCard(cardInfo); + cardMenu->addSeparator(); int index = 0; QAction *createRelatedCards = nullptr; for (const CardRelation *cardRelation : relatedCards) { - CardInfoPtr relatedCard = CardDatabaseManager::getInstance()->getCard(cardRelation->getName()); - if (relatedCard == nullptr) + CardInfoPtr relatedCard = CardDatabaseManager::getInstance()->getCardByNameAndProviderId( + cardRelation->getName(), currentCardSet.getProperty("uuid")); + if (relatedCard == nullptr) { + relatedCard = CardDatabaseManager::getInstance()->getCard(cardRelation->getName()); + } + if (relatedCard == nullptr) { continue; + } QString relatedCardName; if (relatedCard->getPowTough().size() > 0) { @@ -3714,7 +4082,7 @@ void Player::addRelatedCardActions(const CardItem *card, QMenu *cardMenu) auto *createRelated = new QAction(text, this); createRelated->setData(QVariant(index++)); - connect(createRelated, SIGNAL(triggered()), this, SLOT(actCreateRelatedCard())); + connect(createRelated, &QAction::triggered, this, &Player::actCreateRelatedCard); cardMenu->addAction(createRelated); } @@ -3723,7 +4091,7 @@ void Player::addRelatedCardActions(const CardItem *card, QMenu *cardMenu) createRelatedCards->setShortcut( SettingsCache::instance().shortcuts().getSingleShortcut("Player/aCreateRelatedTokens")); } - connect(createRelatedCards, SIGNAL(triggered()), this, SLOT(actCreateAllRelatedCards())); + connect(createRelatedCards, &QAction::triggered, this, &Player::actCreateAllRelatedCards); cardMenu->addAction(createRelatedCards); } } @@ -3732,7 +4100,9 @@ void Player::setCardMenu(QMenu *menu) { if (aCardMenu != nullptr) { aCardMenu->setEnabled(menu != nullptr); - aCardMenu->setMenu(menu); + if (menu) { + aCardMenu->setMenu(menu); + } } } diff --git a/cockatrice/src/game/player/player.h b/cockatrice/src/game/player/player.h index 832e613b7..26df3ae50 100644 --- a/cockatrice/src/game/player/player.h +++ b/cockatrice/src/game/player/player.h @@ -9,10 +9,13 @@ #include "pb/game_event.pb.h" #include +#include #include #include #include +inline Q_LOGGING_CATEGORY(PlayerLog, "player"); + namespace google { namespace protobuf @@ -68,6 +71,9 @@ class ZoneViewZone; const int MAX_TOKENS_PER_DIALOG = 99; +/** + * The entire graphical area belonging to a single player. + */ class PlayerArea : public QObject, public QGraphicsItem { Q_OBJECT @@ -135,7 +141,7 @@ signals: void logSetDoesntUntap(Player *player, CardItem *card, bool doesntUntap); void logSetPT(Player *player, CardItem *card, QString newPT); void logSetAnnotation(Player *player, CardItem *card, QString newAnnotation); - void logDumpZone(Player *player, CardZone *zone, int numberCards); + void logDumpZone(Player *player, CardZone *zone, int numberCards, bool isReversed = false); void logRevealCards(Player *player, CardZone *zone, int cardId, @@ -155,10 +161,17 @@ public slots: void actCreateToken(); void actCreateAnotherToken(); void actShuffle(); + void actShuffleTop(); + void actShuffleBottom(); void actDrawCard(); void actDrawCards(); void actUndoDraw(); void actMulligan(); + + void actPlay(); + void actPlayFacedown(); + void actHide(); + void actMoveTopCardToPlay(); void actMoveTopCardToPlayFaceDown(); void actMoveTopCardToGrave(); @@ -177,9 +190,14 @@ public slots: void actMoveBottomCardsToExile(); void actMoveBottomCardToTop(); + void actSelectAll(); + void actSelectRow(); + void actSelectColumn(); + void actViewLibrary(); void actViewHand(); void actViewTopCards(); + void actViewBottomCards(); void actAlwaysRevealTopCard(); void actAlwaysLookAtTopCard(); void actViewGraveyard(); @@ -218,9 +236,6 @@ private slots: void actFlowP(); void actFlowT(); void actSetAnnotation(); - void actPlay(); - void actHide(); - void actPlayFacedown(); void actReveal(QAction *action); void refreshShortcuts(); @@ -229,7 +244,8 @@ private slots: public: enum EventProcessingOption { - SKIP_REVEAL_WINDOW = 0x0001 + SKIP_REVEAL_WINDOW = 0x0001, + SKIP_TAP_ANIMATION = 0x0002 }; Q_DECLARE_FLAGS(EventProcessingOptions, EventProcessingOption) @@ -246,24 +262,29 @@ private: QAction *aMoveHandToTopLibrary, *aMoveHandToBottomLibrary, *aMoveHandToGrave, *aMoveHandToRfg, *aMoveGraveToTopLibrary, *aMoveGraveToBottomLibrary, *aMoveGraveToHand, *aMoveGraveToRfg, *aMoveRfgToTopLibrary, *aMoveRfgToBottomLibrary, *aMoveRfgToHand, *aMoveRfgToGrave, *aViewHand, *aViewLibrary, *aViewTopCards, - *aAlwaysRevealTopCard, *aAlwaysLookAtTopCard, *aOpenDeckInDeckEditor, *aMoveTopCardToGraveyard, - *aMoveTopCardToExile, *aMoveTopCardsToGraveyard, *aMoveTopCardsToExile, *aMoveTopCardsUntil, - *aMoveTopCardToBottom, *aViewGraveyard, *aViewRfg, *aViewSideboard, *aDrawCard, *aDrawCards, *aUndoDraw, - *aMulligan, *aShuffle, *aMoveTopToPlay, *aMoveTopToPlayFaceDown, *aUntapAll, *aRollDie, *aCreateToken, - *aCreateAnotherToken, *aCardMenu, *aMoveBottomToPlay, *aMoveBottomToPlayFaceDown, *aMoveBottomCardToTop, - *aMoveBottomCardToGraveyard, *aMoveBottomCardToExile, *aMoveBottomCardsToGraveyard, *aMoveBottomCardsToExile, - *aDrawBottomCard, *aDrawBottomCards; + *aViewBottomCards, *aAlwaysRevealTopCard, *aAlwaysLookAtTopCard, *aOpenDeckInDeckEditor, + *aMoveTopCardToGraveyard, *aMoveTopCardToExile, *aMoveTopCardsToGraveyard, *aMoveTopCardsToExile, + *aMoveTopCardsUntil, *aMoveTopCardToBottom, *aViewGraveyard, *aViewRfg, *aViewSideboard, *aDrawCard, + *aDrawCards, *aUndoDraw, *aMulligan, *aShuffle, *aShuffleTopCards, *aShuffleBottomCards, *aMoveTopToPlay, + *aMoveTopToPlayFaceDown, *aUntapAll, *aRollDie, *aCreateToken, *aCreateAnotherToken, *aMoveBottomToPlay, + *aMoveBottomToPlayFaceDown, *aMoveBottomCardToTop, *aMoveBottomCardToGraveyard, *aMoveBottomCardToExile, + *aMoveBottomCardsToGraveyard, *aMoveBottomCardsToExile, *aDrawBottomCard, *aDrawBottomCards; + QAction *aCardMenu; QList aAddCounter, aSetCounter, aRemoveCounter; QAction *aPlay, *aPlayFacedown, *aHide, *aTap, *aDoesntUntap, *aAttach, *aUnattach, *aDrawArrow, *aSetPT, *aResetPT, *aIncP, *aDecP, *aIncT, *aDecT, *aIncPT, *aDecPT, *aFlowP, *aFlowT, *aSetAnnotation, *aFlip, *aPeek, *aClone, *aMoveToTopLibrary, *aMoveToBottomLibrary, *aMoveToHand, *aMoveToGraveyard, *aMoveToExile, - *aMoveToXfromTopOfLibrary; + *aMoveToXfromTopOfLibrary, *aSelectAll, *aSelectRow, *aSelectColumn; bool movingCardsUntil; QTimer *moveTopCardTimer; - QString previousMovingCardsUntilExpr = {}; + QStringList movingCardsUntilExprs = {}; + int movingCardsUntilNumberOfHits = 1; + bool movingCardsUntilAutoPlay = false; FilterString movingCardsUntilFilter; + int movingCardsUntilCounter = 0; + void stopMoveTopCardsUntil(); bool shortcutsActive; int defaultNumberTopCards = 1; @@ -301,7 +322,8 @@ private: CardItem *card, CardAttribute attribute, const QString &avalue, - bool allCards); + bool allCards, + EventProcessingOptions options); void addRelatedCardActions(const CardItem *card, QMenu *cardMenu); void addRelatedCardView(const CardItem *card, QMenu *cardMenu); void createCard(const CardItem *sourceCard, @@ -309,10 +331,12 @@ private: CardRelation::AttachType attach = CardRelation::DoesNotAttach, bool persistent = false); bool createRelatedFromRelation(const CardItem *sourceCard, const CardRelation *cardRelation); - void moveOneCardUntil(const CardInfoPtr card); + void moveOneCardUntil(CardItem *card); void addPlayerToList(QMenu *playerList, Player *player); static void removePlayerFromList(QMenu *playerList, Player *player); + void playSelectedCards(bool faceDown = false); + QRectF bRect; QMap counters; @@ -328,7 +352,8 @@ private: void eventCreateArrow(const Event_CreateArrow &event); void eventDeleteArrow(const Event_DeleteArrow &event); void eventCreateToken(const Event_CreateToken &event); - void eventSetCardAttr(const Event_SetCardAttr &event, const GameEventContext &context); + void + eventSetCardAttr(const Event_SetCardAttr &event, const GameEventContext &context, EventProcessingOptions options); void eventSetCardCounter(const Event_SetCardCounter &event); void eventCreateCounter(const Event_CreateCounter &event); void eventSetCounter(const Event_SetCounter &event); @@ -378,7 +403,8 @@ public: QRectF boundingRect() const override; void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override; - void playCard(CardItem *c, bool faceDown, bool tapped); + void playCard(CardItem *c, bool faceDown); + void playCardToTable(const CardItem *c, bool faceDown); void addCard(CardItem *c); void deleteCard(CardItem *c); void addZone(CardZone *z); @@ -498,7 +524,7 @@ class AnnotationDialog : public QInputDialog void keyPressEvent(QKeyEvent *e) override; public: - AnnotationDialog(QWidget *parent) : QInputDialog(parent) + explicit AnnotationDialog(QWidget *parent) : QInputDialog(parent) { } }; diff --git a/cockatrice/src/game/player/player_list_widget.cpp b/cockatrice/src/game/player/player_list_widget.cpp index 951f00afb..a5901c392 100644 --- a/cockatrice/src/game/player/player_list_widget.cpp +++ b/cockatrice/src/game/player/player_list_widget.cpp @@ -6,7 +6,8 @@ #include "../../client/tabs/tab_supervisor.h" #include "../../client/ui/pixel_map_generator.h" #include "../../server/user/user_context_menu.h" -#include "../../server/user/user_list.h" +#include "../../server/user/user_list_manager.h" +#include "../../server/user/user_list_widget.h" #include "pb/command_kick_from_game.pb.h" #include "pb/serverinfo_playerproperties.pb.h" #include "pb/session_commands.pb.h" @@ -72,8 +73,7 @@ PlayerListWidget::PlayerListWidget(TabSupervisor *_tabSupervisor, setItemDelegate(itemDelegate); userContextMenu = new UserContextMenu(tabSupervisor, this, game); - connect(userContextMenu, SIGNAL(openMessageDialog(QString, bool)), this, - SIGNAL(openMessageDialog(QString, bool))); + connect(userContextMenu, &UserContextMenu::openMessageDialog, this, &PlayerListWidget::openMessageDialog); } else { userContextMenu = nullptr; } @@ -140,9 +140,9 @@ void PlayerListWidget::updatePlayerProperties(const ServerInfo_PlayerProperties } if (prop.has_user_info()) { player->setData(3, Qt::UserRole, prop.user_info().user_level()); - player->setIcon( - 3, QIcon(UserLevelPixmapGenerator::generatePixmap(12, UserLevelFlags(prop.user_info().user_level()), false, - QString::fromStdString(prop.user_info().privlevel())))); + player->setIcon(3, UserLevelPixmapGenerator::generateIcon( + 12, UserLevelFlags(prop.user_info().user_level()), prop.user_info().pawn_colors(), false, + QString::fromStdString(prop.user_info().privlevel()))); player->setText(4, QString::fromStdString(prop.user_info().name())); const QString country = QString::fromStdString(prop.user_info().country()); if (!country.isEmpty()) diff --git a/cockatrice/src/game/player/player_list_widget.h b/cockatrice/src/game/player/player_list_widget.h index 6cc633856..a0714bc4d 100644 --- a/cockatrice/src/game/player/player_list_widget.h +++ b/cockatrice/src/game/player/player_list_widget.h @@ -15,16 +15,18 @@ class UserContextMenu; class PlayerListItemDelegate : public QStyledItemDelegate { public: - PlayerListItemDelegate(QObject *const parent); - bool - editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, const QModelIndex &index); + explicit PlayerListItemDelegate(QObject *parent); + bool editorEvent(QEvent *event, + QAbstractItemModel *model, + const QStyleOptionViewItem &option, + const QModelIndex &index) override; }; class PlayerListTWI : public QTreeWidgetItem { public: PlayerListTWI(); - bool operator<(const QTreeWidgetItem &other) const; + bool operator<(const QTreeWidgetItem &other) const override; }; class PlayerListWidget : public QTreeWidget diff --git a/cockatrice/src/game/player/player_target.cpp b/cockatrice/src/game/player/player_target.cpp index 699097c38..becf605ab 100644 --- a/cockatrice/src/game/player/player_target.cpp +++ b/cockatrice/src/game/player/player_target.cpp @@ -101,16 +101,21 @@ void PlayerTarget::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*o grad.setColorAt(0, QColor(180, 180, 180)); tempPainter.fillRect(QRectF(0, 0, translatedSize.width(), translatedSize.height()), grad); - QPixmap tempPixmap; - if (fullPixmap.isNull()) - tempPixmap = - UserLevelPixmapGenerator::generatePixmap(translatedSize.height(), UserLevelFlags(info->user_level()), - false, QString::fromStdString(info->privlevel())); - else - tempPixmap = fullPixmap.scaled(translatedSize, Qt::KeepAspectRatio, Qt::SmoothTransformation); + if (fullPixmap.isNull()) { + int sideLength = translatedSize.height(); + QPixmap tempPixmap = UserLevelPixmapGenerator::generatePixmap( + sideLength, UserLevelFlags(info->user_level()), info->pawn_colors(), false, + QString::fromStdString(info->privlevel())); + int x = (translatedSize.width() - sideLength) / 2; + int y = 0; + tempPainter.drawPixmap(x, y, tempPixmap); + } else { + QPixmap tempPixmap = fullPixmap.scaled(translatedSize, Qt::KeepAspectRatio, Qt::SmoothTransformation); + int x = (translatedSize.width() - tempPixmap.width()) / 2; + int y = (translatedSize.height() - tempPixmap.height()) / 2; + tempPainter.drawPixmap(x, y, tempPixmap); + } - tempPainter.drawPixmap((translatedSize.width() - tempPixmap.width()) / 2, - (translatedSize.height() - tempPixmap.height()) / 2, tempPixmap); QPixmapCache::insert(cacheKey, cachedPixmap); } @@ -158,7 +163,7 @@ AbstractCounter *PlayerTarget::addCounter(int _counterId, const QString &_name, playerCounter = new PlayerCounter(owner, _counterId, _name, _value, this, game); playerCounter->setPos(boundingRect().width() - playerCounter->boundingRect().width(), boundingRect().height() - playerCounter->boundingRect().height()); - connect(playerCounter, SIGNAL(destroyed()), this, SLOT(counterDeleted())); + connect(playerCounter, &PlayerCounter::destroyed, this, &PlayerTarget::counterDeleted); return playerCounter; } diff --git a/cockatrice/src/game/player/player_target.h b/cockatrice/src/game/player/player_target.h index 7adcf5d80..f02f3bcea 100644 --- a/cockatrice/src/game/player/player_target.h +++ b/cockatrice/src/game/player/player_target.h @@ -19,8 +19,8 @@ public: int _value, QGraphicsItem *parent = nullptr, QWidget *game = nullptr); - QRectF boundingRect() const; - void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget); + QRectF boundingRect() const override; + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override; }; class PlayerTarget : public ArrowTarget @@ -38,15 +38,15 @@ public: { Type = typePlayerTarget }; - int type() const + int type() const override { return Type; } - PlayerTarget(Player *_player = nullptr, QGraphicsItem *parentItem = nullptr, QWidget *_game = nullptr); - ~PlayerTarget(); - QRectF boundingRect() const; - void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget); + explicit PlayerTarget(Player *_player = nullptr, QGraphicsItem *parentItem = nullptr, QWidget *_game = nullptr); + ~PlayerTarget() override; + QRectF boundingRect() const override; + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override; AbstractCounter *addCounter(int _counterId, const QString &_name, int _value); }; diff --git a/cockatrice/src/game/zones/card_zone.cpp b/cockatrice/src/game/zones/card_zone.cpp index 13732f960..7516923b0 100644 --- a/cockatrice/src/game/zones/card_zone.cpp +++ b/cockatrice/src/game/zones/card_zone.cpp @@ -1,9 +1,11 @@ #include "card_zone.h" +#include "../cards/card_database_manager.h" #include "../cards/card_item.h" #include "../player/player.h" #include "pb/command_move_card.pb.h" #include "pb/serverinfo_user.pb.h" +#include "pile_zone.h" #include "view_zone.h" #include @@ -11,6 +13,14 @@ #include #include +/** + * @param _p the player that the zone belongs to + * @param _name internal name of the zone + * @param _isShufflable whether it makes sense to shuffle this zone by default after viewing it + * @param _contentsKnown whether the cards in the zone are known to the client + * @param parent the parent graphics object. + * @param _isView whether this zone is a view of another zone. Modifications to a view should modify the original + */ CardZone::CardZone(Player *_p, const QString &_name, bool _hasCardAttr, @@ -23,17 +33,11 @@ CardZone::CardZone(Player *_p, { if (!isView) player->addZone(this); -} -CardZone::~CardZone() -{ - qDebug() << "CardZone destructor: " << name; - for (auto *view : views) { - if (view != nullptr) { - view->deleteLater(); - } - } - clearContents(); + // If we join a game before the card db finishes loading, the cards might have the wrong printings. + // Force refresh all cards in the zone when db finishes loading to fix that. + connect(CardDatabaseManager::getInstance(), &CardDatabase::cardDatabaseLoadingFinished, this, + &CardZone::refreshCardInfos); } void CardZone::retranslateUi() @@ -111,6 +115,13 @@ bool CardZone::showContextMenu(const QPoint &screenPos) return false; } +void CardZone::refreshCardInfos() +{ + for (const auto &cardItem : cards) { + cardItem->refreshCardInfo(); + } +} + void CardZone::mousePressEvent(QGraphicsSceneMouseEvent *event) { if (event->button() == Qt::RightButton) { @@ -122,10 +133,15 @@ void CardZone::mousePressEvent(QGraphicsSceneMouseEvent *event) event->ignore(); } -void CardZone::addCard(CardItem *card, bool reorganize, int x, int y) +void CardZone::addCard(CardItem *card, const bool reorganize, const int x, const int y) { + if (!card) { + qCWarning(CardZoneLog) << "CardZone::addCard() card is null; this shouldn't normally happen"; + return; + } + for (auto *view : views) { - if ((x <= view->getCards().size()) || (view->getNumberCards() == -1)) { + if (view->prepareAddCard(x)) { view->addCard(new CardItem(player, nullptr, card->getName(), card->getProviderId(), card->getId()), reorganize, x, y); } @@ -142,10 +158,10 @@ void CardZone::addCard(CardItem *card, bool reorganize, int x, int y) CardItem *CardZone::getCard(int cardId, const QString &cardName) { - CardItem *c = cards.findCard(cardId, false); + CardItem *c = cards.findCard(cardId); if (!c) { - qDebug() << "CardZone::getCard: card id=" << cardId << "not found"; - return 0; + qCWarning(CardZoneLog) << "CardZone::getCard: card id=" << cardId << "not found"; + return nullptr; } // If the card's id is -1, this zone is invisible, // so we need to give the card an id and a name as it comes out. @@ -157,7 +173,7 @@ CardItem *CardZone::getCard(int cardId, const QString &cardName) return c; } -CardItem *CardZone::takeCard(int position, int cardId, bool /*canResize*/) +CardItem *CardZone::takeCard(int position, int cardId, bool toNewZone) { if (position == -1) { // position == -1 means either that the zone is indexed by card id @@ -171,14 +187,14 @@ CardItem *CardZone::takeCard(int position, int cardId, bool /*canResize*/) position = 0; } if (position >= cards.size()) - return 0; - - CardItem *c = cards.takeAt(position); + return nullptr; for (auto *view : views) { - view->removeCard(position); + view->removeCard(position, toNewZone); } + CardItem *c = cards.takeAt(position); + c->setId(cardId); reorganizeCards(); @@ -188,6 +204,11 @@ CardItem *CardZone::takeCard(int position, int cardId, bool /*canResize*/) void CardZone::removeCard(CardItem *card) { + if (!card) { + qCWarning(CardZoneLog) << "CardZone::removeCard: card is null, this shouldn't normally happen"; + return; + } + cards.removeOne(card); reorganizeCards(); emit cardCountChanged(); diff --git a/cockatrice/src/game/zones/card_zone.h b/cockatrice/src/game/zones/card_zone.h index c0ad27bc3..91cad179c 100644 --- a/cockatrice/src/game/zones/card_zone.h +++ b/cockatrice/src/game/zones/card_zone.h @@ -5,8 +5,11 @@ #include "../board/abstract_graphics_item.h" #include "../cards/card_list.h" +#include #include +inline Q_LOGGING_CATEGORY(CardZoneLog, "card_zone"); + class Player; class ZoneViewZone; class QMenu; @@ -14,6 +17,12 @@ class QAction; class QPainter; class CardDragItem; +/** + * A zone in the game that can contain cards. + * This class contains methods to get and modify the cards that are contained inside this zone. + * + * The cards are stored as a list of `CardItem*`. + */ class CardZone : public AbstractGraphicsItem { Q_OBJECT @@ -28,8 +37,8 @@ protected: bool isShufflable; bool isView; bool alwaysRevealTopCard; - void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event); - void mousePressEvent(QGraphicsSceneMouseEvent *event); + void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) override; + void mousePressEvent(QGraphicsSceneMouseEvent *event) override; virtual void addCardImpl(CardItem *card, int x, int y) = 0; signals: void cardCountChanged(); @@ -37,12 +46,15 @@ public slots: void moveAllToZone(); bool showContextMenu(const QPoint &screenPos); +private slots: + void refreshCardInfos(); + public: enum { Type = typeZone }; - int type() const + int type() const override { return Type; } @@ -55,7 +67,6 @@ public: bool _contentsKnown, QGraphicsItem *parent = nullptr, bool _isView = false); - ~CardZone(); void retranslateUi(); void clearContents(); bool getHasCardAttr() const diff --git a/cockatrice/src/game/zones/hand_zone.cpp b/cockatrice/src/game/zones/hand_zone.cpp index 5b6932902..31f0515d4 100644 --- a/cockatrice/src/game/zones/hand_zone.cpp +++ b/cockatrice/src/game/zones/hand_zone.cpp @@ -12,7 +12,7 @@ HandZone::HandZone(Player *_p, bool _contentsKnown, int _zoneHeight, QGraphicsItem *parent) : SelectZone(_p, "hand", false, false, _contentsKnown, parent), zoneHeight(_zoneHeight) { - connect(themeManager, SIGNAL(themeChanged()), this, SLOT(updateBg())); + connect(themeManager, &ThemeManager::themeChanged, this, &HandZone::updateBg); updateBg(); setCacheMode(DeviceCoordinateCache); } @@ -78,12 +78,7 @@ QRectF HandZone::boundingRect() const void HandZone::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*option*/, QWidget * /*widget*/) { - QBrush brush = themeManager->getHandBgBrush(); - - if (player->getZoneId() > 0) { - // If the extra image is not found, load the default one - brush = themeManager->getExtraHandBgBrush(QString::number(player->getZoneId()), brush); - } + QBrush brush = themeManager->getExtraBgBrush(ThemeManager::Hand, player->getZoneId()); painter->fillRect(boundingRect(), brush); } diff --git a/cockatrice/src/game/zones/hand_zone.h b/cockatrice/src/game/zones/hand_zone.h index 004847f7f..8189c7087 100644 --- a/cockatrice/src/game/zones/hand_zone.h +++ b/cockatrice/src/game/zones/hand_zone.h @@ -15,14 +15,14 @@ public slots: public: HandZone(Player *_p, bool _contentsKnown, int _zoneHeight, QGraphicsItem *parent = nullptr); - void handleDropEvent(const QList &dragItems, CardZone *startZone, const QPoint &dropPoint); - QRectF boundingRect() const; - void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget); - void reorganizeCards(); + void handleDropEvent(const QList &dragItems, CardZone *startZone, const QPoint &dropPoint) override; + QRectF boundingRect() const override; + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override; + void reorganizeCards() override; void setWidth(qreal _width); protected: - void addCardImpl(CardItem *card, int x, int y); + void addCardImpl(CardItem *card, int x, int y) override; }; #endif diff --git a/cockatrice/src/game/zones/pile_zone.cpp b/cockatrice/src/game/zones/pile_zone.cpp index 244e137d7..ba23560c9 100644 --- a/cockatrice/src/game/zones/pile_zone.cpp +++ b/cockatrice/src/game/zones/pile_zone.cpp @@ -21,6 +21,13 @@ PileZone::PileZone(Player *_p, const QString &_name, bool _isShufflable, bool _c .translate((float)CARD_WIDTH / 2, (float)CARD_HEIGHT / 2) .rotate(90) .translate((float)-CARD_WIDTH / 2, (float)-CARD_HEIGHT / 2)); + + connect(&SettingsCache::instance(), &SettingsCache::roundCardCornersChanged, this, [this](bool _roundCardCorners) { + Q_UNUSED(_roundCardCorners); + + prepareGeometryChange(); + update(); + }); } QRectF PileZone::boundingRect() const @@ -31,7 +38,8 @@ QRectF PileZone::boundingRect() const QPainterPath PileZone::shape() const { QPainterPath shape; - shape.addRoundedRect(boundingRect(), 0.05 * CARD_WIDTH, 0.05 * CARD_WIDTH); + qreal cardCornerRadius = SettingsCache::instance().getRoundCardCorners() ? 0.05 * CARD_WIDTH : 0.0; + shape.addRoundedRect(boundingRect(), cardCornerRadius, cardCornerRadius); return shape; } @@ -50,7 +58,7 @@ void PileZone::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*optio void PileZone::addCardImpl(CardItem *card, int x, int /*y*/) { - connect(card, SIGNAL(sigPixmapUpdated()), this, SLOT(callUpdate())); + connect(card, &CardItem::sigPixmapUpdated, this, &PileZone::callUpdate); // if x is negative set it to add at end if (x < 0 || x >= cards.size()) { x = cards.size(); diff --git a/cockatrice/src/game/zones/pile_zone.h b/cockatrice/src/game/zones/pile_zone.h index 5d28b3816..a66543ab3 100644 --- a/cockatrice/src/game/zones/pile_zone.h +++ b/cockatrice/src/game/zones/pile_zone.h @@ -3,6 +3,10 @@ #include "card_zone.h" +/** + * A CardZone where the cards are in a single pile instead of being laid out. + * Usually only top card is accessible by clicking. + */ class PileZone : public CardZone { Q_OBJECT diff --git a/cockatrice/src/game/zones/select_zone.cpp b/cockatrice/src/game/zones/select_zone.cpp index 9947bc405..949b3fba2 100644 --- a/cockatrice/src/game/zones/select_zone.cpp +++ b/cockatrice/src/game/zones/select_zone.cpp @@ -58,11 +58,21 @@ void SelectZone::mouseMoveEvent(QGraphicsSceneMouseEvent *event) pos.setY(br.height()); QRectF selectionRect = QRectF(selectionOrigin, pos).normalized(); - for (int i = 0; i < cards.size(); ++i) { - if (cards[i]->getAttachedTo()) - if (cards[i]->getAttachedTo()->getZone() != this) - continue; - cards[i]->setSelected(selectionRect.intersects(cards[i]->mapRectToParent(cards[i]->boundingRect()))); + for (auto card : cards) { + if (card->getAttachedTo() && card->getAttachedTo()->getZone() != this) { + continue; + } + + bool inRect = selectionRect.intersects(card->mapRectToParent(card->boundingRect())); + if (inRect && !cardsInSelectionRect.contains(card)) { + // selection has just expanded to cover the card + cardsInSelectionRect.insert(card); + card->setSelected(!card->isSelected()); + } else if (!inRect && cardsInSelectionRect.contains(card)) { + // selection has just shrunk to no longer cover the card + cardsInSelectionRect.remove(card); + card->setSelected(!card->isSelected()); + } } static_cast(scene())->resizeRubberBand( deviceTransform(static_cast(scene())->getViewportTransform()).map(pos)); @@ -73,7 +83,9 @@ void SelectZone::mouseMoveEvent(QGraphicsSceneMouseEvent *event) void SelectZone::mousePressEvent(QGraphicsSceneMouseEvent *event) { if (event->button() == Qt::LeftButton) { - scene()->clearSelection(); + if (!event->modifiers().testFlag(Qt::ControlModifier)) { + scene()->clearSelection(); + } selectionOrigin = event->pos(); static_cast(scene())->startRubberBand(event->scenePos()); @@ -85,6 +97,7 @@ void SelectZone::mousePressEvent(QGraphicsSceneMouseEvent *event) void SelectZone::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) { selectionOrigin = QPoint(); + cardsInSelectionRect.clear(); static_cast(scene())->stopRubberBand(); event->accept(); } diff --git a/cockatrice/src/game/zones/select_zone.h b/cockatrice/src/game/zones/select_zone.h index efec4bb02..c8c88b450 100644 --- a/cockatrice/src/game/zones/select_zone.h +++ b/cockatrice/src/game/zones/select_zone.h @@ -3,16 +3,22 @@ #include "card_zone.h" +#include + +/** + * A CardZone where the cards are laid out, with each card directly interactable by clicking. + */ class SelectZone : public CardZone { Q_OBJECT private: QPointF selectionOrigin; + QSet cardsInSelectionRect; protected: - void mouseMoveEvent(QGraphicsSceneMouseEvent *event); - void mousePressEvent(QGraphicsSceneMouseEvent *event); - void mouseReleaseEvent(QGraphicsSceneMouseEvent *event); + void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override; + void mousePressEvent(QGraphicsSceneMouseEvent *event) override; + void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override; public: SelectZone(Player *_player, diff --git a/cockatrice/src/game/zones/stack_zone.cpp b/cockatrice/src/game/zones/stack_zone.cpp index 3a6d8d0f2..b597a9e81 100644 --- a/cockatrice/src/game/zones/stack_zone.cpp +++ b/cockatrice/src/game/zones/stack_zone.cpp @@ -14,7 +14,7 @@ StackZone::StackZone(Player *_p, int _zoneHeight, QGraphicsItem *parent) : SelectZone(_p, "stack", false, false, true, parent), zoneHeight(_zoneHeight) { - connect(themeManager, SIGNAL(themeChanged()), this, SLOT(updateBg())); + connect(themeManager, &ThemeManager::themeChanged, this, &StackZone::updateBg); updateBg(); setCacheMode(DeviceCoordinateCache); } @@ -28,7 +28,7 @@ void StackZone::addCardImpl(CardItem *card, int x, int /*y*/) { // if x is negative set it to add at end if (x < 0 || x >= cards.size()) { - x = cards.size(); + x = static_cast(cards.size()); } cards.insert(x, card); @@ -37,52 +37,64 @@ void StackZone::addCardImpl(CardItem *card, int x, int /*y*/) card->setName(); } card->setParentItem(this); - card->resetState(); + card->resetState(true); card->setVisible(true); card->update(); } QRectF StackZone::boundingRect() const { - return QRectF(0, 0, 100, zoneHeight); + return {0, 0, 100, zoneHeight}; } void StackZone::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*option*/, QWidget * /*widget*/) { - QBrush brush = themeManager->getStackBgBrush(); - - if (player->getZoneId() > 0) { - // If the extra image is not found, load the default one - brush = themeManager->getExtraStackBgBrush(QString::number(player->getZoneId()), brush); - } + QBrush brush = themeManager->getExtraBgBrush(ThemeManager::Stack, player->getZoneId()); painter->fillRect(boundingRect(), brush); } void StackZone::handleDropEvent(const QList &dragItems, CardZone *startZone, const QPoint &dropPoint) { + if (startZone == nullptr || startZone->getPlayer() == nullptr) { + return; + } + Command_MoveCard cmd; cmd.set_start_player_id(startZone->getPlayer()->getId()); cmd.set_start_zone(startZone->getName().toStdString()); cmd.set_target_player_id(player->getId()); cmd.set_target_zone(getName().toStdString()); - int index; - if (cards.isEmpty()) { - index = 0; - } else { - const int cardCount = cards.size(); + + int index = 0; + + if (!cards.isEmpty()) { + const auto cardCount = static_cast(cards.size()); + const auto &card = cards.at(0); + index = qRound(divideCardSpaceInZone(dropPoint.y(), cardCount, boundingRect().height(), - cards.at(0)->boundingRect().height(), true)); - } - if (startZone == this) { - if (cards.at(index)->getId() == dragItems.at(0)->getId()) { - return; + card->boundingRect().height(), true)); + + // divideCardSpaceInZone is not guaranteed to return a valid index + // currently, so clamp it to avoid crashes. + index = qBound(0, index, cardCount - 1); + + if (startZone == this) { + const auto &dragItem = dragItems.at(0); + const auto &card = cards.at(index); + + if (card->getId() == dragItem->getId()) { + return; + } } } + cmd.set_x(index); cmd.set_y(0); for (CardDragItem *item : dragItems) { - cmd.mutable_cards_to_move()->add_card()->set_card_id(item->getId()); + if (item) { + cmd.mutable_cards_to_move()->add_card()->set_card_id(item->getId()); + } } player->sendGameCommand(cmd); @@ -91,9 +103,7 @@ void StackZone::handleDropEvent(const QList &dragItems, CardZone void StackZone::reorganizeCards() { if (!cards.isEmpty()) { - QSet arrowsToUpdate; - - const int cardCount = cards.size(); + const auto cardCount = static_cast(cards.size()); qreal totalWidth = boundingRect().width(); qreal cardWidth = cards.at(0)->boundingRect().width(); qreal xspace = 5; @@ -107,16 +117,6 @@ void StackZone::reorganizeCards() divideCardSpaceInZone(i, cardCount, boundingRect().height(), cards.at(0)->boundingRect().height()); card->setPos(x, y); card->setRealZValue(i); - - for (ArrowItem *item : card->getArrowsFrom()) { - arrowsToUpdate.insert(item); - } - for (ArrowItem *item : card->getArrowsTo()) { - arrowsToUpdate.insert(item); - } - } - for (ArrowItem *item : arrowsToUpdate) { - item->updatePath(); } } update(); diff --git a/cockatrice/src/game/zones/stack_zone.h b/cockatrice/src/game/zones/stack_zone.h index f45ec0e9d..38dee0aca 100644 --- a/cockatrice/src/game/zones/stack_zone.h +++ b/cockatrice/src/game/zones/stack_zone.h @@ -13,13 +13,13 @@ private slots: public: StackZone(Player *_p, int _zoneHeight, QGraphicsItem *parent = nullptr); - void handleDropEvent(const QList &dragItems, CardZone *startZone, const QPoint &dropPoint); - QRectF boundingRect() const; - void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget); - void reorganizeCards(); + void handleDropEvent(const QList &dragItems, CardZone *startZone, const QPoint &dropPoint) override; + QRectF boundingRect() const override; + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override; + void reorganizeCards() override; protected: - void addCardImpl(CardItem *card, int x, int y); + void addCardImpl(CardItem *card, int x, int y) override; }; #endif diff --git a/cockatrice/src/game/zones/table_zone.cpp b/cockatrice/src/game/zones/table_zone.cpp index af4b82e18..71327b9ec 100644 --- a/cockatrice/src/game/zones/table_zone.cpp +++ b/cockatrice/src/game/zones/table_zone.cpp @@ -22,8 +22,9 @@ const QColor TableZone::GRADIENT_COLORLESS = QColor(255, 255, 255, 0); TableZone::TableZone(Player *_p, QGraphicsItem *parent) : SelectZone(_p, "table", true, false, true, parent), active(false) { - connect(themeManager, SIGNAL(themeChanged()), this, SLOT(updateBg())); - connect(&SettingsCache::instance(), SIGNAL(invertVerticalCoordinateChanged()), this, SLOT(reorganizeCards())); + connect(themeManager, &ThemeManager::themeChanged, this, &TableZone::updateBg); + connect(&SettingsCache::instance(), &SettingsCache::invertVerticalCoordinateChanged, this, + &TableZone::reorganizeCards); updateBg(); @@ -53,12 +54,7 @@ bool TableZone::isInverted() const void TableZone::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*option*/, QWidget * /*widget*/) { - QBrush brush = themeManager->getTableBgBrush(); - - if (player->getZoneId() > 0) { - // If the extra image is not found, load the default one - brush = themeManager->getExtraTableBgBrush(QString::number(player->getZoneId()), brush); - } + QBrush brush = themeManager->getExtraBgBrush(ThemeManager::Table, player->getZoneId()); painter->fillRect(boundingRect(), brush); if (active) { @@ -156,8 +152,6 @@ void TableZone::handleDropEventByGrid(const QList &dragItems, void TableZone::reorganizeCards() { - QSet arrowsToUpdate; - // Calculate card stack widths so mapping functions work properly computeCardStackWidths(); @@ -188,23 +182,7 @@ void TableZone::reorganizeCards() qreal childY = y + 5; attachedCard->setPos(childX, childY); attachedCard->setRealZValue((childY + CARD_HEIGHT) * 100000 + (childX + 1) * 100); - for (ArrowItem *item : attachedCard->getArrowsFrom()) { - arrowsToUpdate.insert(item); - } - for (ArrowItem *item : attachedCard->getArrowsTo()) { - arrowsToUpdate.insert(item); - } } - - for (ArrowItem *item : cards[i]->getArrowsFrom()) { - arrowsToUpdate.insert(item); - } - for (ArrowItem *item : cards[i]->getArrowsTo()) { - arrowsToUpdate.insert(item); - } - } - for (ArrowItem *item : arrowsToUpdate) { - item->updatePath(); } resizeToContents(); @@ -213,16 +191,24 @@ void TableZone::reorganizeCards() void TableZone::toggleTapped() { - QList selectedItems = scene()->selectedItems(); - bool tapAll = false; - for (int i = 0; i < selectedItems.size(); i++) - if (!qgraphicsitem_cast(selectedItems[i])->getTapped()) { - tapAll = true; - break; + QList selectedItemsRaw = scene()->selectedItems(); + QList selectedItems; + + auto isCardOnTable = [](const QGraphicsItem *item) { + if (auto card = qgraphicsitem_cast(item)) { + return card->getZone()->getName() == "table"; } + return false; + }; + + std::copy_if(selectedItemsRaw.begin(), selectedItemsRaw.end(), std::back_inserter(selectedItems), isCardOnTable); + + bool tapAll = std::any_of(selectedItems.begin(), selectedItems.end(), [](const QGraphicsItem *item) { + return !qgraphicsitem_cast(item)->getTapped(); + }); QList cmdList; - for (int i = 0; i < selectedItems.size(); i++) { - CardItem *temp = qgraphicsitem_cast(selectedItems[i]); + for (const auto &selectedItem : selectedItems) { + CardItem *temp = qgraphicsitem_cast(selectedItem); if (temp->getTapped() != tapAll) { Command_SetCardAttr *cmd = new Command_SetCardAttr; cmd->set_zone(name.toStdString()); @@ -235,10 +221,10 @@ void TableZone::toggleTapped() player->sendGameCommand(player->prepareGameCommand(cmdList)); } -CardItem *TableZone::takeCard(int position, int cardId, bool canResize) +CardItem *TableZone::takeCard(int position, int cardId, bool toNewZone) { CardItem *result = CardZone::takeCard(position, cardId); - if (canResize) + if (toNewZone) resizeToContents(); return result; } diff --git a/cockatrice/src/game/zones/table_zone.h b/cockatrice/src/game/zones/table_zone.h index 3e93ef781..271276967 100644 --- a/cockatrice/src/game/zones/table_zone.h +++ b/cockatrice/src/game/zones/table_zone.h @@ -89,7 +89,7 @@ public slots: /** Reorganizes CardItems in the TableZone */ - void reorganizeCards(); + void reorganizeCards() override; public: /** @@ -98,12 +98,12 @@ public: @param _p the Player @param parent defaults to null */ - TableZone(Player *_p, QGraphicsItem *parent = nullptr); + explicit TableZone(Player *_p, QGraphicsItem *parent = nullptr); /** @return a QRectF of the TableZone bounding box. */ - QRectF boundingRect() const; + QRectF boundingRect() const override; /** Render the TableZone @@ -111,7 +111,7 @@ public: @param painter @param option */ - void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget); + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override; /** Toggles the selected items as tapped. @@ -121,7 +121,7 @@ public: /** See HandleDropEventByGrid */ - void handleDropEvent(const QList &dragItems, CardZone *startZone, const QPoint &dropPoint); + void handleDropEvent(const QList &dragItems, CardZone *startZone, const QPoint &dropPoint) override; /** Handles the placement of cards @@ -138,7 +138,7 @@ public: */ CardItem *getCardFromCoords(const QPointF &point) const; - QPointF closestGridPoint(const QPointF &point); + QPointF closestGridPoint(const QPointF &point) override; static int clampValidTableRow(const int row); @@ -147,10 +147,10 @@ public: @param position card position @param cardId id of card to take - @param canResize defaults to true + @param toNewZone Whether the destination of the card is not the same as the starting zone. Defaults to true @return CardItem that has been removed */ - CardItem *takeCard(int position, int cardId, bool canResize = true); + CardItem *takeCard(int position, int cardId, bool toNewZone = true) override; /** Resizes the TableZone in case CardItems are within or @@ -178,7 +178,7 @@ public: } protected: - void addCardImpl(CardItem *card, int x, int y); + void addCardImpl(CardItem *card, int x, int y) override; private: void paintZoneOutline(QPainter *painter); diff --git a/cockatrice/src/game/zones/view_zone.cpp b/cockatrice/src/game/zones/view_zone.cpp index 6d4908179..9e9ba6e81 100644 --- a/cockatrice/src/game/zones/view_zone.cpp +++ b/cockatrice/src/game/zones/view_zone.cpp @@ -16,28 +16,41 @@ #include #include +/** + * @param _p the player that the cards are revealed to. + * @param _origZone the zone the cards were revealed from. + * @param _revealZone if false, the cards will be face down. + * @param _writeableRevealZone whether the player can interact with the revealed cards. + * @param parent the parent QGraphicsWidget containing the reveal zone + */ ZoneViewZone::ZoneViewZone(Player *_p, CardZone *_origZone, int _numberCards, bool _revealZone, bool _writeableRevealZone, - QGraphicsItem *parent) + QGraphicsItem *parent, + bool _isReversed) : SelectZone(_p, _origZone->getName(), false, false, true, parent, true), bRect(QRectF()), minRows(0), numberCards(_numberCards), origZone(_origZone), revealZone(_revealZone), - writeableRevealZone(_writeableRevealZone), sortByName(false), sortByType(false) + writeableRevealZone(_writeableRevealZone), groupBy(CardList::NoSort), sortBy(CardList::NoSort), + isReversed(_isReversed) { if (!(revealZone && !writeableRevealZone)) { origZone->getViews().append(this); } } -ZoneViewZone::~ZoneViewZone() +/** + * Deletes this ZoneView and removes it from the origZone's views. + * You should normally call this method instead of deleteLater() + */ +void ZoneViewZone::close() { - emit beingDeleted(); - qDebug("ZoneViewZone destructor"); + emit closed(); if (!(revealZone && !writeableRevealZone)) { origZone->getViews().removeOne(this); } + deleteLater(); } QRectF ZoneViewZone::boundingRect() const @@ -57,7 +70,7 @@ void ZoneViewZone::initializeCards(const QList &cardLis if (!cardList.isEmpty()) { for (int i = 0; i < cardList.size(); ++i) addCard(new CardItem(player, this, QString::fromStdString(cardList[i]->name()), - QString::fromStdString(cardList[i]->provider_id()), cardList[i]->id(), revealZone), + QString::fromStdString(cardList[i]->provider_id()), cardList[i]->id()), false, i); reorganizeCards(); } else if (!origZone->contentsKnown()) { @@ -65,18 +78,17 @@ void ZoneViewZone::initializeCards(const QList &cardLis cmd.set_player_id(player->getId()); cmd.set_zone_name(name.toStdString()); cmd.set_number_cards(numberCards); + cmd.set_is_reversed(isReversed); PendingCommand *pend = player->prepareGameCommand(cmd); - connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this, - SLOT(zoneDumpReceived(const Response &))); + connect(pend, &PendingCommand::finished, this, &ZoneViewZone::zoneDumpReceived); player->sendGameCommand(pend); } else { const CardList &c = origZone->getCards(); int number = numberCards == -1 ? c.size() : (numberCards < c.size() ? numberCards : c.size()); for (int i = 0; i < number; i++) { CardItem *card = c.at(i); - addCard(new CardItem(player, this, card->getName(), card->getProviderId(), card->getId(), revealZone), - false, i); + addCard(new CardItem(player, this, card->getName(), card->getProviderId(), card->getId()), false, i); } reorganizeCards(); } @@ -90,100 +102,176 @@ void ZoneViewZone::zoneDumpReceived(const Response &r) const ServerInfo_Card &cardInfo = resp.zone_info().card_list(i); auto cardName = QString::fromStdString(cardInfo.name()); auto cardProviderId = QString::fromStdString(cardInfo.provider_id()); - auto *card = new CardItem(player, this, cardName, cardProviderId, cardInfo.id(), revealZone, this); + auto *card = new CardItem(player, this, cardName, cardProviderId, cardInfo.id(), this); cards.insert(i, card); } + + updateCardIds(INITIALIZE); reorganizeCards(); emit cardCountChanged(); } +void ZoneViewZone::updateCardIds(CardAction action) +{ + if (origZone->contentsKnown()) { + return; + } + + if (cards.isEmpty()) { + return; + } + + int cardCount = cards.size(); + + auto startId = 0; + + if (isReversed) { + // the card has not been added to origZone's cardList at this point + startId = origZone->getCards().size() - cardCount; + switch (action) { + case INITIALIZE: + break; + case ADD_CARD: + startId += 1; + break; + case REMOVE_CARD: + startId -= 1; + break; + } + } + + for (int i = 0; i < cardCount; ++i) { + cards[i]->setId(i + startId); + } +} + // Because of boundingRect(), this function must not be called before the zone was added to a scene. void ZoneViewZone::reorganizeCards() { - int cardCount = cards.size(); - if (!origZone->contentsKnown()) - for (int i = 0; i < cardCount; ++i) - cards[i]->setId(i); - - int cols = qFloor(qSqrt((double)cardCount / 2)); - if (cols > 7) - cols = 7; - int rows = qCeil((double)cardCount / cols); - if (rows < 1) - rows = 1; - if (minRows == 0) - minRows = rows; - else if (rows < minRows) { - rows = minRows; - cols = qCeil((double)cardCount / minRows); - } - if (cols < 2) - cols = 2; - - qDebug() << "reorganizeCards: rows=" << rows << "cols=" << cols; - CardList cardsToDisplay(cards); - if (sortByName || sortByType) - cardsToDisplay.sort((sortByName ? CardList::SortByName : 0) | (sortByType ? CardList::SortByType : 0)); - int typeColumn = 0; - int longestRow = 0; - if (pileView && sortByType) { // we need sort by type enabled for the feature to work - int typeRow = 0; - QString lastCardType; - for (int i = 0; i < cardCount; i++) { - CardItem *c = cardsToDisplay.at(i); - QString cardType = c->getInfo() ? c->getInfo()->getMainCardType() : ""; - - if (i) { // if not the first card - if (cardType == lastCardType) - typeRow++; // add below current card - else { // if no match then move card to next column - typeColumn++; - typeRow = 0; - } - } - - lastCardType = cardType; - qreal x = 7 + (typeColumn * CARD_WIDTH); - qreal y = typeRow * CARD_HEIGHT / 3; - c->setPos(x + 5, y + 5); - c->setRealZValue(i); - longestRow = qMax(typeRow, longestRow); - } - } else { - for (int i = 0; i < cardCount; i++) { - CardItem *c = cardsToDisplay.at(i); - qreal x = 7 + ((i / rows) * CARD_WIDTH); - qreal y = (i % rows) * CARD_HEIGHT / 3; - c->setPos(x + 5, y + 5); - c->setRealZValue(i); - } + // sort cards + QList sortOptions; + if (groupBy != CardList::NoSort) { + sortOptions << groupBy; } + if (sortBy != CardList::NoSort) { + sortOptions << sortBy; + + // implicitly sort by name at the end so that cards with the same name appear together + if (sortBy != CardList::SortByName) { + sortOptions << CardList::SortByName; + } + + // group printings together + sortOptions << CardList::SortByPrinting; + } + + cardsToDisplay.sortBy(sortOptions); + + // position cards + GridSize gridSize; + if (pileView) { + gridSize = positionCardsForDisplay(cardsToDisplay, groupBy); + } else { + gridSize = positionCardsForDisplay(cardsToDisplay); + } + + // determine bounding rect qreal aleft = 0; qreal atop = 0; - qreal awidth = (pileView && sortByType) ? qMax(typeColumn + 1, 3) * CARD_WIDTH + (CARD_WIDTH / 2) - : qMax(cols, 1) * CARD_WIDTH + (CARD_WIDTH / 2); - qreal aheight = (pileView && sortByType) ? (longestRow * CARD_HEIGHT) / 3 + CARD_HEIGHT * 1.3 - : (rows * CARD_HEIGHT) / 3 + CARD_HEIGHT * 1.3; + qreal awidth = gridSize.cols * CARD_WIDTH + (CARD_WIDTH / 2) + HORIZONTAL_PADDING; + qreal aheight = (gridSize.rows * CARD_HEIGHT) / 3 + CARD_HEIGHT * 1.3; optimumRect = QRectF(aleft, atop, awidth, aheight); updateGeometry(); emit optimumRectChanged(); } -void ZoneViewZone::setSortByName(int _sortByName) +/** + * @brief Sets the position of each card to the proper position for the view + * + * @param cards The cards to reposition. Will modify the cards in the list. + * @param pileOption Property used to group cards for the piles. Expects `cards` to be sorted by that property. Pass in + * NoSort to not make piles. + * + * @returns The number of rows and columns to display + */ +ZoneViewZone::GridSize ZoneViewZone::positionCardsForDisplay(CardList &cards, CardList::SortOption pileOption) { - sortByName = _sortByName; + int cardCount = cards.size(); + + if (pileOption != CardList::NoSort) { + int row = 0; + int col = 0; + int longestRow = 0; + + QString lastColumnProp; + + const auto extractor = CardList::getExtractorFor(pileOption); + + for (int i = 0; i < cardCount; i++) { + CardItem *c = cards.at(i); + QString columnProp = extractor(c); + + if (i) { // if not the first card + if (columnProp == lastColumnProp) + row++; // add below current card + else { // if no match then move card to next column + col++; + row = 0; + } + } + + lastColumnProp = columnProp; + qreal x = col * CARD_WIDTH; + qreal y = row * CARD_HEIGHT / 3; + c->setPos(HORIZONTAL_PADDING + x, VERTICAL_PADDING + y); + c->setRealZValue(i); + longestRow = qMax(row, longestRow); + } + + // +1 because the row/col variables used in the calculations are 0-indexed but + // GridSize expects the actual row/col count + return GridSize{longestRow + 1, qMax(col + 1, 3)}; + + } else { + int cols = qBound(1, qFloor(qSqrt((double)cardCount / 2)), 7); + int rows = qMax(qCeil((double)cardCount / cols), 1); + if (minRows == 0) { + minRows = rows; + } else if (rows < minRows) { + rows = minRows; + cols = qCeil((double)cardCount / minRows); + } + + if (cols < 2) + cols = 2; + + qCDebug(ViewZoneLog) << "reorganizeCards: rows=" << rows << "cols=" << cols; + + for (int i = 0; i < cardCount; i++) { + CardItem *c = cards.at(i); + qreal x = (i / rows) * CARD_WIDTH; + qreal y = (i % rows) * CARD_HEIGHT / 3; + c->setPos(HORIZONTAL_PADDING + x, VERTICAL_PADDING + y); + c->setRealZValue(i); + } + + return GridSize{rows, qMax(cols, 1)}; + } +} + +void ZoneViewZone::setGroupBy(CardList::SortOption _groupBy) +{ + groupBy = _groupBy; reorganizeCards(); } -void ZoneViewZone::setSortByType(int _sortByType) +void ZoneViewZone::setSortBy(CardList::SortOption _sortBy) { - sortByType = _sortByType; - if (!sortByType) - pileView = false; + sortBy = _sortBy; reorganizeCards(); } @@ -193,15 +281,72 @@ void ZoneViewZone::setPileView(int _pileView) reorganizeCards(); } +/** + * Checks if inserting a card at the given position requires an actual new card to be created and added to the view. + * Also does any cardId updates that would be required if a card is inserted in that position. + * + * Note that this method can end up modifying the cardIds despite returning false. + * (for example, if the card is inserted into a hidden portion of the deck while the view is reversed) + * + * Make sure to call this method once before calling addCard(), so that you skip creating a new CardItem and calling + * addCard() if it's not required. + * + * @param x The position to insert the card at. + * @return Whether to proceed with calling addCard. + */ +bool ZoneViewZone::prepareAddCard(int x) +{ + bool doInsert = false; + if (!isReversed) { + if (x <= cards.size() || cards.size() == -1) { + doInsert = true; + } + } else { + // map x (which is in origZone indexes) to this viewZone's cardList index + int firstId = cards.isEmpty() ? origZone->getCards().size() : cards.front()->getId(); + int insertionIndex = x - firstId; + if (insertionIndex >= 0) { + // card was put into a portion of the deck that's in the view + doInsert = true; + } else { + // card was put into a portion of the deck that's not in the view; update ids but don't insert card + updateCardIds(ADD_CARD); + } + } + + // autoclose check is done both here and in removeCard + if (cards.isEmpty() && !doInsert && SettingsCache::instance().getCloseEmptyCardView()) { + close(); + } + + return doInsert; +} + +/** + * Make sure prepareAddCard() was called before calling addCard(). + * This method assumes we already checked that the card is being inserted into the visible portion + */ void ZoneViewZone::addCardImpl(CardItem *card, int x, int /*y*/) { - // if x is negative set it to add at end - if (x < 0 || x >= cards.size()) { - x = cards.size(); + if (!isReversed) { + // if x is negative set it to add at end + // if x is out-of-bounds then also set it to add at the end + if (x < 0 || x >= cards.size()) { + x = cards.size(); + } + cards.insert(x, card); + } else { + // map x (which is in origZone indexes) to this viewZone's cardList index + int firstId = cards.isEmpty() ? origZone->getCards().size() : cards.front()->getId(); + int insertionIndex = x - firstId; + // qMin to prevent out-of-bounds error when bottoming a card that is already in the view + cards.insert(qMin(insertionIndex, cards.size()), card); } - cards.insert(x, card); + card->setParentItem(this); card->update(); + + updateCardIds(ADD_CARD); reorganizeCards(); } @@ -216,6 +361,7 @@ void ZoneViewZone::handleDropEvent(const QList &dragItems, cmd.set_target_zone(getName().toStdString()); cmd.set_x(0); cmd.set_y(0); + cmd.set_is_reversed(isReversed); for (int i = 0; i < dragItems.size(); ++i) cmd.mutable_cards_to_move()->add_card()->set_card_id(dragItems[i]->getId()); @@ -223,13 +369,33 @@ void ZoneViewZone::handleDropEvent(const QList &dragItems, player->sendGameCommand(cmd); } -void ZoneViewZone::removeCard(int position) +void ZoneViewZone::removeCard(int position, bool toNewZone) { - if (position >= cards.size()) + if (isReversed) { + position -= cards.first()->getId(); + if (position < 0 || position >= cards.size()) { + updateCardIds(REMOVE_CARD); + return; + } + } + + if (position >= cards.size()) { return; + } CardItem *card = cards.takeAt(position); card->deleteLater(); + + // The toNewZone check is to prevent the view from auto-closing if the view contains only a single card and that + // card gets dragged within the view. + // Another autoclose check is done in prepareAddCard so that the view autocloses if the last card was moved to an + // unrevealed portion of the same zone. + if (cards.isEmpty() && SettingsCache::instance().getCloseEmptyCardView() && toNewZone) { + close(); + return; + } + + updateCardIds(REMOVE_CARD); reorganizeCards(); } diff --git a/cockatrice/src/game/zones/view_zone.h b/cockatrice/src/game/zones/view_zone.h index a0e17b6c3..3c69317aa 100644 --- a/cockatrice/src/game/zones/view_zone.h +++ b/cockatrice/src/game/zones/view_zone.h @@ -4,24 +4,59 @@ #include "select_zone.h" #include +#include +#include + +inline Q_LOGGING_CATEGORY(ViewZoneLog, "view_zone"); class ZoneViewWidget; class Response; class ServerInfo_Card; class QGraphicsSceneWheelEvent; +/** + * A CardZone that is a view into another CardZone. + * If this zone is writable, then modifications to this zone will be reflected in the underlying zone. + * + * Most interactions with StackZones are handled through a zone view. + * For example, viewing the deck/graveyard/exile is handled through a view. + * + * Handles both limited reveals (eg. look at top X cards) and full zone reveals (eg. searching the deck). + */ class ZoneViewZone : public SelectZone, public QGraphicsLayoutItem { Q_OBJECT Q_INTERFACES(QGraphicsLayoutItem) private: + static constexpr int HORIZONTAL_PADDING = 12; + static constexpr int VERTICAL_PADDING = 5; + QRectF bRect, optimumRect; int minRows, numberCards; - void handleDropEvent(const QList &dragItems, CardZone *startZone, const QPoint &dropPoint); CardZone *origZone; bool revealZone, writeableRevealZone; - bool sortByName, sortByType; + CardList::SortOption groupBy, sortBy; bool pileView; + bool isReversed; + + enum CardAction + { + INITIALIZE, + ADD_CARD, + REMOVE_CARD + }; + + void updateCardIds(CardAction action); + + struct GridSize + { + int rows; + int cols; + }; + + GridSize positionCardsForDisplay(CardList &cards, CardList::SortOption pileOption = CardList::NoSort); + + void handleDropEvent(const QList &dragItems, CardZone *startZone, const QPoint &dropPoint) override; public: ZoneViewZone(Player *_p, @@ -29,18 +64,19 @@ public: int _numberCards = -1, bool _revealZone = false, bool _writeableRevealZone = false, - QGraphicsItem *parent = nullptr); - ~ZoneViewZone(); - QRectF boundingRect() const; - void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget); - void reorganizeCards(); + QGraphicsItem *parent = nullptr, + bool _isReversed = false); + QRectF boundingRect() const override; + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override; + void reorganizeCards() override; void initializeCards(const QList &cardList = QList()); - void removeCard(int position); + bool prepareAddCard(int x); + void removeCard(int position, bool toNewZone); int getNumberCards() const { return numberCards; } - void setGeometry(const QRectF &rect); + void setGeometry(const QRectF &rect) override; QRectF getOptimumRect() const { return optimumRect; @@ -54,21 +90,26 @@ public: return writeableRevealZone; } void setWriteableRevealZone(bool _writeableRevealZone); + bool getIsReversed() const + { + return isReversed; + } public slots: - void setSortByName(int _sortByName); - void setSortByType(int _sortByType); + void close(); + void setGroupBy(CardList::SortOption _groupBy); + void setSortBy(CardList::SortOption _sortBy); void setPileView(int _pileView); private slots: void zoneDumpReceived(const Response &r); signals: - void beingDeleted(); + void closed(); void optimumRectChanged(); void wheelEventReceived(QGraphicsSceneWheelEvent *event); protected: - void addCardImpl(CardItem *card, int x, int y); - QSizeF sizeHint(Qt::SizeHint which, const QSizeF &constraint = QSizeF()) const; - void wheelEvent(QGraphicsSceneWheelEvent *event); + void addCardImpl(CardItem *card, int x, int y) override; + QSizeF sizeHint(Qt::SizeHint which, const QSizeF &constraint = QSizeF()) const override; + void wheelEvent(QGraphicsSceneWheelEvent *event) override; }; #endif diff --git a/cockatrice/src/game/zones/view_zone_widget.cpp b/cockatrice/src/game/zones/view_zone_widget.cpp index 974073a88..7dd21b9d5 100644 --- a/cockatrice/src/game/zones/view_zone_widget.cpp +++ b/cockatrice/src/game/zones/view_zone_widget.cpp @@ -17,12 +17,22 @@ #include #include +/** + * @param _player the player the cards were revealed to. + * @param _origZone the zone the cards were revealed from. + * @param numberCards num of cards to reveal from the zone. Ex: scry the top 3 cards. + * Pass in a negative number to reveal the entire zone. + * -1 specifically will give the option to shuffle the zone upon closing the window. + * @param _revealZone if false, the cards will be face down. + * @param _writeableRevealZone whether the player can interact with the revealed cards. + */ ZoneViewWidget::ZoneViewWidget(Player *_player, CardZone *_origZone, int numberCards, bool _revealZone, bool _writeableRevealZone, - const QList &cardList) + const QList &cardList, + bool _isReversed) : QGraphicsWidget(0, Qt::Window), canBeShuffled(_origZone->getIsShufflable()), player(_player) { setAcceptHoverEvents(true); @@ -31,22 +41,27 @@ ZoneViewWidget::ZoneViewWidget(Player *_player, setFlag(ItemIgnoresTransformations); QGraphicsLinearLayout *vbox = new QGraphicsLinearLayout(Qt::Vertical); - QGraphicsLinearLayout *hPilebox = 0; + // If the number is < 0, then it means that we can give the option to make the area sorted if (numberCards < 0) { - hPilebox = new QGraphicsLinearLayout(Qt::Horizontal); - QGraphicsLinearLayout *hFilterbox = new QGraphicsLinearLayout(Qt::Horizontal); + // top row + QGraphicsLinearLayout *hTopRow = new QGraphicsLinearLayout(Qt::Horizontal); - QGraphicsProxyWidget *sortByNameProxy = new QGraphicsProxyWidget; - sortByNameProxy->setWidget(&sortByNameCheckBox); - hFilterbox->addItem(sortByNameProxy); + // groupBy options + QGraphicsProxyWidget *groupBySelectorProxy = new QGraphicsProxyWidget; + groupBySelectorProxy->setWidget(&groupBySelector); + groupBySelectorProxy->setZValue(2000000008); + hTopRow->addItem(groupBySelectorProxy); - QGraphicsProxyWidget *sortByTypeProxy = new QGraphicsProxyWidget; - sortByTypeProxy->setWidget(&sortByTypeCheckBox); - hFilterbox->addItem(sortByTypeProxy); + // sortBy options + QGraphicsProxyWidget *sortBySelectorProxy = new QGraphicsProxyWidget; + sortBySelectorProxy->setWidget(&sortBySelector); + sortBySelectorProxy->setZValue(2000000007); + hTopRow->addItem(sortBySelectorProxy); - vbox->addItem(hFilterbox); + vbox->addItem(hTopRow); + // line QGraphicsProxyWidget *lineProxy = new QGraphicsProxyWidget; QFrame *line = new QFrame; line->setFrameShape(QFrame::HLine); @@ -54,22 +69,26 @@ ZoneViewWidget::ZoneViewWidget(Player *_player, lineProxy->setWidget(line); vbox->addItem(lineProxy); + // bottom row + QGraphicsLinearLayout *hBottomRow = new QGraphicsLinearLayout(Qt::Horizontal); + + // pile view options QGraphicsProxyWidget *pileViewProxy = new QGraphicsProxyWidget; pileViewProxy->setWidget(&pileViewCheckBox); - hPilebox->addItem(pileViewProxy); - } + hBottomRow->addItem(pileViewProxy); - if (_origZone->getIsShufflable() && (numberCards == -1)) { - shuffleCheckBox.setChecked(true); - QGraphicsProxyWidget *shuffleProxy = new QGraphicsProxyWidget; - shuffleProxy->setWidget(&shuffleCheckBox); - hPilebox->addItem(shuffleProxy); - } + // shuffle options + if (_origZone->getIsShufflable() && numberCards == -1) { + shuffleCheckBox.setChecked(true); + QGraphicsProxyWidget *shuffleProxy = new QGraphicsProxyWidget; + shuffleProxy->setWidget(&shuffleCheckBox); + hBottomRow->addItem(shuffleProxy); + } - vbox->addItem(hPilebox); + vbox->addItem(hBottomRow); + } extraHeight = vbox->sizeHint(Qt::PreferredSize).height(); - resize(150, 150); QGraphicsLinearLayout *zoneHBox = new QGraphicsLinearLayout(Qt::Horizontal); @@ -82,53 +101,82 @@ ZoneViewWidget::ZoneViewWidget(Player *_player, scrollBar->setMinimum(0); scrollBar->setSingleStep(20); scrollBar->setPageStep(200); - connect(scrollBar, SIGNAL(valueChanged(int)), this, SLOT(handleScrollBarChange(int))); + connect(scrollBar, &QScrollBar::valueChanged, this, &ZoneViewWidget::handleScrollBarChange); scrollBarProxy = new ScrollableGraphicsProxyWidget; scrollBarProxy->setWidget(scrollBar); zoneHBox->addItem(scrollBarProxy); vbox->addItem(zoneHBox); - zone = new ZoneViewZone(player, _origZone, numberCards, _revealZone, _writeableRevealZone, zoneContainer); - connect(zone, SIGNAL(wheelEventReceived(QGraphicsSceneWheelEvent *)), scrollBarProxy, - SLOT(recieveWheelEvent(QGraphicsSceneWheelEvent *))); - - // numberCard is the num of cards we want to reveal from an area. Ex: scry the top 3 cards. - // If the number is < 0 then it means that we can make the area sorted and we dont care about the order. - if (numberCards < 0) { - connect(&sortByNameCheckBox, SIGNAL(QT_STATE_CHANGED(QT_STATE_CHANGED_T)), this, - SLOT(processSortByName(QT_STATE_CHANGED_T))); - connect(&sortByTypeCheckBox, SIGNAL(QT_STATE_CHANGED(QT_STATE_CHANGED_T)), this, - SLOT(processSortByType(QT_STATE_CHANGED_T))); - connect(&pileViewCheckBox, SIGNAL(QT_STATE_CHANGED(QT_STATE_CHANGED_T)), this, - SLOT(processSetPileView(QT_STATE_CHANGED_T))); - sortByNameCheckBox.setChecked(SettingsCache::instance().getZoneViewSortByName()); - sortByTypeCheckBox.setChecked(SettingsCache::instance().getZoneViewSortByType()); - pileViewCheckBox.setChecked(SettingsCache::instance().getZoneViewPileView()); - if (!SettingsCache::instance().getZoneViewSortByType()) - pileViewCheckBox.setEnabled(false); - } + zone = + new ZoneViewZone(player, _origZone, numberCards, _revealZone, _writeableRevealZone, zoneContainer, _isReversed); + connect(zone, &ZoneViewZone::wheelEventReceived, scrollBarProxy, &ScrollableGraphicsProxyWidget::recieveWheelEvent); retranslateUi(); + + // only wire up sort options after creating ZoneViewZone, since it segfaults otherwise. + if (numberCards < 0) { + connect(&groupBySelector, static_cast(&QComboBox::currentIndexChanged), this, + &ZoneViewWidget::processGroupBy); + connect(&sortBySelector, static_cast(&QComboBox::currentIndexChanged), this, + &ZoneViewWidget::processSortBy); + connect(&pileViewCheckBox, &QCheckBox::QT_STATE_CHANGED, this, &ZoneViewWidget::processSetPileView); + groupBySelector.setCurrentIndex(SettingsCache::instance().getZoneViewGroupByIndex()); + sortBySelector.setCurrentIndex(SettingsCache::instance().getZoneViewSortByIndex()); + pileViewCheckBox.setChecked(SettingsCache::instance().getZoneViewPileView()); + + if (CardList::NoSort == static_cast(groupBySelector.currentData().toInt())) { + pileViewCheckBox.setEnabled(false); + } + } + setLayout(vbox); - connect(zone, SIGNAL(optimumRectChanged()), this, SLOT(resizeToZoneContents())); - connect(zone, SIGNAL(beingDeleted()), this, SLOT(zoneDeleted())); + connect(zone, &ZoneViewZone::optimumRectChanged, this, [this] { resizeToZoneContents(); }); + connect(zone, &ZoneViewZone::closed, this, &ZoneViewWidget::zoneDeleted); zone->initializeCards(cardList); + + // QLabel sizes aren't taken into account until the widget is rendered. + // Force refresh after 1ms to fix glitchy rendering with long QLabels. + auto *lastResizeBeforeVisibleTimer = new QTimer(this); + connect(lastResizeBeforeVisibleTimer, &QTimer::timeout, this, [=, this] { + resizeToZoneContents(); + disconnect(lastResizeBeforeVisibleTimer); + lastResizeBeforeVisibleTimer->deleteLater(); + }); + lastResizeBeforeVisibleTimer->setSingleShot(true); + lastResizeBeforeVisibleTimer->start(1); } -void ZoneViewWidget::processSortByType(QT_STATE_CHANGED_T value) +void ZoneViewWidget::processGroupBy(int index) { - pileViewCheckBox.setEnabled(value); - SettingsCache::instance().setZoneViewSortByType(value); - zone->setPileView(pileViewCheckBox.isChecked()); - zone->setSortByType(value); + auto option = static_cast(groupBySelector.itemData(index).toInt()); + SettingsCache::instance().setZoneViewGroupByIndex(index); + zone->setGroupBy(option); + + // disable pile view checkbox if we're not grouping by anything + pileViewCheckBox.setEnabled(option != CardList::NoSort); + + // reset sortBy if it has the same value as groupBy + if (option != CardList::NoSort && + option == static_cast(sortBySelector.currentData().toInt())) { + sortBySelector.setCurrentIndex(1); // set to SortByName + } } -void ZoneViewWidget::processSortByName(QT_STATE_CHANGED_T value) +void ZoneViewWidget::processSortBy(int index) { - SettingsCache::instance().setZoneViewSortByName(value); - zone->setSortByName(value); + auto option = static_cast(sortBySelector.itemData(index).toInt()); + + // set to SortByName instead if it has the same value as groupBy + if (option != CardList::NoSort && + option == static_cast(groupBySelector.currentData().toInt())) { + sortBySelector.setCurrentIndex(1); // set to SortByName + return; + } + + SettingsCache::instance().setZoneViewSortByIndex(index); + zone->setSortBy(option); } void ZoneViewWidget::processSetPileView(QT_STATE_CHANGED_T value) @@ -140,8 +188,30 @@ void ZoneViewWidget::processSetPileView(QT_STATE_CHANGED_T value) void ZoneViewWidget::retranslateUi() { setWindowTitle(zone->getTranslatedName(false, CaseNominative)); - sortByNameCheckBox.setText(tr("sort by name")); - sortByTypeCheckBox.setText(tr("sort by type")); + + { // We can't change the strings after they're put into the QComboBox, so this is our workaround + int oldIndex = groupBySelector.currentIndex(); + groupBySelector.clear(); + groupBySelector.addItem(tr("Ungrouped"), CardList::NoSort); + groupBySelector.addItem(tr("Group by Type"), CardList::SortByMainType); + groupBySelector.addItem(tr("Group by Mana Value"), CardList::SortByManaValue); + groupBySelector.addItem(tr("Group by Color"), CardList::SortByColorGrouping); + groupBySelector.setCurrentIndex(oldIndex); + } + + { + int oldIndex = sortBySelector.currentIndex(); + sortBySelector.clear(); + sortBySelector.addItem(tr("Unsorted"), CardList::NoSort); + sortBySelector.addItem(tr("Sort by Name"), CardList::SortByName); + sortBySelector.addItem(tr("Sort by Type"), CardList::SortByType); + sortBySelector.addItem(tr("Sort by Mana Cost"), CardList::SortByManaCost); + sortBySelector.addItem(tr("Sort by Colors"), CardList::SortByColors); + sortBySelector.addItem(tr("Sort by P/T"), CardList::SortByPt); + sortBySelector.addItem(tr("Sort by Set"), CardList::SortBySet); + sortBySelector.setCurrentIndex(oldIndex); + } + shuffleCheckBox.setText(tr("shuffle when closing")); pileViewCheckBox.setText(tr("pile view")); } @@ -175,20 +245,74 @@ void ZoneViewWidget::moveEvent(QGraphicsSceneMoveEvent * /* event */) setPos(scenePos); } -void ZoneViewWidget::resizeToZoneContents() +void ZoneViewWidget::resizeEvent(QGraphicsSceneResizeEvent *event) +{ + // We need to manually resize the scroll bar whenever the window gets resized + resizeScrollbar(event->newSize().height() - extraHeight - 10); +} + +void ZoneViewWidget::resizeScrollbar(const qreal newZoneHeight) +{ + qreal totalZoneHeight = zone->getOptimumRect().height(); + qreal newMax = qMax(totalZoneHeight - newZoneHeight, 0.0); + scrollBar->setMaximum(newMax); +} + +/** + * Maps a height that is given as number of rows of cards to the actual height, given in pixels. + * + * @param rows Rows of cards + * @return The height in pixels + */ +static qreal rowsToHeight(int rows) +{ + const qreal cardsHeight = (rows + 1) * (CARD_HEIGHT / 3); + return cardsHeight + 5; // +5 padding to make the cutoff look nicer +} + +/** + * Calculates the max initial height from the settings. + * The max initial height setting is given as number of rows, so we need to map it to a height. + **/ +static qreal calcMaxInitialHeight() +{ + return rowsToHeight(SettingsCache::instance().getCardViewInitialRowsMax()); +} + +/** + * @brief Handles edge cases in determining the next default zone height. We want the height to snap when the number of + * rows changes, but not if the player has already expanded the window. + */ +static qreal determineNewZoneHeight(qreal oldZoneHeight) +{ + // don't snap if window is taller than max initial height + if (oldZoneHeight > calcMaxInitialHeight()) { + return oldZoneHeight; + } + + return calcMaxInitialHeight(); +} + +void ZoneViewWidget::resizeToZoneContents(bool forceInitialHeight) { QRectF zoneRect = zone->getOptimumRect(); qreal totalZoneHeight = zoneRect.height(); - if (zoneRect.height() > 500) - zoneRect.setHeight(500); - QSizeF newSize(qMax(QGraphicsWidget::layout()->effectiveSizeHint(Qt::MinimumSize, QSizeF()).width(), - zoneRect.width() + scrollBar->width() + 10), - zoneRect.height() + extraHeight + 10); - setMaximumSize(newSize); - resize(newSize); + + qreal width = qMax(QGraphicsWidget::layout()->effectiveSizeHint(Qt::MinimumSize, QSizeF()).width(), + zoneRect.width() + scrollBar->width() + 10); + + QSizeF maxSize(width, zoneRect.height() + extraHeight + 10); + + qreal currentZoneHeight = rect().height() - extraHeight - 10; + qreal newZoneHeight = forceInitialHeight ? calcMaxInitialHeight() : determineNewZoneHeight(currentZoneHeight); + + QSizeF initialSize(width, newZoneHeight + extraHeight + 10); + + setMaximumSize(maxSize); + resize(initialSize); + resizeScrollbar(newZoneHeight); zone->setGeometry(QRectF(0, -scrollBar->value(), zoneContainer->size().width(), totalZoneHeight)); - scrollBar->setMaximum(totalZoneHeight - zoneRect.height()); if (layout()) layout()->invalidate(); @@ -201,11 +325,12 @@ void ZoneViewWidget::handleScrollBarChange(int value) void ZoneViewWidget::closeEvent(QCloseEvent *event) { - disconnect(zone, SIGNAL(beingDeleted()), this, 0); + disconnect(zone, &ZoneViewZone::closed, this, 0); + // manually call zone->close in order to remove it from the origZones views + zone->close(); if (shuffleCheckBox.isChecked()) player->sendGameCommand(Command_Shuffle()); - emit closePressed(this); - deleteLater(); + zoneDeleted(); event->accept(); } @@ -221,3 +346,39 @@ void ZoneViewWidget::initStyleOption(QStyleOption *option) const if (titleBar) titleBar->icon = QPixmap("theme:cockatrice"); } + +/** + * Expands/shrinks the window, depending on the current height as well as the configured initial and expanded max + * heights. + */ +void ZoneViewWidget::expandWindow() +{ + qreal maxInitialHeight = calcMaxInitialHeight(); + qreal maxExpandedHeight = rowsToHeight(SettingsCache::instance().getCardViewExpandedRowsMax()); + qreal height = rect().height() - extraHeight - 10; + qreal maxHeight = maximumHeight() - extraHeight - 10; + + // reset window to initial max height if... + bool doResetSize = + // current height is less than that + (height < maxInitialHeight) || + // current height is at expanded max height + (height == maxExpandedHeight) || + // current height is at actual max height, and actual max height is less than expanded max height + (height == maxHeight && height > maxInitialHeight && height < maxExpandedHeight); + + if (doResetSize) { + resizeToZoneContents(true); + } else { + // expand/shrink window to expanded max height if current height is anywhere at or between initial max height + // and actual max height + resize(maximumSize().boundedTo({maximumWidth(), maxExpandedHeight + extraHeight + 10})); + } +} + +void ZoneViewWidget::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) +{ + if (event->pos().y() <= 0) { + expandWindow(); + } +} \ No newline at end of file diff --git a/cockatrice/src/game/zones/view_zone_widget.h b/cockatrice/src/game/zones/view_zone_widget.h index 43e92f425..177bcb92a 100644 --- a/cockatrice/src/game/zones/view_zone_widget.h +++ b/cockatrice/src/game/zones/view_zone_widget.h @@ -4,6 +4,7 @@ #include "../../utility/macros.h" #include +#include #include #include @@ -14,7 +15,6 @@ class ZoneViewZone; class Player; class CardDatabase; class QScrollBar; -class QCheckBox; class GameScene; class ServerInfo_Card; class QGraphicsSceneMouseEvent; @@ -31,6 +31,12 @@ public slots: } }; +/** + * A QGraphicsWidget that holds a ZoneViewZone. + * + * Some zone views allow sorting. + * This widget will display the sort options when relevant, and forward the values of the options to the ZoneViewZone. + */ class ZoneViewWidget : public QGraphicsWidget { Q_OBJECT @@ -41,24 +47,28 @@ private: QPushButton *closeButton; QScrollBar *scrollBar; ScrollableGraphicsProxyWidget *scrollBarProxy; - QCheckBox sortByNameCheckBox; - QCheckBox sortByTypeCheckBox; + QComboBox groupBySelector; + QComboBox sortBySelector; QCheckBox shuffleCheckBox; QCheckBox pileViewCheckBox; bool canBeShuffled; int extraHeight; Player *player; + + void resizeScrollbar(qreal newZoneHeight); signals: void closePressed(ZoneViewWidget *zv); private slots: - void processSortByType(QT_STATE_CHANGED_T value); - void processSortByName(QT_STATE_CHANGED_T value); + void processGroupBy(int value); + void processSortBy(int value); void processSetPileView(QT_STATE_CHANGED_T value); - void resizeToZoneContents(); + void resizeToZoneContents(bool forceInitialHeight = false); void handleScrollBarChange(int value); void zoneDeleted(); - void moveEvent(QGraphicsSceneMoveEvent * /* event */); + void moveEvent(QGraphicsSceneMoveEvent * /* event */) override; + void resizeEvent(QGraphicsSceneResizeEvent * /* event */) override; + void expandWindow(); public: ZoneViewWidget(Player *_player, @@ -66,7 +76,8 @@ public: int numberCards = 0, bool _revealZone = false, bool _writeableRevealZone = false, - const QList &cardList = QList()); + const QList &cardList = QList(), + bool _isReversed = false); ZoneViewZone *getZone() const { return zone; @@ -78,8 +89,9 @@ public: void retranslateUi(); protected: - void closeEvent(QCloseEvent *event); - void initStyleOption(QStyleOption *option) const; + void closeEvent(QCloseEvent *event) override; + void initStyleOption(QStyleOption *option) const override; + void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) override; }; #endif diff --git a/cockatrice/src/main.cpp b/cockatrice/src/main.cpp index 96d521d23..2122ccbd8 100644 --- a/cockatrice/src/main.cpp +++ b/cockatrice/src/main.cpp @@ -58,9 +58,76 @@ QString translationPath; static void CockatriceLogger(QtMsgType type, const QMessageLogContext &ctx, const QString &message) { - Logger::getInstance().log(type, ctx, message); + QString logMessage = qFormatLogMessage(type, ctx, message); + + // Regular expression to match the full path in the square brackets and extract only the filename and line number + QRegularExpression regex(R"(\[(?:.:)?[\/\\].*[\/\\]([^\/\\]+\:\d+)\])"); + QRegularExpressionMatch match = regex.match(logMessage); + + if (match.hasMatch()) { + // Extract the filename and line number (e.g., "main.cpp:211") + QString filenameLine = match.captured(1); + + // Replace the full path in square brackets with just the filename and line number + logMessage.replace(match.captured(0), QString("[%1]").arg(filenameLine)); + } + + Logger::getInstance().log(type, ctx, logMessage); } +#ifdef Q_OS_WIN +// clang-format off +#include +#include +#include +#include +#include +#pragma comment(lib, "DbgHelp.lib") // Link the DbgHelp library +// clang-format on + +LONG WINAPI CockatriceUnhandledExceptionFilter(EXCEPTION_POINTERS *exceptionPointers) +{ + std::filesystem::path path; + + // Find %LOCALAPPDATA% (or cheat at finding it) + wchar_t *localAppDataFolder; + if (SHGetKnownFolderPath(FOLDERID_LocalAppData, KF_FLAG_CREATE, NULL, &localAppDataFolder) != S_OK) { + path = std::filesystem::temp_directory_path().parent_path().parent_path(); + } else { + path = std::filesystem::path(localAppDataFolder); + } + + // Plan on writing crash files into %LOCALAPPDATA%/CrashDumps/Cockatrice/cockatrice.crash.*.dmp + path /= "CrashDumps"; + path /= "Cockatrice"; + if (!std::filesystem::exists(path)) { + std::filesystem::create_directories(path); + } + path /= "cockatrice.crash." + std::to_string(std::time(0)) + ".dmp"; + + // Create and write crash files +#ifdef UNICODE + HANDLE hDumpFile = + CreateFile(path.wstring().c_str(), GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr); +#else + HANDLE hDumpFile = + CreateFile(path.string().c_str(), GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr); +#endif + + MINIDUMP_EXCEPTION_INFORMATION mei; + mei.ExceptionPointers = exceptionPointers; + mei.ThreadId = GetCurrentThreadId(); + mei.ClientPointers = 1; + + MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), hDumpFile, MiniDumpWithFullMemory, &mei, nullptr, + nullptr); + + CloseHandle(hDumpFile); + + return EXCEPTION_EXECUTE_HANDLER; +} +#endif + void installNewTranslator() { QString lang = SettingsCache::instance().getLang(); @@ -74,18 +141,20 @@ void installNewTranslator() bool qtTranslationLoaded = qtTranslator->load(qtNameHint, qtTranslationPath); if (!qtTranslationLoaded) { - qDebug() << "Unable to load qt translation" << qtNameHint << "at" << qtTranslationPath; + qCWarning(QtTranslatorDebug) << "Unable to load qt translation" << qtNameHint << "at" << qtTranslationPath; } else { - qDebug() << "Loaded qt translation" << qtNameHint << "at" << qtTranslationPath; + qCInfo(QtTranslatorDebug) << "Loaded qt translation" << qtNameHint << "at" << qtTranslationPath; } qApp->installTranslator(qtTranslator); QString appNameHint = translationPrefix + "_" + lang; bool appTranslationLoaded = qtTranslator->load(appNameHint, translationPath); if (!appTranslationLoaded) { - qDebug() << "Unable to load" << translationPrefix << "translation" << appNameHint << "at" << translationPath; + qCWarning(QtTranslatorDebug) << "Unable to load" << translationPrefix << "translation" << appNameHint << "at" + << translationPath; } else { - qDebug() << "Loaded" << translationPrefix << "translation" << appNameHint << "at" << translationPath; + qCInfo(QtTranslatorDebug) << "Loaded" << translationPrefix << "translation" << appNameHint << "at" + << translationPath; } qApp->installTranslator(translator); } @@ -93,10 +162,10 @@ void installNewTranslator() QString const generateClientID() { QString macList; - foreach (QNetworkInterface interface, QNetworkInterface::allInterfaces()) { - if (interface.hardwareAddress() != "") - if (interface.hardwareAddress() != "00:00:00:00:00:00:00:E0") - macList += interface.hardwareAddress() + "."; + for (const QNetworkInterface &networkInterface : QNetworkInterface::allInterfaces()) { + if (networkInterface.hardwareAddress() != "") + if (networkInterface.hardwareAddress() != "00:00:00:00:00:00:00:E0") + macList += networkInterface.hardwareAddress() + "."; } QString strClientID = QCryptographicHash::hash(macList.toUtf8(), QCryptographicHash::Sha1).toHex().right(15); return strClientID; @@ -104,9 +173,34 @@ QString const generateClientID() int main(int argc, char *argv[]) { +#ifdef Q_OS_WIN + SetUnhandledExceptionFilter(CockatriceUnhandledExceptionFilter); +#endif + +#ifdef Q_OS_APPLE + // /cockatrice/cockatrice.app/Contents/MacOS/cockatrice + const QByteArray configPath = "../../../qtlogging.ini"; +#elif defined(Q_OS_UNIX) + // /cockatrice/cockatrice + const QByteArray configPath = "./qtlogging.ini"; +#elif defined(Q_OS_WIN) + // /cockatrice/Debug/cockatrice.exe + const QByteArray configPath = "../qtlogging.ini"; +#else + const QByteArray configPath = ""; +#endif + + if (!qEnvironmentVariableIsSet(("QT_LOGGING_CONF"))) { + // Set the QT_LOGGING_CONF environment variable + qputenv("QT_LOGGING_CONF", configPath); + } + qSetMessagePattern( + "\033[0m[%{time yyyy-MM-dd h:mm:ss.zzz} " + "%{if-debug}\033[36mD%{endif}%{if-info}\033[32mI%{endif}%{if-warning}\033[33mW%{endif}%{if-critical}\033[31mC%{" + "endif}%{if-fatal}\033[1;31mF%{endif}\033[0m] [%{function}] - %{message} [%{file}:%{line}]"); QApplication app(argc, argv); - QObject::connect(&app, SIGNAL(lastWindowClosed()), &app, SLOT(quit())); + QObject::connect(&app, &QApplication::lastWindowClosed, &app, &QApplication::quit); qInstallMessageHandler(CockatriceLogger); #ifdef Q_OS_WIN @@ -157,13 +251,13 @@ int main(int argc, char *argv[]) QLocale::setDefault(QLocale::English); - qDebug("main(): starting main program"); + qCInfo(MainLog) << "Starting main program"; MainWindow ui; if (parser.isSet("connect")) { ui.setConnectTo(parser.value("connect")); } - qDebug("main(): MainWindow constructor finished"); + qCInfo(MainLog) << "MainWindow constructor finished"; ui.setWindowIcon(QPixmap("theme:cockatrice")); #if QT_VERSION >= QT_VERSION_CHECK(5, 7, 0) @@ -178,14 +272,17 @@ int main(int argc, char *argv[]) SpoilerBackgroundUpdater spoilerBackgroundUpdater; ui.show(); - qDebug("main(): ui.show() finished"); + qCInfo(MainLog) << "ui.show() finished"; + + // force shortcuts to be shown/hidden in right-click menus, regardless of system defaults + qApp->setAttribute(Qt::AA_DontShowShortcutsInContextMenus, !SettingsCache::instance().getShowShortcuts()); #if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) app.setAttribute(Qt::AA_UseHighDpiPixmaps); #endif app.exec(); - qDebug("Event loop finished, terminating..."); + qCInfo(MainLog) << "Event loop finished, terminating..."; delete rng; PingPixmapGenerator::clear(); CountryPixmapGenerator::clear(); diff --git a/cockatrice/src/main.h b/cockatrice/src/main.h index 1d04b30ce..e7dccd631 100644 --- a/cockatrice/src/main.h +++ b/cockatrice/src/main.h @@ -3,6 +3,11 @@ #include "utility/macros.h" +#include + +inline Q_LOGGING_CATEGORY(MainLog, "main"); +inline Q_LOGGING_CATEGORY(QtTranslatorDebug, "qt_translator"); + class CardDatabase; class QString; class QSystemTrayIcon; diff --git a/cockatrice/src/server/chat_view/chat_view.cpp b/cockatrice/src/server/chat_view/chat_view.cpp index 7e1fd8df4..33249829d 100644 --- a/cockatrice/src/server/chat_view/chat_view.cpp +++ b/cockatrice/src/server/chat_view/chat_view.cpp @@ -5,6 +5,8 @@ #include "../../client/ui/pixel_map_generator.h" #include "../../settings/cache_settings.h" #include "../user/user_context_menu.h" +#include "../user/user_list_manager.h" +#include "../user/user_list_proxy.h" #include "user_level.h" #include @@ -21,13 +23,10 @@ UserMessagePosition::UserMessagePosition(QTextCursor &cursor) relativePosition = cursor.position() - block.position(); } -ChatView::ChatView(TabSupervisor *_tabSupervisor, - const UserlistProxy *_userlistProxy, - TabGame *_game, - bool _showTimestamps, - QWidget *parent) - : QTextBrowser(parent), tabSupervisor(_tabSupervisor), game(_game), userlistProxy(_userlistProxy), evenNumber(true), - showTimestamps(_showTimestamps), hoveredItemType(HoveredNothing) +ChatView::ChatView(TabSupervisor *_tabSupervisor, TabGame *_game, bool _showTimestamps, QWidget *parent) + : QTextBrowser(parent), tabSupervisor(_tabSupervisor), game(_game), + userListProxy(_tabSupervisor->getUserListManager()), evenNumber(true), showTimestamps(_showTimestamps), + hoveredItemType(HoveredNothing) { if (palette().windowText().color().lightness() > 200) { document()->setDefaultStyleSheet(R"( @@ -48,7 +47,7 @@ ChatView::ChatView(TabSupervisor *_tabSupervisor, userContextMenu = new UserContextMenu(tabSupervisor, this, game); connect(userContextMenu, SIGNAL(openMessageDialog(QString, bool)), this, SIGNAL(openMessageDialog(QString, bool))); - ownUserName = userlistProxy->getOwnUsername(); + ownUserName = userListProxy->getOwnUsername(); mention = "@" + ownUserName; mentionFormat.setFontWeight(QFont::Bold); @@ -150,11 +149,12 @@ void ChatView::appendUrlTag(QTextCursor &cursor, QString url) void ChatView::appendMessage(QString message, RoomMessageTypeFlags messageType, - const QString &userName, - UserLevelFlags userLevel, - QString UserPrivLevel, + const ServerInfo_User &userInfo, bool playerBold) { + const QString userName = QString::fromStdString(userInfo.name()); + const UserLevelFlags userLevel = UserLevelFlags(userInfo.user_level()); + bool atBottom = verticalScrollBar()->value() >= verticalScrollBar()->maximum(); // messageType should be Event_RoomSay::UserMessage though we don't actually check bool isUserMessage = !(userName.toLower() == "servatrice" || userName.isEmpty()); @@ -189,9 +189,11 @@ void ChatView::appendMessage(QString message, cursor.insertText(" "); } else { const int pixelSize = QFontInfo(cursor.charFormat().font()).pixelSize(); - bool isBuddy = userlistProxy->isUserBuddy(userName); - cursor.insertImage( - UserLevelPixmapGenerator::generatePixmap(pixelSize, userLevel, isBuddy, UserPrivLevel).toImage()); + bool isBuddy = userListProxy->isUserBuddy(userName); + const QString privLevel = userInfo.has_privlevel() ? QString::fromStdString(userInfo.privlevel()) : "NONE"; + cursor.insertImage(UserLevelPixmapGenerator::generatePixmap(pixelSize, userLevel, userInfo.pawn_colors(), + isBuddy, privLevel) + .toImage()); cursor.insertText(" "); cursor.setCharFormat(senderFormat); cursor.insertText(userName); @@ -328,7 +330,7 @@ void ChatView::checkMention(QTextCursor &cursor, QString &message, const QString QString mentionIntact = fullMentionUpToSpaceOrEnd; while (fullMentionUpToSpaceOrEnd.size()) { - const ServerInfo_User *onlineUser = userlistProxy->getOnlineUser(fullMentionUpToSpaceOrEnd); + const ServerInfo_User *onlineUser = userListProxy->getOnlineUser(fullMentionUpToSpaceOrEnd); if (onlineUser) // Is there a user online named this? { if (ownUserName.toLower() == fullMentionUpToSpaceOrEnd.toLower()) // Is this user you? @@ -402,7 +404,7 @@ void ChatView::checkWord(QTextCursor &cursor, QString &message) } // check word mentions - foreach (QString word, highlightedWords) { + for (const QString &word : highlightedWords) { if (fullWordUpToSpaceOrEnd.compare(word, Qt::CaseInsensitive) == 0) { // You have received a valid mention of custom word!! highlightFormat.setBackground(QBrush(getCustomHighlightColor())); @@ -580,9 +582,9 @@ void ChatView::mousePressEvent(QMouseEvent *event) case HoveredCard: { if ((event->button() == Qt::MiddleButton) || (event->button() == Qt::LeftButton)) #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) - emit showCardInfoPopup(event->globalPosition().toPoint(), hoveredContent); + emit showCardInfoPopup(event->globalPosition().toPoint(), hoveredContent, QString()); #else - emit showCardInfoPopup(event->globalPos(), hoveredContent); + emit showCardInfoPopup(event->globalPos(), hoveredContent, QString()); #endif break; } diff --git a/cockatrice/src/server/chat_view/chat_view.h b/cockatrice/src/server/chat_view/chat_view.h index e99e1388a..586ba90b3 100644 --- a/cockatrice/src/server/chat_view/chat_view.h +++ b/cockatrice/src/server/chat_view/chat_view.h @@ -2,10 +2,9 @@ #define CHATVIEW_H #include "../../client/tabs/tab_supervisor.h" -#include "../user/user_list.h" +#include "../user/user_list_widget.h" #include "room_message_type.h" #include "user_level.h" -#include "user_list_proxy.h" #include #include @@ -16,6 +15,7 @@ class QTextTable; class QMouseEvent; class UserContextMenu; +class UserListProxy; class TabGame; class UserMessagePosition @@ -44,7 +44,7 @@ private: HoveredCard, HoveredUser }; - const UserlistProxy *const userlistProxy; + const UserListProxy *const userListProxy; UserContextMenu *userContextMenu; QString lastSender; QString ownUserName; @@ -83,11 +83,7 @@ private slots: void actMessageClicked(); public: - ChatView(TabSupervisor *_tabSupervisor, - const UserlistProxy *_userlistProxy, - TabGame *_game, - bool _showTimestamps, - QWidget *parent = nullptr); + ChatView(TabSupervisor *_tabSupervisor, TabGame *_game, bool _showTimestamps, QWidget *parent = nullptr); void retranslateUi(); void appendHtml(const QString &html); void virtual appendHtmlServerMessage(const QString &html, @@ -95,27 +91,25 @@ public: QString optionalFontColor = QString()); void appendMessage(QString message, RoomMessageTypeFlags messageType = {}, - const QString &userName = QString(), - UserLevelFlags userLevel = UserLevelFlags(), - QString UserPrivLevel = "NONE", + const ServerInfo_User &userInfo = {}, bool playerBold = false); void clearChat(); void redactMessages(const QString &userName, int amount); protected: #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) - void enterEvent(QEnterEvent *event); + void enterEvent(QEnterEvent *event) override; #else - void enterEvent(QEvent *event); + void enterEvent(QEvent *event) override; #endif - void leaveEvent(QEvent *event); - void mouseMoveEvent(QMouseEvent *event); - void mousePressEvent(QMouseEvent *event); - void mouseReleaseEvent(QMouseEvent *event); + void leaveEvent(QEvent *event) override; + void mouseMoveEvent(QMouseEvent *event) override; + void mousePressEvent(QMouseEvent *event) override; + void mouseReleaseEvent(QMouseEvent *event) override; signals: void openMessageDialog(const QString &userName, bool focus); void cardNameHovered(QString cardName); - void showCardInfoPopup(QPoint pos, QString cardName); + void showCardInfoPopup(const QPoint &pos, const QString &cardName, const QString &providerId); void deleteCardInfoPopup(QString cardName); void addMentionTag(QString mentionTag); void messageClickedSignal(); diff --git a/cockatrice/src/server/local_client.h b/cockatrice/src/server/local_client.h index ce33611ab..06084b4d4 100644 --- a/cockatrice/src/server/local_client.h +++ b/cockatrice/src/server/local_client.h @@ -16,9 +16,9 @@ public: const QString &_playerName, const QString &_clientId, QObject *parent = nullptr); - ~LocalClient(); + ~LocalClient() override; - void sendCommandContainer(const CommandContainer &cont); + void sendCommandContainer(const CommandContainer &cont) override; private slots: void itemFromServer(const ServerMessage &item); }; diff --git a/cockatrice/src/server/local_server.h b/cockatrice/src/server/local_server.h index d3242548e..5f7d480dc 100644 --- a/cockatrice/src/server/local_server.h +++ b/cockatrice/src/server/local_server.h @@ -10,8 +10,8 @@ class LocalServer : public Server { Q_OBJECT public: - LocalServer(QObject *parent = nullptr); - ~LocalServer(); + explicit LocalServer(QObject *parent = nullptr); + ~LocalServer() override; LocalServerInterface *newConnection(); }; @@ -23,27 +23,27 @@ private: LocalServer *localServer; protected: - ServerInfo_User getUserData(const QString &name, bool withId = false); + ServerInfo_User getUserData(const QString &name, bool withId = false) override; public: - LocalServer_DatabaseInterface(LocalServer *_localServer); - ~LocalServer_DatabaseInterface() = default; + explicit LocalServer_DatabaseInterface(LocalServer *_localServer); + ~LocalServer_DatabaseInterface() override = default; AuthenticationResult checkUserPassword(Server_ProtocolHandler *handler, const QString &user, const QString &password, const QString &clientId, QString &reasonStr, int &secondsLeft, - bool passwordNeedsHash); - int getNextGameId() + bool passwordNeedsHash) override; + int getNextGameId() override { return localServer->getNextLocalGameId(); } - int getNextReplayId() + int getNextReplayId() override { return -1; } - int getActiveUserCount(QString /* connectionType */) + int getActiveUserCount(QString /* connectionType */) override { return 0; } diff --git a/cockatrice/src/server/local_server_interface.h b/cockatrice/src/server/local_server_interface.h index 46c82d68c..8e997ae30 100644 --- a/cockatrice/src/server/local_server_interface.h +++ b/cockatrice/src/server/local_server_interface.h @@ -10,17 +10,17 @@ class LocalServerInterface : public Server_ProtocolHandler Q_OBJECT public: LocalServerInterface(LocalServer *_server, Server_DatabaseInterface *_databaseInterface); - ~LocalServerInterface(); + ~LocalServerInterface() override; - QString getAddress() const + QString getAddress() const override { return QString(); } - QString getConnectionType() const + QString getConnectionType() const override { return "local"; }; - void transmitProtocolItem(const ServerMessage &item); + void transmitProtocolItem(const ServerMessage &item) override; signals: void itemToClient(const ServerMessage &item); public slots: diff --git a/cockatrice/src/server/message_log_widget.cpp b/cockatrice/src/server/message_log_widget.cpp index 0538517f5..568253e31 100644 --- a/cockatrice/src/server/message_log_widget.cpp +++ b/cockatrice/src/server/message_log_widget.cpp @@ -378,7 +378,7 @@ void MessageLogWidget::logDrawCards(Player *player, int number, bool deckIsEmpty } } -void MessageLogWidget::logDumpZone(Player *player, CardZone *zone, int numberCards) +void MessageLogWidget::logDumpZone(Player *player, CardZone *zone, int numberCards, bool isReversed) { if (numberCards == -1) { appendHtmlServerMessage(tr("%1 is looking at %2.") @@ -386,10 +386,11 @@ void MessageLogWidget::logDumpZone(Player *player, CardZone *zone, int numberCar .arg(zone->getTranslatedName(zone->getPlayer() == player, CaseLookAtZone))); } else { appendHtmlServerMessage( - tr("%1 is looking at the top %3 card(s) %2.", "top card for singular, top %3 cards for plural", numberCards) + tr("%1 is looking at the %4 %3 card(s) %2.", "top card for singular, top %3 cards for plural", numberCards) .arg(sanitizeHtml(player->getName())) .arg(zone->getTranslatedName(zone->getPlayer() == player, CaseTopCardsOfZone)) - .arg("" + QString::number(numberCards) + "")); + .arg("" + QString::number(numberCards) + "") + .arg(isReversed ? tr("bottom") : tr("top"))); } } @@ -605,8 +606,7 @@ void MessageLogWidget::logRollDie(Player *player, int sides, const QList & void MessageLogWidget::logSay(Player *player, QString message) { - appendMessage(std::move(message), {}, player->getName(), UserLevelFlags(player->getUserInfo()->user_level()), - QString::fromStdString(player->getUserInfo()->privlevel()), true); + appendMessage(std::move(message), {}, *player->getUserInfo(), true); } void MessageLogWidget::logSetActivePhase(int phaseNumber) @@ -782,12 +782,9 @@ void MessageLogWidget::logShuffle(Player *player, CardZone *zone, int start, int } } -void MessageLogWidget::logSpectatorSay(QString spectatorName, - UserLevelFlags spectatorUserLevel, - QString userPrivLevel, - QString message) +void MessageLogWidget::logSpectatorSay(const ServerInfo_User &spectator, QString message) { - appendMessage(std::move(message), {}, spectatorName, spectatorUserLevel, userPrivLevel, false); + appendMessage(std::move(message), {}, spectator, false); } void MessageLogWidget::logUnattachCard(Player *player, QString cardName) @@ -850,7 +847,7 @@ void MessageLogWidget::connectToPlayer(Player *player) connect(player, SIGNAL(logAttachCard(Player *, QString, Player *, QString)), this, SLOT(logAttachCard(Player *, QString, Player *, QString))); connect(player, SIGNAL(logUnattachCard(Player *, QString)), this, SLOT(logUnattachCard(Player *, QString))); - connect(player, SIGNAL(logDumpZone(Player *, CardZone *, int)), this, SLOT(logDumpZone(Player *, CardZone *, int))); + connect(player, &Player::logDumpZone, this, &MessageLogWidget::logDumpZone); connect(player, SIGNAL(logDrawCards(Player *, int, bool)), this, SLOT(logDrawCards(Player *, int, bool))); connect(player, SIGNAL(logUndoDraw(Player *, QString)), this, SLOT(logUndoDraw(Player *, QString))); connect(player, SIGNAL(logRevealCards(Player *, CardZone *, int, QString, Player *, bool, int, bool)), this, @@ -861,11 +858,7 @@ void MessageLogWidget::connectToPlayer(Player *player) SLOT(logAlwaysLookAtTopCard(Player *, CardZone *, bool))); } -MessageLogWidget::MessageLogWidget(TabSupervisor *_tabSupervisor, - const UserlistProxy *_userlistProxy, - TabGame *_game, - QWidget *parent) - : ChatView(_tabSupervisor, _userlistProxy, _game, true, parent), mulliganNumber(0), - currentContext(MessageContext_None) +MessageLogWidget::MessageLogWidget(TabSupervisor *_tabSupervisor, TabGame *_game, QWidget *parent) + : ChatView(_tabSupervisor, _game, true, parent), mulliganNumber(0), currentContext(MessageContext_None) { } diff --git a/cockatrice/src/server/message_log_widget.h b/cockatrice/src/server/message_log_widget.h index 8dc28b1b0..ef66f21e7 100644 --- a/cockatrice/src/server/message_log_widget.h +++ b/cockatrice/src/server/message_log_widget.h @@ -57,7 +57,7 @@ public slots: void logDeckSelect(Player *player, QString deckHash, int sideboardSize); void logDestroyCard(Player *player, QString cardName); void logDrawCards(Player *player, int number, bool deckIsEmpty); - void logDumpZone(Player *player, CardZone *zone, int numberCards); + void logDumpZone(Player *player, CardZone *zone, int numberCards, bool isReversed = false); void logFlipCard(Player *player, QString cardName, bool faceDown); void logGameClosed(); void logGameStart(); @@ -92,8 +92,7 @@ public slots: void logSetSideboardLock(Player *player, bool locked); void logSetTapped(Player *player, CardItem *card, bool tapped); void logShuffle(Player *player, CardZone *zone, int start, int end); - void - logSpectatorSay(QString spectatorName, UserLevelFlags spectatorUserLevel, QString userPrivLevel, QString message); + void logSpectatorSay(const ServerInfo_User &spectator, QString message); void logUnattachCard(Player *player, QString cardName); void logUndoDraw(Player *player, QString cardName); void setContextJudgeName(QString player); @@ -103,10 +102,7 @@ public slots: public: void connectToPlayer(Player *player); - MessageLogWidget(TabSupervisor *_tabSupervisor, - const UserlistProxy *_userlistProxy, - TabGame *_game, - QWidget *parent = nullptr); + MessageLogWidget(TabSupervisor *_tabSupervisor, TabGame *_game, QWidget *parent = nullptr); }; #endif diff --git a/cockatrice/src/server/pending_command.cpp b/cockatrice/src/server/pending_command.cpp index e6a1761b3..4a9943d33 100644 --- a/cockatrice/src/server/pending_command.cpp +++ b/cockatrice/src/server/pending_command.cpp @@ -23,7 +23,6 @@ QVariant PendingCommand::getExtraData() const void PendingCommand::processResponse(const Response &response) { emit finished(response, commandContainer, extraData); - emit finished(response.response_code()); } int PendingCommand::tick() diff --git a/cockatrice/src/server/pending_command.h b/cockatrice/src/server/pending_command.h index c42ef8eb6..d845167ac 100644 --- a/cockatrice/src/server/pending_command.h +++ b/cockatrice/src/server/pending_command.h @@ -11,7 +11,6 @@ class PendingCommand : public QObject Q_OBJECT signals: void finished(const Response &response, const CommandContainer &commandContainer, const QVariant &extraData); - void finished(Response::ResponseCode respCode); private: CommandContainer commandContainer; @@ -19,7 +18,7 @@ private: int ticks; public: - PendingCommand(const CommandContainer &_commandContainer, QVariant _extraData = QVariant()); + explicit PendingCommand(const CommandContainer &_commandContainer, QVariant _extraData = QVariant()); CommandContainer &getCommandContainer(); void setExtraData(const QVariant &_extraData); QVariant getExtraData() const; diff --git a/cockatrice/src/server/remote/remote_client.cpp b/cockatrice/src/server/remote/remote_client.cpp index c29d6440f..5812483ad 100644 --- a/cockatrice/src/server/remote/remote_client.cpp +++ b/cockatrice/src/server/remote/remote_client.cpp @@ -140,7 +140,7 @@ void RemoteClient::processServerIdentificationEvent(const Event_ServerIdentifica hashedPassword = PasswordHasher::computeHash(password, passwordSalt); cmdForgotPasswordReset.set_hashed_new_password(hashedPassword.toStdString()); } else if (!password.isEmpty()) { - qWarning() << "using plain text password to reset password"; + qCWarning(RemoteClientLog) << "using plain text password to reset password"; cmdForgotPasswordReset.set_new_password(password.toStdString()); } PendingCommand *pend = prepareSessionCommand(cmdForgotPasswordReset); @@ -170,7 +170,7 @@ void RemoteClient::processServerIdentificationEvent(const Event_ServerIdentifica hashedPassword = PasswordHasher::computeHash(password, passwordSalt); cmdRegister.set_hashed_password(hashedPassword.toStdString()); } else if (!password.isEmpty()) { - qWarning() << "using plain text password to register"; + qCWarning(RemoteClientLog) << "using plain text password to register"; cmdRegister.set_password(password.toStdString()); } cmdRegister.set_email(email.toStdString()); @@ -241,7 +241,7 @@ void RemoteClient::doLogin() setStatus(StatusLoggingIn); Command_Login cmdLogin = generateCommandLogin(); if (!password.isEmpty()) { - qWarning() << "using plain text password to log in"; + qCWarning(RemoteClientLog) << "using plain text password to log in"; cmdLogin.set_password(password.toStdString()); } @@ -393,9 +393,9 @@ void RemoteClient::readData() ServerMessage newServerMessage; newServerMessage.ParseFromArray(inputBuffer.data(), messageLength); -#ifdef QT_DEBUG - qDebug().noquote() << "IN" << getSafeDebugString(newServerMessage); -#endif + + qCDebug(RemoteClientLog).noquote() << "IN" << getSafeDebugString(newServerMessage); + inputBuffer.remove(0, messageLength); messageInProgress = false; @@ -411,9 +411,9 @@ void RemoteClient::websocketMessageReceived(const QByteArray &message) lastDataReceived = timeRunning; ServerMessage newServerMessage; newServerMessage.ParseFromArray(message.data(), message.length()); -#ifdef QT_DEBUG - qDebug().noquote() << "IN" << getSafeDebugString(newServerMessage); -#endif + + qCDebug(RemoteClientLog).noquote() << "IN" << getSafeDebugString(newServerMessage); + processProtocolItem(newServerMessage); } @@ -424,9 +424,8 @@ void RemoteClient::sendCommandContainer(const CommandContainer &cont) #else auto size = static_cast(cont.ByteSize()); #endif -#ifdef QT_DEBUG - qDebug().noquote() << "OUT" << getSafeDebugString(cont); -#endif + + qCDebug(RemoteClientLog).noquote() << "OUT" << getSafeDebugString(cont); QByteArray buf; if (usingWebSocket) { @@ -579,7 +578,6 @@ void RemoteClient::activateToServer(const QString &_token) void RemoteClient::disconnectFromServer() { - SettingsCache::instance().servers().setAutoConnect(false); emit sigDisconnectFromServer(); } @@ -591,7 +589,7 @@ QString RemoteClient::getSrvClientID(const QString &_hostname) QHostAddress hostAddress = hostInfo.addresses().first(); srvClientID += hostAddress.toString(); } else { - qWarning() << "ClientID generation host lookup failure [" << hostInfo.errorString() << "]"; + qCWarning(RemoteClientLog) << "ClientID generation host lookup failure [" << hostInfo.errorString() << "]"; srvClientID += _hostname; } QString uniqueServerClientID = diff --git a/cockatrice/src/server/remote/remote_client.h b/cockatrice/src/server/remote/remote_client.h index f1f64d4c2..f34fdb71b 100644 --- a/cockatrice/src/server/remote/remote_client.h +++ b/cockatrice/src/server/remote/remote_client.h @@ -4,16 +4,18 @@ #include "../../client/game_logic/abstract_client.h" #include "pb/commands.pb.h" +#include #include #include +inline Q_LOGGING_CATEGORY(RemoteClientLog, "remote_client"); + class QTimer; class RemoteClient : public AbstractClient { Q_OBJECT signals: - void maxPingTime(int seconds, int maxSeconds); void serverTimeout(); void loginError(Response::ResponseCode resp, QString reasonStr, quint32 endTime, QList missingFeatures); void registerError(Response::ResponseCode resp, QString reasonStr, quint32 endTime); diff --git a/cockatrice/src/server/remote/remote_decklist_tree_widget.cpp b/cockatrice/src/server/remote/remote_decklist_tree_widget.cpp index 63b6a1ad8..40ea30da6 100644 --- a/cockatrice/src/server/remote/remote_decklist_tree_widget.cpp +++ b/cockatrice/src/server/remote/remote_decklist_tree_widget.cpp @@ -86,7 +86,6 @@ RemoteDeckList_TreeModel::RemoteDeckList_TreeModel(AbstractClient *_client, QObj fileIcon = fip.icon(QFileIconProvider::File); root = new DirectoryNode; - refreshTree(); } RemoteDeckList_TreeModel::~RemoteDeckList_TreeModel() @@ -270,15 +269,18 @@ void RemoteDeckList_TreeModel::refreshTree() client->sendCommand(pend); } +void RemoteDeckList_TreeModel::clearTree() +{ + beginResetModel(); + root->clearTree(); + endResetModel(); +} + void RemoteDeckList_TreeModel::deckListFinished(const Response &r) { const Response_DeckList &resp = r.GetExtension(Response_DeckList::ext); - beginResetModel(); - - root->clearTree(); - - endResetModel(); + clearTree(); ServerInfo_DeckStorage_TreeItem tempRoot; tempRoot.set_id(0); @@ -300,6 +302,7 @@ RemoteDeckList_TreeWidget::RemoteDeckList_TreeWidget(AbstractClient *_client, QW header()->setSectionResizeMode(QHeaderView::ResizeToContents); setUniformRowHeights(true); + setSelectionMode(QAbstractItemView::ExtendedSelection); setSortingEnabled(true); proxyModel->sort(0, Qt::AscendingOrder); header()->setSortIndicator(0, Qt::AscendingOrder); @@ -315,6 +318,15 @@ RemoteDeckList_TreeModel::Node *RemoteDeckList_TreeWidget::getCurrentItem() cons return getNode(selectionModel()->currentIndex()); } +QList RemoteDeckList_TreeWidget::getCurrentSelection() const +{ + auto list = QList(); + for (const auto &row : selectionModel()->selectedRows()) { + list << getNode(row); + } + return list; +} + RemoteDeckList_TreeModel::DirectoryNode *RemoteDeckList_TreeWidget::getNodeByPath(const QString &path) const { return treeModel->getRoot()->getNodeByPath(path.split("/")); @@ -351,3 +363,8 @@ void RemoteDeckList_TreeWidget::refreshTree() { treeModel->refreshTree(); } + +void RemoteDeckList_TreeWidget::clearTree() +{ + treeModel->clearTree(); +} diff --git a/cockatrice/src/server/remote/remote_decklist_tree_widget.h b/cockatrice/src/server/remote/remote_decklist_tree_widget.h index dae77b0d6..f82378731 100644 --- a/cockatrice/src/server/remote/remote_decklist_tree_widget.h +++ b/cockatrice/src/server/remote/remote_decklist_tree_widget.h @@ -23,10 +23,10 @@ public: QString name; public: - Node(const QString &_name, DirectoryNode *_parent = nullptr) : parent(_parent), name(_name) + explicit Node(const QString &_name, DirectoryNode *_parent = nullptr) : parent(_parent), name(_name) { } - virtual ~Node(){}; + virtual ~Node() = default; DirectoryNode *getParent() const { return parent; @@ -39,8 +39,8 @@ public: class DirectoryNode : public Node, public QList { public: - DirectoryNode(const QString &_name = QString(), DirectoryNode *_parent = nullptr); - ~DirectoryNode(); + explicit DirectoryNode(const QString &_name = QString(), DirectoryNode *_parent = nullptr); + ~DirectoryNode() override; void clearTree(); QString getPath() const; DirectoryNode *getNodeByPath(QStringList path); @@ -87,15 +87,15 @@ private slots: void deckListFinished(const Response &r); public: - RemoteDeckList_TreeModel(AbstractClient *_client, QObject *parent = nullptr); - ~RemoteDeckList_TreeModel(); - int rowCount(const QModelIndex &parent = QModelIndex()) const; - int columnCount(const QModelIndex & /*parent*/ = QModelIndex()) const; - QVariant data(const QModelIndex &index, int role) const; - QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; - QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const; - QModelIndex parent(const QModelIndex &index) const; - Qt::ItemFlags flags(const QModelIndex &index) const; + explicit RemoteDeckList_TreeModel(AbstractClient *_client, QObject *parent = nullptr); + ~RemoteDeckList_TreeModel() override; + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + int columnCount(const QModelIndex & /*parent*/ = QModelIndex()) const override; + QVariant data(const QModelIndex &index, int role) const override; + QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; + QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override; + QModelIndex parent(const QModelIndex &index) const override; + Qt::ItemFlags flags(const QModelIndex &index) const override; DirectoryNode *getRoot() const { @@ -106,6 +106,7 @@ public: DirectoryNode *addNamedFolderToTree(const QString &name, DirectoryNode *parent); void removeNode(Node *node); void refreshTree(); + void clearTree(); }; class RemoteDeckList_TreeWidget : public QTreeView @@ -115,9 +116,10 @@ private: QSortFilterProxyModel *proxyModel; public: - RemoteDeckList_TreeWidget(AbstractClient *_client, QWidget *parent = nullptr); + explicit RemoteDeckList_TreeWidget(AbstractClient *_client, QWidget *parent = nullptr); RemoteDeckList_TreeModel::Node *getNode(const QModelIndex &ind) const; RemoteDeckList_TreeModel::Node *getCurrentItem() const; + QList getCurrentSelection() const; RemoteDeckList_TreeModel::DirectoryNode *getNodeByPath(const QString &path) const; RemoteDeckList_TreeModel::FileNode *getNodeById(int id) const; void addFileToTree(const ServerInfo_DeckStorage_TreeItem &file, RemoteDeckList_TreeModel::DirectoryNode *parent); @@ -126,6 +128,7 @@ public: void addFolderToTree(const QString &name, RemoteDeckList_TreeModel::DirectoryNode *parent); void removeNode(RemoteDeckList_TreeModel::Node *node); void refreshTree(); + void clearTree(); }; #endif diff --git a/cockatrice/src/server/remote/remote_replay_list_tree_widget.cpp b/cockatrice/src/server/remote/remote_replay_list_tree_widget.cpp index 437959949..1f76a1f8b 100644 --- a/cockatrice/src/server/remote/remote_replay_list_tree_widget.cpp +++ b/cockatrice/src/server/remote/remote_replay_list_tree_widget.cpp @@ -37,13 +37,11 @@ RemoteReplayList_TreeModel::RemoteReplayList_TreeModel(AbstractClient *_client, dirIcon = fip.icon(QFileIconProvider::Folder); fileIcon = fip.icon(QFileIconProvider::File); lockIcon = QPixmap("theme:icons/lock"); - - refreshTree(); } RemoteReplayList_TreeModel::~RemoteReplayList_TreeModel() { - clearTree(); + clearAll(); } int RemoteReplayList_TreeModel::rowCount(const QModelIndex &parent) const @@ -192,7 +190,7 @@ QModelIndex RemoteReplayList_TreeModel::parent(const QModelIndex &ind) const return QModelIndex(); else { ReplayNode *replayNode = dynamic_cast(static_cast(ind.internalPointer())); - return createIndex(replayNode->getParent()->indexOf(replayNode), 0, replayNode); + return createIndex(replayNode->getParent()->indexOf(replayNode), 0, replayNode->getParent()); } } @@ -216,6 +214,18 @@ ServerInfo_Replay const *RemoteReplayList_TreeModel::getReplay(const QModelIndex } ServerInfo_ReplayMatch const *RemoteReplayList_TreeModel::getReplayMatch(const QModelIndex &index) const +{ + if (!index.isValid()) + return nullptr; + + auto *node = dynamic_cast(static_cast(index.internalPointer())); + if (!node) + return nullptr; + + return &node->getMatchInfo(); +} + +ServerInfo_ReplayMatch const *RemoteReplayList_TreeModel::getEnclosingReplayMatch(const QModelIndex &index) const { if (!index.isValid()) return nullptr; @@ -230,7 +240,10 @@ ServerInfo_ReplayMatch const *RemoteReplayList_TreeModel::getReplayMatch(const Q return &node->getMatchInfo(); } -void RemoteReplayList_TreeModel::clearTree() +/** + * Deletes all items in the model + */ +void RemoteReplayList_TreeModel::clearAll() { for (int i = 0; i < replayMatches.size(); ++i) delete replayMatches[i]; @@ -246,6 +259,13 @@ void RemoteReplayList_TreeModel::refreshTree() client->sendCommand(pend); } +void RemoteReplayList_TreeModel::clearTree() +{ + beginResetModel(); + clearAll(); + endResetModel(); +} + void RemoteReplayList_TreeModel::addMatchInfo(const ServerInfo_ReplayMatch &matchInfo) { beginInsertRows(QModelIndex(), replayMatches.size(), replayMatches.size()); @@ -282,7 +302,7 @@ void RemoteReplayList_TreeModel::replayListFinished(const Response &r) const Response_ReplayList &resp = r.GetExtension(Response_ReplayList::ext); beginResetModel(); - clearTree(); + clearAll(); for (int i = 0; i < resp.match_list_size(); ++i) replayMatches.append(new MatchNode(resp.match_list(i))); @@ -306,14 +326,60 @@ RemoteReplayList_TreeWidget::RemoteReplayList_TreeWidget(AbstractClient *_client setSortingEnabled(true); proxyModel->sort(0, Qt::AscendingOrder); header()->setSortIndicator(0, Qt::AscendingOrder); + setSelectionMode(QAbstractItemView::ExtendedSelection); } -ServerInfo_Replay const *RemoteReplayList_TreeWidget::getCurrentReplay() const +/** + * Gets the replay at the given index + * @return The replay. Returns nullptr if there is no replay at the index. + */ +ServerInfo_Replay const *RemoteReplayList_TreeWidget::getReplay(const QModelIndex &ind) const { - return treeModel->getReplay(proxyModel->mapToSource(selectionModel()->currentIndex())); + return treeModel->getReplay(proxyModel->mapToSource(ind)); } -ServerInfo_ReplayMatch const *RemoteReplayList_TreeWidget::getCurrentReplayMatch() const +/** + * Gets the replay match at the given index + * @return The replay match. Returns nullptr if there is no replay match at the index. + */ +ServerInfo_ReplayMatch const *RemoteReplayList_TreeWidget::getReplayMatch(const QModelIndex &ind) const { - return treeModel->getReplayMatch(proxyModel->mapToSource(selectionModel()->currentIndex())); + return treeModel->getReplayMatch(proxyModel->mapToSource(ind)); +} + +/** + * Gets all currently selected replays. + * Any selection that isn't a replay file (e.g. a folder) will appear as a nullptr in the list. + * Make sure to check the list for nullptr before using it. + * + * @return A List of pointers to the selected replays, as well as nullptr for any selection that isn't a replay. + */ +QList RemoteReplayList_TreeWidget::getSelectedReplays() const +{ + const auto selection = selectionModel()->selectedRows(); + auto replays = QList(); + for (const auto &row : selection) { + replays << treeModel->getReplay(proxyModel->mapToSource(row)); + } + + return replays; +} + +/** + * Gets all currently selected replayMatches. + * If a non-folder node is selected, it will return the parent folder of that node. + * + * @return A Set of pointers to the selected replayMatches. + */ +QSet RemoteReplayList_TreeWidget::getSelectedReplayMatches() const +{ + const auto selection = selectionModel()->selectedRows(); + auto replayMatches = QSet(); + for (const auto &row : selection) { + if (const auto replayMatch = treeModel->getEnclosingReplayMatch(proxyModel->mapToSource(row))) { + replayMatches << replayMatch; + } + } + + return replayMatches; } diff --git a/cockatrice/src/server/remote/remote_replay_list_tree_widget.h b/cockatrice/src/server/remote/remote_replay_list_tree_widget.h index 042d5a5b4..02082046a 100644 --- a/cockatrice/src/server/remote/remote_replay_list_tree_widget.h +++ b/cockatrice/src/server/remote/remote_replay_list_tree_widget.h @@ -24,10 +24,10 @@ private: QString name; public: - Node(const QString &_name) : name(_name) + explicit Node(const QString &_name) : name(_name) { } - virtual ~Node(){}; + virtual ~Node() = default; QString getName() const { return name; @@ -39,8 +39,8 @@ private: ServerInfo_ReplayMatch matchInfo; public: - MatchNode(const ServerInfo_ReplayMatch &_matchInfo); - ~MatchNode(); + explicit MatchNode(const ServerInfo_ReplayMatch &_matchInfo); + ~MatchNode() override; void clearTree(); const ServerInfo_ReplayMatch &getMatchInfo() { @@ -73,7 +73,7 @@ private: QList replayMatches; QIcon dirIcon, fileIcon, lockIcon; - void clearTree(); + void clearAll(); static const int numberOfColumns; signals: @@ -82,21 +82,23 @@ private slots: void replayListFinished(const Response &r); public: - RemoteReplayList_TreeModel(AbstractClient *_client, QObject *parent = nullptr); - ~RemoteReplayList_TreeModel(); - int rowCount(const QModelIndex &parent = QModelIndex()) const; - int columnCount(const QModelIndex & /*parent*/ = QModelIndex()) const + explicit RemoteReplayList_TreeModel(AbstractClient *_client, QObject *parent = nullptr); + ~RemoteReplayList_TreeModel() override; + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + int columnCount(const QModelIndex & /*parent*/ = QModelIndex()) const override { return numberOfColumns; } - QVariant data(const QModelIndex &index, int role) const; - QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; - QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const; - QModelIndex parent(const QModelIndex &index) const; - Qt::ItemFlags flags(const QModelIndex &index) const; + QVariant data(const QModelIndex &index, int role) const override; + QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; + QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override; + QModelIndex parent(const QModelIndex &index) const override; + Qt::ItemFlags flags(const QModelIndex &index) const override; + void clearTree(); void refreshTree(); ServerInfo_Replay const *getReplay(const QModelIndex &index) const; ServerInfo_ReplayMatch const *getReplayMatch(const QModelIndex &index) const; + ServerInfo_ReplayMatch const *getEnclosingReplayMatch(const QModelIndex &index) const; void addMatchInfo(const ServerInfo_ReplayMatch &matchInfo); void updateMatchInfo(int gameId, const ServerInfo_ReplayMatch &matchInfo); void removeMatchInfo(int gameId); @@ -107,13 +109,21 @@ class RemoteReplayList_TreeWidget : public QTreeView private: RemoteReplayList_TreeModel *treeModel; QSortFilterProxyModel *proxyModel; - ServerInfo_Replay const *getNode(const QModelIndex &ind) const; public: - RemoteReplayList_TreeWidget(AbstractClient *_client, QWidget *parent = nullptr); - ServerInfo_Replay const *getCurrentReplay() const; - ServerInfo_ReplayMatch const *getCurrentReplayMatch() const; - void refreshTree(); + explicit RemoteReplayList_TreeWidget(AbstractClient *_client, QWidget *parent = nullptr); + ServerInfo_Replay const *getReplay(const QModelIndex &ind) const; + ServerInfo_ReplayMatch const *getReplayMatch(const QModelIndex &ind) const; + QList getSelectedReplays() const; + QSet getSelectedReplayMatches() const; + void clearTree() + { + treeModel->clearTree(); + } + void refreshTree() + { + treeModel->refreshTree(); + } void addMatchInfo(const ServerInfo_ReplayMatch &matchInfo) { treeModel->addMatchInfo(matchInfo); diff --git a/cockatrice/src/server/user/user_context_menu.cpp b/cockatrice/src/server/user/user_context_menu.cpp index 944796319..10955a160 100644 --- a/cockatrice/src/server/user/user_context_menu.cpp +++ b/cockatrice/src/server/user/user_context_menu.cpp @@ -11,13 +11,16 @@ #include "pb/commands.pb.h" #include "pb/moderator_commands.pb.h" #include "pb/response_ban_history.pb.h" +#include "pb/response_get_admin_notes.pb.h" #include "pb/response_get_games_of_user.pb.h" #include "pb/response_get_user_info.pb.h" #include "pb/response_warn_history.pb.h" #include "pb/response_warn_list.pb.h" #include "pb/session_commands.pb.h" #include "user_info_box.h" -#include "user_list.h" +#include "user_list_manager.h" +#include "user_list_proxy.h" +#include "user_list_widget.h" #include #include @@ -27,7 +30,8 @@ #include UserContextMenu::UserContextMenu(TabSupervisor *_tabSupervisor, QWidget *parent, TabGame *_game) - : QObject(parent), client(_tabSupervisor->getClient()), tabSupervisor(_tabSupervisor), game(_game) + : QObject(parent), client(_tabSupervisor->getClient()), tabSupervisor(_tabSupervisor), + userListProxy(_tabSupervisor->getUserListManager()), game(_game) { aUserName = new QAction(QString(), this); aUserName->setEnabled(false); @@ -47,6 +51,7 @@ UserContextMenu::UserContextMenu(TabSupervisor *_tabSupervisor, QWidget *parent, aDemoteFromMod = new QAction(QString(), this); aPromoteToJudge = new QAction(QString(), this); aDemoteFromJudge = new QAction(QString(), this); + aGetAdminNotes = new QAction(QString(), this); retranslateUi(); } @@ -69,6 +74,7 @@ void UserContextMenu::retranslateUi() aDemoteFromMod->setText(tr("Dem&ote user from moderator")); aPromoteToJudge->setText(tr("Promote user to &judge")); aDemoteFromJudge->setText(tr("Demote user from judge")); + aGetAdminNotes->setText(tr("View admin notes")); } void UserContextMenu::gamesOfUserReceived(const Response &resp, const CommandContainer &commandContainer) @@ -221,6 +227,21 @@ void UserContextMenu::warnUserHistory_processResponse(const Response &resp) tr("Failed to collect warning information.")); } +void UserContextMenu::getAdminNotes_processResponse(const Response &resp) +{ + const Response_GetAdminNotes &response = resp.GetExtension(Response_GetAdminNotes::ext); + + if (resp.response_code() != Response::RespOk) { + QMessageBox::information(static_cast(parent()), tr("Failed"), tr("Failed to get admin notes.")); + return; + } + + auto *dlg = new AdminNotesDialog(QString::fromStdString(response.user_name()), + QString::fromStdString(response.notes()), static_cast(parent())); + connect(dlg, &AdminNotesDialog::accepted, this, &UserContextMenu::updateAdminNotes_dialogFinished); + dlg->show(); +} + void UserContextMenu::adjustMod_processUserResponse(const Response &resp, const CommandContainer &commandContainer) { @@ -266,7 +287,7 @@ void UserContextMenu::warnUser_dialogFinished() { WarningDialog *dlg = static_cast(sender()); - if (dlg->getName().isEmpty() || tabSupervisor->getOwnUsername().simplified().isEmpty()) + if (dlg->getName().isEmpty() || userListProxy->getOwnUsername().simplified().isEmpty()) return; Command_WarnUser cmd; @@ -281,6 +302,17 @@ void UserContextMenu::warnUser_dialogFinished() client->sendCommand(client->prepareModeratorCommand(cmd)); } +void UserContextMenu::updateAdminNotes_dialogFinished() +{ + auto *dlg = static_cast(sender()); + + Command_UpdateAdminNotes cmd; + cmd.set_user_name(dlg->getName().toStdString()); + cmd.set_notes(dlg->getNotes().toStdString()); + + client->sendCommand(client->prepareModeratorCommand(cmd)); +} + void UserContextMenu::showContextMenu(const QPoint &pos, const QString &userName, UserLevelFlags userLevel, @@ -319,14 +351,14 @@ void UserContextMenu::showContextMenu(const QPoint &pos, menu->addAction(aDetails); menu->addAction(aShowGames); menu->addAction(aChat); - if (userLevel.testFlag(ServerInfo_User::IsRegistered) && tabSupervisor->isOwnUserRegistered()) { + if (userLevel.testFlag(ServerInfo_User::IsRegistered) && userListProxy->isOwnUserRegistered()) { menu->addSeparator(); - if (tabSupervisor->isUserBuddy(userName)) { + if (userListProxy->isUserBuddy(userName)) { menu->addAction(aRemoveFromBuddyList); } else { menu->addAction(aAddToBuddyList); } - if (tabSupervisor->isUserIgnored(userName)) { + if (userListProxy->isUserIgnored(userName)) { menu->addAction(aRemoveFromIgnoreList); } else { menu->addAction(aAddToIgnoreList); @@ -347,6 +379,8 @@ void UserContextMenu::showContextMenu(const QPoint &pos, menu->addSeparator(); menu->addAction(aBan); menu->addAction(aBanHistory); + menu->addSeparator(); + menu->addAction(aGetAdminNotes); menu->addSeparator(); if (userLevel.testFlag(ServerInfo_User::IsModerator) && @@ -367,7 +401,7 @@ void UserContextMenu::showContextMenu(const QPoint &pos, menu->addAction(aPromoteToJudge); } } - bool anotherUser = userName != tabSupervisor->getOwnUsername(); + bool anotherUser = userName != userListProxy->getOwnUsername(); aDetails->setEnabled(true); aChat->setEnabled(anotherUser && online); aShowGames->setEnabled(online); @@ -380,6 +414,7 @@ void UserContextMenu::showContextMenu(const QPoint &pos, aWarnHistory->setEnabled(anotherUser); aBan->setEnabled(anotherUser); aBanHistory->setEnabled(anotherUser); + aGetAdminNotes->setEnabled(anotherUser); aPromoteToMod->setEnabled(anotherUser); aDemoteFromMod->setEnabled(anotherUser); @@ -478,6 +513,14 @@ void UserContextMenu::showContextMenu(const QPoint &pos, connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this, SLOT(warnUserHistory_processResponse(Response))); client->sendCommand(pend); + } else if (actionClicked == aGetAdminNotes) { + Command_GetAdminNotes cmd; + cmd.set_user_name(userName.toStdString()); + auto *pend = client->prepareModeratorCommand(cmd); + connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this, + SLOT(getAdminNotes_processResponse(Response))); + client->sendCommand(pend); + } else if (actionClicked == aCopyToClipBoard) { QClipboard *clipboard = QGuiApplication::clipboard(); clipboard->setText(deckHash); diff --git a/cockatrice/src/server/user/user_context_menu.h b/cockatrice/src/server/user/user_context_menu.h index 848f9ecc8..66c8d28a2 100644 --- a/cockatrice/src/server/user/user_context_menu.h +++ b/cockatrice/src/server/user/user_context_menu.h @@ -5,6 +5,7 @@ #include +class UserListProxy; class AbstractClient; class ChatView; class CommandContainer; @@ -22,6 +23,7 @@ class UserContextMenu : public QObject private: AbstractClient *client; TabSupervisor *tabSupervisor; + const UserListProxy *userListProxy; TabGame *game; QAction *aUserName; @@ -35,6 +37,7 @@ private: QAction *aPromoteToMod, *aDemoteFromMod; QAction *aPromoteToJudge, *aDemoteFromJudge; QAction *aWarnUser, *aWarnHistory; + QAction *aGetAdminNotes; signals: void openMessageDialog(const QString &userName, bool focus); private slots: @@ -43,9 +46,11 @@ private slots: void warnUser_processUserInfoResponse(const Response &resp); void banUserHistory_processResponse(const Response &resp); void warnUserHistory_processResponse(const Response &resp); + void getAdminNotes_processResponse(const Response &resp); void adjustMod_processUserResponse(const Response &resp, const CommandContainer &commandContainer); void banUser_dialogFinished(); void warnUser_dialogFinished(); + void updateAdminNotes_dialogFinished(); void gamesOfUserReceived(const Response &resp, const CommandContainer &commandContainer); public: diff --git a/cockatrice/src/server/user/user_info_box.cpp b/cockatrice/src/server/user/user_info_box.cpp index 364f38afd..3f2381011 100644 --- a/cockatrice/src/server/user/user_info_box.cpp +++ b/cockatrice/src/server/user/user_info_box.cpp @@ -9,6 +9,7 @@ #include "../pending_command.h" #include "passwordhasher.h" #include "pb/response_get_user_info.pb.h" +#include "pb/serverinfo_user.pb.h" #include "pb/session_commands.pb.h" #include @@ -84,14 +85,27 @@ void UserInfoBox::retranslateUi() avatarButton.setText(tr("Change avatar")); } +/** + * Creates the default profile pic that is used when the user doesn't have a custom pic + */ +static QPixmap createDefaultAvatar(int height, const ServerInfo_User &user) +{ + return UserLevelPixmapGenerator::generatePixmap(height, UserLevelFlags(user.user_level()), user.pawn_colors(), + false, QString::fromStdString(user.privlevel())); +} + void UserInfoBox::updateInfo(const ServerInfo_User &user) { + currentUserInfo = &user; + const UserLevelFlags userLevel(user.user_level()); const std::string &bmp = user.avatar_bmp(); if (!avatarPixmap.loadFromData((const uchar *)bmp.data(), static_cast(bmp.size()))) { - avatarPixmap = - UserLevelPixmapGenerator::generatePixmap(64, userLevel, false, QString::fromStdString(user.privlevel())); + avatarPixmap = createDefaultAvatar(64, user); + hasAvatar = false; + } else { + hasAvatar = true; } nameLabel.setText(QString::fromStdString(user.name())); @@ -106,8 +120,8 @@ void UserInfoBox::updateInfo(const ServerInfo_User &user) countryLabel3.setText(""); } - userLevelIcon.setPixmap( - UserLevelPixmapGenerator::generatePixmap(15, userLevel, false, QString::fromStdString(user.privlevel()))); + userLevelIcon.setPixmap(UserLevelPixmapGenerator::generatePixmap(15, userLevel, user.pawn_colors(), false, + QString::fromStdString(user.privlevel()))); QString userLevelText; if (userLevel.testFlag(ServerInfo_User::IsAdmin)) userLevelText = tr("Administrator"); @@ -250,17 +264,14 @@ void UserInfoBox::actPassword() cmd.set_user_name(client->getUserName().toStdString()); PendingCommand *pend = client->prepareSessionCommand(cmd); - connect(pend, - // we need qoverload here in order to select the right version of this function - QOverload::of(&PendingCommand::finished), - this, [=](const Response &response, const CommandContainer &, const QVariant &) { - if (response.response_code() == Response::RespOk) { - changePassword(oldPassword, newPassword); - } else { - QMessageBox::critical(this, tr("Error"), - tr("An error occurred while trying to update your user information.")); - } - }); + connect(pend, &PendingCommand::finished, this, [=, this](const Response &response) { + if (response.response_code() == Response::RespOk) { + changePassword(oldPassword, newPassword); + } else { + QMessageBox::critical(this, tr("Error"), + tr("An error occurred while trying to update your user information.")); + } + }); client->sendCommand(pend); } else { changePassword(oldPassword, newPassword); @@ -366,7 +377,14 @@ void UserInfoBox::processAvatarResponse(const Response &r) void UserInfoBox::resizeEvent(QResizeEvent *event) { - QPixmap resizedPixmap = avatarPixmap.scaled(avatarPic.size(), Qt::KeepAspectRatio, Qt::SmoothTransformation); + QPixmap resizedPixmap; + if (hasAvatar) { + resizedPixmap = avatarPixmap.scaled(avatarPic.size(), Qt::KeepAspectRatio, Qt::SmoothTransformation); + } else { + int height = qMin(avatarPic.size().width(), avatarPic.size().height()); + resizedPixmap = createDefaultAvatar(height, *currentUserInfo); + } avatarPic.setPixmap(resizedPixmap); + QWidget::resizeEvent(event); } diff --git a/cockatrice/src/server/user/user_info_box.h b/cockatrice/src/server/user/user_info_box.h index 1fa5bb308..170c27bb2 100644 --- a/cockatrice/src/server/user/user_info_box.h +++ b/cockatrice/src/server/user/user_info_box.h @@ -20,6 +20,8 @@ private: countryLabel3, userLevelLabel1, userLevelLabel2, accountAgeLabel1, accountAgeLabel2; QPushButton editButton, passwordButton, avatarButton; QPixmap avatarPixmap; + bool hasAvatar; + const ServerInfo_User *currentUserInfo; static QString getAgeString(int ageSeconds); diff --git a/cockatrice/src/server/user/user_info_connection.cpp b/cockatrice/src/server/user/user_info_connection.cpp index db08f2f78..50ac129a8 100644 --- a/cockatrice/src/server/user/user_info_connection.cpp +++ b/cockatrice/src/server/user/user_info_connection.cpp @@ -74,7 +74,7 @@ QStringList UserConnection_Information::getServerInfo(const QString &find) } if (_server.empty()) - qDebug() << "There was a problem!"; + qCWarning(UserInfoConnectionLog) << "There was a problem!"; return _server; } diff --git a/cockatrice/src/server/user/user_info_connection.h b/cockatrice/src/server/user/user_info_connection.h index 137060afe..b2abf65ac 100644 --- a/cockatrice/src/server/user/user_info_connection.h +++ b/cockatrice/src/server/user/user_info_connection.h @@ -4,9 +4,12 @@ #include #include #include +#include #include #include +inline Q_LOGGING_CATEGORY(UserInfoConnectionLog, "user_info_connection"); + class UserConnection_Information { private: diff --git a/cockatrice/src/server/user/user_list_manager.cpp b/cockatrice/src/server/user/user_list_manager.cpp new file mode 100644 index 000000000..6cf0c763d --- /dev/null +++ b/cockatrice/src/server/user/user_list_manager.cpp @@ -0,0 +1,169 @@ +#include "user_list_manager.h" + +#include "../../client/game_logic/abstract_client.h" +#include "../../client/sound_engine.h" +#include "../pending_command.h" +#include "pb/event_add_to_list.pb.h" +#include "pb/event_remove_from_list.pb.h" +#include "pb/event_user_joined.pb.h" +#include "pb/event_user_left.pb.h" +#include "pb/response_list_users.pb.h" +#include "pb/session_commands.pb.h" +#include "user_info_box.h" + +UserListManager::UserListManager(AbstractClient *_client, QObject *parent) + : QObject(parent), client(_client), ownUserInfo(nullptr) +{ + connect(client, &AbstractClient::userJoinedEventReceived, this, &UserListManager::processUserJoinedEvent); + connect(client, &AbstractClient::userLeftEventReceived, this, &UserListManager::processUserLeftEvent); + connect(client, &AbstractClient::buddyListReceived, this, &UserListManager::buddyListReceived); + connect(client, &AbstractClient::ignoreListReceived, this, &UserListManager::ignoreListReceived); + connect(client, &AbstractClient::addToListEventReceived, this, &UserListManager::processAddToListEvent); + connect(client, &AbstractClient::removeFromListEventReceived, this, &UserListManager::processRemoveFromListEvent); + connect(client, &AbstractClient::userInfoChanged, this, &UserListManager::setOwnUserInfo); +} + +UserListManager::~UserListManager() +{ + handleDisconnect(); +} + +void UserListManager::handleConnect() +{ + populateInitialOnlineUsers(); +} + +void UserListManager::handleDisconnect() +{ + onlineUsers.clear(); + buddyUsers.clear(); + ignoredUsers.clear(); + + delete ownUserInfo; + ownUserInfo = nullptr; +} + +void UserListManager::setOwnUserInfo(const ServerInfo_User &userInfo) +{ + ownUserInfo = new ServerInfo_User(userInfo); +} + +void UserListManager::populateInitialOnlineUsers() +{ + PendingCommand *pend = client->prepareSessionCommand(Command_ListUsers()); + connect(pend, &PendingCommand::finished, this, &UserListManager::processListUsersResponse); + client->sendCommand(pend); +} + +void UserListManager::processListUsersResponse(const Response &response) +{ + const auto &resp = response.GetExtension(Response_ListUsers::ext); + + const int userListSize = resp.user_list_size(); + for (int i = 0; i < userListSize; ++i) { + const ServerInfo_User &info = resp.user_list(i); + const QString &userName = QString::fromStdString(info.name()); + onlineUsers.insert(userName, info); + } +} + +void UserListManager::processUserJoinedEvent(const Event_UserJoined &event) +{ + const auto &info = event.user_info(); + const QString &userName = QString::fromStdString(info.name()); + onlineUsers.insert(userName, info); +} + +void UserListManager::processUserLeftEvent(const Event_UserLeft &event) +{ + const auto &userName = QString::fromStdString(event.name()); + onlineUsers.remove(userName); +} + +void UserListManager::buddyListReceived(const QList &_buddyList) +{ + for (const auto &user : _buddyList) { + const auto &userName = QString::fromStdString(user.name()); + buddyUsers.insert(userName, user); + } +} + +void UserListManager::ignoreListReceived(const QList &_ignoreList) +{ + for (const auto &user : _ignoreList) { + const auto &userName = QString::fromStdString(user.name()); + ignoredUsers.insert(userName, user); + } +} + +void UserListManager::processAddToListEvent(const Event_AddToList &event) +{ + const auto &user = event.user_info(); + const auto &userName = QString::fromStdString(user.name()); + + const auto &userListType = QString::fromStdString(event.list_name()); + + QMap *userMap; + if (userListType == "buddy") { + userMap = &buddyUsers; + } else if (userListType == "ignore") { + userMap = &ignoredUsers; + } else { + return; + } + + userMap->insert(userName, user); +} + +void UserListManager::processRemoveFromListEvent(const Event_RemoveFromList &event) +{ + const auto &userListType = QString::fromStdString(event.list_name()); + const auto &userName = QString::fromStdString(event.user_name()); + + QMap *userMap; + if (userListType == "buddy") { + userMap = &buddyUsers; + } else if (userListType == "ignore") { + userMap = &ignoredUsers; + } else { + return; + } + + userMap->remove(userName); +} + +bool UserListManager::isOwnUserRegistered() const +{ + return ownUserInfo != nullptr && (ownUserInfo->user_level() & ServerInfo_User::IsRegistered) != 0; +} + +QString UserListManager::getOwnUsername() const +{ + return ownUserInfo != nullptr ? QString::fromStdString(ownUserInfo->name()) : QString(); +} + +bool UserListManager::isUserBuddy(const QString &userName) const +{ + return buddyUsers.contains(userName); +} + +bool UserListManager::isUserIgnored(const QString &userName) const +{ + return ignoredUsers.contains(userName); +} + +const ServerInfo_User *UserListManager::getOnlineUser(const QString &userName) const +{ + const QString &userNameToMatchLower = userName.toLower(); + + const auto it = + std::find_if(onlineUsers.begin(), onlineUsers.end(), [&userNameToMatchLower](const ServerInfo_User &user) { + return userNameToMatchLower == QString::fromStdString(user.name()).toLower(); + }); + + if (it != onlineUsers.end()) { + return &*it; + } + + return nullptr; +}; \ No newline at end of file diff --git a/cockatrice/src/server/user/user_list_manager.h b/cockatrice/src/server/user/user_list_manager.h new file mode 100644 index 000000000..d83ec45ef --- /dev/null +++ b/cockatrice/src/server/user/user_list_manager.h @@ -0,0 +1,72 @@ +#ifndef COCKATRICE_USER_LIST_MANAGER_H +#define COCKATRICE_USER_LIST_MANAGER_H + +#include "pb/serverinfo_user.pb.h" +#include "user_list_proxy.h" + +#include +#include + +class AbstractClient; +class Event_AddToList; +class Event_ListRooms; +class Event_RemoveFromList; +class Event_UserJoined; +class Event_UserLeft; +class Response; +class ServerInfo_User; +class TabSupervisor; + +class UserListManager : public QObject, public UserListProxy +{ + Q_OBJECT + +private: + AbstractClient *client; + ServerInfo_User *ownUserInfo; + QMap onlineUsers, buddyUsers, ignoredUsers; + +private slots: + void setOwnUserInfo(const ServerInfo_User &userInfo); + void populateInitialOnlineUsers(); + void processListUsersResponse(const Response &response); + void processUserJoinedEvent(const Event_UserJoined &event); + void processUserLeftEvent(const Event_UserLeft &event); + void buddyListReceived(const QList &_buddyList); + void ignoreListReceived(const QList &_ignoreList); + void processAddToListEvent(const Event_AddToList &event); + void processRemoveFromListEvent(const Event_RemoveFromList &event); + +public: + explicit UserListManager(AbstractClient *_client, QObject *parent = nullptr); + ~UserListManager() override; + + [[nodiscard]] QMap getAllUsersList() const + { + return onlineUsers; + } + [[nodiscard]] QMap getBuddyList() const + { + return buddyUsers; + } + [[nodiscard]] QMap getIgnoreList() const + { + return ignoredUsers; + } + + bool isOwnUserRegistered() const override; + QString getOwnUsername() const override; + bool isUserBuddy(const QString &userName) const override; + bool isUserIgnored(const QString &userName) const override; + const ServerInfo_User *getOnlineUser(const QString &userName) const override; + +public slots: + void handleConnect(); + void handleDisconnect(); + +signals: + void userLeft(const QString &userName); + void userJoined(const ServerInfo_User &userInfo); +}; + +#endif // COCKATRICE_USER_LIST_MANAGER_H diff --git a/cockatrice/src/server/chat_view/user_list_proxy.h b/cockatrice/src/server/user/user_list_proxy.h similarity index 97% rename from cockatrice/src/server/chat_view/user_list_proxy.h rename to cockatrice/src/server/user/user_list_proxy.h index 6866be5f7..7fa92770c 100644 --- a/cockatrice/src/server/chat_view/user_list_proxy.h +++ b/cockatrice/src/server/user/user_list_proxy.h @@ -8,7 +8,7 @@ class ServerInfo_User; * Responsible for providing a bare-bones minimal interface into userlist information, * including your current connection to the server as well as buddy/ignore/alluser lists. */ -class UserlistProxy +class UserListProxy { public: virtual bool isOwnUserRegistered() const = 0; diff --git a/cockatrice/src/server/user/user_list.cpp b/cockatrice/src/server/user/user_list_widget.cpp similarity index 74% rename from cockatrice/src/server/user/user_list.cpp rename to cockatrice/src/server/user/user_list_widget.cpp index 8ea6f341f..988257a9d 100644 --- a/cockatrice/src/server/user/user_list.cpp +++ b/cockatrice/src/server/user/user_list_widget.cpp @@ -1,10 +1,11 @@ -#include "user_list.h" +#include "user_list_widget.h" #include "../../client/game_logic/abstract_client.h" #include "../../client/tabs/tab_account.h" #include "../../client/tabs/tab_supervisor.h" #include "../../client/ui/pixel_map_generator.h" #include "../../game/game_selector.h" +#include "../../server/user/user_list_manager.h" #include "../pending_command.h" #include "pb/moderator_commands.pb.h" #include "pb/response_get_games_of_user.pb.h" @@ -284,6 +285,32 @@ int BanDialog::getDeleteMessages() const return deleteMessages->isChecked() ? -1 : 0; } +AdminNotesDialog::AdminNotesDialog(const QString &_userName, const QString &_notes, QWidget *_parent) + : QDialog(_parent), userName(_userName) +{ + setAttribute(Qt::WA_DeleteOnClose); + + auto *updateButton = new QPushButton(tr("Update Notes")); + updateButton->setEnabled(false); + connect(updateButton, &QPushButton::clicked, this, &AdminNotesDialog::accept); + + notes = new QPlainTextEdit(_notes); + notes->setMinimumWidth(500); + connect(notes, &QPlainTextEdit::textChanged, this, [=]() { updateButton->setEnabled(true); }); + + auto *vbox = new QVBoxLayout; + vbox->addWidget(notes); + vbox->addWidget(updateButton); + + setLayout(vbox); + setWindowTitle(tr("Admin Notes for %1").arg(_userName)); +} + +QString AdminNotesDialog::getNotes() const +{ + return notes->toPlainText(); +} + UserListItemDelegate::UserListItemDelegate(QObject *const parent) : QStyledItemDelegate(parent) { } @@ -297,9 +324,9 @@ bool UserListItemDelegate::editorEvent(QEvent *event, QMouseEvent *const mouseEvent = static_cast(event); if (mouseEvent->button() == Qt::RightButton) { #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) - static_cast(parent())->showContextMenu(mouseEvent->globalPosition().toPoint(), index); + static_cast(parent())->showContextMenu(mouseEvent->globalPosition().toPoint(), index); #else - static_cast(parent())->showContextMenu(mouseEvent->globalPos(), index); + static_cast(parent())->showContextMenu(mouseEvent->globalPos(), index); #endif return true; } @@ -317,11 +344,12 @@ void UserListTWI::setUserInfo(const ServerInfo_User &_userInfo) userInfo = _userInfo; setData(0, Qt::UserRole, userInfo.user_level()); - setIcon(0, QIcon(UserLevelPixmapGenerator::generatePixmap(12, UserLevelFlags(userInfo.user_level()), false, - QString::fromStdString(userInfo.privlevel())))); - setIcon(1, QIcon(CountryPixmapGenerator::generatePixmap(12, QString::fromStdString(userInfo.country())))); + setIcon(0, UserLevelPixmapGenerator::generateIcon(18, UserLevelFlags(userInfo.user_level()), userInfo.pawn_colors(), + false, QString::fromStdString(userInfo.privlevel()))); + setIcon(1, QIcon(CountryPixmapGenerator::generatePixmap(18, QString::fromStdString(userInfo.country())))); setData(2, Qt::UserRole, QString::fromStdString(userInfo.name())); setData(2, Qt::DisplayRole, QString::fromStdString(userInfo.name())); + setData(3, Qt::InitialSortOrderRole, QString::fromStdString(userInfo.privlevel())); } void UserListTWI::setOnline(bool online) @@ -330,21 +358,67 @@ void UserListTWI::setOnline(bool online) setData(2, Qt::ForegroundRole, online ? qApp->palette().brush(QPalette::WindowText) : QBrush(Qt::gray)); } +/** + * Sort Users in the following order + * 1) Online Users > Offline Users + * 2) Admins, judge/vip/donator status ignored + * 3) Moderators, judge/vip/donator status ignored + * 4) Judges + * 5) VIPs + * 6) Donators + * 7) Everyone else + * @param other RHS to compare to + * @return Left is less than the Right + */ bool UserListTWI::operator<(const QTreeWidgetItem &other) const { // Sort by online/offline - if (data(0, Qt::UserRole + 1) != other.data(0, Qt::UserRole + 1)) + if (data(0, Qt::UserRole + 1) != other.data(0, Qt::UserRole + 1)) { return data(0, Qt::UserRole + 1).toBool(); + } - // Sort by user level - if ((data(0, Qt::UserRole).toInt() & 15) != (other.data(0, Qt::UserRole).toInt() & 15)) - return (data(0, Qt::UserRole).toInt() & 15) > (other.data(0, Qt::UserRole).toInt() & 15); + const auto &lhsUserLevelFlags = UserLevelFlags(data(0, Qt::UserRole).toInt()); + const auto &rhsUserLevelFlags = UserLevelFlags(other.data(0, Qt::UserRole).toInt()); + + // Admins & Mods need no additional comparison checks, just to see if they're an admin or a moderator + static const QList userLevelWithNoOtherPrefOrder = { + ServerInfo_User_UserLevelFlag_IsAdmin, ServerInfo_User_UserLevelFlag_IsModerator}; + for (const auto &userLevelEntry : userLevelWithNoOtherPrefOrder) { + if (lhsUserLevelFlags.testFlag(userLevelEntry) && + lhsUserLevelFlags.testFlag(userLevelEntry) == rhsUserLevelFlags.testFlag(userLevelEntry)) { + return QString::localeAwareCompare(data(2, Qt::UserRole).toString(), + other.data(2, Qt::UserRole).toString()) < 0; + } else if (lhsUserLevelFlags.testFlag(userLevelEntry) != rhsUserLevelFlags.testFlag(userLevelEntry)) { + return lhsUserLevelFlags.testFlag(userLevelEntry) > rhsUserLevelFlags.testFlag(userLevelEntry); + } + } + + // Judges can be sorted by their additional ranks + static const QList userLevelOrder = {ServerInfo_User_UserLevelFlag_IsJudge, + ServerInfo_User_UserLevelFlag_IsRegistered, + ServerInfo_User_UserLevelFlag_IsUser}; + for (const auto &userLevelEntry : userLevelOrder) { + if (lhsUserLevelFlags.testFlag(userLevelEntry) != rhsUserLevelFlags.testFlag(userLevelEntry)) { + return lhsUserLevelFlags.testFlag(userLevelEntry) > rhsUserLevelFlags.testFlag(userLevelEntry); + } + } + + // Sort by VIP > Donator > None + static const QMap privilegeOrder = {{"VIP", 3}, {"DONATOR", 2}, {"NONE", 1}, {"UNKNOWN", 0}}; + const auto &lhsUserPrivLevel = privilegeOrder.value(data(3, Qt::InitialSortOrderRole).toString(), 0); + const auto &rhsUserPrivLevel = privilegeOrder.value(other.data(3, Qt::InitialSortOrderRole).toString(), 0); + if (lhsUserPrivLevel != rhsUserPrivLevel) { + return lhsUserPrivLevel > rhsUserPrivLevel; + } // Sort by name return QString::localeAwareCompare(data(2, Qt::UserRole).toString(), other.data(2, Qt::UserRole).toString()) < 0; } -UserList::UserList(TabSupervisor *_tabSupervisor, AbstractClient *_client, UserListType _type, QWidget *parent) +UserListWidget::UserListWidget(TabSupervisor *_tabSupervisor, + AbstractClient *_client, + UserListType _type, + QWidget *parent) : QGroupBox(parent), tabSupervisor(_tabSupervisor), client(_client), type(_type), onlineCount(0) { itemDelegate = new UserListItemDelegate(this); @@ -354,9 +428,10 @@ UserList::UserList(TabSupervisor *_tabSupervisor, AbstractClient *_client, UserL userTree = new QTreeWidget; userTree->setColumnCount(3); userTree->header()->setSectionResizeMode(QHeaderView::ResizeToContents); + userTree->header()->setMinimumSectionSize(0); userTree->setHeaderHidden(true); userTree->setRootIsDecorated(false); - userTree->setIconSize(QSize(20, 12)); + userTree->setIconSize(QSize(20, 18)); userTree->setItemDelegate(itemDelegate); userTree->setAlternatingRowColors(true); connect(userTree, SIGNAL(itemActivated(QTreeWidgetItem *, int)), this, SLOT(userClicked(QTreeWidgetItem *, int))); @@ -369,7 +444,7 @@ UserList::UserList(TabSupervisor *_tabSupervisor, AbstractClient *_client, UserL retranslateUi(); } -void UserList::retranslateUi() +void UserListWidget::retranslateUi() { userContextMenu->retranslateUi(); switch (type) { @@ -389,7 +464,7 @@ void UserList::retranslateUi() updateCount(); } -void UserList::processUserInfo(const ServerInfo_User &user, bool online) +void UserListWidget::processUserInfo(const ServerInfo_User &user, bool online) { const QString userName = QString::fromStdString(user.name()); UserListTWI *item = users.value(userName); @@ -406,7 +481,7 @@ void UserList::processUserInfo(const ServerInfo_User &user, bool online) item->setOnline(online); } -bool UserList::deleteUser(const QString &userName) +bool UserListWidget::deleteUser(const QString &userName) { UserListTWI *twi = users.value(userName); if (twi) { @@ -422,7 +497,7 @@ bool UserList::deleteUser(const QString &userName) return false; } -void UserList::setUserOnline(const QString &userName, bool online) +void UserListWidget::setUserOnline(const QString &userName, bool online) { UserListTWI *twi = users.value(userName); if (!twi) @@ -436,7 +511,7 @@ void UserList::setUserOnline(const QString &userName, bool online) updateCount(); } -void UserList::updateCount() +void UserListWidget::updateCount() { QString str = titleStr; if ((type == BuddyList) || (type == IgnoreList)) @@ -444,12 +519,12 @@ void UserList::updateCount() setTitle(str.arg(userTree->topLevelItemCount())); } -void UserList::userClicked(QTreeWidgetItem *item, int /*column*/) +void UserListWidget::userClicked(QTreeWidgetItem *item, int /*column*/) { emit openMessageDialog(item->data(2, Qt::UserRole).toString(), true); } -void UserList::showContextMenu(const QPoint &pos, const QModelIndex &index) +void UserListWidget::showContextMenu(const QPoint &pos, const QModelIndex &index) { const ServerInfo_User &userInfo = static_cast(userTree->topLevelItem(index.row()))->getUserInfo(); bool online = index.sibling(index.row(), 0).data(Qt::UserRole + 1).toBool(); @@ -458,7 +533,7 @@ void UserList::showContextMenu(const QPoint &pos, const QModelIndex &index) UserLevelFlags(userInfo.user_level()), online); } -void UserList::sortItems() +void UserListWidget::sortItems() { userTree->sortItems(1, Qt::AscendingOrder); } diff --git a/cockatrice/src/server/user/user_list.h b/cockatrice/src/server/user/user_list_widget.h similarity index 75% rename from cockatrice/src/server/user/user_list.h rename to cockatrice/src/server/user/user_list_widget.h index 525fa279d..561d91e77 100644 --- a/cockatrice/src/server/user/user_list.h +++ b/cockatrice/src/server/user/user_list_widget.h @@ -8,6 +8,7 @@ #include #include #include +#include #include class QTreeWidget; @@ -38,7 +39,7 @@ private slots: void enableTemporaryEdits(bool enabled); public: - BanDialog(const ServerInfo_User &info, QWidget *parent = nullptr); + explicit BanDialog(const ServerInfo_User &info, QWidget *parent = nullptr); QString getBanName() const; QString getBanIP() const; QString getBanId() const; @@ -69,12 +70,31 @@ public: void addWarningOption(const QString warning); }; +class AdminNotesDialog : public QDialog +{ + Q_OBJECT + +private: + QString userName; + QPlainTextEdit *notes; + +public: + explicit AdminNotesDialog(const QString &_userName, const QString &_notes, QWidget *_parent = nullptr); + QString getName() const + { + return userName; + } + QString getNotes() const; +}; + class UserListItemDelegate : public QStyledItemDelegate { public: - UserListItemDelegate(QObject *const parent); - bool - editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, const QModelIndex &index); + explicit UserListItemDelegate(QObject *const parent); + bool editorEvent(QEvent *event, + QAbstractItemModel *model, + const QStyleOptionViewItem &option, + const QModelIndex &index) override; }; class UserListTWI : public QTreeWidgetItem @@ -83,17 +103,17 @@ private: ServerInfo_User userInfo; public: - UserListTWI(const ServerInfo_User &_userInfo); + explicit UserListTWI(const ServerInfo_User &_userInfo); const ServerInfo_User &getUserInfo() const { return userInfo; } void setUserInfo(const ServerInfo_User &_userInfo); void setOnline(bool online); - bool operator<(const QTreeWidgetItem &other) const; + bool operator<(const QTreeWidgetItem &other) const override; }; -class UserList : public QGroupBox +class UserListWidget : public QGroupBox { Q_OBJECT public: @@ -126,7 +146,10 @@ signals: void removeIgnore(const QString &userName); public: - UserList(TabSupervisor *_tabSupervisor, AbstractClient *_client, UserListType _type, QWidget *parent = nullptr); + UserListWidget(TabSupervisor *_tabSupervisor, + AbstractClient *_client, + UserListType _type, + QWidget *parent = nullptr); void retranslateUi(); void processUserInfo(const ServerInfo_User &user, bool online); bool deleteUser(const QString &userName); diff --git a/cockatrice/src/settings/cache_settings.cpp b/cockatrice/src/settings/cache_settings.cpp index da0e5c0b0..d7a9dac75 100644 --- a/cockatrice/src/settings/cache_settings.cpp +++ b/cockatrice/src/settings/cache_settings.cpp @@ -1,6 +1,7 @@ #include "cache_settings.h" #include "../client/network/release_channel.h" +#include "card_override_settings.h" #include #include @@ -103,11 +104,7 @@ void SettingsCache::translateLegacySettings() // Game filters legacySetting.beginGroup("filter_games"); - gameFilters().setShowFullGames(legacySetting.value("unavailable_games_visible").toBool()); - gameFilters().setShowGamesThatStarted(legacySetting.value("unavailable_games_visible").toBool()); - gameFilters().setShowPasswordProtectedGames(legacySetting.value("show_password_protected_games").toBool()); gameFilters().setGameNameFilter(legacySetting.value("game_name_filter").toString()); - gameFilters().setShowBuddiesOnlyGames(legacySetting.value("show_buddies_only_games").toBool()); gameFilters().setHideIgnoredUserGames(legacySetting.value("hide_ignored_user_games").toBool()); gameFilters().setMinPlayers(legacySetting.value("min_players").toInt()); @@ -143,7 +140,7 @@ QString SettingsCache::getSafeConfigPath(QString configEntry, QString defaultPat // ensure that the defaut path exists and return it if (tmp.isEmpty() || !QDir(tmp).exists()) { if (!QDir().mkpath(defaultPath)) - qDebug() << "[SettingsCache] Could not create folder:" << defaultPath; + qCInfo(SettingsCacheLog) << "[SettingsCache] Could not create folder:" << defaultPath; tmp = defaultPath; } return tmp; @@ -164,7 +161,7 @@ SettingsCache::SettingsCache() // first, figure out if we are running in portable mode isPortableBuild = QFile::exists(qApp->applicationDirPath() + "/portable.dat"); if (isPortableBuild) - qDebug() << "Portable mode enabled"; + qCInfo(SettingsCacheLog) << "Portable mode enabled"; // define a dummy context that will be used where needed QString dummy = QT_TRANSLATE_NOOP("i18n", "English"); @@ -178,6 +175,9 @@ SettingsCache::SettingsCache() gameFiltersSettings = new GameFiltersSettings(settingsPath, this); layoutsSettings = new LayoutsSettings(settingsPath, this); downloadSettings = new DownloadSettings(settingsPath, this); + recentsSettings = new RecentsSettings(settingsPath, this); + cardOverrideSettings = new CardOverrideSettings(settingsPath, this); + debugSettings = new DebugSettings(settingsPath, this); if (!QFile(settingsPath + "global.ini").exists()) translateLegacySettings(); @@ -189,6 +189,7 @@ SettingsCache::SettingsCache() mbDownloadSpoilers = settings->value("personal/downloadspoilers", false).toBool(); + checkUpdatesOnStartup = settings->value("personal/startupUpdateCheck", true).toBool(); notifyAboutUpdates = settings->value("personal/updatenotification", true).toBool(); notifyAboutNewVersion = settings->value("personal/newversionnotification", true).toBool(); updateReleaseChannel = settings->value("personal/updatereleasechannel", 0).toInt(); @@ -207,6 +208,14 @@ SettingsCache::SettingsCache() themeName = settings->value("theme/name").toString(); + tabVisualDeckStorageOpen = settings->value("tabs/visualDeckStorage", true).toBool(); + tabServerOpen = settings->value("tabs/server", true).toBool(); + tabAccountOpen = settings->value("tabs/account", true).toBool(); + tabDeckStorageOpen = settings->value("tabs/deckStorage", true).toBool(); + tabReplaysOpen = settings->value("tabs/replays", true).toBool(); + tabAdminOpen = settings->value("tabs/admin", true).toBool(); + tabLogOpen = settings->value("tabs/log", true).toBool(); + // we only want to reset the cache once, then its up to the user bool updateCache = settings->value("revert/pixmapCacheSize", false).toBool(); if (!updateCache) { @@ -232,19 +241,53 @@ SettingsCache::SettingsCache() spectatorNotificationsEnabled = settings->value("interface/specnotificationsenabled", false).toBool(); buddyConnectNotificationsEnabled = settings->value("interface/buddyconnectnotificationsenabled", true).toBool(); doubleClickToPlay = settings->value("interface/doubleclicktoplay", true).toBool(); + clickPlaysAllSelected = settings->value("interface/clickPlaysAllSelected", true).toBool(); playToStack = settings->value("interface/playtostack", true).toBool(); startingHandSize = settings->value("interface/startinghandsize", 7).toInt(); annotateTokens = settings->value("interface/annotatetokens", false).toBool(); tabGameSplitterSizes = settings->value("interface/tabgame_splittersizes").toByteArray(); knownMissingFeatures = settings->value("interface/knownmissingfeatures", "").toString(); useTearOffMenus = settings->value("interface/usetearoffmenus", true).toBool(); + cardViewInitialRowsMax = settings->value("interface/cardViewInitialRowsMax", 14).toInt(); + cardViewExpandedRowsMax = settings->value("interface/cardViewExpandedRowsMax", 20).toInt(); + closeEmptyCardView = settings->value("interface/closeEmptyCardView", true).toBool(); + showShortcuts = settings->value("menu/showshortcuts", true).toBool(); displayCardNames = settings->value("cards/displaycardnames", true).toBool(); + roundCardCorners = settings->value("cards/roundcardcorners", true).toBool(); + overrideAllCardArtWithPersonalPreference = + settings->value("cards/overrideallcardartwithpersonalpreference", false).toBool(); + bumpSetsWithCardsInDeckToTop = settings->value("cards/bumpsetswithcardsindecktotop", true).toBool(); + printingSelectorSortOrder = settings->value("cards/printingselectorsortorder", 1).toInt(); + printingSelectorCardSize = settings->value("cards/printingselectorcardsize", 100).toInt(); + includeRebalancedCards = settings->value("cards/includerebalancedcards", true).toBool(); + printingSelectorNavigationButtonsVisible = + settings->value("cards/printingselectornavigationbuttonsvisible", true).toBool(); + visualDeckStorageCardSize = settings->value("interface/visualdeckstoragecardsize", 100).toInt(); + visualDeckStorageSortingOrder = settings->value("interface/visualdeckstoragesortingorder", 0).toInt(); + visualDeckStorageShowFolders = settings->value("interface/visualdeckstorageshowfolders", true).toBool(); + visualDeckStorageShowTagFilter = settings->value("interface/visualdeckstorageshowtagfilter", true).toBool(); + visualDeckStorageSearchFolderNames = settings->value("interface/visualdeckstoragesearchfoldernames", true).toBool(); + visualDeckStorageShowBannerCardComboBox = + settings->value("interface/visualdeckstorageshowbannercardcombobox", true).toBool(); + visualDeckStorageShowTagsOnDeckPreviews = + settings->value("interface/visualdeckstorageshowtagsondeckpreviews", true).toBool(); + visualDeckStorageDrawUnusedColorIdentities = + settings->value("interface/visualdeckstoragedrawunusedcoloridentities", true).toBool(); + visualDeckStorageUnusedColorIdentitiesOpacity = + settings->value("interface/visualdeckstorageunusedcoloridentitiesopacity", 15).toInt(); + visualDeckStoragePromptForConversion = + settings->value("interface/visualdeckstoragepromptforconversion", true).toBool(); + visualDeckStorageAlwaysConvert = settings->value("interface/visualdeckstoragealwaysconvert", false).toBool(); + visualDeckStorageInGame = settings->value("interface/visualdeckstorageingame", true).toBool(); horizontalHand = settings->value("hand/horizontal", true).toBool(); invertVerticalCoordinate = settings->value("table/invert_vertical", false).toBool(); minPlayersForMultiColumnLayout = settings->value("interface/min_players_multicolumn", 4).toInt(); tapAnimation = settings->value("cards/tapanimation", true).toBool(); + autoRotateSidewaysLayoutCards = settings->value("cards/autorotatesidewayslayoutcards", true).toBool(); + openDeckInNewTab = settings->value("editor/openDeckInNewTab", false).toBool(); + rewindBufferingMs = settings->value("replay/rewindBufferingMs", 200).toInt(); chatMention = settings->value("chat/mention", true).toBool(); chatMentionCompleter = settings->value("chat/mentioncompleter", true).toBool(); chatMentionForeground = settings->value("chat/mentionforeground", true).toBool(); @@ -252,8 +295,8 @@ SettingsCache::SettingsCache() chatMentionColor = settings->value("chat/mentioncolor", "A6120D").toString(); chatHighlightColor = settings->value("chat/highlightcolor", "A6120D").toString(); - zoneViewSortByName = settings->value("zoneview/sortbyname", true).toBool(); - zoneViewSortByType = settings->value("zoneview/sortbytype", true).toBool(); + zoneViewGroupByIndex = settings->value("zoneview/groupby", 1).toInt(); + zoneViewSortByIndex = settings->value("zoneview/sortby", 1).toInt(); zoneViewPileView = settings->value("zoneview/pileview", true).toBool(); soundEnabled = settings->value("sound/enabled", false).toBool(); @@ -286,6 +329,7 @@ SettingsCache::SettingsCache() spectatorsCanTalk = settings->value("game/spectatorscantalk", false).toBool(); spectatorsCanSeeEverything = settings->value("game/spectatorscanseeeverything", false).toBool(); createGameAsSpectator = settings->value("game/creategameasspectator", false).toBool(); + defaultStartingLifeTotal = settings->value("game/defaultstartinglifetotal", 20).toInt(); rememberGameSettings = settings->value("game/remembergamesettings", true).toBool(); clientID = settings->value("personal/clientid", CLIENT_INFO_NOT_SET).toString(); clientVersion = settings->value("personal/clientversion", CLIENT_INFO_NOT_SET).toString(); @@ -298,6 +342,24 @@ void SettingsCache::setUseTearOffMenus(bool _useTearOffMenus) emit useTearOffMenusChanged(useTearOffMenus); } +void SettingsCache::setCardViewInitialRowsMax(int _cardViewInitialRowsMax) +{ + cardViewInitialRowsMax = _cardViewInitialRowsMax; + settings->setValue("interface/cardViewInitialRowsMax", cardViewInitialRowsMax); +} + +void SettingsCache::setCardViewExpandedRowsMax(int value) +{ + cardViewExpandedRowsMax = value; + settings->setValue("interface/cardViewExpandedRowsMax", cardViewExpandedRowsMax); +} + +void SettingsCache::setCloseEmptyCardView(QT_STATE_CHANGED_T value) +{ + closeEmptyCardView = value; + settings->setValue("interface/closeEmptyCardView", closeEmptyCardView); +} + void SettingsCache::setKnownMissingFeatures(const QString &_knownMissingFeatures) { knownMissingFeatures = _knownMissingFeatures; @@ -450,6 +512,48 @@ void SettingsCache::setThemeName(const QString &_themeName) emit themeChanged(); } +void SettingsCache::setTabVisualDeckStorageOpen(bool value) +{ + tabVisualDeckStorageOpen = value; + settings->setValue("tabs/visualDeckStorage", tabVisualDeckStorageOpen); +} + +void SettingsCache::setTabServerOpen(bool value) +{ + tabServerOpen = value; + settings->setValue("tabs/server", tabServerOpen); +} + +void SettingsCache::setTabAccountOpen(bool value) +{ + tabAccountOpen = value; + settings->setValue("tabs/account", tabAccountOpen); +} + +void SettingsCache::setTabDeckStorageOpen(bool value) +{ + tabDeckStorageOpen = value; + settings->setValue("tabs/deckStorage", tabDeckStorageOpen); +} + +void SettingsCache::setTabReplaysOpen(bool value) +{ + tabReplaysOpen = value; + settings->setValue("tabs/replays", tabReplaysOpen); +} + +void SettingsCache::setTabAdminOpen(bool value) +{ + tabAdminOpen = value; + settings->setValue("tabs/admin", tabAdminOpen); +} + +void SettingsCache::setTabLogOpen(bool value) +{ + tabLogOpen = value; + settings->setValue("tabs/log", tabLogOpen); +} + void SettingsCache::setPicDownload(QT_STATE_CHANGED_T _picDownload) { picDownload = static_cast(_picDownload); @@ -481,6 +585,12 @@ void SettingsCache::setDoubleClickToPlay(QT_STATE_CHANGED_T _doubleClickToPlay) settings->setValue("interface/doubleclicktoplay", doubleClickToPlay); } +void SettingsCache::setClickPlaysAllSelected(QT_STATE_CHANGED_T _clickPlaysAllSelected) +{ + clickPlaysAllSelected = static_cast(_clickPlaysAllSelected); + settings->setValue("interface/clickPlaysAllSelected", clickPlaysAllSelected); +} + void SettingsCache::setPlayToStack(QT_STATE_CHANGED_T _playToStack) { playToStack = static_cast(_playToStack); @@ -505,6 +615,12 @@ void SettingsCache::setTabGameSplitterSizes(const QByteArray &_tabGameSplitterSi settings->setValue("interface/tabgame_splittersizes", tabGameSplitterSizes); } +void SettingsCache::setShowShortcuts(QT_STATE_CHANGED_T _showShortcuts) +{ + showShortcuts = static_cast(_showShortcuts); + settings->setValue("menu/showshortcuts", showShortcuts); +} + void SettingsCache::setDisplayCardNames(QT_STATE_CHANGED_T _displayCardNames) { displayCardNames = static_cast(_displayCardNames); @@ -512,6 +628,133 @@ void SettingsCache::setDisplayCardNames(QT_STATE_CHANGED_T _displayCardNames) emit displayCardNamesChanged(); } +void SettingsCache::setOverrideAllCardArtWithPersonalPreference(QT_STATE_CHANGED_T _overrideAllCardArt) +{ + overrideAllCardArtWithPersonalPreference = static_cast(_overrideAllCardArt); + settings->setValue("cards/overrideallcardartwithpersonalpreference", overrideAllCardArtWithPersonalPreference); + emit overrideAllCardArtWithPersonalPreferenceChanged(overrideAllCardArtWithPersonalPreference); +} + +void SettingsCache::setBumpSetsWithCardsInDeckToTop(QT_STATE_CHANGED_T _bumpSetsWithCardsInDeckToTop) +{ + bumpSetsWithCardsInDeckToTop = static_cast(_bumpSetsWithCardsInDeckToTop); + settings->setValue("cards/bumpsetswithcardsindecktotop", bumpSetsWithCardsInDeckToTop); + emit bumpSetsWithCardsInDeckToTopChanged(); +} + +void SettingsCache::setPrintingSelectorSortOrder(int _printingSelectorSortOrder) +{ + printingSelectorSortOrder = _printingSelectorSortOrder; + settings->setValue("cards/printingselectorsortorder", printingSelectorSortOrder); + emit printingSelectorSortOrderChanged(); +} + +void SettingsCache::setPrintingSelectorCardSize(int _printingSelectorCardSize) +{ + printingSelectorCardSize = _printingSelectorCardSize; + settings->setValue("cards/printingselectorcardsize", printingSelectorCardSize); + emit printingSelectorCardSizeChanged(); +} + +void SettingsCache::setIncludeRebalancedCards(bool _includeRebalancedCards) +{ + if (includeRebalancedCards == _includeRebalancedCards) + return; + + includeRebalancedCards = _includeRebalancedCards; + settings->setValue("cards/includerebalancedcards", includeRebalancedCards); + emit includeRebalancedCardsChanged(includeRebalancedCards); +} + +void SettingsCache::setPrintingSelectorNavigationButtonsVisible(QT_STATE_CHANGED_T _navigationButtonsVisible) +{ + printingSelectorNavigationButtonsVisible = _navigationButtonsVisible; + settings->setValue("cards/printingselectornavigationbuttonsvisible", printingSelectorNavigationButtonsVisible); + emit printingSelectorNavigationButtonsVisibleChanged(); +} + +void SettingsCache::setVisualDeckStorageSortingOrder(int _visualDeckStorageSortingOrder) +{ + visualDeckStorageSortingOrder = _visualDeckStorageSortingOrder; + settings->setValue("interface/visualdeckstoragesortingorder", visualDeckStorageSortingOrder); +} + +void SettingsCache::setVisualDeckStorageShowFolders(QT_STATE_CHANGED_T value) +{ + visualDeckStorageShowFolders = value; + settings->setValue("interface/visualdeckstorageshowfolders", visualDeckStorageShowFolders); +} + +void SettingsCache::setVisualDeckStorageShowTagFilter(QT_STATE_CHANGED_T _showTags) +{ + visualDeckStorageShowTagFilter = _showTags; + settings->setValue("interface/visualdeckstorageshowtagfilter", visualDeckStorageShowTagFilter); + emit visualDeckStorageShowTagFilterChanged(visualDeckStorageShowTagFilter); +} + +void SettingsCache::setVisualDeckStorageSearchFolderNames(QT_STATE_CHANGED_T value) +{ + visualDeckStorageSearchFolderNames = value; + settings->setValue("interface/visualdeckstoragesearchfoldernames", visualDeckStorageSearchFolderNames); +} + +void SettingsCache::setVisualDeckStorageShowBannerCardComboBox(QT_STATE_CHANGED_T _showBannerCardComboBox) +{ + visualDeckStorageShowBannerCardComboBox = _showBannerCardComboBox; + settings->setValue("interface/visualdeckstorageshowbannercardcombobox", visualDeckStorageShowBannerCardComboBox); + emit visualDeckStorageShowBannerCardComboBoxChanged(visualDeckStorageShowBannerCardComboBox); +} + +void SettingsCache::setVisualDeckStorageShowTagsOnDeckPreviews(QT_STATE_CHANGED_T _showTags) +{ + visualDeckStorageShowTagsOnDeckPreviews = _showTags; + settings->setValue("interface/visualdeckstorageshowtagsondeckpreviews", visualDeckStorageShowTagsOnDeckPreviews); + emit visualDeckStorageShowTagsOnDeckPreviewsChanged(visualDeckStorageShowTagsOnDeckPreviews); +} + +void SettingsCache::setVisualDeckStorageCardSize(int _visualDeckStorageCardSize) +{ + visualDeckStorageCardSize = _visualDeckStorageCardSize; + settings->setValue("interface/visualdeckstoragecardsize", visualDeckStorageCardSize); + emit visualDeckStorageCardSizeChanged(); +} + +void SettingsCache::setVisualDeckStorageDrawUnusedColorIdentities( + QT_STATE_CHANGED_T _visualDeckStorageDrawUnusedColorIdentities) +{ + visualDeckStorageDrawUnusedColorIdentities = _visualDeckStorageDrawUnusedColorIdentities; + settings->setValue("interface/visualdeckstoragedrawunusedcoloridentities", + visualDeckStorageDrawUnusedColorIdentities); + emit visualDeckStorageDrawUnusedColorIdentitiesChanged(visualDeckStorageDrawUnusedColorIdentities); +} + +void SettingsCache::setVisualDeckStorageUnusedColorIdentitiesOpacity(int _visualDeckStorageUnusedColorIdentitiesOpacity) +{ + visualDeckStorageUnusedColorIdentitiesOpacity = _visualDeckStorageUnusedColorIdentitiesOpacity; + settings->setValue("interface/visualdeckstorageunusedcoloridentitiesopacity", + visualDeckStorageUnusedColorIdentitiesOpacity); + emit visualDeckStorageUnusedColorIdentitiesOpacityChanged(visualDeckStorageUnusedColorIdentitiesOpacity); +} + +void SettingsCache::setVisualDeckStoragePromptForConversion(QT_STATE_CHANGED_T _visualDeckStoragePromptForConversion) +{ + visualDeckStoragePromptForConversion = _visualDeckStoragePromptForConversion; + settings->setValue("interface/visualdeckstoragepromptforconversion", visualDeckStoragePromptForConversion); +} + +void SettingsCache::setVisualDeckStorageAlwaysConvert(QT_STATE_CHANGED_T _visualDeckStorageAlwaysConvert) +{ + visualDeckStorageAlwaysConvert = _visualDeckStorageAlwaysConvert; + settings->setValue("interface/visualdeckstoragealwaysconvert", visualDeckStorageAlwaysConvert); +} + +void SettingsCache::setVisualDeckStorageInGame(QT_STATE_CHANGED_T value) +{ + visualDeckStorageInGame = value; + settings->setValue("interface/visualdeckstorageingame", visualDeckStorageInGame); + emit visualDeckStorageInGameChanged(visualDeckStorageInGame); +} + void SettingsCache::setHorizontalHand(QT_STATE_CHANGED_T _horizontalHand) { horizontalHand = static_cast(_horizontalHand); @@ -539,12 +782,24 @@ void SettingsCache::setTapAnimation(QT_STATE_CHANGED_T _tapAnimation) settings->setValue("cards/tapanimation", tapAnimation); } +void SettingsCache::setAutoRotateSidewaysLayoutCards(QT_STATE_CHANGED_T _autoRotateSidewaysLayoutCards) +{ + autoRotateSidewaysLayoutCards = static_cast(_autoRotateSidewaysLayoutCards); + settings->setValue("cards/autorotatesidewayslayoutcards", autoRotateSidewaysLayoutCards); +} + void SettingsCache::setOpenDeckInNewTab(QT_STATE_CHANGED_T _openDeckInNewTab) { openDeckInNewTab = static_cast(_openDeckInNewTab); settings->setValue("editor/openDeckInNewTab", openDeckInNewTab); } +void SettingsCache::setRewindBufferingMs(int _rewindBufferingMs) +{ + rewindBufferingMs = _rewindBufferingMs; + settings->setValue("replay/rewindBufferingMs", rewindBufferingMs); +} + void SettingsCache::setChatMention(QT_STATE_CHANGED_T _chatMention) { chatMention = static_cast(_chatMention); @@ -582,16 +837,16 @@ void SettingsCache::setChatHighlightColor(const QString &_chatHighlightColor) settings->setValue("chat/highlightcolor", chatHighlightColor); } -void SettingsCache::setZoneViewSortByName(QT_STATE_CHANGED_T _zoneViewSortByName) +void SettingsCache::setZoneViewGroupByIndex(int _zoneViewGroupByIndex) { - zoneViewSortByName = static_cast(_zoneViewSortByName); - settings->setValue("zoneview/sortbyname", zoneViewSortByName); + zoneViewGroupByIndex = _zoneViewGroupByIndex; + settings->setValue("zoneview/groupby", zoneViewGroupByIndex); } -void SettingsCache::setZoneViewSortByType(QT_STATE_CHANGED_T _zoneViewSortByType) +void SettingsCache::setZoneViewSortByIndex(int _zoneViewSortByIndex) { - zoneViewSortByType = static_cast(_zoneViewSortByType); - settings->setValue("zoneview/sortbytype", zoneViewSortByType); + zoneViewSortByIndex = _zoneViewSortByIndex; + settings->setValue("zoneview/sortby", zoneViewSortByIndex); } void SettingsCache::setZoneViewPileView(QT_STATE_CHANGED_T _zoneViewPileView) @@ -994,6 +1249,18 @@ void SettingsCache::setCreateGameAsSpectator(const bool _createGameAsSpectator) settings->setValue("game/creategameasspectator", createGameAsSpectator); } +void SettingsCache::setDefaultStartingLifeTotal(const int _defaultStartingLifeTotal) +{ + defaultStartingLifeTotal = _defaultStartingLifeTotal; + settings->setValue("game/defaultstartinglifetotal", defaultStartingLifeTotal); +}; + +void SettingsCache::setCheckUpdatesOnStartup(QT_STATE_CHANGED_T value) +{ + checkUpdatesOnStartup = static_cast(value); + settings->setValue("personal/startupUpdateCheck", checkUpdatesOnStartup); +} + void SettingsCache::setRememberGameSettings(const bool _rememberGameSettings) { rememberGameSettings = _rememberGameSettings; @@ -1019,9 +1286,9 @@ void SettingsCache::setDownloadSpoilerStatus(bool _spoilerStatus) emit downloadSpoilerStatusChanged(); } -void SettingsCache::setUpdateReleaseChannel(int _updateReleaseChannel) +void SettingsCache::setUpdateReleaseChannelIndex(int value) { - updateReleaseChannel = _updateReleaseChannel; + updateReleaseChannel = value; settings->setValue("personal/updatereleasechannel", updateReleaseChannel); } @@ -1031,6 +1298,16 @@ void SettingsCache::setMaxFontSize(int _max) settings->setValue("game/maxfontsize", maxFontSize); } +void SettingsCache::setRoundCardCorners(bool _roundCardCorners) +{ + if (_roundCardCorners == roundCardCorners) + return; + + roundCardCorners = _roundCardCorners; + settings->setValue("cards/roundcardcorners", _roundCardCorners); + emit roundCardCornersChanged(roundCardCorners); +} + void SettingsCache::loadPaths() { QString dataPath = getDataPath(); diff --git a/cockatrice/src/settings/cache_settings.h b/cockatrice/src/settings/cache_settings.h index fdfdb6f53..4440ca0ac 100644 --- a/cockatrice/src/settings/cache_settings.h +++ b/cockatrice/src/settings/cache_settings.h @@ -3,17 +3,23 @@ #include "../utility/macros.h" #include "card_database_settings.h" +#include "card_override_settings.h" +#include "debug_settings.h" #include "download_settings.h" #include "game_filters_settings.h" #include "layouts_settings.h" #include "message_settings.h" +#include "recents_settings.h" #include "servers_settings.h" #include "shortcuts_settings.h" +#include #include #include #include +inline Q_LOGGING_CATEGORY(SettingsCacheLog, "settings_cache"); + class ReleaseChannel; // In MB (Increments of 64) @@ -41,6 +47,7 @@ class QSettings; class SettingsCache : public QObject { Q_OBJECT + signals: void langChanged(); void picsPathChanged(); @@ -48,6 +55,19 @@ signals: void themeChanged(); void picDownloadChanged(); void displayCardNamesChanged(); + void overrideAllCardArtWithPersonalPreferenceChanged(bool _overrideAllCardArtWithPersonalPreference); + void bumpSetsWithCardsInDeckToTopChanged(); + void printingSelectorSortOrderChanged(); + void printingSelectorCardSizeChanged(); + void includeRebalancedCardsChanged(bool _includeRebalancedCards); + void printingSelectorNavigationButtonsVisibleChanged(); + void visualDeckStorageShowTagFilterChanged(bool _visible); + void visualDeckStorageShowBannerCardComboBoxChanged(bool _visible); + void visualDeckStorageShowTagsOnDeckPreviewsChanged(bool _visible); + void visualDeckStorageCardSizeChanged(); + void visualDeckStorageDrawUnusedColorIdentitiesChanged(bool _visible); + void visualDeckStorageUnusedColorIdentitiesOpacityChanged(bool value); + void visualDeckStorageInGameChanged(bool enabled); void horizontalHandChanged(); void handJustificationChanged(); void invertVerticalCoordinateChanged(); @@ -64,6 +84,7 @@ signals: void downloadSpoilerTimeIndexChanged(); void downloadSpoilerStatusChanged(); void useTearOffMenusChanged(bool state); + void roundCardCornersChanged(bool roundCardCorners); private: QSettings *settings; @@ -74,6 +95,9 @@ private: GameFiltersSettings *gameFiltersSettings; LayoutsSettings *layoutsSettings; DownloadSettings *downloadSettings; + RecentsSettings *recentsSettings; + CardOverrideSettings *cardOverrideSettings; + DebugSettings *debugSettings; QByteArray mainWindowGeometry; QByteArray tokenDialogGeometry; @@ -81,6 +105,9 @@ private: QString lang; QString deckPath, replaysPath, picsPath, redirectCachePath, customPicsPath, cardDatabasePath, customCardDatabasePath, themesPath, spoilerDatabasePath, tokenDatabasePath, themeName; + bool tabVisualDeckStorageOpen, tabServerOpen, tabAccountOpen, tabDeckStorageOpen, tabReplaysOpen, tabAdminOpen, + tabLogOpen; + bool checkUpdatesOnStartup; bool notifyAboutUpdates; bool notifyAboutNewVersion; bool showTipsOnStartup; @@ -93,23 +120,46 @@ private: bool spectatorNotificationsEnabled; bool buddyConnectNotificationsEnabled; bool doubleClickToPlay; + bool clickPlaysAllSelected; bool playToStack; int startingHandSize; bool annotateTokens; QByteArray tabGameSplitterSizes; + bool showShortcuts; bool displayCardNames; + bool overrideAllCardArtWithPersonalPreference; + bool bumpSetsWithCardsInDeckToTop; + int printingSelectorSortOrder; + int printingSelectorCardSize; + bool includeRebalancedCards; + bool printingSelectorNavigationButtonsVisible; + int visualDeckStorageSortingOrder; + bool visualDeckStorageShowFolders; + bool visualDeckStorageShowBannerCardComboBox; + bool visualDeckStorageShowTagsOnDeckPreviews; + bool visualDeckStorageShowTagFilter; + bool visualDeckStorageSearchFolderNames; + int visualDeckStorageCardSize; + bool visualDeckStorageDrawUnusedColorIdentities; + int visualDeckStorageUnusedColorIdentitiesOpacity; + bool visualDeckStoragePromptForConversion; + bool visualDeckStorageAlwaysConvert; + bool visualDeckStorageInGame; bool horizontalHand; bool invertVerticalCoordinate; int minPlayersForMultiColumnLayout; bool tapAnimation; + bool autoRotateSidewaysLayoutCards; bool openDeckInNewTab; + int rewindBufferingMs; bool chatMention; bool chatMentionCompleter; QString chatMentionColor; QString chatHighlightColor; bool chatMentionForeground; bool chatHighlightForeground; - bool zoneViewSortByName, zoneViewSortByType, zoneViewPileView; + int zoneViewSortByIndex, zoneViewGroupByIndex; + bool zoneViewPileView; bool soundEnabled; QString soundThemeName; bool ignoreUnregisteredUsers; @@ -120,6 +170,9 @@ private: QString clientVersion; QString knownMissingFeatures; bool useTearOffMenus; + int cardViewInitialRowsMax; + int cardViewExpandedRowsMax; + bool closeEmptyCardView; int pixmapCacheSize; int networkCacheSize; int redirectCacheTtl; @@ -142,6 +195,7 @@ private: bool spectatorsCanTalk; bool spectatorsCanSeeEverything; bool createGameAsSpectator; + int defaultStartingLifeTotal; int keepalive; int timeout; void translateLegacySettings(); @@ -151,6 +205,7 @@ private: bool rememberGameSettings; QList releaseChannels; bool isPortableBuild; + bool roundCardCorners; public: SettingsCache(); @@ -218,6 +273,34 @@ public: { return themeName; } + bool getTabVisualDeckStorageOpen() const + { + return tabVisualDeckStorageOpen; + } + bool getTabServerOpen() const + { + return tabServerOpen; + } + bool getTabAccountOpen() const + { + return tabAccountOpen; + } + bool getTabDeckStorageOpen() const + { + return tabDeckStorageOpen; + } + bool getTabReplaysOpen() const + { + return tabReplaysOpen; + } + bool getTabAdminOpen() const + { + return tabAdminOpen; + } + bool getTabLogOpen() const + { + return tabLogOpen; + } QString getChatMentionColor() const { return chatMentionColor; @@ -242,6 +325,10 @@ public: { return buddyConnectNotificationsEnabled; } + bool getCheckUpdatesOnStartup() const + { + return checkUpdatesOnStartup; + } bool getNotifyAboutUpdates() const { return notifyAboutUpdates; @@ -258,9 +345,13 @@ public: { return seenTips; } + int getUpdateReleaseChannelIndex() const + { + return updateReleaseChannel; + } ReleaseChannel *getUpdateReleaseChannel() const { - return releaseChannels.at(updateReleaseChannel); + return releaseChannels.at(qMax(0, updateReleaseChannel)); } QList getUpdateReleaseChannels() const { @@ -271,6 +362,10 @@ public: { return doubleClickToPlay; } + bool getClickPlaysAllSelected() const + { + return clickPlaysAllSelected; + } bool getPlayToStack() const { return playToStack; @@ -287,10 +382,86 @@ public: { return tabGameSplitterSizes; } + bool getShowShortcuts() const + { + return showShortcuts; + } bool getDisplayCardNames() const { return displayCardNames; } + bool getOverrideAllCardArtWithPersonalPreference() const + { + return overrideAllCardArtWithPersonalPreference; + } + bool getBumpSetsWithCardsInDeckToTop() const + { + return bumpSetsWithCardsInDeckToTop; + } + int getPrintingSelectorSortOrder() const + { + return printingSelectorSortOrder; + } + int getPrintingSelectorCardSize() const + { + return printingSelectorCardSize; + } + bool getIncludeRebalancedCards() const + { + return includeRebalancedCards; + } + bool getPrintingSelectorNavigationButtonsVisible() const + { + return printingSelectorNavigationButtonsVisible; + } + int getVisualDeckStorageSortingOrder() const + { + return visualDeckStorageSortingOrder; + } + bool getVisualDeckStorageShowFolders() const + { + return visualDeckStorageShowFolders; + } + bool getVisualDeckStorageShowTagFilter() const + { + return visualDeckStorageShowTagFilter; + } + bool getVisualDeckStorageSearchFolderNames() const + { + return visualDeckStorageSearchFolderNames; + } + bool getVisualDeckStorageShowBannerCardComboBox() const + { + return visualDeckStorageShowBannerCardComboBox; + } + bool getVisualDeckStorageShowTagsOnDeckPreviews() const + { + return visualDeckStorageShowTagsOnDeckPreviews; + } + int getVisualDeckStorageCardSize() const + { + return visualDeckStorageCardSize; + } + bool getVisualDeckStorageDrawUnusedColorIdentities() const + { + return visualDeckStorageDrawUnusedColorIdentities; + } + int getVisualDeckStorageUnusedColorIdentitiesOpacity() const + { + return visualDeckStorageUnusedColorIdentitiesOpacity; + } + bool getVisualDeckStoragePromptForConversion() const + { + return visualDeckStoragePromptForConversion; + } + bool getVisualDeckStorageAlwaysConvert() const + { + return visualDeckStorageAlwaysConvert; + } + bool getVisualDeckStorageInGame() const + { + return visualDeckStorageInGame; + } bool getHorizontalHand() const { return horizontalHand; @@ -307,10 +478,18 @@ public: { return tapAnimation; } + bool getAutoRotateSidewaysLayoutCards() const + { + return autoRotateSidewaysLayoutCards; + } bool getOpenDeckInNewTab() const { return openDeckInNewTab; } + int getRewindBufferingMs() const + { + return rewindBufferingMs; + } bool getChatMention() const { return chatMention; @@ -327,13 +506,19 @@ public: { return chatHighlightForeground; } - bool getZoneViewSortByName() const + /** + * Currently selected index for the `Group by X` QComboBox + */ + int getZoneViewGroupByIndex() const { - return zoneViewSortByName; + return zoneViewGroupByIndex; } - bool getZoneViewSortByType() const + /** + * Currently selected index for the `Sort by X` QComboBox + */ + int getZoneViewSortByIndex() const { - return zoneViewSortByType; + return zoneViewSortByIndex; } /** Returns if the view should be sorted into pile view. @@ -444,6 +629,10 @@ public: { return spectatorsCanSeeEverything; } + int getDefaultStartingLifeTotal() const + { + return defaultStartingLifeTotal; + } bool getCreateGameAsSpectator() const { return createGameAsSpectator; @@ -468,6 +657,9 @@ public: void setClientVersion(const QString &clientVersion); void setKnownMissingFeatures(const QString &_knownMissingFeatures); void setUseTearOffMenus(bool _useTearOffMenus); + void setCardViewInitialRowsMax(int _cardViewInitialRowsMax); + void setCardViewExpandedRowsMax(int value); + void setCloseEmptyCardView(QT_STATE_CHANGED_T value); QString getClientID() { return clientID; @@ -484,6 +676,18 @@ public: { return useTearOffMenus; } + int getCardViewInitialRowsMax() const + { + return cardViewInitialRowsMax; + } + int getCardViewExpandedRowsMax() const + { + return cardViewExpandedRowsMax; + } + bool getCloseEmptyCardView() const + { + return closeEmptyCardView; + } ShortcutsSettings &shortcuts() const { return *shortcutsSettings; @@ -512,6 +716,18 @@ public: { return *downloadSettings; } + RecentsSettings &recents() const + { + return *recentsSettings; + } + CardOverrideSettings &cardOverrides() const + { + return *cardOverrideSettings; + } + DebugSettings &debug() const + { + return *debugSettings; + } bool getIsPortableBuild() const { return isPortableBuild; @@ -520,6 +736,10 @@ public: { return mbDownloadSpoilers; } + bool getRoundCardCorners() const + { + return roundCardCorners; + } static SettingsCache &instance(); void resetPaths(); @@ -542,6 +762,13 @@ public slots: void setSpoilerDatabasePath(const QString &_spoilerDatabasePath); void setTokenDatabasePath(const QString &_tokenDatabasePath); void setThemeName(const QString &_themeName); + void setTabVisualDeckStorageOpen(bool value); + void setTabServerOpen(bool value); + void setTabAccountOpen(bool value); + void setTabDeckStorageOpen(bool value); + void setTabReplaysOpen(bool value); + void setTabAdminOpen(bool value); + void setTabLogOpen(bool value); void setChatMentionColor(const QString &_chatMentionColor); void setChatHighlightColor(const QString &_chatHighlightColor); void setPicDownload(QT_STATE_CHANGED_T _picDownload); @@ -549,22 +776,44 @@ public slots: void setSpectatorNotificationsEnabled(QT_STATE_CHANGED_T _spectatorNotificationsEnabled); void setBuddyConnectNotificationsEnabled(QT_STATE_CHANGED_T _buddyConnectNotificationsEnabled); void setDoubleClickToPlay(QT_STATE_CHANGED_T _doubleClickToPlay); + void setClickPlaysAllSelected(QT_STATE_CHANGED_T _clickPlaysAllSelected); void setPlayToStack(QT_STATE_CHANGED_T _playToStack); void setStartingHandSize(int _startingHandSize); void setAnnotateTokens(QT_STATE_CHANGED_T _annotateTokens); void setTabGameSplitterSizes(const QByteArray &_tabGameSplitterSizes); + void setShowShortcuts(QT_STATE_CHANGED_T _showShortcuts); void setDisplayCardNames(QT_STATE_CHANGED_T _displayCardNames); + void setOverrideAllCardArtWithPersonalPreference(QT_STATE_CHANGED_T _overrideAllCardArt); + void setBumpSetsWithCardsInDeckToTop(QT_STATE_CHANGED_T _bumpSetsWithCardsInDeckToTop); + void setPrintingSelectorSortOrder(int _printingSelectorSortOrder); + void setPrintingSelectorCardSize(int _printingSelectorCardSize); + void setIncludeRebalancedCards(bool _includeRebalancedCards); + void setPrintingSelectorNavigationButtonsVisible(QT_STATE_CHANGED_T _navigationButtonsVisible); + void setVisualDeckStorageSortingOrder(int _visualDeckStorageSortingOrder); + void setVisualDeckStorageShowFolders(QT_STATE_CHANGED_T value); + void setVisualDeckStorageShowTagFilter(QT_STATE_CHANGED_T _showTags); + void setVisualDeckStorageSearchFolderNames(QT_STATE_CHANGED_T value); + void setVisualDeckStorageShowBannerCardComboBox(QT_STATE_CHANGED_T _showBannerCardComboBox); + void setVisualDeckStorageShowTagsOnDeckPreviews(QT_STATE_CHANGED_T _showTags); + void setVisualDeckStorageCardSize(int _visualDeckStorageCardSize); + void setVisualDeckStorageDrawUnusedColorIdentities(QT_STATE_CHANGED_T _visualDeckStorageDrawUnusedColorIdentities); + void setVisualDeckStorageUnusedColorIdentitiesOpacity(int _visualDeckStorageUnusedColorIdentitiesOpacity); + void setVisualDeckStoragePromptForConversion(QT_STATE_CHANGED_T _visualDeckStoragePromptForConversion); + void setVisualDeckStorageAlwaysConvert(QT_STATE_CHANGED_T _visualDeckStorageAlwaysConvert); + void setVisualDeckStorageInGame(QT_STATE_CHANGED_T value); void setHorizontalHand(QT_STATE_CHANGED_T _horizontalHand); void setInvertVerticalCoordinate(QT_STATE_CHANGED_T _invertVerticalCoordinate); void setMinPlayersForMultiColumnLayout(int _minPlayersForMultiColumnLayout); void setTapAnimation(QT_STATE_CHANGED_T _tapAnimation); + void setAutoRotateSidewaysLayoutCards(QT_STATE_CHANGED_T _autoRotateSidewaysLayoutCards); void setOpenDeckInNewTab(QT_STATE_CHANGED_T _openDeckInNewTab); + void setRewindBufferingMs(int _rewindBufferingMs); void setChatMention(QT_STATE_CHANGED_T _chatMention); void setChatMentionCompleter(QT_STATE_CHANGED_T _chatMentionCompleter); void setChatMentionForeground(QT_STATE_CHANGED_T _chatMentionForeground); void setChatHighlightForeground(QT_STATE_CHANGED_T _chatHighlightForeground); - void setZoneViewSortByName(QT_STATE_CHANGED_T _zoneViewSortByName); - void setZoneViewSortByType(QT_STATE_CHANGED_T _zoneViewSortByType); + void setZoneViewGroupByIndex(const int _zoneViewGroupByIndex); + void setZoneViewSortByIndex(const int _zoneViewSortByIndex); void setZoneViewPileView(QT_STATE_CHANGED_T _zoneViewPileView); void setSoundEnabled(QT_STATE_CHANGED_T _soundEnabled); void setSoundThemeName(const QString &_soundThemeName); @@ -592,11 +841,14 @@ public slots: void setSpectatorsCanTalk(const bool _spectatorsCanTalk); void setSpectatorsCanSeeEverything(const bool _spectatorsCanSeeEverything); void setCreateGameAsSpectator(const bool _createGameAsSpectator); + void setDefaultStartingLifeTotal(const int _defaultStartingLifeTotal); void setRememberGameSettings(const bool _rememberGameSettings); + void setCheckUpdatesOnStartup(QT_STATE_CHANGED_T value); void setNotifyAboutUpdate(QT_STATE_CHANGED_T _notifyaboutupdate); void setNotifyAboutNewVersion(QT_STATE_CHANGED_T _notifyaboutnewversion); - void setUpdateReleaseChannel(int _updateReleaseChannel); + void setUpdateReleaseChannelIndex(int value); void setMaxFontSize(int _max); + void setRoundCardCorners(bool _roundCardCorners); }; #endif diff --git a/cockatrice/src/settings/card_database_settings.cpp b/cockatrice/src/settings/card_database_settings.cpp index 777d7b568..f0b8d58b1 100644 --- a/cockatrice/src/settings/card_database_settings.cpp +++ b/cockatrice/src/settings/card_database_settings.cpp @@ -1,6 +1,6 @@ #include "card_database_settings.h" -CardDatabaseSettings::CardDatabaseSettings(QString settingPath, QObject *parent) +CardDatabaseSettings::CardDatabaseSettings(const QString &settingPath, QObject *parent) : SettingsManager(settingPath + "cardDatabase.ini", parent) { } diff --git a/cockatrice/src/settings/card_database_settings.h b/cockatrice/src/settings/card_database_settings.h index 9923a8003..421fb60bb 100644 --- a/cockatrice/src/settings/card_database_settings.h +++ b/cockatrice/src/settings/card_database_settings.h @@ -25,7 +25,7 @@ signals: public slots: private: - explicit CardDatabaseSettings(QString settingPath, QObject *parent = nullptr); + explicit CardDatabaseSettings(const QString &settingPath, QObject *parent = nullptr); CardDatabaseSettings(const CardDatabaseSettings & /*other*/); }; diff --git a/cockatrice/src/settings/card_override_settings.cpp b/cockatrice/src/settings/card_override_settings.cpp new file mode 100644 index 000000000..c3331fd0a --- /dev/null +++ b/cockatrice/src/settings/card_override_settings.cpp @@ -0,0 +1,21 @@ +#include "card_override_settings.h" + +CardOverrideSettings::CardOverrideSettings(const QString &settingPath, QObject *parent) + : SettingsManager(settingPath + "cardPreferenceOverrides.ini", parent) +{ +} + +void CardOverrideSettings::setCardPreferenceOverride(const QString &cardName, const QString &providerId) +{ + setValue(providerId, cardName, "cards"); +} + +void CardOverrideSettings::deleteCardPreferenceOverride(const QString &cardName) +{ + deleteValue(cardName, "cards"); +} + +QString CardOverrideSettings::getCardPreferenceOverride(const QString &cardName) +{ + return getValue(cardName, "cards").toString(); +} \ No newline at end of file diff --git a/cockatrice/src/settings/card_override_settings.h b/cockatrice/src/settings/card_override_settings.h new file mode 100644 index 000000000..dae3e4daf --- /dev/null +++ b/cockatrice/src/settings/card_override_settings.h @@ -0,0 +1,25 @@ +#ifndef COCKATRICE_CARD_OVERRIDE_SETTINGS_H +#define COCKATRICE_CARD_OVERRIDE_SETTINGS_H + +#include "settings_manager.h" + +#include + +class CardOverrideSettings : public SettingsManager +{ + Q_OBJECT + friend class SettingsCache; + +public: + void setCardPreferenceOverride(const QString &cardName, const QString &providerId); + + void deleteCardPreferenceOverride(const QString &cardName); + + QString getCardPreferenceOverride(const QString &cardName); + +private: + explicit CardOverrideSettings(const QString &settingPath, QObject *parent = nullptr); + CardOverrideSettings(const CardOverrideSettings & /*other*/); +}; + +#endif // COCKATRICE_CARD_OVERRIDE_SETTINGS_H diff --git a/cockatrice/src/settings/debug_settings.cpp b/cockatrice/src/settings/debug_settings.cpp new file mode 100644 index 000000000..f6f12f60e --- /dev/null +++ b/cockatrice/src/settings/debug_settings.cpp @@ -0,0 +1,32 @@ +#include "debug_settings.h" + +#include + +DebugSettings::DebugSettings(const QString &settingPath, QObject *parent) + : SettingsManager(settingPath + "debug.ini", parent) +{ + // Create the default debug.ini if it doesn't exist yet + if (!QFile(settingPath + "debug.ini").exists()) { + QFile::copy(":/resources/config/debug.ini", settingPath + "debug.ini"); + } +} + +bool DebugSettings::getShowCardId() +{ + return getValue("showCardId", "debug").toBool(); +} + +bool DebugSettings::getLocalGameOnStartup() +{ + return getValue("onStartup", "localgame").toBool(); +} + +int DebugSettings::getLocalGamePlayerCount() +{ + return getValue("playerCount", "localgame").toInt(); +} + +QString DebugSettings::getDeckPathForPlayer(const QString &playerName) +{ + return getValue(playerName, "localgame", "deck").toString(); +} \ No newline at end of file diff --git a/cockatrice/src/settings/debug_settings.h b/cockatrice/src/settings/debug_settings.h new file mode 100644 index 000000000..89ea33e4e --- /dev/null +++ b/cockatrice/src/settings/debug_settings.h @@ -0,0 +1,22 @@ +#ifndef DEBUG_SETTINGS_H +#define DEBUG_SETTINGS_H +#include "settings_manager.h" + +class DebugSettings : public SettingsManager +{ + Q_OBJECT + friend class SettingsCache; + + explicit DebugSettings(const QString &settingPath, QObject *parent = nullptr); + DebugSettings(const DebugSettings & /*other*/); + +public: + bool getShowCardId(); + + bool getLocalGameOnStartup(); + int getLocalGamePlayerCount(); + + QString getDeckPathForPlayer(const QString &playerName); +}; + +#endif // DEBUG_SETTINGS_H diff --git a/cockatrice/src/settings/download_settings.cpp b/cockatrice/src/settings/download_settings.cpp index 1af9d4f71..adbe26363 100644 --- a/cockatrice/src/settings/download_settings.cpp +++ b/cockatrice/src/settings/download_settings.cpp @@ -2,56 +2,28 @@ #include "settings_manager.h" +const QStringList DownloadSettings::DEFAULT_DOWNLOAD_URLS = { + "https://api.scryfall.com/cards/!set:uuid!?format=image&face=!prop:side!", + "https://api.scryfall.com/cards/multiverse/!set:muid!?format=image", + "https://gatherer.wizards.com/Handlers/Image.ashx?multiverseid=!set:muid!&type=card", + "https://gatherer.wizards.com/Handlers/Image.ashx?name=!name!&type=card"}; + DownloadSettings::DownloadSettings(const QString &settingPath, QObject *parent = nullptr) : SettingsManager(settingPath + "downloads.ini", parent) { - downloadURLs = getValue("urls", "downloads").value(); } -void DownloadSettings::setDownloadUrlAt(int index, const QString &url) +void DownloadSettings::setDownloadUrls(const QStringList &downloadURLs) { - downloadURLs.insert(index, url); setValue(QVariant::fromValue(downloadURLs), "urls", "downloads"); } -/** - * If reset or first run, this method contains the default URLs we will populate - */ QStringList DownloadSettings::getAllURLs() { - // First run, these will be empty - if (downloadURLs.count() == 0) { - populateDefaultURLs(); - } - - return downloadURLs; + return getValue("urls", "downloads").toStringList(); } -void DownloadSettings::populateDefaultURLs() +void DownloadSettings::resetToDefaultURLs() { - downloadURLs.clear(); - downloadURLs.append("https://api.scryfall.com/cards/!set:uuid!?format=image&face=!prop:side!"); - downloadURLs.append("https://api.scryfall.com/cards/multiverse/!set:muid!?format=image"); - downloadURLs.append("https://gatherer.wizards.com/Handlers/Image.ashx?multiverseid=!set:muid!&type=card"); - downloadURLs.append("https://gatherer.wizards.com/Handlers/Image.ashx?name=!name!&type=card"); - setValue(QVariant::fromValue(downloadURLs), "urls", "downloads"); + setValue(QVariant::fromValue(DEFAULT_DOWNLOAD_URLS), "urls", "downloads"); } - -QString DownloadSettings::getDownloadUrlAt(int index) -{ - if (0 <= index && index < downloadURLs.size()) { - return downloadURLs[index]; - } - - return ""; -} - -int DownloadSettings::getCount() -{ - return downloadURLs.size(); -} - -void DownloadSettings::clear() -{ - downloadURLs.clear(); -} \ No newline at end of file diff --git a/cockatrice/src/settings/download_settings.h b/cockatrice/src/settings/download_settings.h index 5916b0083..a961199b3 100644 --- a/cockatrice/src/settings/download_settings.h +++ b/cockatrice/src/settings/download_settings.h @@ -10,20 +10,14 @@ class DownloadSettings : public SettingsManager Q_OBJECT friend class SettingsCache; + static const QStringList DEFAULT_DOWNLOAD_URLS; + public: explicit DownloadSettings(const QString &, QObject *); QStringList getAllURLs(); - QString getDownloadUrlAt(int); - void setDownloadUrlAt(int, const QString &); - int getCount(); - void clear(); - -private: - QStringList downloadURLs; - -private: - void populateDefaultURLs(); + void setDownloadUrls(const QStringList &downloadURLs); + void resetToDefaultURLs(); }; #endif // COCKATRICE_DOWNLOADSETTINGS_H diff --git a/cockatrice/src/settings/game_filters_settings.cpp b/cockatrice/src/settings/game_filters_settings.cpp index adb0e094f..776cd9edc 100644 --- a/cockatrice/src/settings/game_filters_settings.cpp +++ b/cockatrice/src/settings/game_filters_settings.cpp @@ -3,7 +3,7 @@ #include #include -GameFiltersSettings::GameFiltersSettings(QString settingPath, QObject *parent) +GameFiltersSettings::GameFiltersSettings(const QString &settingPath, QObject *parent) : SettingsManager(settingPath + "gamefilters.ini", parent) { } @@ -17,48 +17,48 @@ QString GameFiltersSettings::hashGameType(const QString &gameType) const return QCryptographicHash::hash(gameType.toUtf8(), QCryptographicHash::Md5).toHex(); } -void GameFiltersSettings::setShowBuddiesOnlyGames(bool show) +void GameFiltersSettings::setHideBuddiesOnlyGames(bool hide) { - setValue(show, "show_buddies_only_games", "filter_games"); + setValue(hide, "hide_buddies_only_games", "filter_games"); } -bool GameFiltersSettings::isShowBuddiesOnlyGames() +bool GameFiltersSettings::isHideBuddiesOnlyGames() { - QVariant previous = getValue("show_buddies_only_games", "filter_games"); - return previous == QVariant() ? true : previous.toBool(); + QVariant previous = getValue("hide_buddies_only_games", "filter_games"); + return previous == QVariant() || previous.toBool(); } -void GameFiltersSettings::setShowFullGames(bool show) +void GameFiltersSettings::setHideFullGames(bool hide) { - setValue(show, "show_full_games", "filter_games"); + setValue(hide, "hide_full_games", "filter_games"); } -bool GameFiltersSettings::isShowFullGames() +bool GameFiltersSettings::isHideFullGames() { - QVariant previous = getValue("show_full_games", "filter_games"); - return previous == QVariant() ? false : previous.toBool(); + QVariant previous = getValue("hide_full_games", "filter_games"); + return !(previous == QVariant()) && previous.toBool(); } -void GameFiltersSettings::setShowGamesThatStarted(bool show) +void GameFiltersSettings::setHideGamesThatStarted(bool hide) { - setValue(show, "show_games_that_started", "filter_games"); + setValue(hide, "hide_games_that_started", "filter_games"); } -bool GameFiltersSettings::isShowGamesThatStarted() +bool GameFiltersSettings::isHideGamesThatStarted() { - QVariant previous = getValue("show_games_that_started", "filter_games"); - return previous == QVariant() ? false : previous.toBool(); + QVariant previous = getValue("hide_games_that_started", "filter_games"); + return !(previous == QVariant()) && previous.toBool(); } -void GameFiltersSettings::setShowPasswordProtectedGames(bool show) +void GameFiltersSettings::setHidePasswordProtectedGames(bool hide) { - setValue(show, "show_password_protected_games", "filter_games"); + setValue(hide, "hide_password_protected_games", "filter_games"); } -bool GameFiltersSettings::isShowPasswordProtectedGames() +bool GameFiltersSettings::isHidePasswordProtectedGames() { - QVariant previous = getValue("show_password_protected_games", "filter_games"); - return previous == QVariant() ? true : previous.toBool(); + QVariant previous = getValue("hide_password_protected_games", "filter_games"); + return previous == QVariant() || previous.toBool(); } void GameFiltersSettings::setHideIgnoredUserGames(bool hide) @@ -69,7 +69,7 @@ void GameFiltersSettings::setHideIgnoredUserGames(bool hide) bool GameFiltersSettings::isHideIgnoredUserGames() { QVariant previous = getValue("hide_ignored_user_games", "filter_games"); - return previous == QVariant() ? false : previous.toBool(); + return !(previous == QVariant()) && previous.toBool(); } void GameFiltersSettings::setGameNameFilter(QString gameName) diff --git a/cockatrice/src/settings/game_filters_settings.h b/cockatrice/src/settings/game_filters_settings.h index 6129f36ad..7f6b75077 100644 --- a/cockatrice/src/settings/game_filters_settings.h +++ b/cockatrice/src/settings/game_filters_settings.h @@ -9,10 +9,10 @@ class GameFiltersSettings : public SettingsManager friend class SettingsCache; public: - bool isShowBuddiesOnlyGames(); - bool isShowFullGames(); - bool isShowGamesThatStarted(); - bool isShowPasswordProtectedGames(); + bool isHideBuddiesOnlyGames(); + bool isHideFullGames(); + bool isHideGamesThatStarted(); + bool isHidePasswordProtectedGames(); bool isHideIgnoredUserGames(); QString getGameNameFilter(); QString getCreatorNameFilter(); @@ -25,11 +25,11 @@ public: bool isShowOnlyIfSpectatorsCanChat(); bool isShowOnlyIfSpectatorsCanSeeHands(); - void setShowBuddiesOnlyGames(bool show); + void setHideBuddiesOnlyGames(bool hide); void setHideIgnoredUserGames(bool hide); - void setShowFullGames(bool show); - void setShowGamesThatStarted(bool show); - void setShowPasswordProtectedGames(bool show); + void setHideFullGames(bool hide); + void setHideGamesThatStarted(bool hide); + void setHidePasswordProtectedGames(bool hide); void setGameNameFilter(QString gameName); void setCreatorNameFilter(QString creatorName); void setMinPlayers(int min); @@ -46,7 +46,7 @@ signals: public slots: private: - explicit GameFiltersSettings(QString settingPath, QObject *parent = nullptr); + explicit GameFiltersSettings(const QString &settingPath, QObject *parent = nullptr); GameFiltersSettings(const GameFiltersSettings & /*other*/); QString hashGameType(const QString &gameType) const; diff --git a/cockatrice/src/settings/layouts_settings.cpp b/cockatrice/src/settings/layouts_settings.cpp index 7e17451b9..5791cbfe9 100644 --- a/cockatrice/src/settings/layouts_settings.cpp +++ b/cockatrice/src/settings/layouts_settings.cpp @@ -1,6 +1,6 @@ #include "layouts_settings.h" -LayoutsSettings::LayoutsSettings(QString settingPath, QObject *parent) +LayoutsSettings::LayoutsSettings(const QString &settingPath, QObject *parent) : SettingsManager(settingPath + "layouts.ini", parent) { } @@ -25,7 +25,7 @@ void LayoutsSettings::setDeckEditorGeometry(const QByteArray &value) setValue(value, "layouts/deckEditor_geometry"); } -const QSize LayoutsSettings::getDeckEditorCardSize() +QSize LayoutsSettings::getDeckEditorCardSize() { QVariant previous = getValue("layouts/deckEditor_CardSize"); return previous == QVariant() ? QSize(250, 500) : previous.toSize(); @@ -36,7 +36,7 @@ void LayoutsSettings::setDeckEditorCardSize(const QSize &value) setValue(value, "layouts/deckEditor_CardSize"); } -const QSize LayoutsSettings::getDeckEditorDeckSize() +QSize LayoutsSettings::getDeckEditorDeckSize() { QVariant previous = getValue("layouts/deckEditor_DeckSize"); return previous == QVariant() ? QSize(250, 360) : previous.toSize(); @@ -47,7 +47,18 @@ void LayoutsSettings::setDeckEditorDeckSize(const QSize &value) setValue(value, "layouts/deckEditor_DeckSize"); } -const QSize LayoutsSettings::getDeckEditorFilterSize() +QSize LayoutsSettings::getDeckEditorPrintingSelectorSize() +{ + QVariant previous = getValue("layouts/deckEditor_PrintingSelectorSize"); + return previous == QVariant() ? QSize(525, 250) : previous.toSize(); +} + +void LayoutsSettings::setDeckEditorPrintingSelectorSize(const QSize &value) +{ + setValue(value, "layouts/deckEditor_PrintingSelectorSize"); +} + +QSize LayoutsSettings::getDeckEditorFilterSize() { QVariant previous = getValue("layouts/deckEditor_FilterSize"); return previous == QVariant() ? QSize(250, 250) : previous.toSize(); diff --git a/cockatrice/src/settings/layouts_settings.h b/cockatrice/src/settings/layouts_settings.h index 80bd71fe8..0464bec01 100644 --- a/cockatrice/src/settings/layouts_settings.h +++ b/cockatrice/src/settings/layouts_settings.h @@ -15,6 +15,7 @@ public: void setDeckEditorGeometry(const QByteArray &value); void setDeckEditorCardSize(const QSize &value); void setDeckEditorDeckSize(const QSize &value); + void setDeckEditorPrintingSelectorSize(const QSize &value); void setDeckEditorFilterSize(const QSize &value); void setDeckEditorDbHeaderState(const QByteArray &value); void setSetsDialogHeaderState(const QByteArray &value); @@ -34,9 +35,10 @@ public: const QByteArray getDeckEditorLayoutState(); const QByteArray getDeckEditorGeometry(); - const QSize getDeckEditorCardSize(); - const QSize getDeckEditorDeckSize(); - const QSize getDeckEditorFilterSize(); + QSize getDeckEditorCardSize(); + QSize getDeckEditorDeckSize(); + QSize getDeckEditorPrintingSelectorSize(); + QSize getDeckEditorFilterSize(); const QByteArray getDeckEditorDbHeaderState(); const QByteArray getSetsDialogHeaderState(); @@ -57,7 +59,7 @@ signals: public slots: private: - explicit LayoutsSettings(QString settingPath, QObject *parent = nullptr); + explicit LayoutsSettings(const QString &settingPath, QObject *parent = nullptr); LayoutsSettings(const LayoutsSettings & /*other*/); }; diff --git a/cockatrice/src/settings/message_settings.cpp b/cockatrice/src/settings/message_settings.cpp index 75f43cc64..786ea9cd1 100644 --- a/cockatrice/src/settings/message_settings.cpp +++ b/cockatrice/src/settings/message_settings.cpp @@ -1,6 +1,6 @@ #include "message_settings.h" -MessageSettings::MessageSettings(QString settingPath, QObject *parent) +MessageSettings::MessageSettings(const QString &settingPath, QObject *parent) : SettingsManager(settingPath + "messages.ini", parent) { } diff --git a/cockatrice/src/settings/message_settings.h b/cockatrice/src/settings/message_settings.h index 4c4dcc5fa..6d65ce6e8 100644 --- a/cockatrice/src/settings/message_settings.h +++ b/cockatrice/src/settings/message_settings.h @@ -20,7 +20,7 @@ signals: public slots: private: - explicit MessageSettings(QString settingPath, QObject *parent = nullptr); + explicit MessageSettings(const QString &settingPath, QObject *parent = nullptr); MessageSettings(const MessageSettings & /*other*/); }; diff --git a/cockatrice/src/settings/recents_settings.cpp b/cockatrice/src/settings/recents_settings.cpp new file mode 100644 index 000000000..5bbec3c8c --- /dev/null +++ b/cockatrice/src/settings/recents_settings.cpp @@ -0,0 +1,42 @@ +#include "recents_settings.h" + +#define MAX_RECENT_DECK_COUNT 10 + +RecentsSettings::RecentsSettings(const QString &settingPath, QObject *parent) + : SettingsManager(settingPath + "recents.ini", parent) +{ +} + +QStringList RecentsSettings::getRecentlyOpenedDeckPaths() +{ + return getValue("deckpaths", "deckbuilder").toStringList(); +} +void RecentsSettings::clearRecentlyOpenedDeckPaths() +{ + deleteValue("deckpaths", "deckbuilder"); + emit recentlyOpenedDeckPathsChanged(); +} +void RecentsSettings::updateRecentlyOpenedDeckPaths(const QString &deckPath) +{ + auto deckPaths = getValue("deckpaths", "deckbuilder").toStringList(); + deckPaths.removeAll(deckPath); + + deckPaths.prepend(deckPath); + + while (deckPaths.size() > MAX_RECENT_DECK_COUNT) { + deckPaths.removeLast(); + } + + setValue(deckPaths, "deckpaths", "deckbuilder"); + emit recentlyOpenedDeckPathsChanged(); +} + +QString RecentsSettings::getLatestDeckDirPath() +{ + return getValue("latestDeckDir", "dirs").toString(); +} + +void RecentsSettings::setLatestDeckDirPath(const QString &dirPath) +{ + setValue(dirPath, "latestDeckDir", "dirs"); +} \ No newline at end of file diff --git a/cockatrice/src/settings/recents_settings.h b/cockatrice/src/settings/recents_settings.h new file mode 100644 index 000000000..6e91cd2f0 --- /dev/null +++ b/cockatrice/src/settings/recents_settings.h @@ -0,0 +1,26 @@ +#ifndef RECENTS_SETTINGS_H +#define RECENTS_SETTINGS_H + +#include "settings_manager.h" + +class RecentsSettings : public SettingsManager +{ + Q_OBJECT + friend class SettingsCache; + + explicit RecentsSettings(const QString &settingPath, QObject *parent = nullptr); + RecentsSettings(const RecentsSettings & /*other*/); + +public: + QStringList getRecentlyOpenedDeckPaths(); + void clearRecentlyOpenedDeckPaths(); + void updateRecentlyOpenedDeckPaths(const QString &deckPath); + + QString getLatestDeckDirPath(); + void setLatestDeckDirPath(const QString &dirPath); + +signals: + void recentlyOpenedDeckPathsChanged(); +}; + +#endif // RECENTS_SETTINGS_H diff --git a/cockatrice/src/settings/servers_settings.cpp b/cockatrice/src/settings/servers_settings.cpp index 88ffac398..01a6c795b 100644 --- a/cockatrice/src/settings/servers_settings.cpp +++ b/cockatrice/src/settings/servers_settings.cpp @@ -3,7 +3,7 @@ #include #include -ServersSettings::ServersSettings(QString settingPath, QObject *parent) +ServersSettings::ServersSettings(const QString &settingPath, QObject *parent) : SettingsManager(settingPath + "servers.ini", parent) { } @@ -76,7 +76,7 @@ QString ServersSettings::getPort(QString defaultPort) { int index = getPrevioushostindex(getPrevioushostName()); QVariant port = getValue(QString("port%1").arg(index), "server", "server_details"); - qDebug() << "getPort() index = " << index << " port.val = " << port.toString(); + qCDebug(ServersSettingsLog) << "getPort() index = " << index << " port.val = " << port.toString(); return port == QVariant() ? std::move(defaultPort) : port.toString(); } @@ -84,7 +84,7 @@ QString ServersSettings::getPlayerName(QString defaultName) { int index = getPrevioushostindex(getPrevioushostName()); QVariant name = getValue(QString("username%1").arg(index), "server", "server_details"); - qDebug() << "getPlayerName() index = " << index << " name.val = " << name.toString(); + qCDebug(ServersSettingsLog) << "getPlayerName() index = " << index << " name.val = " << name.toString(); return name == QVariant() ? std::move(defaultName) : name.toString(); } diff --git a/cockatrice/src/settings/servers_settings.h b/cockatrice/src/settings/servers_settings.h index ec9ad10fb..9bf5c47d7 100644 --- a/cockatrice/src/settings/servers_settings.h +++ b/cockatrice/src/settings/servers_settings.h @@ -3,10 +3,13 @@ #include "settings_manager.h" +#include #include #define SERVERSETTINGS_DEFAULT_HOST "server.cockatrice.us" #define SERVERSETTINGS_DEFAULT_PORT "4748" +inline Q_LOGGING_CATEGORY(ServersSettingsLog, "servers_settings"); + class ServersSettings : public SettingsManager { Q_OBJECT @@ -61,7 +64,7 @@ public: bool getClearDebugLogStatus(bool abDefaultValue); private: - explicit ServersSettings(QString settingPath, QObject *parent = nullptr); + explicit ServersSettings(const QString &settingPath, QObject *parent = nullptr); ServersSettings(const ServersSettings & /*other*/); }; diff --git a/cockatrice/src/settings/settings_manager.cpp b/cockatrice/src/settings/settings_manager.cpp index 211c8d3b3..dc2da741e 100644 --- a/cockatrice/src/settings/settings_manager.cpp +++ b/cockatrice/src/settings/settings_manager.cpp @@ -1,11 +1,14 @@ #include "settings_manager.h" -SettingsManager::SettingsManager(QString settingPath, QObject *parent) +SettingsManager::SettingsManager(const QString &settingPath, QObject *parent) : QObject(parent), settings(settingPath, QSettings::IniFormat) { } -void SettingsManager::setValue(QVariant value, QString name, QString group, QString subGroup) +void SettingsManager::setValue(const QVariant &value, + const QString &name, + const QString &group, + const QString &subGroup) { if (!group.isEmpty()) { settings.beginGroup(group); @@ -26,7 +29,7 @@ void SettingsManager::setValue(QVariant value, QString name, QString group, QStr } } -void SettingsManager::deleteValue(QString name, QString group, QString subGroup) +void SettingsManager::deleteValue(const QString &name, const QString &group, const QString &subGroup) { if (!group.isEmpty()) { settings.beginGroup(group); @@ -47,7 +50,7 @@ void SettingsManager::deleteValue(QString name, QString group, QString subGroup) } } -QVariant SettingsManager::getValue(QString name, QString group, QString subGroup) +QVariant SettingsManager::getValue(const QString &name, const QString &group, const QString &subGroup) { if (!group.isEmpty()) { settings.beginGroup(group); @@ -69,3 +72,11 @@ QVariant SettingsManager::getValue(QString name, QString group, QString subGroup return value; } + +/** + * Calls sync on the underlying QSettings object + */ +void SettingsManager::sync() +{ + settings.sync(); +} \ No newline at end of file diff --git a/cockatrice/src/settings/settings_manager.h b/cockatrice/src/settings/settings_manager.h index 648e6a0d3..cd1242962 100644 --- a/cockatrice/src/settings/settings_manager.h +++ b/cockatrice/src/settings/settings_manager.h @@ -10,17 +10,14 @@ class SettingsManager : public QObject { Q_OBJECT public: - explicit SettingsManager(QString settingPath, QObject *parent = nullptr); - QVariant getValue(QString name, QString group = "", QString subGroup = ""); - -signals: - -public slots: + explicit SettingsManager(const QString &settingPath, QObject *parent = nullptr); + QVariant getValue(const QString &name, const QString &group = "", const QString &subGroup = ""); + void sync(); protected: QSettings settings; - void setValue(QVariant value, QString name, QString group = "", QString subGroup = ""); - void deleteValue(QString name, QString group = "", QString subGroup = ""); + void setValue(const QVariant &value, const QString &name, const QString &group = "", const QString &subGroup = ""); + void deleteValue(const QString &name, const QString &group = "", const QString &subGroup = ""); }; #endif // SETTINGSMANAGER_H diff --git a/cockatrice/src/settings/shortcut_treeview.cpp b/cockatrice/src/settings/shortcut_treeview.cpp new file mode 100644 index 000000000..02bc08164 --- /dev/null +++ b/cockatrice/src/settings/shortcut_treeview.cpp @@ -0,0 +1,167 @@ +#include "shortcut_treeview.h" + +#include "cache_settings.h" +#include "shortcuts_settings.h" + +#include + +ShortcutFilterProxyModel::ShortcutFilterProxyModel(QObject *parent) : QSortFilterProxyModel(parent) +{ +} + +/** + * Appends the parent and source row together before doing the regex match. + */ +bool ShortcutFilterProxyModel::filterAcceptsRow(const int sourceRow, const QModelIndex &sourceParent) const +{ + QModelIndex nameIndex = sourceModel()->index(sourceRow, filterKeyColumn(), sourceParent); + QModelIndex parentIndex = sourceModel()->index(sourceParent.row(), filterKeyColumn(), sourceParent.parent()); + + QString name = sourceModel()->data(nameIndex).toString(); + QString parentName = sourceModel()->data(parentIndex).toString(); + + QString searchedString = parentName + " " + name; + + return searchedString.contains(filterRegularExpression()); +} + +ShortcutTreeView::ShortcutTreeView(QWidget *parent) : QTreeView(parent) +{ + // model + shortcutsModel = new QStandardItemModel(this); + shortcutsModel->setColumnCount(3); + populateShortcutsModel(); + + // filter proxy + proxyModel = new ShortcutFilterProxyModel(this); + proxyModel->setRecursiveFilteringEnabled(true); + proxyModel->setSourceModel(shortcutsModel); + proxyModel->setDynamicSortFilter(true); + proxyModel->setSortCaseSensitivity(Qt::CaseInsensitive); + proxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive); + proxyModel->setFilterKeyColumn(0); + + QTreeView::setModel(proxyModel); + + // treeview + hideColumn(2); + + setUniformRowHeights(true); + setAlternatingRowColors(true); + + setSortingEnabled(true); + proxyModel->sort(0, Qt::AscendingOrder); + + header()->setSectionResizeMode(QHeaderView::Interactive); + header()->setSortIndicator(0, Qt::AscendingOrder); + setSelectionMode(SingleSelection); + setSelectionBehavior(SelectRows); + + expandAll(); + + connect(&SettingsCache::instance().shortcuts(), &ShortcutsSettings::shortCutChanged, this, + &ShortcutTreeView::refreshShortcuts); +} + +void ShortcutTreeView::populateShortcutsModel() +{ + QHash parentItems; + QStandardItem *curParent = nullptr; + for (const auto &key : SettingsCache::instance().shortcuts().getAllShortcutKeys()) { + QString name = SettingsCache::instance().shortcuts().getShortcut(key).getName(); + QString group = SettingsCache::instance().shortcuts().getShortcut(key).getGroupName(); + QString shortcut = SettingsCache::instance().shortcuts().getShortcutString(key); + + if (parentItems.contains(group)) { + curParent = parentItems.value(group); + } else { + curParent = new QStandardItem(group); + static QFont font = curParent->font(); + font.setBold(true); + curParent->setFont(font); + parentItems.insert(group, curParent); + } + + QList list = {}; + list << new QStandardItem(name) << new QStandardItem(shortcut) << new QStandardItem(key); + curParent->appendRow(list); + } + + for (const auto &parent : parentItems) { + shortcutsModel->appendRow(parent); + } +} + +void ShortcutTreeView::retranslateUi() +{ + shortcutsModel->setHeaderData(0, Qt::Horizontal, tr("Action")); + shortcutsModel->setHeaderData(1, Qt::Horizontal, tr("Shortcut")); + refreshShortcuts(); +} + +/** + * Loops over the model and reloads all rows + */ +static void loopOverModel(QAbstractItemModel *model, const QModelIndex &parent = QModelIndex()) +{ + for (int r = 0; r < model->rowCount(parent); ++r) { + const auto friendlyNameIndex = model->index(r, 0, parent); + + if (model->hasChildren(friendlyNameIndex)) { + const auto childIndex = model->index(0, 2, friendlyNameIndex); + const auto key = model->data(childIndex).toString(); + const auto shortcutGroupName = SettingsCache::instance().shortcuts().getShortcut(key).getGroupName(); + model->setData(friendlyNameIndex, shortcutGroupName); + + loopOverModel(model, friendlyNameIndex); + } else { + const auto shortcutSequenceIndex = model->index(r, 1, parent); + const auto keyIndex = model->index(r, 2, parent); + const auto key = model->data(keyIndex).toString(); + + const auto shortcutKey = SettingsCache::instance().shortcuts().getShortcut(key).getName(); + const auto shortcutSequence = SettingsCache::instance().shortcuts().getShortcutString(key); + model->setData(friendlyNameIndex, shortcutKey); + model->setData(shortcutSequenceIndex, shortcutSequence); + } + } +} + +void ShortcutTreeView::refreshShortcuts() +{ + loopOverModel(shortcutsModel); +} + +void ShortcutTreeView::currentChanged(const QModelIndex ¤t, const QModelIndex & /* previous */) +{ + QTreeView::scrollTo(current, QAbstractItemView::EnsureVisible); + if (current.parent().isValid()) { + auto shortcutName = model()->data(model()->index(current.row(), 2, current.parent())).toString(); + emit currentItemChanged(shortcutName); + } else { + // emit empty string if the selection is a category header + emit currentItemChanged(""); + } +} + +/** + * The search string is split by word. + * A String is a match as long as it contains all the words in the search string in order + */ +void ShortcutTreeView::updateSearchString(const QString &searchString) +{ +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + const auto skipEmptyParts = Qt::SkipEmptyParts; +#else + const auto skipEmptyParts = QString::SkipEmptyParts; +#endif + QStringList searchWords = searchString.split(" ", skipEmptyParts); + + auto escapeRegex = [](const QString &s) { return QRegularExpression::escape(s); }; + std::transform(searchWords.begin(), searchWords.end(), searchWords.begin(), escapeRegex); + + auto regex = QRegularExpression(searchWords.join(".*"), QRegularExpression::CaseInsensitiveOption); + + proxyModel->setFilterRegularExpression(regex); + expandAll(); +} diff --git a/cockatrice/src/settings/shortcut_treeview.h b/cockatrice/src/settings/shortcut_treeview.h new file mode 100644 index 000000000..dc96d0360 --- /dev/null +++ b/cockatrice/src/settings/shortcut_treeview.h @@ -0,0 +1,47 @@ +#ifndef SHORTCUT_TREEVIEW_H +#define SHORTCUT_TREEVIEW_H + +#include +#include +#include +#include + +/** + * Custom implementation of QSortFilterProxyModel that appends the source and parent strings together when filtering + */ +class ShortcutFilterProxyModel : public QSortFilterProxyModel +{ + Q_OBJECT +public: + explicit ShortcutFilterProxyModel(QObject *parent = nullptr); + +protected: + bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override; +}; + +class ShortcutTreeView : public QTreeView +{ + Q_OBJECT +public: + explicit ShortcutTreeView(QWidget *parent); + void retranslateUi(); + +signals: + void currentItemChanged(const QString &shortcut); + +public slots: + void updateSearchString(const QString &searchString); + +private: + QStandardItemModel *shortcutsModel; + ShortcutFilterProxyModel *proxyModel; + void populateShortcutsModel(); + +private slots: + void refreshShortcuts(); + +protected: + void currentChanged(const QModelIndex ¤t, const QModelIndex &previous) override; +}; + +#endif // SHORTCUT_TREEVIEW_H diff --git a/cockatrice/src/settings/shortcuts_settings.cpp b/cockatrice/src/settings/shortcuts_settings.cpp index 7479b7bb4..db5559d8f 100644 --- a/cockatrice/src/settings/shortcuts_settings.cpp +++ b/cockatrice/src/settings/shortcuts_settings.cpp @@ -28,6 +28,13 @@ ShortcutsSettings::ShortcutsSettings(const QString &settingsPath, QObject *paren QMap invalidItems; for (QStringList::const_iterator it = customKeys.constBegin(); it != customKeys.constEnd(); ++it) { QString stringSequence = shortCutsFile.value(*it).toString(); + + // check whether shortcut name exists + if (!shortCuts.contains(*it)) { + qCWarning(ShortcutsSettingsLog) << "Unknown shortcut name:" << *it; + continue; + } + // check whether shortcut is forbidden if (isKeyAllowed(*it, stringSequence)) { auto shortcut = getShortcut(*it); @@ -67,7 +74,7 @@ void ShortcutsSettings::migrateShortcuts() shortCutsFile.beginGroup(custom); if (shortCutsFile.contains("Textbox/unfocusTextBox")) { - qDebug() + qCInfo(ShortcutsSettingsLog) << "[ShortcutsSettings] Textbox/unfocusTextBox shortcut found. Migrating to Player/unfocusTextBox."; QString unfocusTextBox = shortCutsFile.value("Textbox/unfocusTextBox", "").toString(); this->setShortcuts("Player/unfocusTextBox", unfocusTextBox); @@ -75,12 +82,21 @@ void ShortcutsSettings::migrateShortcuts() } if (shortCutsFile.contains("tab_game/aFocusChat")) { - qDebug() << "[ShortcutsSettings] tab_game/aFocusChat shortcut found. Migrating to Player/aFocusChat."; + qCInfo(ShortcutsSettingsLog) + << "[ShortcutsSettings] tab_game/aFocusChat shortcut found. Migrating to Player/aFocusChat."; QString aFocusChat = shortCutsFile.value("tab_game/aFocusChat", "").toString(); this->setShortcuts("Player/aFocusChat", aFocusChat); shortCutsFile.remove("tab_game/aFocusChat"); } + // PR #5564 changes "MainWindow/aDeckEditor" to "Tabs/aTabDeckEditor" + if (shortCutsFile.contains("MainWindow/aDeckEditor")) { + qCInfo(ShortcutsSettingsLog) << "MainWindow/aDeckEditor shortcut found. Migrating to Tabs/aTabDeckEditor."; + QString keySequence = shortCutsFile.value("MainWindow/aDeckEditor", "").toString(); + this->setShortcuts("Tabs/aTabDeckEditor", keySequence); + shortCutsFile.remove("MainWindow/aDeckEditor"); + } + shortCutsFile.endGroup(); } } @@ -114,6 +130,17 @@ QString ShortcutsSettings::getShortcutString(const QString &name) const return stringifySequence(getShortcut(name)); } +QString ShortcutsSettings::getShortcutFriendlyName(const QString &shortcutName) const +{ + for (auto it = defaultShortCuts.cbegin(); it != defaultShortCuts.cend(); ++it) { + if (shortcutName == it.key()) { + return it.value().getName(); + } + } + + return {}; +} + QString ShortcutsSettings::stringifySequence(const QList &Sequence) const { QStringList stringSequence; @@ -150,9 +177,9 @@ void ShortcutsSettings::setShortcuts(const QString &name, const QKeySequence &Se setShortcuts(name, QList{Sequence}); } -void ShortcutsSettings::setShortcuts(const QString &name, const QString &Sequences) +void ShortcutsSettings::setShortcuts(const QString &name, const QString &sequences) { - setShortcuts(name, parseSequenceString(Sequences)); + setShortcuts(name, parseSequenceString(sequences)); } void ShortcutsSettings::resetAllShortcuts() @@ -177,33 +204,53 @@ void ShortcutsSettings::clearAllShortcuts() emit shortCutChanged(); } -bool ShortcutsSettings::isKeyAllowed(const QString &name, const QString &Sequences) const +bool ShortcutsSettings::isKeyAllowed(const QString &name, const QString &sequences) const { // if the shortcut is not to be used in deck-editor then it doesn't matter if (name.startsWith("Player") || name.startsWith("Replays")) { return true; } - QString checkSequence = Sequences.split(sep).last(); + QString checkSequence = sequences.split(sep).last(); QStringList forbiddenKeys{"Del", "Backspace", "Down", "Up", "Left", "Right", "Return", "Enter", "Menu", "Ctrl+Alt+-", "Ctrl+Alt+=", "Ctrl+Alt+[", "Ctrl+Alt+]", "Tab", "Space", "Shift+S", "Shift+Left", "Shift+Right"}; return !forbiddenKeys.contains(checkSequence); } -bool ShortcutsSettings::isValid(const QString &name, const QString &Sequences) const +/** + * Checks that the shortcut doesn't overlap with an existing shortcut + * + * @param name The name of the shortcut + * @param sequences The shortcut key sequence + * @return Whether the shortcut is valid. + */ +bool ShortcutsSettings::isValid(const QString &name, const QString &sequences) const { - QString checkSequence = Sequences.split(sep).last(); + return findOverlaps(name, sequences).isEmpty(); +} + +/** + * Checks if the shortcut is a shortcut that is active in all windows + */ +static bool isAlwaysActiveShortcut(const QString &shortcutName) +{ + return shortcutName.startsWith("MainWindow") || shortcutName.startsWith("Tabs"); +} + +QStringList ShortcutsSettings::findOverlaps(const QString &name, const QString &sequences) const +{ + QString checkSequence = sequences.split(sep).last(); QString checkKey = name.left(name.indexOf("/")); - QList allKeys = shortCuts.keys(); - for (const auto &key : allKeys) { - if (key.startsWith(checkKey) || key.startsWith("MainWindow") || checkKey.startsWith("MainWindow")) { + QStringList overlaps; + for (const auto &key : shortCuts.keys()) { + if (key.startsWith(checkKey) || isAlwaysActiveShortcut(key) || isAlwaysActiveShortcut(checkKey)) { QString storedSequence = stringifySequence(shortCuts.value(key)); - QStringList stringSequences = storedSequence.split(sep); - if (stringSequences.contains(checkSequence)) { - return false; + if (storedSequence.split(sep).contains(checkSequence)) { + overlaps.append(getShortcutFriendlyName(key)); } } } - return true; -} + + return overlaps; +} \ No newline at end of file diff --git a/cockatrice/src/settings/shortcuts_settings.h b/cockatrice/src/settings/shortcuts_settings.h index d4e40c2b3..595932623 100644 --- a/cockatrice/src/settings/shortcuts_settings.h +++ b/cockatrice/src/settings/shortcuts_settings.h @@ -2,11 +2,12 @@ #define SHORTCUTSSETTINGS_H #include -#include #include -#include +#include #include +inline Q_LOGGING_CATEGORY(ShortcutsSettingsLog, "shortcuts_settings"); + class ShortcutGroup { public: @@ -29,7 +30,8 @@ public: Chat_room, Game_window, Load_deck, - Replays + Replays, + Tabs }; static QString getGroupName(ShortcutGroup::Groups group) @@ -71,6 +73,8 @@ public: return QApplication::translate("shortcutsTab", "Load Deck from Clipboard"); case Replays: return QApplication::translate("shortcutsTab", "Replays"); + case Tabs: + return QApplication::translate("shortcutsTab", "Tabs"); } return {}; @@ -80,18 +84,18 @@ public: class ShortcutKey : public QList { public: - ShortcutKey(const QString &_name = QString(), - QList _sequence = QList(), - ShortcutGroup::Groups _group = ShortcutGroup::Main_Window); - void setSequence(QList _sequence) + explicit ShortcutKey(const QString &_name = QString(), + QList _sequence = QList(), + ShortcutGroup::Groups _group = ShortcutGroup::Main_Window); + void setSequence(const QList &_sequence) { - QList::operator=(_sequence); + QList::operator=(_sequence); }; - const QString getName() const + QString getName() const { return QApplication::translate("shortcutsTab", name.toUtf8().data()); }; - const QString getGroupName() const + QString getGroupName() const { return ShortcutGroup::getGroupName(group); }; @@ -105,13 +109,14 @@ class ShortcutsSettings : public QObject { Q_OBJECT public: - ShortcutsSettings(const QString &settingsFilePath, QObject *parent = nullptr); + explicit ShortcutsSettings(const QString &settingsFilePath, QObject *parent = nullptr); ShortcutKey getDefaultShortcut(const QString &name) const; ShortcutKey getShortcut(const QString &name) const; QKeySequence getSingleShortcut(const QString &name) const; QString getDefaultShortcutString(const QString &name) const; QString getShortcutString(const QString &name) const; + QString getShortcutFriendlyName(const QString &shortcutName) const; QList getAllShortcutKeys() const { return shortCuts.keys(); @@ -119,10 +124,11 @@ public: void setShortcuts(const QString &name, const QList &Sequence); void setShortcuts(const QString &name, const QKeySequence &Sequence); - void setShortcuts(const QString &name, const QString &Sequences); + void setShortcuts(const QString &name, const QString &sequences); - bool isKeyAllowed(const QString &name, const QString &Sequences) const; - bool isValid(const QString &name, const QString &Sequences) const; + bool isKeyAllowed(const QString &name, const QString &sequences) const; + bool isValid(const QString &name, const QString &sequences) const; + QStringList findOverlaps(const QString &name, const QString &sequences) const; void resetAllShortcuts(); void clearAllShortcuts(); @@ -147,9 +153,6 @@ private: {"MainWindow/aConnect", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Connect..."), parseSequenceString("Ctrl+L"), ShortcutGroup::Main_Window)}, - {"MainWindow/aDeckEditor", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Deck Editor"), - parseSequenceString(""), - ShortcutGroup::Main_Window)}, {"MainWindow/aDisconnect", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Disconnect"), parseSequenceString(""), ShortcutGroup::Main_Window)}, @@ -170,9 +173,13 @@ private: {"MainWindow/aWatchReplay", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Watch Replay..."), parseSequenceString(""), ShortcutGroup::Main_Window)}, - {"TabDeckEditor/aAnalyzeDeck", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Analyze Deck"), + {"TabDeckEditor/aAnalyzeDeck", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Analyze Deck (deckstats.net)"), parseSequenceString(""), ShortcutGroup::Deck_Editor)}, + {"TabDeckEditor/aAnalyzeDeckTappedout", + ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Analyze Deck (tappedout.net)"), + parseSequenceString(""), + ShortcutGroup::Deck_Editor)}, {"TabDeckEditor/aClearFilterAll", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Clear All Filters"), parseSequenceString(""), ShortcutGroup::Deck_Editor)}, @@ -190,9 +197,14 @@ private: {"TabDeckEditor/aEditTokens", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Edit Custom Tokens..."), parseSequenceString(""), ShortcutGroup::Deck_Editor)}, - {"TabDeckEditor/aExportDeckDecklist", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Export Deck"), - parseSequenceString(""), - ShortcutGroup::Deck_Editor)}, + {"TabDeckEditor/aExportDeckDecklist", + ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Export Deck (decklist.org)"), + parseSequenceString(""), + ShortcutGroup::Deck_Editor)}, + {"TabDeckEditor/aExportDeckDecklistXyz", + ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Export Deck (decklist.xyz)"), + parseSequenceString(""), + ShortcutGroup::Deck_Editor)}, {"TabDeckEditor/aIncrement", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Add Card"), parseSequenceString("+"), ShortcutGroup::Deck_Editor)}, @@ -203,6 +215,14 @@ private: ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Load Deck from Clipboard..."), parseSequenceString("Ctrl+Shift+V"), ShortcutGroup::Deck_Editor)}, + {"TabDeckEditor/aEditDeckInClipboard", + ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Edit Deck in Clipboard, Annotated"), + parseSequenceString(""), + ShortcutGroup::Deck_Editor)}, + {"TabDeckEditor/aEditDeckInClipboardRaw", + ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Edit Deck in Clipboard"), + parseSequenceString(""), + ShortcutGroup::Deck_Editor)}, {"TabDeckEditor/aNewDeck", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "New Deck"), parseSequenceString("Ctrl+N"), ShortcutGroup::Deck_Editor)}, @@ -229,10 +249,18 @@ private: ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Save Deck to Clipboard, Annotated"), parseSequenceString("Ctrl+Shift+C"), ShortcutGroup::Deck_Editor)}, + {"TabDeckEditor/aSaveDeckToClipboardNoSetInfo", + ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Save Deck to Clipboard, Annotated (No Set Info)"), + parseSequenceString(""), + ShortcutGroup::Deck_Editor)}, {"TabDeckEditor/aSaveDeckToClipboardRaw", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Save Deck to Clipboard"), parseSequenceString("Ctrl+Shift+R"), ShortcutGroup::Deck_Editor)}, + {"TabDeckEditor/aSaveDeckToClipboardRawNoSetInfo", + ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Save Deck to Clipboard (No Set Info)"), + parseSequenceString(""), + ShortcutGroup::Deck_Editor)}, {"DeckViewContainer/loadLocalButton", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Load Local Deck..."), parseSequenceString("Ctrl+O"), ShortcutGroup::Game_Lobby)}, @@ -411,6 +439,9 @@ private: {"Player/aNextTurn", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Next Turn"), parseSequenceString("Ctrl+Return;Ctrl+Enter"), ShortcutGroup::Game_Phases)}, + {"Player/aHide", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Hide Card in Reveal Window"), + parseSequenceString("Alt+H"), + ShortcutGroup::Playing_Area)}, {"Player/aTap", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Tap / Untap Card"), parseSequenceString(""), ShortcutGroup::Playing_Area)}, @@ -450,6 +481,15 @@ private: {"Player/aSetAnnotation", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Set Annotation..."), parseSequenceString("Alt+N"), ShortcutGroup::Playing_Area)}, + {"Player/aSelectAll", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Select All Cards in Zone"), + parseSequenceString("Ctrl+A"), + ShortcutGroup::Playing_Area)}, + {"Player/aSelectRow", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Select All Cards in Row"), + parseSequenceString("Ctrl+Shift+X"), + ShortcutGroup::Playing_Area)}, + {"Player/aSelectColumn", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Select All Cards in Column"), + parseSequenceString("Ctrl+Shift+C"), + ShortcutGroup::Playing_Area)}, {"Player/aMoveToBottomLibrary", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Bottom of Library"), parseSequenceString("Ctrl+B"), ShortcutGroup::Move_selected)}, @@ -484,6 +524,9 @@ private: {"Player/aViewTopCards", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Top Cards of Library"), parseSequenceString("Ctrl+W"), ShortcutGroup::View)}, + {"Player/aViewBottomCards", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Bottom Cards of Library"), + parseSequenceString("Ctrl+Shift+W"), + ShortcutGroup::View)}, {"Player/aCloseMostRecentZoneView", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Close Recent View"), parseSequenceString("Esc"), ShortcutGroup::View)}, @@ -536,7 +579,7 @@ private: parseSequenceString(""), ShortcutGroup::Move_bottom)}, {"Player/aDrawArrow", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Draw Arrow..."), - parseSequenceString("Ctrl+A"), + parseSequenceString("Alt+A"), ShortcutGroup::Gameplay)}, {"Player/aRemoveLocalArrows", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Remove Local Arrows"), parseSequenceString("Ctrl+R"), @@ -552,6 +595,12 @@ private: {"Player/aShuffle", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Shuffle Library"), parseSequenceString("Ctrl+S"), ShortcutGroup::Gameplay)}, + {"Player/aShuffleTopCards", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Shuffle Top Cards of Library"), + parseSequenceString(""), + ShortcutGroup::Gameplay)}, + {"Player/aShuffleBottomCards", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Shuffle Bottom Cards of Library"), + parseSequenceString(""), + ShortcutGroup::Gameplay)}, {"Player/aMulligan", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Mulligan"), parseSequenceString("Ctrl+M"), ShortcutGroup::Drawing)}, @@ -608,7 +657,26 @@ private: ShortcutGroup::Replays)}, {"Replays/fastForwardButton", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Toggle Fast Forward"), parseSequenceString("Ctrl+P"), - ShortcutGroup::Replays)}}; + ShortcutGroup::Replays)}, + {"Tabs/aTabDeckEditor", + ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Deck Editor"), parseSequenceString(""), ShortcutGroup::Tabs)}, + {"Tabs/aTabVisualDeckStorage", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Visual Deck Storage"), + parseSequenceString(""), + ShortcutGroup::Tabs)}, + {"Tabs/aTabDeckStorage", + ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Deck Storage"), parseSequenceString(""), ShortcutGroup::Tabs)}, + {"Tabs/aTabReplays", + ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Replays"), parseSequenceString(""), ShortcutGroup::Tabs)}, + {"Tabs/aTabServer", + ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Server"), parseSequenceString(""), ShortcutGroup::Tabs)}, + {"Tabs/aTabAccount", + ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Account"), parseSequenceString(""), ShortcutGroup::Tabs)}, + {"Tabs/aTabAdmin", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Administration"), + parseSequenceString(""), + ShortcutGroup::Tabs)}, + {"Tabs/aTabLogs", + ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Logs"), parseSequenceString(""), ShortcutGroup::Tabs)}, + }; }; #endif // SHORTCUTSSETTINGS_H diff --git a/cockatrice/src/utility/card_set_comparator.h b/cockatrice/src/utility/card_set_comparator.h index 1aa1500fb..107b618fb 100644 --- a/cockatrice/src/utility/card_set_comparator.h +++ b/cockatrice/src/utility/card_set_comparator.h @@ -21,4 +21,40 @@ public: } }; +class SetReleaseDateComparator +{ +public: + /* + * Returns true if a has higher download priority than b + * Enabled sets have priority over disabled sets + * Both groups follow the user-defined order + */ + inline bool operator()(const CardSetPtr &a, const CardSetPtr &b) const + { + if (a->getEnabled()) { + return !b->getEnabled() || a->getReleaseDate() < b->getReleaseDate(); + } else { + return !b->getEnabled() && a->getReleaseDate() < b->getReleaseDate(); + } + } +}; + +class CardSetPriorityComparator +{ +public: + /* + * Returns true if a has higher download priority than b + * Enabled sets have priority over disabled sets + * Both groups follow the user-defined order + */ + inline bool operator()(const CardInfoPerSet &a, const CardInfoPerSet &b) const + { + if (a.getPtr()->getEnabled()) { + return !b.getPtr()->getEnabled() || a.getPtr()->getSortKey() < b.getPtr()->getSortKey(); + } else { + return !b.getPtr()->getEnabled() && a.getPtr()->getSortKey() < b.getPtr()->getSortKey(); + } + } +}; + #endif // SET_PRIORITY_COMPARATOR_H \ No newline at end of file diff --git a/cockatrice/src/utility/sequence_edit.cpp b/cockatrice/src/utility/sequence_edit.cpp index 183e2096e..c5f0fd1c1 100644 --- a/cockatrice/src/utility/sequence_edit.cpp +++ b/cockatrice/src/utility/sequence_edit.cpp @@ -24,8 +24,8 @@ SequenceEdit::SequenceEdit(const QString &_shortcutName, QWidget *parent) : QWid layout->addWidget(clearButton); layout->addWidget(defaultButton); - connect(clearButton, SIGNAL(clicked()), this, SLOT(removeLastShortcut())); - connect(defaultButton, SIGNAL(clicked()), this, SLOT(restoreDefault())); + connect(clearButton, &QPushButton::clicked, this, &SequenceEdit::removeLastShortcut); + connect(defaultButton, &QPushButton::clicked, this, &SequenceEdit::restoreDefault); lineEdit->installEventFilter(this); setShortcutName(_shortcutName); @@ -40,12 +40,14 @@ void SequenceEdit::setShortcutName(const QString &_shortcutName) defaultButton->setEnabled(false); lineEdit->setEnabled(false); lineEdit->setText(""); + // Correct as in-line translation lineEdit->setPlaceholderText(tr("Choose an action from the table")); } else { clearButton->setEnabled(true); defaultButton->setEnabled(true); lineEdit->setEnabled(true); lineEdit->setText(SettingsCache::instance().shortcuts().getShortcutString(shortcutName)); + // Correct as in-line translation lineEdit->setPlaceholderText(tr("Hit the key/combination of keys you want to set for this action")); } } @@ -154,26 +156,49 @@ int SequenceEdit::translateModifiers(Qt::KeyboardModifiers state, const QString return result; } +/** + *Validates that shortcut is valid (is a valid shortcut key sequence and doesn't conflict with any other shortcuts). + *Displays warning messages if it's not valid. + * + * @param sequence The shortcut key sequence + * @return True if the sequence isn't already self-contained + */ +bool SequenceEdit::validateShortcut(const QKeySequence &sequence) +{ + if (sequence.isEmpty() || !valid) { + return true; + } + + const auto &shortcutsSettings = SettingsCache::instance().shortcuts(); + const QString sequenceString = sequence.toString(); + + if (!shortcutsSettings.isKeyAllowed(shortcutName, sequenceString)) { + QToolTip::showText(lineEdit->mapToGlobal(QPoint()), tr("Invalid key")); + return true; + } + + if (!shortcutsSettings.isValid(shortcutName, sequenceString)) { + auto overlaps = shortcutsSettings.findOverlaps(shortcutName, sequenceString); + QToolTip::showText(lineEdit->mapToGlobal(QPoint()), + tr("Shortcut already in use by:") + " " + overlaps.join(", ")); + return true; + } + + if (!lineEdit->text().isEmpty()) { + if (lineEdit->text().contains(sequenceString)) { + return false; + } + lineEdit->setText(lineEdit->text() + ";"); + } + lineEdit->setText(lineEdit->text() + sequenceString); + + return true; +} + void SequenceEdit::finishShortcut() { - QKeySequence sequence(keys); - if (!sequence.isEmpty() && valid) { - QString sequenceString = sequence.toString(); - if (SettingsCache::instance().shortcuts().isKeyAllowed(shortcutName, sequenceString)) { - if (SettingsCache::instance().shortcuts().isValid(shortcutName, sequenceString)) { - if (!lineEdit->text().isEmpty()) { - if (lineEdit->text().contains(sequenceString)) { - return; - } - lineEdit->setText(lineEdit->text() + ";"); - } - lineEdit->setText(lineEdit->text() + sequenceString); - } else { - QToolTip::showText(lineEdit->mapToGlobal(QPoint()), tr("Shortcut already in use")); - } - } else { - QToolTip::showText(lineEdit->mapToGlobal(QPoint()), tr("Invalid key")); - } + if (!validateShortcut(QKeySequence(keys))) { + return; } currentKey = 0; diff --git a/cockatrice/src/utility/sequence_edit.h b/cockatrice/src/utility/sequence_edit.h index 6bd490abd..e2ce6845e 100644 --- a/cockatrice/src/utility/sequence_edit.h +++ b/cockatrice/src/utility/sequence_edit.h @@ -11,7 +11,7 @@ class SequenceEdit : public QWidget { Q_OBJECT public: - SequenceEdit(const QString &_shortcutName, QWidget *parent = nullptr); + explicit SequenceEdit(const QString &_shortcutName, QWidget *parent = nullptr); QString getSequence(); void setShortcutName(const QString &_shortcutName); void refreshShortcut(); @@ -23,7 +23,7 @@ private slots: void restoreDefault(); protected: - bool eventFilter(QObject *, QEvent *event); + bool eventFilter(QObject *, QEvent *event) override; private: QString shortcutName; @@ -36,6 +36,7 @@ private: void processKey(QKeyEvent *e); int translateModifiers(Qt::KeyboardModifiers state, const QString &text); + bool validateShortcut(const QKeySequence &sequence); void finishShortcut(); void updateSettings(); }; diff --git a/cockatrice/translations/cockatrice_cs.ts b/cockatrice/translations/cockatrice_cs.ts index d05b21e9d..f020d125b 100644 --- a/cockatrice/translations/cockatrice_cs.ts +++ b/cockatrice/translations/cockatrice_cs.ts @@ -2,7 +2,7 @@ AbstractCounter - + &Set counter... &Nastavit žeton... @@ -10,95 +10,247 @@ AbstractCounterDialog - + Set counter - + New value for counter '%1': Nová hodnota pro žeton '%1': + + AbstractDlgDeckTextEdit + + + &Refresh + + + + + Parse Set Name and Number (if available) + + + + + AbstractTabDeckEditor + + + Open in new tab + + + + + Are you sure? + + + + + The decklist has been modified. +Do you want to save the changes? + + + + + + + + + + + Error + + + + + Could not open deck at %1 + + + + + Could not save remote deck + + + + + + The deck could not be saved. +Please check that the directory is writable and try again. + + + + + Save deck + + + + + The deck could not be saved. + + + + + There are no cards in your deck to be exported + + + + + No deck was selected to be exported. + + + + + AdminNotesDialog + + + Update Notes + + + + + Admin Notes for %1 + + + + + AllZonesCardAmountWidget + + + Mainboard + + + + + Sideboard + + + AppearanceSettingsPage - + Error Chyba - + Could not create themes directory at '%1'. - + Theme settings Nastavení vzhledu - + Current theme: Aktuální vzhled - + Open themes folder - + + Menu settings + + + + + Show keyboard shortcuts in right-click menus + + + + Card rendering Vykreslování karet - + Display card names on cards having a picture Zobrazit jména karet na kartách s obrázky - + + Auto-Rotate cards with sideways layout + + + + + Override all card art with personal set preference (Pre-ProviderID change behavior) [Requires Client restart] + + + + + Bump sets that the deck contains cards from to the top in the printing selector + + + + Scale cards on mouse over Zvětšovat karty při najetí myši - + + Use rounded card corners + + + + Minimum overlap percentage of cards on the stack and in vertical hand - + + Maximum initial height for card view window: + + + + + + rows + + + + + Maximum expanded height for card view window: + + + + Hand layout Rozvržení ruky - + Display hand horizontally (wastes space) Zobrazit ruku horizontálně (plýtvá místem) - + Enable left justification Zapnout - + Table grid layout Rozložení herní mřížky - + Invert vertical coordinate Převrátit vertikální souřadnice - + Minimum player count for multi-column layout: Minimální počet hráčů pro víceřádkové rozvržení: - + Maximum font size for information displayed on cards: Maximální velikost písma pro informace na kartách @@ -106,112 +258,112 @@ BanDialog - + ban &user name Blokovat & Uživatelské jméno - + ban &IP address Blokovat & IP Adresu - + ban client I&D Banovat I&D klienta - + Ban type Typ banu - + &permanent ban &trvalý ban - + &temporary ban &dočasný ban - + &Days: &Dní: - + &Hours: &Hodin: - + &Minutes: &Minut: - + Duration of the ban Trvání banu - + Please enter the reason for the ban. This is only saved for moderators and cannot be seen by the banned person. Uveďte prosím důvod pro ban. Toto je uloženo pouze pro moderátory a banovaná osoba to neuvidí. - + Please enter the reason for the ban that will be visible to the banned person. Uveďte prosám důvod pro ban, který uvidí banovaná osoba. - + Redact all messages from this user in all rooms - + &OK &OK - + &Cancel &Zrušit - + Ban user from server Zabanovat uživatele ze serveru - - - - + + + + Error Chyba - + You have to select a name-based, IP-based, clientId based, or some combination of the three to place a ban. - + You must have a value in the name ban when selecting the name ban checkbox. - + You must have a value in the ip ban when selecting the ip ban checkbox. - + You must have a value in the clientid ban when selecting the clientid ban checkbox. @@ -219,22 +371,22 @@ Toto je uloženo pouze pro moderátory a banovaná osoba to neuvidí. BetaReleaseChannel - - Beta Releases - Beta vydání + + Beta + - + No reply received from the release update server. - + Invalid reply received from the release update server. - + No reply received from the file update server. @@ -242,32 +394,32 @@ Toto je uloženo pouze pro moderátory a banovaná osoba to neuvidí. CardDatabaseModel - + Name Jméno - + Sets Edice - + Mana cost Sesílací cena - + Card type Typ karty - + P/T S/O - + Color(s) Barva(y) @@ -360,325 +512,621 @@ Toto je uloženo pouze pro moderátory a banovaná osoba to neuvidí. - CardFrame + CardInfoFrameWidget - + Image - Obrázek + - + Description - Popis + - + Both - Obojí + + + + + View transformation + - CardInfoText + CardInfoPictureWidget - + + View related cards + + + + + Add card to deck + + + + + Mainboard + + + + + Sideboard + + + + + CardInfoTextWidget + + Name: - Jméno: + - + Related cards: - Související karty: + - + Unknown card: - Neznámá karta + CardItem - + &Move to &Přesunout - + &Power / toughness &Síla / odolnost + + CardSizeWidget + + + Card Size + + + CardZone - + their hand nominative jejich ruka - + %1's hand nominative %1ova/ina ruka - + their library look at zone jejich knihovnu - + %1's library look at zone %1ovu/inu knihovnu - + of their library top cards of zone, jejich knihovny - + of %1's library top cards of zone %1ovy/iny knihovny - + their library reveal zone jejich knihovnu - - - %1's library - reveal zone - %1ovu/inu knihovnu - - - - their library - shuffle - jejich knihovnu - - - - %1's library - shuffle - %1ovu/inu knihovnu - + %1's library + reveal zone + %1ovu/inu knihovnu + + + + their library + shuffle + jejich knihovnu + + + + %1's library + shuffle + %1ovu/inu knihovnu + + + their library nominative jejich knihovna - + %1's library nominative %1ova/ina knihovna - + their graveyard nominative jejich hřbitov - + %1's graveyard nominative %1ův/in hřbitov - + their exile nominative jejich exil - + %1's exile nominative %1ův/in exil - + their sideboard look at zone jejich sideboard - + %1's sideboard look at zone %1ův/in sideboard - + their sideboard nominative jejich sideboard - + %1's sideboard nominative %1ův/in sideboard + + DeckEditorCardInfoDockWidget + + + Card Info + + + + + DeckEditorDatabaseDisplayWidget + + + Search by card name (or search expressions) + + + + + Add to Deck + + + + + Add to Sideboard + + + + + Select Printing + + + + + Show on EDHREC (Commander) + + + + + Show on EDHREC (Card) + + + + + Show Related cards + + + + + Add card to &maindeck + + + + + Add card to &sideboard + + + + + DeckEditorDeckDockWidget + + + Banner Card + + + + + Select Printing + + + + + Deck + + + + + Deck &name: + + + + + &Comments: + + + + + Hash: + + + + + &Increment number + + + + + &Decrement number + + + + + &Remove row + + + + + Swap card to/from sideboard + + + + + DeckEditorFilterDockWidget + + + Filters + + + + + &Clear all filters + + + + + Delete selected + + + + + DeckEditorMenu + + + &Deck Editor + + + + + &New deck + + + + + &Load deck... + + + + + Load recent deck... + + + + + Clear + + + + + &Save deck + + + + + Save deck &as... + + + + + Load deck from cl&ipboard... + + + + + Edit deck in clipboard + + + + + + Annotated + + + + + + Not Annotated + + + + + Save deck to clipboard + + + + + Annotated (No set info) + + + + + Not Annotated (No set info) + + + + + &Print deck... + + + + + &Send deck to online service + + + + + Create decklist (decklist.org) + + + + + Create decklist (decklist.xyz) + + + + + Analyze deck (deckstats.net) + + + + + Analyze deck (tappedout.net) + + + + + &Close + + + + + DeckEditorPrintingSelectorDockWidget + + + Printing Selector + + + DeckEditorSettingsPage - - + + Update Spoilers Aktualizovat spoilery - - + + Success Úspěch - + Download URLs have been reset. - + Downloaded card pictures have been reset. Stáhnuté obrázky karet byly resetovány. - + Error Chyba - + One or more downloaded card pictures could not be cleared. Některé stáhnuté obrázky karet nemohli být vymazány. - + Add URL - - + + URL: URL: - - + + Edit URL - + + Network Cache Size: + + + + + Redirect Cache TTL: + + + + + How long cached redirects for urls are valid for. + + + + + Picture Cache Size: + + + + Add New URL - + Remove URL - + + Day(s) + + + + Updating... Aktualizuji... - + Choose path Vyberte cestu - + URL Download Priority - + Spoilers Spoilery - + Download Spoilers Automatically Stahovat spoilery automaticky - + Spoiler Location: - + Last Change Poslední změna - + Spoilers download automatically on launch Automaticky stahovat spoilery při spuštění - + Press the button to manually update without relaunching Manuálně aktualizovat bez restartování - + Do not close settings until manual update is complete - + Download card pictures on the fly - + How to add a custom URL - + Delete Downloaded Images Smazat stáhnuté obrázky - + Reset Download URLs - - Downloaded images directory size: - - - - + On-disk cache for downloaded pictures - - Picture cache size: - - - - + In-memory cache for pictures not currently on screen @@ -686,16 +1134,218 @@ Toto je uloženo pouze pro moderátory a banovaná osoba to neuvidí. DeckListModel - + + Count + + + + + Set + + + + Number Počet - + + Provider ID + + + + Card Karta + + DeckLoader + + + Common deck formats (%1) + + + + + All files (*.*) + + + + + DeckPreviewColorIdentityFilterWidget + + + Mode: Exact Match + + + + + Mode: Includes + + + + + DeckPreviewDeckTagsDisplayWidget + + + Edit tags ... + + + + + DeckPreviewTagDialog + + + Deck Tags Manager + + + + + Manage your deck tags. Check or uncheck tags as needed, or add new ones: + + + + + Add a new tag (e.g., Aggro️) + + + + + Add Tag + + + + + Filter tags... + + + + + OK + + + + + Cancel + + + + + Invalid Input + + + + + Tag name cannot be empty! + + + + + Duplicate Tag + + + + + This tag already exists. + + + + + DeckPreviewWidget + + + Banner Card + + + + + Open in deck editor + + + + + Edit Tags + + + + + Rename Deck + + + + + Save Deck to Clipboard + + + + + Annotated + + + + + Annotated (No set info) + + + + + Not Annotated + + + + + Not Annotated (No set info) + + + + + Rename File + + + + + Delete File + + + + + Set Banner Card + + + + + + New name: + + + + + + Error + + + + + Rename failed + + + + + Delete file + + + + + Are you sure you want to delete the selected file? + + + + + Delete failed + + + DeckStatsInterface @@ -713,181 +1363,217 @@ Toto je uloženo pouze pro moderátory a banovaná osoba to neuvidí. DeckViewContainer - + Load deck... Načíst balíček... - + Load remote deck... Načíst balíček ze serveru... - + + Unload deck + Unload deck... + + + + Ready to start Připraven začít - + + Force start + + + + Sideboard unlocked - + Sideboard locked - - Load deck - Načíst balíček - - - + Error Chyba - + The selected file could not be loaded. Vybraný soubor se nepodařilo načíst. + + DialogConvertDeckToCodFormat + + + Deck Format Conversion + + + + + You tried to add a tag to a .txt format deck. + Tags can only be added to .cod format decks. + Do you want to convert the deck to the .cod format? + + + + + Remember and automatically apply choice in the future + + + DlgConnect - + Downloading... Stahuji... - + Known Hosts Známí hostitelé - + + Delete the currently selected saved server + + + + Refresh the server list with known public servers Znovu načíst seznam serverů se známými veřejnými servery - + New Host Nový hostitel - + Name: Jméno: - + &Host: &Hostitel: - + &Port: &Port: - + Player &name: Jméno &hráče: - + P&assword: H&eslo: - + &Save password &Uložit heslo - + A&uto connect A&utomatické připojení - + Automatically connect to the most recent login when Cockatrice opens Automaticky se připojit k poslednímu přihlášení při otevření Cockatrice - + If you have any trouble connecting or registering then contact the server staff for help! Jestli máte potíže se připájením nebo registrací, kontaktujte pracovníky servery pro pomoc! Pouze Anglicky - - + + Webpage Webová stránka - + Reset Password - + + Forgot password? + + + + &Connect &Připojit - + Server Server - + Login Přihlášení - + Server Contact - + Connect to Server Připojit k serveru - + Server URL - + Communication Port Komunikační port - + Unique Server Name - + Connection Warning - + You need to name your new connection profile. - + Connect Warning Varování přihlášení - + The player name can't be empty. Jméno hráče nemůže být prázdné @@ -970,27 +1656,37 @@ Toto je uloženo pouze pro moderátory a banovaná osoba to neuvidí.Diváci
- + + Starting life total: + + + + + Game setup options + + + + &Clear &Smazat - + Create game Vytvořit hru - + Game information Informace o hře - + Error Chyba - + Server error. Chyba serveru. @@ -998,92 +1694,92 @@ Toto je uloženo pouze pro moderátory a banovaná osoba to neuvidí. DlgCreateToken - + &Name: &Jméno: - + Token Token - + C&olor: B&arva: - + white bílá - + blue modrá - + black černá - + red červená - + green zelená - + multicolor vícebarevný - + colorless bezbarvý - + &P/T: &S/O: - + &Annotation: &Poznámka: - + &Destroy token when it leaves the table &Token se při odchodu z bojiště zničí - + Token data Data tokenů - + Show &all tokens Ukázat &všechny tokeny - + Show tokens from this &deck Ukázat tokeny z tohto &balíčku - + Choose token from list Vybrat token ze seznamu. - + Create token Vytvořit token @@ -1129,6 +1825,24 @@ Pro odstranění současného avatara potvrďte bez vybrání nového obrázku.< Vybrán neplatný obrázek
+ + DlgEditDeckInClipboard + + + Edit deck in clipboard + + + + + Error + + + + + Invalid deck list. + + + DlgEditPassword @@ -1171,93 +1885,93 @@ Pro odstranění současného avatara potvrďte bez vybrání nového obrázku.< DlgEditTokens - + &Name: &Jméno - + C&olor: B&arva - + white bílá - + blue modrá - + black černá - + red červená - + green zelená - + multicolor vícebarevná - + colorless bezbarvá - + &P/T: &S/O - + &Annotation: &Poznámka: - + Token data Data k tokenu - - + + Add token Přidat token - + Remove token Odstranit token - + Edit custom tokens Upravit vlastní tokeny - + Please enter the name of the token: Zadejte prosím jméno pro token: - + Error Chyba - + The chosen name conflicts with an existing card or token. Make sure to enable the 'Token' set in the "Manage sets" dialog to display them correctly. Vybrané jméno se nemůže shodovat s již existující kartou nebo tokenem. @@ -1295,132 +2009,132 @@ Pro správné zobrazení si aktivujte si edici 'Token' v "Nastavi DlgFilterGames - + no limit bez limitu - + 5 minutes 5 Minut - + 10 minutes - + 30 minutes - + 1 hour - + 2 hours - - Show '&buddies only' games - Zobrazit hry 'Jen pro &přátele' - - - - Show &full games + + Hide 'buddies only' games - - Show games &that have started + + Hide full games - - Show &password protected games - Zobrazit hry s potřebou &hesla - - - - Hide '&ignored user' games + + Hide games that have started - + + Hide password protected games + + + + + Hide 'ignored user' games + + + + &Newer than: - + Game &description: &Popis hry: - + &Creator name: Jméno &tvůrce: - + General Obecné - + &Game types Typ &hry - + at &least: &minimálně: - + at &most: &maximálně: - + Maximum player count Maximální počet hráčů - + Restrictions Omezení - + Show games only if &spectators can watch - + Show spectator password p&rotected games - + Show only if spectators can ch&at - + Show only if spectators can see &hands - + Spectators - + Filter games Filtrovat hry @@ -1603,27 +2317,28 @@ Pro správné zobrazení si aktivujte si edici 'Token' v "Nastavi Hesla se neshodují. + + DlgLoadDeck + + + Load Deck + + + DlgLoadDeckFromClipboard - - &Refresh - &Znovu načíst - - - + Load deck from clipboard Nahrát balíček ze schránky - - + Error Chyba - - + Invalid deck list. Neplatný formát balíčku. @@ -1631,11 +2346,49 @@ Pro správné zobrazení si aktivujte si edici 'Token' v "Nastavi DlgLoadRemoteDeck - + Load deck Nahrát balíček + + DlgMoveTopCardsUntil + + + Card name (or search expressions): + + + + + Number of hits: + + + + + Auto play hits + + + + + Put top cards on stack until... + + + + + No cards matching the search expression exists in the card database. Proceed anyways? + + + + + Cockatrice + + + + + Invalid filter + + + DlgRegister @@ -1749,12 +2502,12 @@ Your email will be used to verify your account. DlgSettings - + Unknown Error loading card database Neznámý error při nahrávání databáze karet. - + Your card database is invalid. Cockatrice may not function correctly with an invalid database @@ -1771,7 +2524,7 @@ Je možné že bude nutné znovu spustit Oracle pro obnovení databáze karet. Chtěl/a by jste změnit nastavení lokace databáze? - + Your card database version is too old. This can cause problems loading card information or images @@ -1788,7 +2541,7 @@ Je možné to spravit spuštěním Oracle pro aktualizaci vaší databáze karet Chcete změnit nastavení lokace databáze? - + Your card database did not finish loading Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues with your cards.xml attached @@ -1797,7 +2550,7 @@ Would you like to change your database location setting? - + File Error loading your card database. Would you like to change your database location setting? @@ -1806,7 +2559,7 @@ Would you like to change your database location setting? Chcete změnit nastavení lokace databáze? - + Your card database was loaded but contains no cards. Would you like to change your database location setting? @@ -1815,7 +2568,7 @@ Would you like to change your database location setting? Chcete změnit nastavení lokace databáze? - + Unknown card database load status Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues @@ -1824,59 +2577,59 @@ Would you like to change your database location setting? - - - + + + Error Chyba - + The path to your deck directory is invalid. Would you like to go back and set the correct path? Cesta k adresáři s balíčky je neplatná. Chcete se vrátit a nastavit správnou? - + The path to your card pictures directory is invalid. Would you like to go back and set the correct path? Cesta k adresáři s obrázky je neplatná. Chcete se vrátit a nastavit správnou? - + Settings Nastavení - + General Obecné - + Appearance Vzhled - + User Interface Rozhraní - + Card Sources - + Chat Chat - + Sound Zvuk - + Shortcuts Skratky @@ -1902,164 +2655,168 @@ Would you like to change your database location setting? DlgUpdate - + Current release channel - + Reinstall Znovu instalovat - + Cancel Download Přerušit stahování - + Open Download Page - + Check for Client Updates - - - - + + + + Error Chyba - + Cockatrice was not built with SSL support, therefore you cannot download updates automatically! Please visit the download page to update manually. - - Downloading update... - Stahuji aktualizaci... + + Downloading update: %1 + Downloading update... + - + Checking for updates... Hledám aktualizace... - + Finished checking for updates Aktualizace byly skontrolovány - + No Update Available Žádná dostupná aktualizace - + Cockatrice is up to date! Cockatrice je v aktuální verzi! - + You are already running the latest version available in the chosen release channel. - + Current version Aktuální verze - + Selected release channel - - + + Update Available Dostupná aktualizace - - + + A new version of Cockatrice is available! Je dostupná nová verze Cockatrice! - - + + New version Nová verze - - + + Released - - + + Changelog - + Do you want to update now? Chcete aktualizovat nyní? - - Unfortunately there are no download packages available for your operating system. -You may have to build from source yourself. + + Unfortunately, the automatic updater failed to find a compatible download. +You may have to manually download the new version. + Unfortunately there are no download packages available for your operating system. +You may have to build from source yourself. - - Please check the download page manually and visit the wiki for instructions on compiling. + + Please check the <a href="%1">releases page</a> on our Github and download the build for your system. + Please check the download page manually and visit the wiki for instructions on compiling. - - - + + + Update Error Chyba aktualizace - + An error occurred while checking for updates: Došlo k chybě při kontrole aktualizací: - + An error occurred while downloading an update: Došlo k chybě při stahování aktualizace: - + Installing... Instaluji... - + Cockatrice is unable to open the installer. - + Try to update manually by closing Cockatrice and running the installer. - + Download location @@ -2077,6 +2834,14 @@ You may have to build from source yourself. + + EdhrecCommanderResponseCommanderDetailsDisplayWidget + + + Salt: + + + FilterBuilder @@ -2088,105 +2853,120 @@ You may have to build from source yourself. GameSelector - - - - - - - - - + + + + + + + + + Error Chyba - + Please join the appropriate room first. - + Wrong password. Špatné heslo. - + Spectators are not allowed in this game. Do této hry je divákům přístup zakázán. - + The game is already full. Hra je plná. - + The game does not exist any more. Hra již neexistuje. - + This game is only open to registered users. Hra je určena jen pro registrované. - + This game is only open to its creator's buddies. Hra je dostupná jen pro přátele zakládajícícho. - + You are being ignored by the creator of this game. Zakladatel hry vás ignoruje. - + + Join Game + + + + + Spectate Game + + + + + Game Information + + + + Join game Připojit ke hře - + Password: Heslo: - + Please join the respective room first. - + &Filter games &Filtrovat hry: - + C&lear filter Z&rušit filtr: - + C&reate V&ytvořit - + &Join &Připojit se - + J&oin as spectator P&řipojit se jako divák - + Games shown: %1 / %2 - + Games Hry @@ -2194,100 +2974,100 @@ You may have to build from source yourself. GamesModel - + >1 day - + %1%2 hr short age in hours - + new - + %1%2 min short age in minutes - + password heslo - + buddies only jen pro přátele - + reg. users only jen pro registrované - - + + can chat může chatovat - + see hands vidět do rukou - + can see hands může vidět do rukou - + not allowed nepovolené - + Room Místnost - + Age Doba od otevření - + Description Popis - + Creator Zakladatel - + Type Typ - + Restrictions Omezení - + Players Hráči - + Spectators Diváci @@ -2295,92 +3075,102 @@ You may have to build from source yourself. GeneralSettingsPage - + Reset all paths - + All paths have been reset - - + + Choose path Vyberte cestu - + Personal settings Osobní nastavení - + Language: Jazyk: - + Paths (editing disabled in portable mode) - + Paths Cesty - + + How to help with translations + + + + Decks directory: Adresář s balíčky: - + Replays directory: Adresář se záznamy her - + Pictures directory: Adresář s obrázky: - + Card database: Databáze karet: - + Custom database directory: - + Token database: Databáze tokenů: - + Update channel - + + Check for client updates on startup + + + + Notify if a feature supported by the server is missing in my client - + Automatically run Oracle when running a new version of Cockatrice Automaticky spustit Oracle při spouštění nové verze Cockatrice - + Show tips on startup Ukázat tip při spuštění @@ -2388,715 +3178,726 @@ You may have to build from source yourself. MainWindow - - + + The server has reached its maximum user capacity, please check back later. Na serveru je maximální počet uživatelů, skuste později. - + There are too many concurrent connections from your address. Z vaší adresy jde mnoho současných připojení. - + Banned by moderator - + Expected end time: %1 - + This ban lasts indefinitely. - + Scheduled server shutdown. - - + + Invalid username. Neplatné uživatelské jméno. - + You have been logged out due to logging in at another location. - + Connection closed Připojení uzavřeno - + The server has terminated your connection. Reason: %1 Server přerušil spojení. Důvod: %1 - + The server is going to be restarted in %n minute(s). All running games will be lost. Reason for shutdown: %1 - + Scheduled server shutdown - - + + Success - + Registration accepted. Will now login. - + Account activation accepted. Will now login. - + Number of players Počet hráčů - + Please enter the number of players. Vložte počet hráčů. - - + + Player %1 Hráč %1 - + Load replay - + About Cockatrice O Cockatrice - + Version - + Cockatrice Webpage - + Project Manager: - + Past Project Managers: - + Developers: - + Our Developers - + Help Develop! - + Translators: Překlad: - + Our Translators - + Help Translate! - + Support: - + Report an Issue - + Troubleshooting - + F.A.Q. - - - - - - - - - - - - - - - + + + + + + + + + + + + + - - - + + + + + Error Chyba - + Server timeout Vypršel časový limit - + Failed Login - + Your client seems to be missing features this server requires for connection. - + To update your client, go to 'Help -> Check for Client Updates'. - + Incorrect username or password. Please check your authentication information and try again. - + There is already an active session using this user name. Please close that session first and re-login. S tímto uživatelským jménem jste již připojeni. Přerušte spojení a znovu se přihlašte. - - + + You are banned until %1. - - + + You are banned indefinitely. - + This server requires user registration. Do you want to register now? - + This server requires client IDs. Your client is either failing to generate an ID or you are running a modified client. Please close and reopen your client to try again. - + An internal error has occurred, please close and reopen Cockatrice before trying again. If the error persists, ensure you are running the latest version of the software and if needed contact the software developers. - + Account activation - + Your account has not been activated yet. You need to provide the activation token received in the activation email. - + Server Full - + Unknown login error: %1 - - + + This usually means that your client version is out of date, and the server sent a reply your client doesn't understand. - + Your username must respect these rules: - + is %1 - %2 characters long - + can %1 contain lowercase characters - - - - + + + + NOT - + can %1 contain uppercase characters - + can %1 contain numeric characters - + can contain the following punctuation: %1 - + first character can %1 be a punctuation mark - + no unacceptable language as specified by these server rules: note that the following lines will not be translated - + can not contain any of the following words: %1 - + can not match any of the following expressions: %1 - + You may only use A-Z, a-z, 0-9, _, ., and - in your username. - - - - - - + + + + + + Registration denied - + Registration is currently disabled on this server - + There is already an existing account with the same user name. - + It's mandatory to specify a valid email address when registering. - + It appears you are attempting to register a new account on this server yet you already have an account registered with the email provided. This server restricts the number of accounts a user can register per address. Please contact the server operator for further assistance or to obtain your credential information. - + Password too short. - + Registration failed for a technical problem on the server. - + Unknown registration error: %1 - + Account activation failed - + Socket error: %1 Chyba socketu: %1 - + You are trying to connect to an obsolete server. Please downgrade your Cockatrice version or connect to a suitable server. Local version is %1, remote version is %2. Snažíte se připojit na zastaralý server. Prosíme, stáhněte si nižší verzi Cockatrice, nebo se připojte k odpovídajícímu serveru. Lokální verze je %1, verze serveru je %2. - + Your Cockatrice client is obsolete. Please update your Cockatrice version. Local version is %1, remote version is %2. Váš klient je zastaralý. Prosíme, aktualizujte Cockatrice na vyšší verzi. Lokální verze je %1, verze serveru je %2. - + Connecting to %1... Připojování k %1... - + Registering to %1 as %2... - + Disconnected Odpojeno - + Connected, logging in at %1 - - - + + + Requesting forgotten password to %1 as %2... - + &Connect... &Připojit... - + &Disconnect &Odpojit - + Start &local game... Spustit &lokální hru... - + &Watch replay... - - &Deck editor - &Editor balíčků - - - + &Full screen &Celá obrazovka - + &Register to server... - + &Restore password... - + &Settings... &Nastavení... - + &Exit &Konec - + A&ctions - + &Cockatrice &Cockatrice - + C&ard Database - + &Manage sets... - + Edit custom &tokens... - + Open custom image folder - + Open custom sets folder - + Add custom sets/cards - + + Reload card database + + + + + Tabs + + + + &Help &Nápověda - + &About Cockatrice &O Cockatrice - + &Tip of the Day - + Check for Client Updates - + Check for Card Updates... - + View &Debug Log - + + Open Settings Folder + + + + Show/Hide - + New Version - + Congratulations on updating to Cockatrice %1! Oracle will now launch to update your card database. - + Cockatrice installed - + Congratulations on installing Cockatrice %1! Oracle will now launch to install the initial card database. - + Card database - + Cockatrice is unable to load the card database. Do you want to update your card database now? If unsure or first time user, choose "Yes" - - + + Yes - - + + No - + Open settings - + New sets found - + %n new set(s) found in the card database Set code(s): %1 Do you want to enable it/them? - + View sets - + Welcome - + Hi! It seems like you're running this version of Cockatrice for the first time. All the sets in the card database have been enabled. Read more about changing the set order or disabling specific sets and consequent effects in the "Manage Sets" dialog. - - + + Information - + A card database update is already running. - + Unable to run the card database updater: - - failed to start. + + Failed to start. The file might be missing, or permissions might be incorrect. - - crashed. + + The process crashed some time after starting successfully. - - timed out. + + Timed out. The process took too long to respond. The last waitFor...() function timed out. - - write error. + + An error occurred when attempting to write to the process. For example, the process may not be running, or it may have closed its input channel. - - read error. + + An error occurred when attempting to read from the process. For example, the process may not be running. - - unknown error. + + Unknown error occurred. - - The card database updater exited with an error: %1 + + The card database updater exited with an error: +%1 - + This server supports additional features that your client doesn't have. This is most likely not a problem, but this message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -3104,54 +3905,54 @@ To update your client, go to Help -> Check for Updates. - - - - - + + + + + Load sets/cards - + Selected file cannot be found. - + You can only import XML databases at this time. - + The new sets/cards have been added successfully. Cockatrice will now reload the card database. - + Sets/cards failed to import. - - - + + + Reset Password - + Your password has been reset successfully, you can now log in using the new credentials. - + Failed to reset user account password, please contact the server operator to reset your password. - + Activation request received, please check your email for an activation token. @@ -3425,179 +4226,190 @@ Cockatrice will now reload the card database. - %1 is looking at the top %3 card(s) %2. + %1 is looking at the %4 %3 card(s) %2. + %1 is looking at the top %3 card(s) %2. top card for singular, top %3 cards for plural - + + bottom + + + + + top + + + + %1 turns %2 face-down. - + %1 turns %2 face-up. - + The game has been closed. Hra byla ukončena. - + The game has started. Hra začíná. - + %1 has joined the game. - + %1 is now watching the game. %1 nyní sleduje hru. - + You have been kicked out of the game. - + %1 has left the game (%2). - + %1 is not watching the game any more (%2). - + %1 is not ready to start the game any more. - + %1 shuffles their deck and draws a new hand of %2 card(s). - + %1 shuffles their deck and draws a new hand. - + You are watching a replay of game #%1. - + %1 is ready to start the game. - + cards an unknown amount of cards - + %1 card(s) a card for singular, %1 cards for plural - + %1 lends %2 to %3. - + %1 reveals %2 to %3. - + %1 reveals %2. - + %1 randomly reveals %2%3 to %4. - + %1 randomly reveals %2%3. - + %1 peeks at face down card #%2. - + %1 peeks at face down card #%2: %3. - + %1 reveals %2%3 to %4. - + %1 reveals %2%3. - + %1 reversed turn order, now it's %2. - + reversed - + normal - + Heads - + Tails - + %1 flipped a coin. It landed as %2. - + %1 rolls a %2 with a %3-sided die. - + %1 flips %2 coins. There are %3 heads and %4 tails. - + %1 rolls a %2-sided dice %3 times: %4. @@ -3717,17 +4529,17 @@ Cockatrice will now reload the card database. - + %1 unattaches %2. - + %1 undoes their last draw. - + %1 undoes their last draw (%2). @@ -3735,110 +4547,110 @@ Cockatrice will now reload the card database. MessagesSettingsPage - + Word1 Word2 Word3 - + Add New Message - + Edit Message - + Remove Message - + Add message Přidat zprávu - - + + Message: Zpráva: - + Edit message - + Chat settings - + Custom alert words - + Enable chat mentions - + Enable mention completer - + In-game message macros - + How to use in-game message macros - + Ignore chat room messages sent by unregistered users - + Ignore private messages sent by unregistered users - - + + Invert text color - + Enable desktop notifications for private messages - + Enable desktop notification for mentions - + Enable room message history on join - - + + (Color is hexadecimal) - + Separate words with a space, alphanumeric characters only @@ -3896,6 +4708,24 @@ Cockatrice will now reload the card database. + + OverlapControlWidget + + + Cards to overlap: + + + + + Overlap percentage: + + + + + Overlap direction: + + + Phase @@ -3962,57 +4792,57 @@ Cockatrice will now reload the card database. PhasesToolbar - + Untap step Odtapovací fáze - + Upkeep step Upkeep - + Draw step Lízací fáze - + First main phase První hlavní fáze - + Beginning of combat step Začátek bojové fáze - + Declare attackers step Oznámení útočníků - + Declare blockers step Oznámení blokujících - + Combat damage step Udělení bojového zranění - + End of combat step Konec bojové fáze - + Second main phase Druhá hlavní fáze - + End of turn step Konec kola @@ -4020,7 +4850,7 @@ Cockatrice will now reload the card database. PictureLoader - + en code for scryfall's language property, not available for all languages @@ -4029,647 +4859,781 @@ Cockatrice will now reload the card database. Player - + Reveal top cards of library - - - - - - - - - + + + + + + + + + + + + Number of cards: (max. %1) - + &View graveyard &Zobrazit hřbitov - + &View exile &Zobrazit exilnuté karty - + Player "%1" Hráč "%1" - - - - + + + + &Graveyard &Hřbitov - - - - + + + + &Exile &Exilnuté karty - + &Move hand to... - - - + + + &Top of library - - - + + + &Bottom of library - + &Move graveyard to... - - - - + + + + &Hand &Ruka - + &Move exile to... - + &View library &Zobrazit knihovnu - + &View hand - + View &top cards of library... Zobrazit &vrchní karty knihovny... - + Reveal &library to... - + &Always reveal top card - + &View sideboard &Zobrazit sideboard - + &Draw card &Líznout kartu - + D&raw cards... L&íznout karty... - + &Undo last draw &Vrátit zpět poslední líznutí - + Take &mulligan &Mulliganovat - - &Shuffle - &Zamíchat - - - + Play top card &face down - + Move top card to grave&yard - + Move top card to e&xile - + Move top cards to &graveyard... Přesunout vrchní karty do &hřbitova... - + Move top cards to &exile... &Exilnout vrchní karty... - + Put top card on &bottom Dát kartu na &spodek - + + View bottom cards of library... + + + + Lend library to... - + + Shuffle + + + + Put top cards on stack &until... Take top cards &until... - + + Shuffle top cards... + + + + + Shuffle bottom cards... + + + + &Reveal hand to... - + Reveal r&andom card to... - + Reveal random card to... - + &Sideboard &Sideboard - + &Library &Knihovna - + &Counters &Žetony - + &Untap all permanents &Odtapnout všechny permanenty - + R&oll die... H&odit kostkou... - + &Create token... &Vytvořit token... - + C&reate another token V&ytvořit další token - + Cr&eate predefined token - - + + &All players &Všem hráčům - + S&ay &Chat - + + &Select All + + + + + S&elect Row + + + + + S&elect Column + + + + &Play - + &Hide - + Play &Face Down - + &Tap / Untap Turn sideways or back again - + Toggle &normal untapping - + T&urn Over Turn face up/face down - + &Peek at card face - + &Clone - + Attac&h to card... - + Unattac&h - + &Draw arrow... - + &Increase power - + &Decrease power - + I&ncrease toughness - + D&ecrease toughness - + In&crease power and toughness - + Dec&rease power and toughness - + Increase power and decrease toughness - + Decrease power and increase toughness - + Set &power and toughness... - + Reset p&ower and toughness - + &Set annotation... - + Red - + Yellow - + Green - + &Add counter (%1) - + &Remove counter (%1) - + &Set counters (%1)... - + &Top of library in random order - + X cards from the top of library... - + &Bottom of library in random order - + View top cards of library Zobrazit vrchní karty knihovny - + + View bottom cards of library + + + + + Shuffle top cards of library + + + + + Shuffle bottom cards of library + + + + Which position should this card be placed: - + (max. %1) - + Draw hand - + 0 and lower are in comparison to current hand size - + Draw cards Líznout karty - - + + Number: Počet: - + Move top cards to grave Přesunout vrchní karty do hřbitova - + Reveal &top cards to... - + &Top of library... - + &Bottom of library... - + &Always look at top card - + &Open deck in deck editor - + &Play top card - + &Draw bottom card - + D&raw bottom cards... - + &Play bottom card - + Play bottom card &face down - + Move bottom card to grave&yard - + Move bottom card to e&xile - + Move bottom cards to &graveyard... - + Move bottom cards to &exile... - + Put bottom card on &top - + Selec&ted cards - + Move top cards to exile Exilnout vrchní karty - + Move bottom cards to grave - + Move bottom cards to exile - + Draw bottom cards - - + + C&reate another %1 token - + Create tokens - + Place card X cards from top of library - + Change power/toughness - + Change stats to: - + Set annotation Nastavit poznámku - + Please enter the new annotation: Vložte novou poznámku: - + Set counters Nastavit žetony - + Re&veal to... - + View related cards - + Token: - + All tokens + + PrintingSelector + + + Display Navigation Buttons + + + + + <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. + + + + + PrintingSelectorCardOverlayWidget + + + Preference + + + + + Pin Printing + + + + + Unpin Printing + + + + + Show Related cards + + + + + PrintingSelectorCardSearchWidget + + + Search by set name or set code + + + + + PrintingSelectorCardSelectionWidget + + + Previous Card in Deck + + + + + Next Card in Deck + + + + + PrintingSelectorCardSortingWidget + + + Alphabetical + + + + + Preference + + + + + Release Date + + + + + + Descending + + + + + Ascending + + + QMenuBar - + Services - + Hide %1 - + Hide Others - + Show All - + Preferences... - + Quit %1 - + About %1 @@ -4677,38 +5641,32 @@ Cockatrice will now reload the card database. QObject - + Cockatrice card database (*.xml) - - + All files (*.*) - + Cockatrice replays (*.cor) - - Common deck formats (*.cod *.dec *.dek *.txt *.mwDeck) - - - - + Maindeck - + Sideboard - + Tokens @@ -4716,92 +5674,92 @@ Cockatrice will now reload the card database. QPlatformTheme - + OK - + Save - + Save All - + Open - + &Yes - + Yes to &All - + &No - + N&o to All - + Abort - + Retry - + Ignore - + Close - + Cancel - + Discard - + Help - + Apply - + Reset - + Restore Defaults @@ -4809,17 +5767,17 @@ Cockatrice will now reload the card database. RemoteDeckList_TreeModel - + Name Jméno - + ID ID - + Upload time Průběh nahrávání @@ -4827,32 +5785,32 @@ Cockatrice will now reload the card database. RemoteReplayList_TreeModel - + ID - + Name - + Players - + Keep - + Time started - + Duration (sec) @@ -4898,32 +5856,32 @@ Cockatrice will now reload the card database. SequenceEdit - + Choose an action from the table - + Hit the key/combination of keys you want to set for this action - - Shortcut already in use - - - - + Invalid key - + + Shortcut already in use by: + + + + Clear - + Restore default @@ -4959,72 +5917,80 @@ Cockatrice will now reload the card database. ShortcutSettingsPage - - + + Restore all default shortcuts - + Do you really want to restore all default shortcuts? - + Clear all default shortcuts - + Do you really want to clear all shortcuts? - - Action - - - - - Shortcut - - - - + Section: - + Action: - + Shortcut: - + How to set custom shortcuts - + Clear all shortcuts + + + Search by shortcut name + + + + + ShortcutTreeView + + + Action + + + + + Shortcut + + ShortcutsSettings - + Your configuration file contained invalid shortcuts. Please check your shortcut settings! - + The following shortcuts have been set to default: @@ -5033,17 +5999,17 @@ Please check your shortcut settings! ShutdownDialog - + &Reason for shutdown: - + &Time until shutdown (minutes): - + Shut down server @@ -5051,27 +6017,27 @@ Please check your shortcut settings! SoundSettingsPage - + Enable &sounds - + Current sounds theme: - + Test system sound engine - + Sound settings - + Master volume @@ -5079,48 +6045,48 @@ Please check your shortcut settings! SpoilerBackgroundUpdater - + Spoilers season has ended - + Deleting spoiler.xml. Please run Oracle - - + + Spoilers download failed - + No internet connection - + Error - + Spoilers already up to date - + No new spoilers added - + Spoilers have been updated! - + Last change: @@ -5128,636 +6094,594 @@ Please check your shortcut settings! StableReleaseChannel - - Stable Releases + + Default - + No reply received from the release update server. - + Invalid reply received from the release update server. - + No reply received from the tag update server. - + Invalid reply received from the tag update server. + + TabAccount + + + Add to Buddy List + + + + + Add to Ignore List + + + + + Account + + + TabAdmin - + Administration - + Update server &message Aktualizovat &zprávu serveru - + &Shut down server - + &Reload configuration - + Server administration functions Administrační funkce serveru - + + Server moderator functions + + + + + Replay ID + + + + + Grant Replay Access + + + + + Username to Activate + + + + + Force Activate User + + + + &Unlock functions &Odemknout funkce - + &Lock functions &Zamknout funkce + + + + Success + + + + + Replay access granted + + + + + + + + + Error + + + + + Unable to grant replay access. Replay ID invalid + + + + + Unable to grant replay access. Internal error + + + + + User successfully activated + + + + + Unable to activate user. Username invalid + + + + + Unable to activate user. User already active + + + + + Unable to activate user. Internal error + + TabDeckEditor - - Search by card name (or search expressions) - Search by card name - - - - - Add to Deck - - - - - Add to Sideboard - - - - - Show Related cards - - - - - &Clear all filters - - - - - Delete selected - - - - - Deck &name: - - - - - &Comments: - - - - - Hash: - - - - - &New deck - - - - - &Load deck... - - - - - &Save deck - - - - - Save deck &as... - - - - - Load deck from cl&ipboard... - - - - - Save deck to clipboard - - - - - Annotated - - - - - Not Annotated - - - - - &Print deck... - - - - - &Send deck to online service - - - - - Create decklist (decklist.org) - - - - - Analyze deck (deckstats.net) - - - - - Analyze deck (tappedout.net) - - - - - &Close - - - - - Add card to &maindeck - - - - - Add card to &sideboard - - - - - &Remove row - - - - - &Increment number - - - - - &Decrement number - - - - - &Deck Editor - - - - - + Card Info - - + Deck - - + Filters - + &View - - - + + Printing + + + + + + + Visible - - - + + + + Floating - + Reset layout - + Deck: %1 - - - Are you sure? - - - - - The decklist has been modified. -Do you want to save the changes? - - - - - Load deck - - - - - - - - - - Error - Chyba - - - - The deck could not be saved. - - - - - Could not save remote deck - - - - - - The deck could not be saved. -Please check that the directory is writable and try again. - - - - - Save deck - - - - - There are no cards in your deck to be exported - - - - - No deck was selected to be saved. - - TabDeckStorage - - Deck storage - - - - + Local file system Lokální systém souborů - + Server deck storage Balíčky na serveru - - + + Open in deck editor Otevřít v editoru balíčků - + + Rename deck or folder + + + + Upload deck Nahrát balíček - + Download deck Stáhnout balíček - - + + + + New folder Nová složka - - + + Delete Smazat - - + + Open decks folder + + + + + Rename local folder + + + + + Rename local file + + + + + New name: + + + + + + + Error - + + Rename failed + + + + + Invalid deck file - + Enter deck name Vložit jméno balíčku - + This decklist does not have a name. Please enter a name: Tentobalíček nemá jméno. Prosím vložte jméno: - + Unnamed deck Bezejmenný balíček - + Failed to upload deck to server - + Delete local file - - - - Are you sure you want to delete "%1"? + + Are you sure you want to delete the selected files? - + + Delete remote decks + + + + + Are you sure you want to delete the selected decks? + + + + + Name of new folder: Název nové složky: - - Delete remote folder + + Deck Storage + + + + + TabDeckStorageVisual + + + Visual Deck Storage - - Delete remote deck + + Error + + + + + Could not open deck at %1 + + + + + TabEdhRec + + + EDHREC: TabGame - - - + + + Replay - - + + Game - - + + Player List - - + + Card Info - - + + Messages - - + + Replay Timeline - + &Phases &Fáze - + &Game &Hra - + Next &phase Další &fáze - + Next phase with &action - + Next &turn Další &kolo - + Reverse turn order - + &Remove all local arrows &Odstranit všechny lokální šipky - + Rotate View Cl&ockwise - + Rotate View Co&unterclockwise - + Game &information - + + Un&concede + + + + &Concede &Ukončit hru - + &Leave game &Opustit hru - + C&lose replay - + &Focus Chat - + &Say: &Chat: - + &View - - - - + + + + Visible - - - - + + + + Floating - + Reset layout - + Concede Ukončit hru - + Are you sure you want to concede this game? Opravdu chcete ukončit tuto hru? - + Unconcede - + You have already conceded. Do you want to return to this game? - + Leave game Opustit hru - + Are you sure you want to leave this game? Opravdu chcete opustit tuto hru? - + You are flooding the game. Please wait a couple of seconds. - + A player has joined game #%1 - + %1 has joined the game - + kicked by game host or moderator - + player left the game - + player disconnected from server - + reason unknown - + You have been kicked out of the game. @@ -5770,152 +6694,152 @@ Prosím vložte jméno: - - - + + + Time;SenderName;SenderIP;Message;TargetID;TargetName - + Room Logs - + Game Logs - + Chat Logs - - + + Error Chyba - + You must select at least one filter. - + You have to select a valid number of days to locate. - + Username: - + IP Address: - + Game Name: - + GameID: - + Message: - + Main Room - + Game Room - + Private Chat - + Past X Days: - + Today - + Last Hour - + Maximum Results: - + At least one filter is required. The more information you put in, the more specific your results will be. - + Get User Logs - + Clear Filters - + Filters - + Log Locations - + Date Range - + Maximum Results - - + + Message History - + Failed to collect message history information. - + There are no messages for the selected filters. @@ -5938,22 +6862,22 @@ The more information you put in, the more specific your results will be. - + This user is ignoring you, they cannot see your messages in main chat and you cannot join their games. - + Private message from - + %1 has left the server. %1 opustil server. - + %1 has joined the server. %1 se připojil k serveru. @@ -5961,117 +6885,153 @@ The more information you put in, the more specific your results will be. TabReplays - - Game replays - - - - + Local file system - + Server replay storage - - + + Watch replay - - + + Rename + + + + + + New folder + + + + + Delete - + + Open replays folder + + + + Download replay - + Toggle expiration lock - + + Rename local folder + + + + + Rename local file + + + + + New name: + + + + + Error + + + + + Rename failed + + + + + Name of new folder: + + + + Delete local file - - Are you sure you want to delete "%1"? + + Are you sure you want to delete the selected files? - - Downloading Replays + + Are you sure you want to delete the selected replays? - - Folder download is not yet supported. Please download replays individually. - - - - + Delete remote replay - - Are you sure you want to delete the replay of game %1? + + Game Replays TabRoom - + &Say: &Chat: - + Chat Chatovat - + &Room &Místnost - + &Leave room &Opustit místnost - + &Clear chat - + Chat Settings... - + mentioned you. - + Click to view - + You are flooding the chat. Please wait a couple of seconds. Píšete příliš intenzivně. Počkejte pár sekund. @@ -6084,30 +7044,30 @@ The more information you put in, the more specific your results will be. - - - - + + + + Error Chyba - + Failed to join the server room: it doesn't exist on the server. - + The server thinks you are in the server room but your client is unable to display it. Try restarting your client. - + You do not have the required permission to join this server room. - + Failed to join the server room due to an unknown error: %1. @@ -6115,32 +7075,72 @@ The more information you put in, the more specific your results will be. TabSupervisor - + + Deck Editor + + + + + &Visual Deck Storage + + + + + Server + + + + + Account + + + + + Deck Storage + + + + + Game Replays + + + + + Administration + + + + + Logs + + + + Are you sure? - + There are still open games. Are you sure you want to quit? - + Click to view - + Your buddy %1 has signed on! - + Unknown Event - + The server has sent you a message that your client does not understand. This message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -6148,61 +7148,43 @@ To update your client, go to Help -> Check for Updates. - + Idle Timeout - + You are about to be logged out due to inactivity. - + Promotion - + You have been promoted. Please log out and back in for changes to take effect. - + Warned - + You have received a warning due to %1. Please refrain from engaging in this activity or further actions may be taken against you. If you have any questions, please private message a moderator. - + You have received the following message from the server. (custom messages like these could be untranslated) - - TabUserLists - - - Account - - - - - Add to Buddy List - - - - - Add to Ignore List - - - TappedOutInterface @@ -6286,173 +7268,184 @@ Please refrain from engaging in this activity or further actions may be taken ag UserContextMenu - + User &details - + Private &chat - + Show this user's &games - + Add to &buddy list - + Remove from &buddy list - + Add to &ignore list - + Remove from &ignore list - + Kick from &game - + Warn user - + View user's war&n history - + Ban from &server - + View user's &ban history - + &Promote user to moderator - + Dem&ote user from moderator - + Promote user to &judge - + Demote user from judge - + + View admin notes + + + + %1's games - - - + + + Ban History - + Ban Time;Moderator;Ban Length;Ban Reason;Visible Reason - + User has never been banned. - + Failed to collect ban information. - - - + + + Warning History - + Warning Time;Moderator;User Name;Reason - + User has never been warned. - + Failed to collect warning information. - - + + Failed to get admin notes. + + + + + Success - + Successfully promoted user. - + Successfully demoted user. - - + + + Failed - + Failed to promote user. - + Failed to demote user. - + Copy hash to clipboard - + Remove this user's messages @@ -6460,173 +7453,173 @@ Please refrain from engaging in this activity or further actions may be taken ag UserInfoBox - + Location: Místo: - + Account Age: - + Edit - + Change password - + Change avatar - + Administrator Administrátor - + Moderator - + Registered user Registrovaný - - + + Unregistered user Neregistrovaný - + Judge - + Unknown - + The entered password does not match your account. - - - + + + Information - + User information updated. - - - - - - - - - - + + + + + + + + + + Error Chyba - + User Information - + Real Name: - + User Level: - + %n Year(s), amount of years (only shown if more than 0) - + %10%n Day(s) %20 amount of years (if more than 0), amount of days, date in local short format - + Enter Password - + Password verification is required in order to change your email address - - - + + + An error occurred while trying to update your user information. - + This server does not permit you to update your user informations. - + Password changed. - + This server does not permit you to change your password. - + The new password is too short. - + The old password is incorrect. - + Avatar updated. - + This server does not permit you to update your avatar. - + An error occured while trying to updater your avatar. @@ -6634,124 +7627,251 @@ Please refrain from engaging in this activity or further actions may be taken ag UserInterfaceSettingsPage - + General interface settings Obecné - + &Double-click cards to play them (instead of single-click) &Pro zahraní karty je třeba dvojklik - + + &Clicking plays all selected cards (instead of just the clicked card) + + + + &Play all nonlands onto the stack (not the battlefield) by default - + + Close card view window when last card is removed + + + + Annotate card text on tokens - + Use tear-off menus, allowing right click menus to persist on screen - + Notifications settings - + Enable notifications in taskbar - + Notify in the taskbar for game events while you are spectating - + Notify in the taskbar when users in your buddy list connect - + Animation settings Animace - + &Tap/untap animation &Animace tapnutí/odtapnutí + + + Deck editor/storage settings + + + + + Open deck in new tab by default + + + + + Prompt before converting .txt decks to .cod format + + + + + Always convert if not prompted + + + + + Use visual deck storage in game lobby + + + + + Replay settings + + + + + Buffer time for backwards skip via shortcut: + + - UserList + UserListWidget - + Users connected to server: %1 - + Users in this room: %1 - Uživatelů v této místnosti: %1 + - + Buddies online: %1 / %2 - Přátelé online: %1 / %2 + - + Ignored users online: %1 / %2 - Ignorovaných online: %1 / %2 + + + + + VisualDeckStorageFolderDisplayWidget + + + Deck Storage + + + + + VisualDeckStorageSearchWidget + + + Search by filename + + + + + VisualDeckStorageSortWidget + + + Sort Alphabetically (Deck Name) + + + + + Sort Alphabetically (Filename) + + + + + Sort by Last Modified + + + + + Sort by Last Loaded + + + + + VisualDeckStorageWidget + + + Loading database ... + + + + + Show Folders + + + + + Show Tag Filter + + + + + Show Tags On Deck Previews + + + + + Show Banner Card Selection Option + + + + + Include Folder Names in Search + + + + + Draw unused Color Identities + + + + + Unused Color Identities Opacity + WarningDialog - + Which warning would you like to send? - + Redact all messages from this user in all rooms - + &OK - + &Cancel - + Warn user for misconduct - - + + Error Chyba - + User name to send a warning to can not be blank, please specify a user to warn. - + Warning to use can not be blank, please select a valid warning to send. @@ -6759,128 +7879,133 @@ Please refrain from engaging in this activity or further actions may be taken ag WndSets - + Move selected set to the top - + Move selected set up - + Move selected set down - + Move selected set to the bottom - + Search by set name, code, or type - + Default order - + Restore original art priority order - + Enable all sets - + Disable all sets - + Enable selected set(s) - + Disable selected set(s) - + Deck Editor - + Use CTRL+A to select all sets in the view. - + Only cards in enabled sets will appear in the card list of the deck editor. - + Image priority is decided in the following order: - + first the CUSTOM Folder (%1), then the Enabled Sets in this dialog (Top to Bottom) %1 is a link to the wiki - + Card Art - + How to use custom card art - + Hints - + Note - + Sorting by column allows you to find a set while not changing set priority. - + To enable ordering again, click the column header until this message disappears. - + Use the current sorting as the set priority instead - + Sorts the set priority using the same column - + + Include online-only (Arena) cards [requires restart] + + + + Manage sets @@ -6888,22 +8013,67 @@ Please refrain from engaging in this activity or further actions may be taken ag ZoneViewWidget - - sort by name - seřadit dle jména + + Ungrouped + - - sort by type - seřadit dle typu + + Group by Type + - + + Group by Mana Value + + + + + Group by Color + + + + + Unsorted + + + + + Sort by Name + + + + + Sort by Type + + + + + Sort by Mana Cost + + + + + Sort by Colors + + + + + Sort by P/T + + + + + Sort by Set + + + + shuffle when closing zamíchat po zavření - + pile view @@ -6911,7 +8081,7 @@ Please refrain from engaging in this activity or further actions may be taken ag i18n - + English Česky (Czech) @@ -6919,12 +8089,12 @@ Please refrain from engaging in this activity or further actions may be taken ag main - + Connect on startup - + Debug to file @@ -6932,786 +8102,908 @@ Please refrain from engaging in this activity or further actions may be taken ag shortcutsTab - + Main Window - - + + Deck Editor - + Game Lobby - + Card Counters - + Player Counters - + Power and Toughness Síla a obrana - + Game Phases Herní fáze - + Playing Area Herní oblast - + Move Selected Card - + View Pohled - + Move Top Card - + Move Bottom Card - + Gameplay Průběh hry - + Drawing - + Chat Room - + Game Window - + Load Deck from Clipboard - + + Replays - + + Tabs + + + + Check for Card Updates... - + Connect... - + Disconnect - + Exit - + Full screen - + Register... - + Settings... - + Start a Local Game... - + Watch Replay... - - Analyze Deck + + Analyze Deck (deckstats.net) + Analyze Deck - + + Analyze Deck (tappedout.net) + + + + Clear All Filters - + Clear Selected Filter - + Close Zavřít - + Remove Card - + Manage Sets... - + Edit Custom Tokens... - - Export Deck - - - - - Add Card - - - - - Load Deck... - - - - - Load Deck from Clipboard... + + Export Deck (decklist.org) - New Deck + Export Deck (decklist.xyz) - - Open Custom Pictures Folder + + Add Card - - Print Deck... + + Load Deck... + Load Deck from Clipboard... + + + + + Edit Deck in Clipboard, Annotated + + + + + Edit Deck in Clipboard + + + + + New Deck + + + + + Open Custom Pictures Folder + + + + + Print Deck... + + + + Delete Card - - + + Reset Layout - + Save Deck - + Save Deck as... - + Save Deck to Clipboard, Annotated - - Save Deck to Clipboard - - - - - Load Local Deck... - - - - - Load Remote Deck... - - - - - Set Ready to Start - - - - - Toggle Sideboard Lock - - - - - - Add Green Counter - - - - - - Remove Green Counter - - - - - - Set Green Counters... + + Save Deck to Clipboard, Annotated (No Set Info) + Save Deck to Clipboard + + + + + Save Deck to Clipboard (No Set Info) + + + + + Load Local Deck... + + + + + Load Remote Deck... + + + + + Set Ready to Start + + + + + Toggle Sideboard Lock + + + + + + Add Green Counter + + + + + + Remove Green Counter + + + + + + Set Green Counters... + + + + Add Yellow Counter - + Remove Yellow Counter - + Set Yellow Counters... - - + + Add Red Counter - - + + Remove Red Counter - - + + Set Red Counters... - + Add Life Counter - + Remove Life Counter - + Set Life Counters... - + Add White Counter - + Remove White Counter - + Set White Counters... - + Add Blue Counter - + Remove Blue Counter - + Set Blue Counters... - + Add Black Counter - + Remove Black Counter - + Set Black Counters... - + Add Colorless Counter - + Remove Colorless Counter - + Set Colorless Counters... - + Add Other Counter - + Remove Other Counter - + Set Other Counters... - + Add Power (+1/+0) - + Remove Power (-1/-0) - + Move Toughness to Power (+1/-1) - + Add Toughness (+0/+1) - + Remove Toughness (-0/-1) - + Move Power to Toughness (-1/+1) - + Add Power and Toughness (+1/+1) - + Remove Power and Toughness (-1/-1) - + Set Power and Toughness... - + Reset Power and Toughness - + Untap Odtapnutí - + Upkeep Upkeep - + Draw Lízání - + First Main Phase - + Start Combat - + Attack Útok - + Block Bránění - + Damage Zranění - + End Combat - + Second Main Phase - + End Konec - + Next Phase - + Next Phase Action - + Next Turn - + + Hide Card in Reveal Window + + + + Tap / Untap Card - + Untap All - + Toggle Untap - + Turn Card Over - + Peek Card - + Play Card - + Attach Card... - + Unattach Card - + Clone Card - + Create Token... - + Create All Related Tokens - + Create Another Token - + Set Annotation... - - + + Select All Cards in Zone + + + + + Select All Cards in Row + + + + + Select All Cards in Column + + + + + Bottom of Library - - - - + + + + Exile Exil - - - - + + + + Graveyard Hřbitov - - + + Hand Ruka - - + + Top of Library - - - + + + Battlefield, Face Down - + Battlefield - + Library Knihovna - + Sideboard Sideboard - + Top Cards of Library - + + Bottom Cards of Library + + + + Close Recent View - - + + Stack - - + + Graveyard (Multiple) - - + + Exile (Multiple) - + Stack Until Found - + Draw Bottom Card - + Draw Multiple Cards from Bottom... - + Draw Arrow... - + Remove Local Arrows - + Leave Game - + Concede Vzdát hru - + Roll Dice... - + Shuffle Library - + + Shuffle Top Cards of Library + + + + + Shuffle Bottom Cards of Library + + + + Mulligan Mulligan - + Draw a Card - + Draw Multiple Cards... - + Undo Draw - + Always Reveal Top Card - + Always Look At Top Card - + Rotate View Clockwise - + Rotate View Counterclockwise - + Unfocus Text Box - + Focus Chat - + Clear Chat - + Refresh - + + Skip Forward + + + + + Skip Backward + + + + + Skip Forward by a lot + + + + + Skip Backward by a lot + + + + Play/Pause - + Toggle Fast Forward + + + Visual Deck Storage + + + + + Deck Storage + + + + + Server + + + + + Account + + + + + Administration + + + + + Logs + + \ No newline at end of file diff --git a/cockatrice/translations/cockatrice_de.ts b/cockatrice/translations/cockatrice_de.ts index 5791a88c5..a903e177c 100644 --- a/cockatrice/translations/cockatrice_de.ts +++ b/cockatrice/translations/cockatrice_de.ts @@ -2,7 +2,7 @@ AbstractCounter - + &Set counter... Zähler &setzen... @@ -10,95 +10,247 @@ AbstractCounterDialog - + Set counter Zähler setzen - + New value for counter '%1': Neuer Wert für den Zähler '%1': + + AbstractDlgDeckTextEdit + + + &Refresh + + + + + Parse Set Name and Number (if available) + + + + + AbstractTabDeckEditor + + + Open in new tab + + + + + Are you sure? + + + + + The decklist has been modified. +Do you want to save the changes? + + + + + + + + + + + Error + + + + + Could not open deck at %1 + + + + + Could not save remote deck + + + + + + The deck could not be saved. +Please check that the directory is writable and try again. + + + + + Save deck + + + + + The deck could not be saved. + + + + + There are no cards in your deck to be exported + + + + + No deck was selected to be exported. + + + + + AdminNotesDialog + + + Update Notes + Aktualisierungsnotizen + + + + Admin Notes for %1 + Administrator Notizen für %1 + + + + AllZonesCardAmountWidget + + + Mainboard + Hauptdeck + + + + Sideboard + Nebendeck + + AppearanceSettingsPage - + Error Fehler - + Could not create themes directory at '%1'. Das Themeverzeichnis '%1' konnte nicht erstellt werden. - + Theme settings Darstellungseinstellungen - + Current theme: Aktuelles Theme: - + Open themes folder Öffne Themeverzeichnis - + + Menu settings + Einstellungsmenü + + + + Show keyboard shortcuts in right-click menus + Tastatur-Kürzel anzeigen bei Rechtsklickmenü + + + Card rendering Kartendarstellung - + Display card names on cards having a picture Kartennamen auch bei Karten mit Bildern darstellen - + + Auto-Rotate cards with sideways layout + Automatisches Drehen von Karten mit Seitwärts-Ausrichtung + + + + Override all card art with personal set preference (Pre-ProviderID change behavior) [Requires Client restart] + Überschreiben aller Kartenbilder mit persönlichen Set-Präferenzen (Vor-AnbieterID Wechselverhalten) [Benötigt Klientenneustart] + + + + Bump sets that the deck contains cards from to the top in the printing selector + Priorisieren von Sets, in welchen Karten des ausgewählten Decks vorhanden sind, nach oben in der Druckauswahl + + + Scale cards on mouse over Karten beim Darüberfahren mit der Maus vergrößern - + + Use rounded card corners + + + + Minimum overlap percentage of cards on the stack and in vertical hand Minimaler Überschneidungsanteil der Karten auf dem Stapel und in der vertikalen Hand - + + Maximum initial height for card view window: + Maximale Starthöhe für das Kartenansichtsfenster + + + + + rows + Reihen + + + + Maximum expanded height for card view window: + + + + Hand layout Handdarstellung - + Display hand horizontally (wastes space) Hand horizontal anzeigen (verschwendet Platz) - + Enable left justification Linksbündige Ausrichtung aktivieren - + Table grid layout Spielfeldraster - + Invert vertical coordinate Vertikale Koordinate umkehren - + Minimum player count for multi-column layout: Mindestspieleranzahl für mehrspaltige Anordnung: - + Maximum font size for information displayed on cards: Maximale Schriftgröße für die Anzeige von Informationen auf Karten: @@ -106,112 +258,112 @@ BanDialog - + ban &user name &Benutzername - + ban &IP address &IP-Adresse - + ban client I&D Client I&D bannen - + Ban type Art des Banns - + &permanent ban &permanenter Bann - + &temporary ban &temporärer Bann - + &Days: &Tage: - + &Hours: &Stunden: - + &Minutes: &Minuten: - + Duration of the ban Länge des Banns - + Please enter the reason for the ban. This is only saved for moderators and cannot be seen by the banned person. Bitte geben Sie den Grund für den Bann ein. Dies wird nur für Moderatoren gespeichert und kann von der gebannten Person nicht gesehen werden. - + Please enter the reason for the ban that will be visible to the banned person. Bitte geben Sie den Grund ein, den die gebannte Person sehen kann. - + Redact all messages from this user in all rooms Entferne alle Nachrichten dieses Nutzers in allen Räumen - + &OK &OK - + &Cancel &Abbrechen - + Ban user from server Benutzer vom Server bannen - - - - + + + + Error Fehler - + You have to select a name-based, IP-based, clientId based, or some combination of the three to place a ban. Bitte wählen Sie einen Namens-, Adress- oder Client-ID-Bann oder eine Kombination aus diesen. - + You must have a value in the name ban when selecting the name ban checkbox. Wenn Sie das Namensbannkästchen auswählen, muss ein Wert für den Namen eingegeben werden. - + You must have a value in the ip ban when selecting the ip ban checkbox. Wenn Sie das Adressbannkästchen auswählen, muss ein Wert für die IP eingegeben werden. - + You must have a value in the clientid ban when selecting the clientid ban checkbox. Wenn Sie das Client-ID-Kästchen auswählen, muss ein Wert für die Client-ID eingegeben werden. @@ -219,22 +371,22 @@ Dies wird nur für Moderatoren gespeichert und kann von der gebannten Person nic BetaReleaseChannel - - Beta Releases - Beta Veröffentlichungen + + Beta + Beta - + No reply received from the release update server. Keine Antwort vom Veröffentlichungsaktualisierungsserver erhalten. - + Invalid reply received from the release update server. Ungültige Antwort vom Veröffentlichungsaktualisierungsserver erhalten. - + No reply received from the file update server. Keine Antwort vom Dateiaktualisierungsserver erhalten. @@ -242,32 +394,32 @@ Dies wird nur für Moderatoren gespeichert und kann von der gebannten Person nic CardDatabaseModel - + Name Name - + Sets Editionen - + Mana cost Manakosten - + Card type Kartentyp - + P/T S/W - + Color(s) Farbe(n) @@ -360,37 +512,65 @@ Dies wird nur für Moderatoren gespeichert und kann von der gebannten Person nic - CardFrame + CardInfoFrameWidget - + Image Bild - + Description Beschreibung - + Both - Beides + Beide + + + + View transformation + Transformation anzeigen - CardInfoText + CardInfoPictureWidget - + + View related cards + Zugehörige Karten ansehen + + + + Add card to deck + Karte zum Deck hinzufügen + + + + Mainboard + Mainboard + + + + Sideboard + Sideboard + + + + CardInfoTextWidget + + Name: Name: - + Related cards: - Ähnliche Karten: + Zugehörige Karten: - + Unknown card: Unbekannte Karte: @@ -398,287 +578,555 @@ Dies wird nur für Moderatoren gespeichert und kann von der gebannten Person nic CardItem - + &Move to &Verschieben - + &Power / toughness &Kampfwerte + + CardSizeWidget + + + Card Size + Kartengröße + + CardZone - + their hand nominative ihre Hand - + %1's hand nominative %1s Hand - + their library look at zone ihre Bibliothek - + %1's library look at zone %1s Bibliothek - + of their library top cards of zone, von ihrer Bibliothek - + of %1's library top cards of zone von %1s Bibliothek - + their library reveal zone ihre Bibliothek - + %1's library reveal zone %1s Bibliothek - + their library shuffle ihre Bibliothek - + %1's library shuffle %1s Bibliothek - + their library nominative ihre Bibliothek - + %1's library nominative %1s Bibliothek - + their graveyard nominative ihr Friedhof - + %1's graveyard nominative %1s Friedhof - + their exile nominative ihr Exil - + %1's exile nominative %1s Exil - + their sideboard look at zone ihr Sideboard - + %1's sideboard look at zone %1s Sideboard - + their sideboard nominative ihr Sideboard - + %1's sideboard nominative %1s Sideboard + + DeckEditorCardInfoDockWidget + + + Card Info + + + + + DeckEditorDatabaseDisplayWidget + + + Search by card name (or search expressions) + + + + + Add to Deck + + + + + Add to Sideboard + + + + + Select Printing + + + + + Show on EDHREC (Commander) + + + + + Show on EDHREC (Card) + + + + + Show Related cards + + + + + Add card to &maindeck + + + + + Add card to &sideboard + + + + + DeckEditorDeckDockWidget + + + Banner Card + + + + + Select Printing + + + + + Deck + + + + + Deck &name: + + + + + &Comments: + + + + + Hash: + + + + + &Increment number + + + + + &Decrement number + + + + + &Remove row + + + + + Swap card to/from sideboard + + + + + DeckEditorFilterDockWidget + + + Filters + + + + + &Clear all filters + + + + + Delete selected + + + + + DeckEditorMenu + + + &Deck Editor + + + + + &New deck + + + + + &Load deck... + + + + + Load recent deck... + + + + + Clear + + + + + &Save deck + + + + + Save deck &as... + + + + + Load deck from cl&ipboard... + + + + + Edit deck in clipboard + + + + + + Annotated + + + + + + Not Annotated + + + + + Save deck to clipboard + + + + + Annotated (No set info) + + + + + Not Annotated (No set info) + + + + + &Print deck... + + + + + &Send deck to online service + + + + + Create decklist (decklist.org) + + + + + Create decklist (decklist.xyz) + + + + + Analyze deck (deckstats.net) + + + + + Analyze deck (tappedout.net) + + + + + &Close + + + + + DeckEditorPrintingSelectorDockWidget + + + Printing Selector + + + DeckEditorSettingsPage - - + + Update Spoilers Spoiler aktualisieren - - + + Success Erfolgreich - + Download URLs have been reset. Download-URLs wurden zurückgesetzt. - + Downloaded card pictures have been reset. Heruntergeladene Kartenbilder wurden zurückgesetzt. - + Error Fehler - + One or more downloaded card pictures could not be cleared. Eines oder mehrere Kartenbilder konnten nicht gelöscht werden. - + Add URL URL hinzufügen - - + + URL: URL - - + + Edit URL URL bearbeiten - + + Network Cache Size: + Größe des Netzwerkzwischenspeichers: + + + + Redirect Cache TTL: + Umleitungszwischenspeicher TTL: + + + + How long cached redirects for urls are valid for. + Wie lange gespeicherte Umleitungen für URLS gültig sind. + + + + Picture Cache Size: + Größe des Bilderzwischenspeichers: + + + Add New URL Neue URL hinzufügen - + Remove URL URL entfernen - + + Day(s) + Tag(e) + + + Updating... Aktualisiere... - + Choose path Pfad auswählen - + URL Download Priority URL Downloadpriorität - + Spoilers Spoiler - + Download Spoilers Automatically Lade Spoiler automatisch herunter - + Spoiler Location: Spoiler Verzeichnis: - + Last Change Letzte Änderung - + Spoilers download automatically on launch Spoiler werden beim Start automatisch heruntergeladen - + Press the button to manually update without relaunching Drücke den Knopf um manuell zu aktualisieren ohne neu zu starten - + Do not close settings until manual update is complete Schließen Sie die Einstellungen nicht solange die manuelle Aktualisierung nicht abgeschlossen ist. - + Download card pictures on the fly Kartenbilder dynamisch herunterladen - + How to add a custom URL Wie eine benutzerdefinierte URL hinzugefügt wird - + Delete Downloaded Images Heruntergeladene Bilder löschen - + Reset Download URLs Download-URLs zurücksetzen - - Downloaded images directory size: - Ordnergröße der heruntergeladenen Bilder - - - + On-disk cache for downloaded pictures Festplatten-Cache für heruntergeladene Bilder - - Picture cache size: - Cachegröße für Bilder: - - - + In-memory cache for pictures not currently on screen Arbeitsspeicher-Cache für Bilder die gerade nicht angezeigt werden @@ -686,16 +1134,218 @@ Dies wird nur für Moderatoren gespeichert und kann von der gebannten Person nic DeckListModel - + + Count + Anzahl + + + + Set + Festlegen + + + Number Nummer - + + Provider ID + ID des Anbieters + + + Card Karte + + DeckLoader + + + Common deck formats (%1) + + + + + All files (*.*) + + + + + DeckPreviewColorIdentityFilterWidget + + + Mode: Exact Match + Modus: Exakte Übereinstimmung + + + + Mode: Includes + Modus: Enthält + + + + DeckPreviewDeckTagsDisplayWidget + + + Edit tags ... + Tags bearbeiten ... + + + + DeckPreviewTagDialog + + + Deck Tags Manager + Decktagsmanager + + + + Manage your deck tags. Check or uncheck tags as needed, or add new ones: + Verwalte Decktags. Wähle Tags nach Bedarf aus oder füge neue hinzu: + + + + Add a new tag (e.g., Aggro️) + Neuen Tag hinzufügen (bspw. Aggro) + + + + Add Tag + Tag hinzufügen + + + + Filter tags... + Tags filtern... + + + + OK + OK + + + + Cancel + Abbrechen + + + + Invalid Input + Ungültige Eingabe + + + + Tag name cannot be empty! + Tagname kann nicht leer sein! + + + + Duplicate Tag + Tag duplizieren + + + + This tag already exists. + Dieser Tag existiert bereits + + + + DeckPreviewWidget + + + Banner Card + + + + + Open in deck editor + + + + + Edit Tags + + + + + Rename Deck + + + + + Save Deck to Clipboard + + + + + Annotated + + + + + Annotated (No set info) + + + + + Not Annotated + + + + + Not Annotated (No set info) + + + + + Rename File + + + + + Delete File + + + + + Set Banner Card + + + + + + New name: + + + + + + Error + + + + + Rename failed + + + + + Delete file + + + + + Are you sure you want to delete the selected file? + + + + + Delete failed + + + DeckStatsInterface @@ -713,181 +1363,219 @@ Dies wird nur für Moderatoren gespeichert und kann von der gebannten Person nic DeckViewContainer - + Load deck... Deck laden... - + Load remote deck... Deck vom Server laden... - + + Unload deck + Unload deck... + Deck entnehmen + + + Ready to start Bereit zum Start - + + Force start + Zwangsstart + + + Sideboard unlocked Sideboard entsperrt - + Sideboard locked Sideboard gesperrt - - Load deck - Deck laden - - - + Error Fehler - + The selected file could not be loaded. Die gewählte Datei konnte nicht geladen werden. + + DialogConvertDeckToCodFormat + + + Deck Format Conversion + Deckformatkonvertierung + + + + You tried to add a tag to a .txt format deck. + Tags can only be added to .cod format decks. + Do you want to convert the deck to the .cod format? + Sie haben versucht einen Tag zu einem Deck im .txt Format hinzuzufügen. +Tags können nur zu Decks im .cod Format hinzugefügt werden. +Möchten sie das Deck ins .cod Format konvertieren? + + + + Remember and automatically apply choice in the future + Auswahl speichern und automatisch in der Zukunft anwenden + + DlgConnect - + Downloading... Lade herunter... - + Known Hosts Bekannte Hosts - + + Delete the currently selected saved server + Den derzeit gespeicherten Server löschen + + + Refresh the server list with known public servers Aktualisiere die Serverliste der bekannten öffentlichen Server - + New Host Neuer Host - + Name: Name: - + &Host: &Server: - + &Port: &Port: - + Player &name: Spieler&name: - + P&assword: P&asswort: - + &Save password Passwort &speichern - + A&uto connect a&utomatisch verbinden - + Automatically connect to the most recent login when Cockatrice opens Automatisch mit dem letzten Login verbinden, wenn sich Cockatrice öffnet - + If you have any trouble connecting or registering then contact the server staff for help! Falls Probleme mit der Verbindung oder Registrierung bestehen, kontaktieren Sie das Personal für Hilfe! - - + + Webpage Webseite - + Reset Password Passwort zurücksetzen - + + Forgot password? + Passwort vergessen? + + + &Connect &Verbinden... - + Server Server - + Login Login - + Server Contact Serverkontakt - + Connect to Server Verbinde zum Server - + Server URL Server URL - + Communication Port Kommunikationsport - + Unique Server Name Einzigartiger Servername - + Connection Warning Verbindungswarnung - + You need to name your new connection profile. Sie müssen Ihr neues Verbindungsprofil benennen. - + Connect Warning Verbindungswarnung - + The player name can't be empty. Der Spielername kann nicht leer sein. @@ -970,27 +1658,37 @@ Dies wird nur für Moderatoren gespeichert und kann von der gebannten Person nic Zuschauer - + + Starting life total: + Anfangslebenspunktzahl + + + + Game setup options + Spielaufsetzungsoptionen + + + &Clear Löschen - + Create game Spiel erstellen - + Game information &Spielinformationen - + Error Fehler - + Server error. Serverfehler. @@ -998,92 +1696,92 @@ Dies wird nur für Moderatoren gespeichert und kann von der gebannten Person nic DlgCreateToken - + &Name: &Name: - + Token Spielstein - + C&olor: &Farbe: - + white weiß - + blue blau - + black schwarz - + red rot - + green grün - + multicolor mehrfarbig - + colorless farblos - + &P/T: &Kampfwerte: - + &Annotation: &Hinweis: - + &Destroy token when it leaves the table Spielstein &zerstören, wenn er das Spielfeld verlässt - + Token data Spielstein-Daten - + Show &all tokens &Alle möglichen Spielsteine zeigen - + Show tokens from this &deck Spielsteine dieses &Decks zeigen - + Choose token from list Spielstein aus Liste auswählen - + Create token Spielstein erstellen @@ -1129,6 +1827,24 @@ Um Ihren derzeitigen Avatar zu entfernen, bestätigen Sie, ohne ein neues Bild z Ungültiges Bild gewählt. + + DlgEditDeckInClipboard + + + Edit deck in clipboard + + + + + Error + + + + + Invalid deck list. + + + DlgEditPassword @@ -1171,93 +1887,93 @@ Um Ihren derzeitigen Avatar zu entfernen, bestätigen Sie, ohne ein neues Bild z DlgEditTokens - + &Name: &Name: - + C&olor: &Farbe: - + white weiß - + blue blau - + black schwarz - + red rot - + green grün - + multicolor mehrfarbig - + colorless farblos - + &P/T: &Kampfwerte: - + &Annotation: &Hinweis: - + Token data Spielstein-Daten - - + + Add token Spielstein hinzufügen - + Remove token Spielstein entfernen - + Edit custom tokens Benutzerdefinierte Spielsteine bearbeiten - + Please enter the name of the token: Bitte geben Sie den Namen des Spielsteins ein: - + Error Fehler - + The chosen name conflicts with an existing card or token. Make sure to enable the 'Token' set in the "Manage sets" dialog to display them correctly. Es existiert bereits eine Karte oder ein Spielstein mit dem gewählten Namen. @@ -1295,132 +2011,132 @@ Aktivieren Sie die Edition „Token" im „Editionen verwalten...“ Menü DlgFilterGames - + no limit keine Begrenzung - + 5 minutes 5 Minuten - + 10 minutes 10 Minuten - + 30 minutes 30 Minuten - + 1 hour 1 Stunde - + 2 hours 2 Stunden - - Show '&buddies only' games - Zeige 'Nur Freunde' Spiele + + Hide 'buddies only' games + „Nur Freunde“ Spiele ausblenden - - Show &full games - Zeige &vollständige Spiele + + Hide full games + Volle Spiele ausblenden - - Show games &that have started - Zeige &begonnene Spiele + + Hide games that have started + Bereits begonnene Spiele ausblenden - - Show &password protected games - &passwortgeschützte Spiele anzeigen + + Hide password protected games + Passwortgeschützte Spiele ausblenden - - Hide '&ignored user' games - '&Ignorierter Benutzer' Spiele ausblenden + + Hide 'ignored user' games + Spiele von „Ignorierten Benutzern“ ausblenden - + &Newer than: &Neuer als: - + Game &description: &Spielbeschreibung: - + &Creator name: &Name des Erstellers: - + General Allgemeines - + &Game types Spiel&typen - + at &least: &mindestens: - + at &most: &höchstens: - + Maximum player count Maximale Spielerzahl - + Restrictions Einschränkungen - + Show games only if &spectators can watch Zeige Spiele nur, wenn &Zuschauer erlaubt sind - + Show spectator password p&rotected games Zeige für Zuschauer passwo&rtgeschützte Spiele - + Show only if spectators can ch&at Zeige nur, wenn Zuschauer den &Chat nutzen können - + Show only if spectators can see &hands Zeige nur, wenn Zuschauer die &Hände sehen können - + Spectators Zuschauer - + Filter games Spiele filtern @@ -1603,27 +2319,28 @@ Aktivieren Sie die Edition „Token" im „Editionen verwalten...“ Menü Die Passwörter stimmen nicht überein. + + DlgLoadDeck + + + Load Deck + Deck laden + + DlgLoadDeckFromClipboard - - &Refresh - &Aktualisieren - - - + Load deck from clipboard Deck aus der Zwischenablage laden - - + Error Fehler - - + Invalid deck list. Ungültige Deckliste. @@ -1631,11 +2348,49 @@ Aktivieren Sie die Edition „Token" im „Editionen verwalten...“ Menü DlgLoadRemoteDeck - + Load deck Deck laden + + DlgMoveTopCardsUntil + + + Card name (or search expressions): + Kartenname (oder Suchbezeichnung) + + + + Number of hits: + Anzahl der Treffer: + + + + Auto play hits + Automatisches Ausspielen von Treffern + + + + Put top cards on stack until... + Oberste Karten auf den Stapel legen bis... + + + + No cards matching the search expression exists in the card database. Proceed anyways? + Keine Karten, die den Suchbezeichnungen entsprechen, existieren in der Kartendatenbank. Trotzdem fortfahren? + + + + Cockatrice + Basilisk + + + + Invalid filter + Ungültiger Filter + + DlgRegister @@ -1750,12 +2505,12 @@ Die E-Mail-Adresse wird zur Accountverifikation genutzt. DlgSettings - + Unknown Error loading card database Unbekannter Fehler beim Laden der Kartendatenbank - + Your card database is invalid. Cockatrice may not function correctly with an invalid database @@ -1772,7 +2527,7 @@ Sie müssen Oracle unter Umständen nochmals ausführen um Ihre Kartendatenbank Möchten Sie Ihren Speicherort der Datenbank aktualisieren?? - + Your card database version is too old. This can cause problems loading card information or images @@ -1789,7 +2544,7 @@ Normalerweise kann dies durch einen erneuten Start von Oracle, um die Kartendate Möchten Sie Ihren Speicherort der Datenbank aktualisieren? - + Your card database did not finish loading Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues with your cards.xml attached @@ -1802,7 +2557,7 @@ Bitte erstellen Sie ein Ticket auf https://github.com/Cockatrice/Cockatrice/issu Möchten Sie die Einstellung des Datenbankspeicherorts ändern? - + File Error loading your card database. Would you like to change your database location setting? @@ -1811,7 +2566,7 @@ Would you like to change your database location setting? Möchten Sie Ihren Speicherort der Datenbank aktualisieren? - + Your card database was loaded but contains no cards. Would you like to change your database location setting? @@ -1820,7 +2575,7 @@ Would you like to change your database location setting? Möchten Sie Ihren Speicherort der Datenbank aktualisieren? - + Unknown card database load status Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues @@ -1833,59 +2588,59 @@ Bitte erstellen Sie ein Ticket auf https://github.com/Cockatrice/Cockatrice/issu Möchten Sie die Einstellung des Datenbankspeicherorts ändern? - - - + + + Error Fehler - + The path to your deck directory is invalid. Would you like to go back and set the correct path? Der Pfad zu Ihrem Deckordner ist ungültig. Möchten Sie zurückgehen und den korrekten Pfad einstellen? - + The path to your card pictures directory is invalid. Would you like to go back and set the correct path? Der Pfad zu Ihrem Kartenbilderordner ist ungültig. Möchten Sie zurückgehen und den korrekten Pfad einstellen? - + Settings Einstellungen - + General Allgemeines - + Appearance Erscheinungsbild - + User Interface Benutzeroberfläche - + Card Sources Kartenquellen - + Chat Chat - + Sound Töne - + Shortcuts Tastaturkürzel @@ -1911,166 +2666,169 @@ Möchten Sie die Einstellung des Datenbankspeicherorts ändern? DlgUpdate - + Current release channel Momentaner Veröffentlichungskanal - + Reinstall Neu installieren - + Cancel Download Download abbrechen - + Open Download Page Downloadseite öffnen - + Check for Client Updates Auf Clientaktualisierungen prüfen - - - - + + + + Error Fehler - + Cockatrice was not built with SSL support, therefore you cannot download updates automatically! Please visit the download page to update manually. Ihre Version von Cockatrice hat keine SSL-Unterstützung, dadurch können Sie keine Aktualisierungen automatisch herunterladen! Bitte besuchen Sie die Downloadseite, um Cockatrice manuell zu aktualisieren. - - Downloading update... - Lade Aktualisierung herunter... + + Downloading update: %1 + Downloading update... + - + Checking for updates... Suche nach Aktualisierungen... - + Finished checking for updates Suche nach Aktualisierungen abgeschlossen - + No Update Available Keine Aktualisierung verfügbar - + Cockatrice is up to date! Cockatrice ist aktuell! - + You are already running the latest version available in the chosen release channel. Sie verwenden bereits die aktuellste Version des gewählten Veröffentlichungskanals. - + Current version Momentane Version - + Selected release channel Ausgewählter Veröffentlichungskanal - - + + Update Available Aktualisierung verfügbar - - + + A new version of Cockatrice is available! Eine neue Cockatrice-Version ist verfügbar! - - + + New version Neue Version - - + + Released Veröffentlicht - - + + Changelog Änderungsprotokoll - + Do you want to update now? Möchten Sie jetzt aktualisieren? - - Unfortunately there are no download packages available for your operating system. -You may have to build from source yourself. - Leider sind keine Pakete zum Download für Ihr Betriebssystem verfügbar. -Eventuell müssen Sie die Quellen selbst kompilieren. + + Unfortunately, the automatic updater failed to find a compatible download. +You may have to manually download the new version. + Unfortunately there are no download packages available for your operating system. +You may have to build from source yourself. + - - Please check the download page manually and visit the wiki for instructions on compiling. - Bitte überprüfen Sie die Downloadseite manuell und besuchen Sie das Wiki für Kompilieranweisungen. + + Please check the <a href="%1">releases page</a> on our Github and download the build for your system. + Please check the download page manually and visit the wiki for instructions on compiling. + - - - + + + Update Error Aktualisierungsfehler - + An error occurred while checking for updates: Während der Suche nach Aktualisierungen ist ein Fehler aufgetreten: - + An error occurred while downloading an update: Während des Herunterladens der Aktualisierung ist ein Fehler aufgetreten: - + Installing... Installiere... - + Cockatrice is unable to open the installer. Cockatrice kann das Installationsprogramm nicht öffnen. - + Try to update manually by closing Cockatrice and running the installer. Versuchen Sie manuell zu aktualisieren, indem Sie Cockatrice beenden und das Installationsprogramm aufrufen. - + Download location Downloadverzeichnis @@ -2088,6 +2846,14 @@ Eventuell müssen Sie die Quellen selbst kompilieren. Fehlerprotokoll + + EdhrecCommanderResponseCommanderDetailsDisplayWidget + + + Salt: + Salt: + + FilterBuilder @@ -2099,105 +2865,120 @@ Eventuell müssen Sie die Quellen selbst kompilieren. GameSelector - - - - - - - - - + + + + + + + + + Error Fehler - + Please join the appropriate room first. Bitte betreten Sie erst den entsprechenden Raum. - + Wrong password. Falsches Passwort. - + Spectators are not allowed in this game. In diesem Spiel sind keine Zuschauer zugelassen. - + The game is already full. Das Spiel ist bereits voll. - + The game does not exist any more. Dieses Spiel gibt es nicht mehr. - + This game is only open to registered users. Dieses Spiel kann nur von registrierten Benutzern betreten werden. - + This game is only open to its creator's buddies. Dieses Spiel kann nur von Freunden des Erstellers betreten werden. - + You are being ignored by the creator of this game. Der Ersteller dieses Spiels ignoriert Sie. - + + Join Game + Spiel beitreten + + + + Spectate Game + Spiel beobachten + + + + Game Information + Spielinformationen + + + Join game Spiel beitreten - + Password: Passwort: - + Please join the respective room first. Bitte betreten Sie zuerst den entsprechenden Raum. - + &Filter games Spiele &filtern - + C&lear filter Filter &zurücksetzen - + C&reate E&rstellen - + &Join &Beitreten - + J&oin as spectator Als Zuschauer beitreten - + Games shown: %1 / %2 Angezeigte Spiele: %1 / %2 - + Games Spiele @@ -2205,100 +2986,100 @@ Eventuell müssen Sie die Quellen selbst kompilieren. GamesModel - + >1 day >1 Tag - + %1%2 hr short age in hours %1%2 h%1%2 h - + new neu - + %1%2 min short age in minutes %1%2 min%1%2 min - + password Passwort - + buddies only nur Freunde - + reg. users only nur reg. Benutzer - - + + can chat können chatten - + see hands sehen Hände - + can see hands können Hände sehen - + not allowed nicht erlaubt - + Room Raum - + Age Alter - + Description Beschreibung - + Creator Ersteller - + Type Typ - + Restrictions Bedingungen - + Players Spieler - + Spectators Zuschauer @@ -2306,92 +3087,102 @@ Eventuell müssen Sie die Quellen selbst kompilieren. GeneralSettingsPage - + Reset all paths Alle Pfade zurücksetzen - + All paths have been reset Alle Pfade wurden zurückgesetzt - - + + Choose path Pfad auswählen - + Personal settings Persönliche Einstellungen - + Language: Sprache: - + Paths (editing disabled in portable mode) Pfade (Anpassungen im portablen Modus deaktiviert) - + Paths Pfade - + + How to help with translations + + + + Decks directory: Verzeichnis mit Decklisten: - + Replays directory: Verzeichnis mit aufgezeichneten Spielen: - + Pictures directory: Verzeichnis mit Bilddateien: - + Card database: Kartendatenbank: - + Custom database directory: Verzeichnis für benutzerdefinierte Datenbank: - + Token database: Spielsteindatenbank: - + Update channel Aktualisierungskanal - + + Check for client updates on startup + Nach Aktualisierungen des Klienten beim Start der Anwendung suchen + + + Notify if a feature supported by the server is missing in my client Benachrichtigung wenn ein vom Server unterstütze Funktion in meinem Client fehlt - + Automatically run Oracle when running a new version of Cockatrice Starte Oracle automatisch, wenn eine neue Version von Cockatrice gestartet wird - + Show tips on startup Zeige Tipps beim Start @@ -2399,61 +3190,61 @@ Eventuell müssen Sie die Quellen selbst kompilieren. MainWindow - - + + The server has reached its maximum user capacity, please check back later. Der Server hat seine maximale Benutzeranzahl erreicht. Bitte versuchen Sie es später noch einmal. - + There are too many concurrent connections from your address. Es gibt zu viele gleichzeitige Verbindungen von Ihrer Adresse. - + Banned by moderator Gebannt von einem Moderator - + Expected end time: %1 Voraussichtliches Ende: %1 - + This ban lasts indefinitely. Dieser Bann ist unbefristet. - + Scheduled server shutdown. Planmäßige Serverabschaltung. - - + + Invalid username. Ungültiger Benutzername. - + You have been logged out due to logging in at another location. Sie wurden abgemeldet, da Sie sich an einer anderen Stelle angemeldet haben. - + Connection closed Verbindung geschlossen - + The server has terminated your connection. Reason: %1 Der Server hat Ihre Verbindung beendet. Grund: %1 - + The server is going to be restarted in %n minute(s). All running games will be lost. Reason for shutdown: %1 @@ -2464,558 +3255,568 @@ Alle laufenden Spiele werden beendet. Grund für die Abschaltung: %1 - + Scheduled server shutdown Planmäßige Serverabschaltung - - + + Success Erfolgreich - + Registration accepted. Will now login. Registrierung angenommen. Login läuft. - + Account activation accepted. Will now login. Accountaktivierung angenommen. Login läuft. - + Number of players Spieleranzahl - + Please enter the number of players. Bitte die Spieleranzahl eingeben: - - + + Player %1 Spieler %1 - + Load replay Aufgezeichnetes Spiel laden - + About Cockatrice Über Cockatrice - + Version Version - + Cockatrice Webpage Cockatrice Webseite - + Project Manager: Projektmanager: - + Past Project Managers: frühere Projektmanager: - + Developers: Entwickler: - + Our Developers unsere Entwickler - + Help Develop! Hilf bei der Entwicklung! - + Translators: Übersetzer: - + Our Translators Unsere Übersetzer - + Help Translate! Hilf bei der Übersetzung! - + Support: Unterstützung: - + Report an Issue Problem melden - + Troubleshooting Fehlersuche - + F.A.Q. F.A.Q. - - - - - - - - - - - - - - - + + + + + + + + + + + + + - - - + + + + + Error Fehler - + Server timeout Server Zeitüberschreitung - + Failed Login Anmeldung fehlgeschlagen - + Your client seems to be missing features this server requires for connection. Ihrem Client scheinen Funktionen, die der Server für die Verbindung benötigt, zu fehlen. - + To update your client, go to 'Help -> Check for Client Updates'. Um Ihren Client zu aktualisieren, gehen Sie auf 'Hilfe -> Auf Clientaktualisierungen prüfen'. - + Incorrect username or password. Please check your authentication information and try again. Falscher Benutzername oder Passwort. Bitte überprüfe deine Login Informationen und versuche es erneut. - + There is already an active session using this user name. Please close that session first and re-login. Es gibt bereits eine aktive Verbindung mit diesem Benutzernamen. Bitte schließen Sie diese Verbindung zuerst und versuchen Sie es dann erneut. - - + + You are banned until %1. Sie sind gebannt bis: %1. - - + + You are banned indefinitely. Sie sind auf unbestimmte Zeit gebannt. - + This server requires user registration. Do you want to register now? Diese Server benötigt eine Benutzerregistrierung. Möchten Sie sich jetzt registrieren? - + This server requires client IDs. Your client is either failing to generate an ID or you are running a modified client. Please close and reopen your client to try again. Dieser Server verlangt Client-IDs. Entweder das Erzeugen der ID schlägt fehl oder Sie verwenden einen modifizierten Client. Bitte starten Sie ihren Client neu, um es erneut zu versuchen. - + An internal error has occurred, please close and reopen Cockatrice before trying again. If the error persists, ensure you are running the latest version of the software and if needed contact the software developers. Ein interner Fehler ist aufgetreten, bitte starten Sie Cockatrice neu, bevor Sie es erneut versuchen. Falls der Fehler erneut auftritt, versichern Sie sich, dass Sie die neuste Softwareversion verwenden, und kontaktieren Sie falls nötig die Softwareentwickler. - + Account activation Accountaktivierung - + Your account has not been activated yet. You need to provide the activation token received in the activation email. Ihr Account wurde noch nicht aktiviert. Sie müssen den Aktivierungstoken aus der Aktivierungsemail verwenden - + Server Full Server voll - + Unknown login error: %1 Unbekannter Login-Fehler: %1 - - + + This usually means that your client version is out of date, and the server sent a reply your client doesn't understand. Dies bedeutet normalerweise, dass Ihre Clientversion veraltet ist und der Server eine Antwort geschickt hat, die Ihr Client nicht versteht. - + Your username must respect these rules: Ihr Benutzername muss diesen Regeln entsprechen: - + is %1 - %2 characters long %1 - %2 Zeichen lang - + can %1 contain lowercase characters kann %1 Kleinbuchstaben beinhalten - - - - + + + + NOT NICHT - + can %1 contain uppercase characters kann %1 Großbuchstaben beinhalten - + can %1 contain numeric characters kann %1 Zahlen beinhalten - + can contain the following punctuation: %1 kann folgende Sonderzeichen beinhalten: %1 - + first character can %1 be a punctuation mark Das erste Zeichen kann %1 ein Sonderzeichen sein - + no unacceptable language as specified by these server rules: note that the following lines will not be translated Keine inakzeptable Sprache wie durch folgende Serverregeln festgelegt: - + can not contain any of the following words: %1 kann keines der folgenden Wörter enthalten: %1 - + can not match any of the following expressions: %1 kann keinem der folgenden Ausdrücke entsprechen: %1 - + You may only use A-Z, a-z, 0-9, _, ., and - in your username. Sie können nur A-Z, a-z, 0-9, _, ., und - in Ihrem Benutzernamen verwenden. - - - - - - + + + + + + Registration denied Registrierung verweigert - + Registration is currently disabled on this server Momentan ist die Registrierung auf diesem Server deaktiviert - + There is already an existing account with the same user name. Es existiert bereits ein Account mit dem gleichen Benutzernamen. - + It's mandatory to specify a valid email address when registering. Es ist erforderlich, eine korrekte Emailadresse während der Registrierung anzugeben. - + It appears you are attempting to register a new account on this server yet you already have an account registered with the email provided. This server restricts the number of accounts a user can register per address. Please contact the server operator for further assistance or to obtain your credential information. Scheinbar versuchen Sie, ein neues Benutzerkonto auf diesem Server zu registrieren, obwohl Sie bereits ein Konto mit der verwendeten E-Mailadresse haben. Dieser Server beschränkt die Anzahl an Benutzerkonten, die ein Nutzer pro E-Mailadresse registrieren kann. Bitte kontaktieren Sie den Server-Betreiber für weitere Hilfe oder um Ihre Zugangsinformationen zu erhalten. - + Password too short. Passwort zu kurz. - + Registration failed for a technical problem on the server. Registrierung aufgrund eines technischen Problems des Servers fehlgeschlagen. - + Unknown registration error: %1 Unbekannter Registrierungsfehler: %1 - + Account activation failed Accountaktivierung fehlgeschlagen - + Socket error: %1 Netzwerkfehler: %1 - + You are trying to connect to an obsolete server. Please downgrade your Cockatrice version or connect to a suitable server. Local version is %1, remote version is %2. Sie versuchen sich an einem veralteten Server anzumelden. Bitte verwenden Sie eine ältere Cockatrice-Version oder melden Sie sich an einem aktuellen Server an. Lokale Version ist %1, Serverversion ist %2. - + Your Cockatrice client is obsolete. Please update your Cockatrice version. Local version is %1, remote version is %2. Ihr Cockatrice-Client ist veraltet. Bitte laden Sie sich die neueste Version herunter. Lokale Version ist %1, Serverversion ist %2. - + Connecting to %1... Verbinde zu %1... - + Registering to %1 as %2... Registrierung bei %1 als %2... - + Disconnected nicht verbunden - + Connected, logging in at %1 Verbunden, Anmeldung bei %1 - - - + + + Requesting forgotten password to %1 as %2... Fordere vergessenes Password von %1 als %2 an... - + &Connect... &Verbinden... - + &Disconnect Verbindung &trennen - + Start &local game... &Lokales Spiel starten... - + &Watch replay... &Aufgezeichnetes Spiel abspielen... - - &Deck editor - &Deckeditor - - - + &Full screen &Vollbild - + &Register to server... Auf Server &registrieren... - + &Restore password... Passwort wiede&rherstellen... - + &Settings... &Einstellungen... - + &Exit &Beenden - + A&ctions Aktionen - + &Cockatrice &Cockatrice - + C&ard Database K&artendatenbank - + &Manage sets... Editionen &verwalten... - + Edit custom &tokens... Bearbeite benutzerdefinierte Spiels&teine... - + Open custom image folder Öffne den Ordner für benutzerdefinierte Kartenbilder - + Open custom sets folder Öffne den Ordner für benutzerdefinierte Sets - + Add custom sets/cards Benutzerdefinierte Karten/Sets hinzufügen - + + Reload card database + Neu laden der Kartendatenbank + + + + Tabs + Reiter + + + &Help &Hilfe - + &About Cockatrice &Über Cockatrice - + &Tip of the Day &Tipp des Tages - + Check for Client Updates Auf Clientaktualisierungen prüfen - + Check for Card Updates... Nach Kartenaktualisierungen suchen... - + View &Debug Log Fehlerprotokoll ansehen - + + Open Settings Folder + Einstellungsordner öffnen + + + Show/Hide Ein-/Ausblenden - + New Version Neue Version - + Congratulations on updating to Cockatrice %1! Oracle will now launch to update your card database. Herzlichen Glückwunsch zur Aktualisierung auf Cockatrice %1! Oracle wird gestartet um ihre Kartendatenbank zu aktualisieren. - + Cockatrice installed Cockatrice wurde installiert - + Congratulations on installing Cockatrice %1! Oracle will now launch to install the initial card database. Herzlichen Glückwunsch, dass Sie Cockatrice %1 installiert haben! Oracle wird jetzt gestartet und installiert die initiale Kartendatenbank. - + Card database Kartendatenbank - + Cockatrice is unable to load the card database. Do you want to update your card database now? If unsure or first time user, choose "Yes" @@ -3024,29 +3825,29 @@ Möchten Sie Ihre Kartendatenbank jetzt aktualisieren? Falls Sie unsicher sind oder Cockatrice das erste Mal nutzen, wählen Sie „Ja“ - - + + Yes Ja - - + + No Nein - + Open settings Einstellungen öffnen - + New sets found Neue Editionen gefunden - + %n new set(s) found in the card database Set code(s): %1 Do you want to enable it/them? @@ -3057,17 +3858,17 @@ Setcode(s): %1 Möchten Sie es/sie aktivieren? - + View sets Editionen ansehen - + Welcome Willkommen - + Hi! It seems like you're running this version of Cockatrice for the first time. All the sets in the card database have been enabled. Read more about changing the set order or disabling specific sets and consequent effects in the "Manage Sets" dialog. @@ -3076,58 +3877,59 @@ Alle Editionen der Kartendatenbank wurden aktiviert. Lesen Sie mehr über das Ändern der Editionsreihenfolge oder die Deaktivierung bestimmter Editionen im „Editionen verwalten...“ Fenster. - - + + Information Information - + A card database update is already running. Eine Datenbankaktualisierung wird bereits durchgeführt. - + Unable to run the card database updater: Kartendatenbankaktualisierung nicht ausführbar: - - failed to start. - Start fehlgeschlagen. + + Failed to start. The file might be missing, or permissions might be incorrect. + - - crashed. - abgestürzt. + + The process crashed some time after starting successfully. + - - timed out. - Zeitüberschreitung. + + Timed out. The process took too long to respond. The last waitFor...() function timed out. + - - write error. - Fehler beim Schreiben. + + An error occurred when attempting to write to the process. For example, the process may not be running, or it may have closed its input channel. + - - read error. - Fehler beim Lesen. + + An error occurred when attempting to read from the process. For example, the process may not be running. + - - unknown error. - Unbekannter Fehler. + + Unknown error occurred. + - - The card database updater exited with an error: %1 - Die Kartendatenbankaktualisierung brach mit einem Fehler ab: %1 + + The card database updater exited with an error: +%1 + - + This server supports additional features that your client doesn't have. This is most likely not a problem, but this message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -3138,55 +3940,55 @@ Dies stellt wahrscheinlich kein Problem dar, allerdings könnte dies bedeuten, d Um Ihren Client zu aktualisieren, navigieren Sie zu Hilfe -> Auf Aktualisierungen prüfen. - - - - - + + + + + Load sets/cards Lade Sets/Karten - + Selected file cannot be found. Die ausgewählte Datei wurde nicht gefunden. - + You can only import XML databases at this time. Im Moment ist es nur möglich XML Datenbanken zu importieren. - + The new sets/cards have been added successfully. Cockatrice will now reload the card database. Die neuen Sets/Karten wurden erfolgreich hinzugefügt. Cockatrice wird jetzt die Kartendatenbank neu laden. - + Sets/cards failed to import. Set-/Kartenimport fehlgeschlagen. - - - + + + Reset Password Passwort zurücksetzen - + Your password has been reset successfully, you can now log in using the new credentials. Ihr Passwort wurde erfolgreich zurückgesetzt, Sie können sich jetzt mit den neuen Anmeldedaten anmelden. - + Failed to reset user account password, please contact the server operator to reset your password. Passwortzurücksetzung fehlgeschlagen. Bitte kontaktieren Sie den Serverbetreiber, um Ihr Passwort zurücksetzen zu lassen. - + Activation request received, please check your email for an activation token. Aktivierungsanfrage erhalten. Bitte prüfen Sie Ihre E-Mails nach einem Aktivierungstoken. @@ -3460,179 +4262,190 @@ Cockatrice wird jetzt die Kartendatenbank neu laden. - %1 is looking at the top %3 card(s) %2. + %1 is looking at the %4 %3 card(s) %2. + %1 is looking at the top %3 card(s) %2. top card for singular, top %3 cards for plural - %1 sieht sich die oberste %3 Karte %2 an.%1 sieht sich die obersten %3 Karten %2 an. + %1 sieht sich die %4 %3 Karte(n) %2 an.%1 sieht sich die %4 %3 Karte(n) %2 an. - + + bottom + untersten + + + + top + obersten + + + %1 turns %2 face-down. %1 wendet %2 auf die Rückseite. - + %1 turns %2 face-up. %1 wendet %2 auf die Vorderseite. - + The game has been closed. Das Spiel wurde geschlossen. - + The game has started. Das Spiel hat begonnen. - + %1 has joined the game. %1 ist dem Spiel beigetreten. - + %1 is now watching the game. %1 schaut nun dem Spiel zu. - + You have been kicked out of the game. Sie wurden aus dem Spiel geworfen. - + %1 has left the game (%2). %1 hat das Spiel verlassen (%2). - + %1 is not watching the game any more (%2). %1 schaut dem Spiel nicht mehr zu (%2). - + %1 is not ready to start the game any more. %1 ist nicht mehr bereit, das Spiel zu starten. - + %1 shuffles their deck and draws a new hand of %2 card(s). %1 mischt sein Deck und zieht eine Hand mit %2 Karte(n).%1 mischt sein Deck und zieht eine neue Hand mit %2 Karte(n). - + %1 shuffles their deck and draws a new hand. %1 mischt sein Deck und zieht eine neue Hand. - + You are watching a replay of game #%1. Sie sehen eine Aufzeichnung des Spiels #%1. - + %1 is ready to start the game. %1 ist bereit, das Spiel zu starten. - + cards an unknown amount of cards Karten - + %1 card(s) a card for singular, %1 cards for plural %1 Karte(n)%1 Karte(n) - + %1 lends %2 to %3. %1 verleiht %2 an %3. - + %1 reveals %2 to %3. %1 zeigt %3 %2. - + %1 reveals %2. %1 zeigt %2 offen vor. - + %1 randomly reveals %2%3 to %4. %1 zeigt %4 zufällig %2%3 vor. - + %1 randomly reveals %2%3. %1 zeigt zufällig %2%3 offen vor. - + %1 peeks at face down card #%2. %1 schaut sich die umgedrehte Karte #%2 an. - + %1 peeks at face down card #%2: %3. %1 schaut sich die umgedrehte Karte #%2 an: %3. - + %1 reveals %2%3 to %4. %1 zeigt %4 %2%3 vor. - + %1 reveals %2%3. %1 zeigt %2%3 offen vor. - + %1 reversed turn order, now it's %2. %1 kehrte die Zugreihenfolge um, nun ist es %2. - + reversed umgekehrt - + normal normal - + Heads Kopf - + Tails Zahl - + %1 flipped a coin. It landed as %2. %1 warf eine Münze. Es fiel %2. - + %1 rolls a %2 with a %3-sided die. %1 würfelt eine %2 mit einem %3-seitigen Würfel. - + %1 flips %2 coins. There are %3 heads and %4 tails. %1 wirft %2 Münzen. Es ergab %3 mal Kopf und %4 mal Zahl. - + %1 rolls a %2-sided dice %3 times: %4. %1 wirft einen %2-seitigen Würfel %3 mal: %4. @@ -3752,17 +4565,17 @@ Cockatrice wird jetzt die Kartendatenbank neu laden. %1 mischt die Karten %3 - %4 von %2. - + %1 unattaches %2. %1 löst %2 ab. - + %1 undoes their last draw. %1 legt die zuletzt gezogene Karte zurück. - + %1 undoes their last draw (%2). %1 legt die zuletzt gezogene Karte zurück (%2) @@ -3770,110 +4583,110 @@ Cockatrice wird jetzt die Kartendatenbank neu laden. MessagesSettingsPage - + Word1 Word2 Word3 Wort1 Wort2 Wort3 - + Add New Message Neue Nachricht hinzufügen - + Edit Message Nachricht bearbeiten - + Remove Message Nachricht entfernen - + Add message Nachricht hinzufügen - - + + Message: Nachricht: - + Edit message Nachricht bearbeiten - + Chat settings Chat Einstellungen - + Custom alert words Benutzerdefinierte Benachrichtigungswörter - + Enable chat mentions Chat Erwähnungen aktivieren - + Enable mention completer Autovervollständigung aktivieren - + In-game message macros Makros für Nachrichten in Spielen - + How to use in-game message macros Anleitung zum Verwenden der Makros für Nachrichten in Spielen - + Ignore chat room messages sent by unregistered users Nachrichten von unregistrierten Benutzern im Chatroom ignorieren - + Ignore private messages sent by unregistered users Private Nachrichten von unregistrierten Benutzern ignorieren - - + + Invert text color Textfarbe invertieren - + Enable desktop notifications for private messages Desktop Benachrichtigungen für private Nachrichten aktivieren - + Enable desktop notification for mentions Desktop Benachrichtigungen für Erwähnungen aktivieren - + Enable room message history on join Nachrichtenverlauf beim Betreten eines Raumes aktivieren - - + + (Color is hexadecimal) (Farbcode in hexadezimal) - + Separate words with a space, alphanumeric characters only Wörter durch Leerzeichen trennen, nur alphanumerische Zeichen @@ -3931,6 +4744,24 @@ Cockatrice wird jetzt die Kartendatenbank neu laden. Farbidentität + + OverlapControlWidget + + + Cards to overlap: + Karten, die sich überschneiden: + + + + Overlap percentage: + Prozentzahl der Überschneidungen: + + + + Overlap direction: + Richtung der Überschreitung: + + Phase @@ -3997,57 +4828,57 @@ Cockatrice wird jetzt die Kartendatenbank neu laden. PhasesToolbar - + Untap step Enttappsegment - + Upkeep step Versorgungssegment - + Draw step Ziehsegment - + First main phase erste Hauptphase - + Beginning of combat step Anfangssegment der Kampfphase - + Declare attackers step Angreifer-Deklarieren-Segment - + Declare blockers step Blocker-Deklarieren-Segment - + Combat damage step Kampfschadenssegment - + End of combat step Endsegment der Kampfphase - + Second main phase zweite Hauptphase - + End of turn step Ende-des-Zuges-Segment @@ -4055,7 +4886,7 @@ Cockatrice wird jetzt die Kartendatenbank neu laden. PictureLoader - + en code for scryfall's language property, not available for all languages de @@ -4064,647 +4895,781 @@ Cockatrice wird jetzt die Kartendatenbank neu laden. Player - + Reveal top cards of library Zeige die obersten Karten der Bibliothek - - - - - - - - - + + + + + + + + + + + + Number of cards: (max. %1) Anzahl an Karten: (max. %1) - + &View graveyard &Zeige Friedhof - + &View exile &Zeige Exil - + Player "%1" Spieler "%1" - - - - + + + + &Graveyard &Friedhof - - - - + + + + &Exile &Exil - + &Move hand to... Lege Handkarten ... - - - + + + &Top of library auf die Bibliothek - - - + + + &Bottom of library unter die Bibliothek - + &Move graveyard to... Verschiebe Friedhof... - - - - + + + + &Hand &Hand - + &Move exile to... Verschiebe Exil... - + &View library Bibliothek &ansehen - + &View hand Hand ansehen - + View &top cards of library... Oberste Karten der Bibliothek a&nsehen... - + Reveal &library to... Zeige Bibliothek... - + &Always reveal top card &Oberste Karte aufgedeckt lassen - + &View sideboard Zeige &Sideboard - + &Draw card Karte &ziehen - + D&raw cards... Ka&rten ziehen... - + &Undo last draw Zuletzt gezogene Karte zur&ücklegen - + Take &mulligan &Mulligan nehmen - - &Shuffle - Mi&schen - - - + Play top card &face down Oberste Karte verdeckt spielen - + Move top card to grave&yard Oberste Karte auf den F&riedhof legen - + Move top card to e&xile Oberste Karte ins E&xil schicken - + Move top cards to &graveyard... Oberste Karten auf den F&riedhof legen... - + Move top cards to &exile... Oberste Karten ins &Exil schicken... - + Put top card on &bottom Oberste Karte nach &unten legen - + + View bottom cards of library... + Unterste Karten der Bibliothek ansehen... + + + Lend library to... Verleihe Bibliothek an... - + + Shuffle + + + + Put top cards on stack &until... Take top cards &until... Lege oberste Karten auf den Stapel bis... - + + Shuffle top cards... + + + + + Shuffle bottom cards... + + + + &Reveal hand to... Zeige meine Hand... - + Reveal r&andom card to... Zeige eine zufällige Karte... - + Reveal random card to... Zeige eine zufällige Karte an... - + &Sideboard &Sideboard - + &Library Bib&liothek - + &Counters &Zähler - + &Untap all permanents &Enttappe alle bleibenden Karten - + R&oll die... &Würfeln... - + &Create token... Spiels&tein erstellen... - + C&reate another token &Noch einen Spielstein erstellen - + Cr&eate predefined token &Vordefinierten Spielstein erstellen - - + + &All players &allen Spielern - + S&ay S&agen - + + &Select All + &Alle auswählen + + + + S&elect Row + &Reihe auswählen + + + + S&elect Column + &Spalte auswählen + + + &Play &Ausspielen - + &Hide &Verstecken - + Play &Face Down Karte verdeckt spielen - + &Tap / Untap Turn sideways or back again &Tappen / Enttappen - + Toggle &normal untapping &Normales Enttappen umschalten - + T&urn Over Turn face up/face down Drehe her&um - + &Peek at card face &Vorderseite anschauen - + &Clone &Kopieren - + Attac&h to card... An Karte &anlegen... - + Unattac&h &Von Karte lösen - + &Draw arrow... &Pfeil zeichnen... - + &Increase power &Stärke erhöhen - + &Decrease power S&tärke verringern - + I&ncrease toughness &Widerstandskraft erhöhen - + D&ecrease toughness W&iderstandskraft verringern - + In&crease power and toughness Stärke und Widerstandskraft &erhöhen - + Dec&rease power and toughness Stärke und Widerstandskraft v&erringern - + Increase power and decrease toughness Erhöhe Stärke und verringere Widerstandskraft - + Decrease power and increase toughness Verringere Stärke und erhöhe Widerstandskraft - + Set &power and toughness... Stär&ke und Widerstandskraft setzen... - + Reset p&ower and toughness Stärke und Widerstandskraft zurücksetzen - + &Set annotation... &Notiz setzen... - + Red Rot - + Yellow Gelb - + Green Grün - + &Add counter (%1) Marke &hinzufügen (%1) - + &Remove counter (%1) Marke &entfernen (%1) - + &Set counters (%1)... Marken &setzen (%1)... - + &Top of library in random order Oberseite der Bibliothek in zufälliger Reihenfolge - + X cards from the top of library... X Karten vom Beginn der Bibliothek... - + &Bottom of library in random order Unter die Bibliothek in zufälliger Reihenfolge - + View top cards of library Zeige die obersten Karten der Bibliothek - + + View bottom cards of library + Unterste Karten der Bibliothek ansehen + + + + Shuffle top cards of library + + + + + Shuffle bottom cards of library + + + + Which position should this card be placed: An welche Stelle soll diese Karte gelegt werden: - + (max. %1) (max. %1) - + Draw hand Ziehe Hand - + 0 and lower are in comparison to current hand size 0 und weniger sind im Vergleich zur momentanen Handgröße - + Draw cards Karten ziehen - - + + Number: Anzahl: - + Move top cards to grave Oberste Karten in den Friedhof legen - + Reveal &top cards to... Zeige die obers&ten Karten... - + &Top of library... Obere Karte(n) der Bibliothek... - + &Bottom of library... Untere Karte(n) der Bibliothek... - + &Always look at top card &Oberste Karte immer betrachten - + &Open deck in deck editor Deck im Deckedit&or öffnen - + &Play top card Oberste Karte s&pielen - + &Draw bottom card Unterste Karte ziehen - + D&raw bottom cards... Unterste Karten ziehen... - + &Play bottom card Unterste Karte s&pielen - + Play bottom card &face down Unterste Karte verdeckt spielen - + Move bottom card to grave&yard Unterste Karte auf den Friedhof legen - + Move bottom card to e&xile Unterste Karte ins E&xil schicken - + Move bottom cards to &graveyard... Unterste Karten auf den Friedhof legen... - + Move bottom cards to &exile... Unterste Karten ins &Exil schicken... - + Put bottom card on &top Unterste Kar&te nach oben legen - + Selec&ted cards Ausgewähl&te Karten - + Move top cards to exile Oberste Karten ins Exil schicken - + Move bottom cards to grave Unterste Karten auf den Friedhof legen - + Move bottom cards to exile Unterste Karten ins Exil schicken - + Draw bottom cards Unterste Karten ziehen - - + + C&reate another %1 token Einen weiteren %1 Spielstein e&rstellen - + Create tokens Spielsteine erstellen - + Place card X cards from top of library Karte an x'ter Stelle von oben in die Bibliothek legen - + Change power/toughness Ändere Stärke/Widerstandskraft - + Change stats to: Ändere Werte auf: - + Set annotation Notiz setzen - + Please enter the new annotation: Bitte die Notiz eingeben: - + Set counters Setze Marken - + Re&veal to... Zeige... - + View related cards Zeige zugehörige Karten - + Token: Spielstein: - + All tokens Alle Spielsteine + + PrintingSelector + + + Display Navigation Buttons + + + + + <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. + + + + + PrintingSelectorCardOverlayWidget + + + Preference + Preferenz + + + + Pin Printing + Version festsetzen + + + + Unpin Printing + Version lösen + + + + Show Related cards + Zugehörige Karten anzeigen + + + + PrintingSelectorCardSearchWidget + + + Search by set name or set code + Suche nach Erweiterungsname oder Erweiterungskennzeichen + + + + PrintingSelectorCardSelectionWidget + + + Previous Card in Deck + Vorherige Karte im Deck + + + + Next Card in Deck + Nächste Karte im Deck + + + + PrintingSelectorCardSortingWidget + + + Alphabetical + Alphabetisch + + + + Preference + Präferenz + + + + Release Date + Erscheinungsdatum + + + + + Descending + Absteigend + + + + Ascending + Aufsteigend + + QMenuBar - + Services Dienste - + Hide %1 %1 ausblenden - + Hide Others Andere ausblenden - + Show All Alle anzeigen - + Preferences... Einstellungen... - + Quit %1 Schließe %1 - + About %1 Über %1 @@ -4712,38 +5677,32 @@ Cockatrice wird jetzt die Kartendatenbank neu laden. QObject - + Cockatrice card database (*.xml) Cockatrice Kartendatenbank (*.xml) - - + All files (*.*) Alle Dateien (*.*) - + Cockatrice replays (*.cor) Aufgezeichnete Cockatrice-Spiele (*.cor) - - Common deck formats (*.cod *.dec *.dek *.txt *.mwDeck) - Standarddeckformate (*.cod *.dec *.dek *.txt *.mwDeck) - - - + Maindeck Hauptdeck - + Sideboard Sideboard - + Tokens Spielsteine @@ -4751,92 +5710,92 @@ Cockatrice wird jetzt die Kartendatenbank neu laden. QPlatformTheme - + OK OK - + Save Speichern - + Save All Alle speichern - + Open Öffnen - + &Yes Ja - + Yes to &All Ja zu &Allem - + &No &Nein - + N&o to All Nein zu Allem - + Abort Abbrechen - + Retry Erneut versuchen - + Ignore Ignorieren - + Close Schließen - + Cancel Abbrechen - + Discard Verwerfen - + Help Hilfe - + Apply Anwenden - + Reset Zurücksetzen - + Restore Defaults Standard wiederherstellen @@ -4844,17 +5803,17 @@ Cockatrice wird jetzt die Kartendatenbank neu laden. RemoteDeckList_TreeModel - + Name Name - + ID ID - + Upload time Änderungsdatum @@ -4862,32 +5821,32 @@ Cockatrice wird jetzt die Kartendatenbank neu laden. RemoteReplayList_TreeModel - + ID ID - + Name Name - + Players Spieler - + Keep Behalten - + Time started Startzeit - + Duration (sec) Länge (Sekunden) @@ -4933,32 +5892,32 @@ Cockatrice wird jetzt die Kartendatenbank neu laden. SequenceEdit - + Choose an action from the table Wähle eine Aktion aus der Tabelle - + Hit the key/combination of keys you want to set for this action Gebe die für diese Aktion gewünschte Taste/Tastenkombination ein - - Shortcut already in use - Tastenkürzel wird bereits verwendet - - - + Invalid key Ungültige Taste - + + Shortcut already in use by: + Tastenkürzel bereits in Verwendung durch: + + + Clear Löschen - + Restore default Auf Standard zurücksetzen @@ -4994,73 +5953,81 @@ Cockatrice wird jetzt die Kartendatenbank neu laden. ShortcutSettingsPage - - + + Restore all default shortcuts Alle Standard-Tastaturkürzel wiederherstellen - + Do you really want to restore all default shortcuts? Möchten Sie wirklich alle Standard-Tastaturkürzel wiederherstellen? - + Clear all default shortcuts Alle Standard-Tastaturkürzel entfernen - + Do you really want to clear all shortcuts? Möchten Sie wirklich alle Tastaturkürzel entfernen? - - Action - Aktion - - - - Shortcut - Tastaturkürzel - - - + Section: Abschnitt: - + Action: Aktion: - + Shortcut: Tastaturkürzel: - + How to set custom shortcuts Wie werden benutzerdefinierte Tastaturkürzel gesetzt - + Clear all shortcuts Alle Tastaturkürzel entfernen + + + Search by shortcut name + Suchen nach Name des Tastenkürzels + + + + ShortcutTreeView + + + Action + Aktion + + + + Shortcut + Abkürzung + ShortcutsSettings - + Your configuration file contained invalid shortcuts. Please check your shortcut settings! Ihre Konfigurationsdatei enthielt ungültige Verknüpfungen. Bitte überprüfen Sie die Verknüpfungseinstellungen! - + The following shortcuts have been set to default: Die folgenden Verknüpfungen wurden auf den Standard gesetzt: @@ -5070,17 +6037,17 @@ Bitte überprüfen Sie die Verknüpfungseinstellungen! ShutdownDialog - + &Reason for shutdown: G&rund für die Abschaltung: - + &Time until shutdown (minutes): &Zeit bis zur Abschaltung (Minuten): - + Shut down server Server abschalten @@ -5088,27 +6055,27 @@ Bitte überprüfen Sie die Verknüpfungseinstellungen! SoundSettingsPage - + Enable &sounds Töne aktivieren - + Current sounds theme: Aktuelles Ton-Theme: - + Test system sound engine Systemsound testen - + Sound settings Toneinstellungen - + Master volume Masterlautstärke @@ -5116,48 +6083,48 @@ Bitte überprüfen Sie die Verknüpfungseinstellungen! SpoilerBackgroundUpdater - + Spoilers season has ended Spoiler Saison ist beendet - + Deleting spoiler.xml. Please run Oracle spoiler.xml gelöscht. Bitte führe Oracle aus - - + + Spoilers download failed Spoiler Download fehlgeschlagen - + No internet connection Keine Internetverbindung - + Error Fehler - + Spoilers already up to date Spoiler bereits aktuell - + No new spoilers added Keine neuen Spoiler hinzugefügt - + Spoilers have been updated! Spoiler wurden aktualisiert! - + Last change: Letzte Änderung: @@ -5165,638 +6132,594 @@ Bitte überprüfen Sie die Verknüpfungseinstellungen! StableReleaseChannel - - Stable Releases - Stabile Veröffentlichungen + + Default + Standard - + No reply received from the release update server. Keine Antwort vom Veröffentlichungsaktualisierungsserver erhalten. - + Invalid reply received from the release update server. Ungültige Antwort vom Veröffentlichungsaktualisierungsserver erhalten. - + No reply received from the tag update server. Keine Antwort vom Kennzeichnungsaktualisierungsserver. - + Invalid reply received from the tag update server. Ungültige Antwort vom Kennzeichungsaktualisierungsserver. + + TabAccount + + + Add to Buddy List + Zur Freundesliste hinzufügen + + + + Add to Ignore List + Zur Liste ignorierter Benutzer hinzufügen + + + + Account + Benutzerkonto + + TabAdmin - + Administration Administration - + Update server &message Server&nachricht aktualisieren - + &Shut down server &Server abschalten - + &Reload configuration Konfigu&ration neuladen - + Server administration functions Funktionen zur Serverwartung - + + Server moderator functions + Servermoderationsfunktionen + + + + Replay ID + ID der Wiederholung + + + + Grant Replay Access + Zugang zur Wiederholung gewähren + + + + Username to Activate + Benutzername zur Aktivierung + + + + Force Activate User + Zwangsaktivierung des Benutzers + + + &Unlock functions &Sperre aufheben - + &Lock functions Funktionen s&perren + + + + Success + Erfolg + + + + Replay access granted + Zugang zur Wiederholung gewährt + + + + + + + + Error + Fehler + + + + Unable to grant replay access. Replay ID invalid + Kein Zugang zur Freigabe der Wiederholung möglich. ID der Wiederholung ungültig + + + + Unable to grant replay access. Internal error + Kein Zugang zur Freigabe der Wiederholung möglich. Interner Fehler + + + + User successfully activated + Benutzer erfolgreich freigeschaltet + + + + Unable to activate user. Username invalid + Benutzer nicht freischaltbar. Benutzername ungültig + + + + Unable to activate user. User already active + Benutzer nicht freischaltbar. Benutzer ist bereits freigeschaltet + + + + Unable to activate user. Internal error + Benutzer nicht freischaltbar. Interner Fehler + TabDeckEditor - - Search by card name (or search expressions) - Search by card name - Suche nach Kartennamen (oder Suchausdrücken) - - - - Add to Deck - Zu Deck hinzufügen - - - - Add to Sideboard - Zum Sideboard hinzufügen - - - - Show Related cards - Zeige zugehörige Karten - - - - &Clear all filters - Alle Filter entfernen - - - - Delete selected - Ausgewählten löschen - - - - Deck &name: - Deck&name: - - - - &Comments: - &Kommentare: - - - - Hash: - Hash: - - - - &New deck - &Neues Deck - - - - &Load deck... - Deck &laden... - - - - &Save deck - Deck &speichern - - - - Save deck &as... - Deck s&peichern unter... - - - - Load deck from cl&ipboard... - Deck aus &Zwischenablage laden... - - - - Save deck to clipboard - Deck in Zwischenablage speichern - - - - Annotated - Kommentiert - - - - Not Annotated - Nicht kommentiert - - - - &Print deck... - Deck &drucken... - - - - &Send deck to online service - &Sende Deck an Onlineservice - - - - Create decklist (decklist.org) - Erstelle Deckliste (decklist.org) - - - - Analyze deck (deckstats.net) - Analysiere Deck (deckstats.net) - - - - Analyze deck (tappedout.net) - Analysiere Deck (tappedout.net) - - - - &Close - S&chließen - - - - Add card to &maindeck - Karte zu&m Hauptdeck hinzufügen - - - - Add card to &sideboard - Karte zum &Sideboard hinzufügen - - - - &Remove row - Zeile entfe&rnen - - - - &Increment number - Anzahl er&höhen - - - - &Decrement number - Anzahl v&erringern - - - - &Deck Editor - &Deckeditor - - - - + Card Info Karteninformationen - - + Deck Deck - - + Filters Filter - + &View Ansicht - - - + + Printing + Auflage + + + + + + Visible Sichtbar - - - + + + + Floating Schwebend - + Reset layout Darstellung zurücksetzen - + Deck: %1 Deck: %1 - - - Are you sure? - Sind Sie sicher? - - - - The decklist has been modified. -Do you want to save the changes? - Die Deckliste wurde verändert. -Möchten Sie die Änderungen speichern? - - - - Load deck - Deck laden - - - - - - - - - Error - Fehler - - - - The deck could not be saved. - Das Deck konnte nicht gespeichert werden. - - - - Could not save remote deck - Konnte Deck nicht remote speichern - - - - - The deck could not be saved. -Please check that the directory is writable and try again. - Das Deck konnte nicht gespeichert werden. -Bitte überprüfen Sie, dass Sie Schreibrechte in dem Verzeichnis haben, und versuchen Sie es erneut. - - - - Save deck - Deck speichern - - - - There are no cards in your deck to be exported - Es werden in Ihrem Deck keine Karten exportiert werden - - - - No deck was selected to be saved. - Es wurde kein Deck zum Speichern ausgewählt. - TabDeckStorage - - Deck storage - Deck Ablage - - - + Local file system Lokales Dateisystem - + Server deck storage Deckablage auf dem Server - - + + Open in deck editor Im Deckeditor öffnen - + + Rename deck or folder + + + + Upload deck Deck hochladen - + Download deck Deck herunterladen - - + + + + New folder Neuer Ordner - - + + Delete Löschen - - + + Open decks folder + Deckordner öffnen + + + + Rename local folder + + + + + Rename local file + + + + + New name: + + + + + + + Error Fehler - + + Rename failed + + + + + Invalid deck file Ungültige Deckdatei - + Enter deck name Decknamen eingeben - + This decklist does not have a name. Please enter a name: Diese Deckliste hat keinen Namen. Bitte geben Sie einen Namen ein: - + Unnamed deck Unbenanntes Deck - + Failed to upload deck to server Hochladen des Decks zum Server fehlgeschlagen - + Delete local file Lokale Datei löschen - - - - Are you sure you want to delete "%1"? - Sind Sie sicher, dass Sie "%1" löschen möchten? + + Are you sure you want to delete the selected files? + Sind sie sicher, dass die ausgewählten Dateien gelöscht werden sollen? - + + Delete remote decks + Löschen der online gespeicherten Decks + + + + Are you sure you want to delete the selected decks? + Sind sie sicher, dass sie die ausgewählten Decks löschen wollen? + + + + Name of new folder: Name für den neuen Ordner: - - Delete remote folder - Verzeichnis auf Server löschen + + Deck Storage + Deckablage + + + + TabDeckStorageVisual + + + Visual Deck Storage + Visuelle Deckablage - - Delete remote deck - Deck auf dem Server löschen + + Error + + + + + Could not open deck at %1 + + + + + TabEdhRec + + + EDHREC: + EDHREC: TabGame - - - + + + Replay Wiederholung - - + + Game Spiel - - + + Player List Spielerliste - - + + Card Info Karteninformationen - - + + Messages Nachrichten - - + + Replay Timeline Zeitleiste der Aufzeichnung - + &Phases &Phasen - + &Game Spi&el - + Next &phase Nächste &Phase - + Next phase with &action Nächste Phase mit &Aktion - + Next &turn Nächster &Zug - + Reverse turn order Kehre Zugreihenfolge um - + &Remove all local arrows &Lokale Pfeile entfernen - + Rotate View Cl&ockwise Ansicht im Uhrzeigesinn drehen - + Rotate View Co&unterclockwise Ansicht gegen den Uhrzeigersinn drehen - + Game &information &Spielinformationen - + + Un&concede + Aufgabe zurückziehen + + + &Concede &Aufgeben - + &Leave game Spiel ver&lassen - + C&lose replay Wiederholung sch&ließen - + &Focus Chat Chat &fokussieren - + &Say: &Sagen: - + &View Ansicht - - - - + + + + Visible Sichtbar - - - - + + + + Floating Schwebend - + Reset layout Darstellung zurücksetzen - + Concede Aufgeben - + Are you sure you want to concede this game? Sind Sie sicher, dass Sie das Spiel aufgeben möchten? - + Unconcede Doch nicht aufgeben - + You have already conceded. Do you want to return to this game? Sie haben bereits aufgegeben. Möchten Sie zu diesem Spiel zurückkehren? - + Leave game Spiel verlassen - + Are you sure you want to leave this game? Sind Sie sicher, dass Sie das Spiel verlassen möchten? - + You are flooding the game. Please wait a couple of seconds. Sie senden zu viele Befehle. Bitte warte ein paar Sekunden. - + A player has joined game #%1 Ein Spieler ist Spiel #%1 beigetreten - + %1 has joined the game %1 ist dem Spiel beigetreten - + kicked by game host or moderator Von Spielhost oder Moderator gekickt - + player left the game Spieler hat das Spiel verlassen - + player disconnected from server Spieler hat die Verbindung zum Server getrennt - + reason unknown Grund unbekannt - + You have been kicked out of the game. Sie wurden aus dem Spiel geworfen. @@ -5809,153 +6732,153 @@ Bitte geben Sie einen Namen ein: Logs - - - + + + Time;SenderName;SenderIP;Message;TargetID;TargetName Zeit;SenderName;SenderIP;Nachricht;ZielID;ZielName - + Room Logs Raumlogs - + Game Logs Spielelogs - + Chat Logs Chatlogs - - + + Error Fehler - + You must select at least one filter. Sie müssen mindestens einen Filter auswählen. - + You have to select a valid number of days to locate. Sie müssen eine gültige Anzahl an zu lokalisierenden Tagen auswählen. - + Username: Benutzername: - + IP Address: IP-Adresse: - + Game Name: Spielname: - + GameID: Spiel-ID: - + Message: Nachricht: - + Main Room Hauptraum - + Game Room Spielraum - + Private Chat Privater Chat - + Past X Days: Die letzten X Tage: - + Today Heute - + Last Hour Letzte Stunde - + Maximum Results: Maximale Ergebnisse: - + At least one filter is required. The more information you put in, the more specific your results will be. Mindestens ein Filter ist notwendig. Je mehr Informationen Sie angeben, desto spezifischer fallen die Ergebnisse aus. - + Get User Logs Benutzerlogs anzeigen - + Clear Filters Filter entfernen - + Filters Filter - + Log Locations Logstandorte - + Date Range Datumsbereich - + Maximum Results Maximale Ergebnisse - - + + Message History Nachrichtenverlauf - + Failed to collect message history information. Das Sammeln von Nachrichtenverlaufsinformationen ist fehlgeschlagen. - + There are no messages for the selected filters. Keine Nachrichten für die gewählten Filter. @@ -5978,22 +6901,22 @@ Je mehr Informationen Sie angeben, desto spezifischer fallen die Ergebnisse aus. %1 - privater Chat - + This user is ignoring you, they cannot see your messages in main chat and you cannot join their games. Dieser Benutzer ignoriert dich. Er kann deine Nachrichten im Hauptchat nicht sehen und du kannst seinen Spielen nicht beitreten. - + Private message from Private Nachricht von - + %1 has left the server. %1 hat den Server verlassen. - + %1 has joined the server. %1 hat den Server betreten. @@ -6001,117 +6924,153 @@ Je mehr Informationen Sie angeben, desto spezifischer fallen die Ergebnisse aus. TabReplays - - Game replays - Aufgezeichnete Spiele - - - + Local file system Lokales Dateisystem - + Server replay storage Wiederholungsablage auf dem Server - - + + Watch replay Wiederholung abspielen - - + + Rename + Umbenennen + + + + + New folder + Neuer Ordner + + + + Delete Löschen - + + Open replays folder + Wiederholungsorder öffnen + + + Download replay Wiederholung herunterladen - + Toggle expiration lock automatische Löschung umschalten - + + Rename local folder + Umbenennen des lokalen Ordners + + + + Rename local file + Umbenennen der lokalen Datei + + + + New name: + Neuer Name: + + + + Error + Fehler + + + + Rename failed + Umbenennen fehlgeschlagen + + + + Name of new folder: + Name des neuen Ordners: + + + Delete local file Lokale Datei löschen - - Are you sure you want to delete "%1"? - Sind Sie sicher, dass Sie "%1" löschen möchten? + + Are you sure you want to delete the selected files? + Sind sie sicher, dass die ausgewählten Dateien gelöscht werden sollen? - - Downloading Replays - Wiederholungen herunterladen + + Are you sure you want to delete the selected replays? + Sind sie sicher, dass die ausgewählten Wiederholungen gelöscht werden sollen? - - Folder download is not yet supported. Please download replays individually. - Ordnerherunterladen wird noch nicht unterstützt. Laden Sie bitte die Replays einzeln herunter. - - - + Delete remote replay Wiederholung vom Server löschen - - Are you sure you want to delete the replay of game %1? - Sind Sie sicher, dass Sie das Replay des Spiels %1 löschen möchten? + + Game Replays + Aufgezeichnete Spiele TabRoom - + &Say: &Sagen: - + Chat Chat - + &Room &Raum - + &Leave room Raum ver&lassen - + &Clear chat Chat leeren - + Chat Settings... Chat Einstellungen - + mentioned you. hat dich erwähnt. - + Click to view Zum Anzeigen klicken. - + You are flooding the chat. Please wait a couple of seconds. Sie überfluten den Chatraum. Bitte warten Sie ein paar Sekunden. @@ -6124,30 +7083,30 @@ Je mehr Informationen Sie angeben, desto spezifischer fallen die Ergebnisse aus. Server - - - - + + + + Error Fehler - + Failed to join the server room: it doesn't exist on the server. Serverraumbeitritt fehlgeschlagen: der Raum existiert auf dem Server nicht. - + The server thinks you are in the server room but your client is unable to display it. Try restarting your client. Der Server denkt Sie seien im Serverraum, allerdings kann Ihr Client diesen nicht anzeigen. Versuchen Sie, Ihren Client neu zu starten. - + You do not have the required permission to join this server room. Sie haben nicht die erforderlichen Berechtigungen, um diesem Serverraum beizutreten. - + Failed to join the server room due to an unknown error: %1. Beitritt des Serverraums aufgrund eines unbekannten Fehlers fehlgeschlagen: %1. @@ -6155,32 +7114,72 @@ Je mehr Informationen Sie angeben, desto spezifischer fallen die Ergebnisse aus. TabSupervisor - + + Deck Editor + Deckeditor + + + + &Visual Deck Storage + &Visuelle Deckablage + + + + Server + Server + + + + Account + Benutzerkonto + + + + Deck Storage + Deckablage + + + + Game Replays + Aufgezeichnete Spiele + + + + Administration + Administration + + + + Logs + Logs + + + Are you sure? Sind Sie sicher? - + There are still open games. Are you sure you want to quit? Es sind noch Spiele offen. Wollen Sie das Programm trotzdem beenden? - + Click to view Zum Anzeigen klicken - + Your buddy %1 has signed on! Dein Freund %1 hat sich eingeloggt! - + Unknown Event Unbekanntes Ereignis - + The server has sent you a message that your client does not understand. This message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -6191,63 +7190,45 @@ Dies könnte bedeuten, dass eine neue Version von Cockatrice verfügbar ist oder Um Ihren Client zu aktualisieren, navigieren Sie zu Hilfe -> Auf Aktualisierungen prüfen. - + Idle Timeout Inaktivitätszeitüberschreitung - + You are about to be logged out due to inactivity. Sie werden wegen Inaktivität abgemeldet. - + Promotion Beförderung - + You have been promoted. Please log out and back in for changes to take effect. Sie wurden befördert. Bitte loggen Sie sich aus und wieder ein, damit die Änderungen in Kraft treten. - + Warned Gewarnt - + You have received a warning due to %1. Please refrain from engaging in this activity or further actions may be taken against you. If you have any questions, please private message a moderator. Sie haben eine Warnung erhalten aufgrund von %1. Bitte unterlassen Sie diese Aktivitäten oder weitere Schritte werden gegen Sie eingeleitet. Sollten Sie Fragen haben, stellen Sie diese bitte über eine private Nachricht an einen Moderator. - + You have received the following message from the server. (custom messages like these could be untranslated) Sie haben die folgende Nachricht vom Server erhalten. (benutzerdefinierte Nachrichten wie diese könnten nicht übersetzt sein) - - TabUserLists - - - Account - Benutzerkonto - - - - Add to Buddy List - als Freund hinzufügen - - - - Add to Ignore List - zu ignorierten Benutzern hinzufügen - - TappedOutInterface @@ -6333,173 +7314,184 @@ Bitte unterlassen Sie diese Aktivitäten oder weitere Schritte werden gegen Sie UserContextMenu - + User &details Benutzer&details - + Private &chat Privater &Chat - + Show this user's &games Spiele dieses &Benutzers anzeigen - + Add to &buddy list Zur &Freundesliste hinzufügen - + Remove from &buddy list Von &Freundesliste entfernen - + Add to &ignore list &Ignorieren - + Remove from &ignore list Nicht mehr &ignorieren - + Kick from &game Aus dem &Spiel werfen - + Warn user Nutzer warnen - + View user's war&n history Verwar&nungsverlauf des Nutzers ansehen - + Ban from &server Vom &Server bannen - + View user's &ban history &Bannverlauf des Nutzers ansehen - + &Promote user to moderator Nutzer zum Moderator befördern - + Dem&ote user from moderator Moderator degradieren - + Promote user to &judge Nutzer zum Richter befördern - + Demote user from judge Richter degradieren - + + View admin notes + Administratornotizen ansehen + + + %1's games %1s Spiele - - - + + + Ban History Bannverlauf - + Ban Time;Moderator;Ban Length;Ban Reason;Visible Reason Bannzeit;Moderator;Banndauer;Banngrund;Sichtbarer Grund - + User has never been banned. Nutzer wurde bisher nie gebannt. - + Failed to collect ban information. Sammeln von Informationen über Bans fehlgeschlagen. - - - + + + Warning History Warnungsverlauf - + Warning Time;Moderator;User Name;Reason Warnungszeit;Moderator;Nutzername;Grund - + User has never been warned. Nutzer wurde bisher nie gewarnt. - + Failed to collect warning information. Sammeln von Informationen über Verwarnungen fehlgeschlagen. - - + + Failed to get admin notes. + Administratornotizen konnten nicht abgerufen werden. + + + + Success Erfolgreich - + Successfully promoted user. Nutzer erfolgreich befördert. - + Successfully demoted user. Nutzer erfolgreich degradiert. - - + + + Failed Fehlgeschlagen - + Failed to promote user. Beförderung des Nutzers fehlgeschlagen. - + Failed to demote user. Degradierung des Nutzers fehlgeschlagen. - + Copy hash to clipboard Kopiere Hash in die Zwischenablage - + Remove this user's messages Nachrichten dieses Nutzers entfernen @@ -6507,173 +7499,173 @@ Bitte unterlassen Sie diese Aktivitäten oder weitere Schritte werden gegen Sie UserInfoBox - + Location: Ort: - + Account Age: Accountalter: - + Edit Bearbeiten - + Change password Passwort ändern - + Change avatar Avatar wechseln - + Administrator Administrator - + Moderator Moderator - + Registered user Registrierter Benutzer - - + + Unregistered user Unregistrierter Benutzer - + Judge Richter - + Unknown Unbekannt - + The entered password does not match your account. Das eingegebene Passwort stimmt nicht mit dem Konto überein. - - - + + + Information Information - + User information updated. Benutzerinformationen aktualisiert. - - - - - - - - - - + + + + + + + + + + Error Fehler - + User Information Benutzerinformationen - + Real Name: Echter Name: - + User Level: Nutzerlevel: - + %n Year(s), amount of years (only shown if more than 0) %n Jahre,%n Jahre, - + %10%n Day(s) %20 amount of years (if more than 0), amount of days, date in local short format %10%n Tage %20%10%n Tage %20 - + Enter Password Passwort eingeben - + Password verification is required in order to change your email address Passwortverifikation benötigt, um die E-Mail-Adresse zu ändern - - - + + + An error occurred while trying to update your user information. Ein Fehler ist während der Aktualisierung Ihrer Benutzerinformationen aufgetreten. - + This server does not permit you to update your user informations. Dieser Server erlaubt Ihnen nicht, Ihre Benutzerinformationen zu aktualisieren. - + Password changed. Passwort geändert. - + This server does not permit you to change your password. Dieser Server erlaubt Ihnen nicht, Ihr Passwort zu ändern. - + The new password is too short. Das neue Passwort ist zu kurz. - + The old password is incorrect. Altes Passwort inkorrekt. - + Avatar updated. Avatar gewechselt. - + This server does not permit you to update your avatar. Dieser Server erlaubt Ihnen nicht, Ihren Avatar zu ändern. - + An error occured while trying to updater your avatar. Ein Fehler ist während des Wechselns Ihres Avatars aufgetreten. @@ -6681,124 +7673,251 @@ Bitte unterlassen Sie diese Aktivitäten oder weitere Schritte werden gegen Sie UserInterfaceSettingsPage - + General interface settings Allgemeine Bedienung - + &Double-click cards to play them (instead of single-click) Karten durch &Doppelklick ausspielen (statt Einzelklick) - + + &Clicking plays all selected cards (instead of just the clicked card) + &Anklicken spielt alle ausgewählten Karten aus (anstatt nur der angeklickten Karte) + + + &Play all nonlands onto the stack (not the battlefield) by default Alle Nichtländer standardmäßig über den Stapel spielen (anstatt direkt auf das Spielfeld) - + + Close card view window when last card is removed + Schließe Kartenanzeigefenster, wenn die letzte Karte entfernt wird + + + Annotate card text on tokens Kartentext auf Spielsteinen anzeigen - + Use tear-off menus, allowing right click menus to persist on screen Benutze Abreißmenüs, erlaubt Kontextmenüs auf dem Bildschirm zu verbleiben - + Notifications settings Benachrichtigungseinstellungen - + Enable notifications in taskbar Benachrichtigungen in der Taskleiste aktivieren - + Notify in the taskbar for game events while you are spectating Benachrichtigungen für Spielereignisse auch beim Zuschauen anderer Spiele in der Taskbar anzeigen - + Notify in the taskbar when users in your buddy list connect Benachrichtige in der Taskleiste wenn sich Benutzer aus der Freundesliste anmelden - + Animation settings Animationseinstellungen - + &Tap/untap animation Animiertes &Tappen/Enttappen + + + Deck editor/storage settings + Deckeditor/-ablage Einstellungen + + + + Open deck in new tab by default + Decks in neuem Tab öffnen als Standard + + + + Prompt before converting .txt decks to .cod format + Vor der Deckkonvertierung von .txt ins .cod Format nachfragen + + + + Always convert if not prompted + Immer konvertieren, wenn nicht gefragt + + + + Use visual deck storage in game lobby + Visuelle Deckablage in der Spielelobby verwenden + + + + Replay settings + Wiederholungsoptionen + + + + Buffer time for backwards skip via shortcut: + Pufferzeit für Rückwärtsüberspringen durch Tastenkürzel + - UserList + UserListWidget - + Users connected to server: %1 Mit dem Server verbundene Nutzer: %1 - + Users in this room: %1 Benutzer in diesem Raum: %1 - + Buddies online: %1 / %2 Freunde online: %1 / %2 - + Ignored users online: %1 / %2 Ignorierte Benutzer online: %1 / %2 + + VisualDeckStorageFolderDisplayWidget + + + Deck Storage + Deckablage + + + + VisualDeckStorageSearchWidget + + + Search by filename + Suche anhand des Dateinamens + + + + VisualDeckStorageSortWidget + + + Sort Alphabetically (Deck Name) + Alphabetisch sortieren (Deckname) + + + + Sort Alphabetically (Filename) + Alphabetisch sortieren (Dateiname) + + + + Sort by Last Modified + Nach Änderungsdatum sortieren + + + + Sort by Last Loaded + Nach letzter Nutzung sortieren + + + + VisualDeckStorageWidget + + + Loading database ... + Datenbank wird geladen ... + + + + Show Folders + + + + + Show Tag Filter + + + + + Show Tags On Deck Previews + + + + + Show Banner Card Selection Option + + + + + Include Folder Names in Search + + + + + Draw unused Color Identities + + + + + Unused Color Identities Opacity + + + WarningDialog - + Which warning would you like to send? Welche Warnung möchten Sie versenden? - + Redact all messages from this user in all rooms Entferne alle Nachrichten dieses Nutzers in allen Räumen - + &OK &OK - + &Cancel Abbre&chen - + Warn user for misconduct Nutzer aufgrund von Fehlverhalten warnen - - + + Error Fehler - + User name to send a warning to can not be blank, please specify a user to warn. Der zu warnende Nutzername kann nicht leer gelassen werden, bitte geben Sie einen Nutzernamen an. - + Warning to use can not be blank, please select a valid warning to send. Warnungsart kann nicht leer gelassen werden, bitte wählen Sie die zu sendende Warnungsart. @@ -6806,128 +7925,133 @@ Bitte unterlassen Sie diese Aktivitäten oder weitere Schritte werden gegen Sie WndSets - + Move selected set to the top Ausgewählte Edition an die Spitze verschieben - + Move selected set up nach oben verschieben - + Move selected set down nach unten verschieben - + Move selected set to the bottom Ausgewählte Edition ans Ende verschieben - + Search by set name, code, or type Suche nach Setname, Code oder Typ - + Default order Standardreihenfolge - + Restore original art priority order Stelle die ursprüngliche Reihenfolge für Bilderprioritäten wieder her - + Enable all sets Alle Editionen aktivieren - + Disable all sets Alle Editionen deaktivieren - + Enable selected set(s) Ausgewählte Edition(en) aktivieren - + Disable selected set(s) Ausgewählte Edition(en) deaktivieren - + Deck Editor Deckeditor - + Use CTRL+A to select all sets in the view. Benutzen Sie Strg+A, um alle Sets in der Ansicht auszuwählen. - + Only cards in enabled sets will appear in the card list of the deck editor. Nur Karten aus aktivierten Sets werden in der Kartenliste des Deckeditors angezeigt. - + Image priority is decided in the following order: Bildpriorität wird auf folgende Reihenfolge festgelegt: - + first the CUSTOM Folder (%1), then the Enabled Sets in this dialog (Top to Bottom) %1 is a link to the wiki zuerst der BENUTZERDEFINIERTE Ordner (%1), dann die aktivierten Sets aus diesem Dialog (von oben nach unten) - + Card Art Kartenzeichnung - + How to use custom card art Wie benutzerdefinierte Kartenbilder verwendet werden - + Hints Tipps - + Note Notiz - + Sorting by column allows you to find a set while not changing set priority. Nach einer Spalte sortieren erlaubt, eine Edition zu finden, ohne die Editionspriorität zu verändern. - + To enable ordering again, click the column header until this message disappears. Um die Sortierung erneut zu aktivieren, klicken Sie auf den Spaltenkopf bis diese Nachricht verschwindet. - + Use the current sorting as the set priority instead Benutze die momentane Sortierung als Editionspriorität. - + Sorts the set priority using the same column Sortiert die Editionspriorität mit der selben Spalte - + + Include online-only (Arena) cards [requires restart] + + + + Manage sets Editionen verwalten @@ -6935,22 +8059,67 @@ Bitte unterlassen Sie diese Aktivitäten oder weitere Schritte werden gegen Sie ZoneViewWidget - - sort by name - nach Namen sortieren + + Ungrouped + Ungruppiert - - sort by type - nach Kartentypen sortieren + + Group by Type + Gruppieren nach Kartentyp - + + Group by Mana Value + Gruppieren nach Manabetrag + + + + Group by Color + Gruppieren nach Farbe + + + + Unsorted + Unsortiert + + + + Sort by Name + Sortieren nach Name + + + + Sort by Type + Sortieren nach Kartentyp + + + + Sort by Mana Cost + Sortieren nach Manakosten + + + + Sort by Colors + Sortieren nach Farben + + + + Sort by P/T + Sortieren nach P/T + + + + Sort by Set + Sortieren nach Auflage + + + shuffle when closing beim Schließen mischen - + pile view Stapelansicht @@ -6958,7 +8127,7 @@ Bitte unterlassen Sie diese Aktivitäten oder weitere Schritte werden gegen Sie i18n - + English Deutsch (German) @@ -6966,12 +8135,12 @@ Bitte unterlassen Sie diese Aktivitäten oder weitere Schritte werden gegen Sie main - + Connect on startup Verbinde beim Start - + Debug to file Debugge in Datei @@ -6979,786 +8148,908 @@ Bitte unterlassen Sie diese Aktivitäten oder weitere Schritte werden gegen Sie shortcutsTab - + Main Window Hauptfenster - - + + Deck Editor Deckeditor - + Game Lobby Spielelobby - + Card Counters Kartenmarken - + Player Counters Spielermarken - + Power and Toughness Stärke und Widerstandskraft - + Game Phases Spielphasen - + Playing Area Spielebereich - + Move Selected Card Ausgewählte Karte verschieben - + View Ansehen - + Move Top Card Oberste Karte verschieben - + Move Bottom Card Unterste Karte verschieben - + Gameplay Spielmechanik - + Drawing Ziehen - + Chat Room Chatraum - + Game Window Spielfenster - + Load Deck from Clipboard Deck aus der Zwischenablage laden - + + Replays Wiederholungen - + + Tabs + Reiter + + + Check for Card Updates... Nach Kartenaktualisierungen suchen... - + Connect... Verbinden... - + Disconnect Getrennt - + Exit Beenden - + Full screen Vollbild - + Register... Registrieren... - + Settings... Einstellungen... - + Start a Local Game... Lokales Spiel starten... - + Watch Replay... Aufgezeichnetes Spiel ansehen... - - Analyze Deck - Deck analysieren + + Analyze Deck (deckstats.net) + Analyze Deck + - + + Analyze Deck (tappedout.net) + + + + Clear All Filters Alle Filter entfernen - + Clear Selected Filter Ausgewählten Filter entfernen - + Close Schließen - + Remove Card Karte entfernen - + Manage Sets... Editionen verwalten... - + Edit Custom Tokens... Benutzerdefinierte Spielsteine bearbeiten... - - Export Deck - Deck exportieren + + Export Deck (decklist.org) + - + + Export Deck (decklist.xyz) + + + + Add Card Karte hinzufügen - + Load Deck... Deck laden... - + Load Deck from Clipboard... Deck aus der Zwischenablage laden... - + + Edit Deck in Clipboard, Annotated + + + + + Edit Deck in Clipboard + + + + New Deck Neues Deck - + Open Custom Pictures Folder Benutzerdefinierten Bilderordner öffnen - + Print Deck... Deck drucken... - + Delete Card Karte löschen - - + + Reset Layout Darstellung zurücksetzen - + Save Deck Deck speichern - + Save Deck as... Deck speichern unter... - + Save Deck to Clipboard, Annotated Deck kommentiert in die Zwischenablage speichern - + + Save Deck to Clipboard, Annotated (No Set Info) + + + + Save Deck to Clipboard Deck in die Zwischenablage speichern - + + Save Deck to Clipboard (No Set Info) + + + + Load Local Deck... Lokales Deck laden... - + Load Remote Deck... Deck vom Server laden... - + Set Ready to Start Bereit zum Start setzen - + Toggle Sideboard Lock Sideboardsperre umschalten - - + + Add Green Counter Grüne Marke hinzufügen - - + + Remove Green Counter Grüne Marke entfernen - - + + Set Green Counters... Grüne Marken setzen... - + Add Yellow Counter Gelbe Marke hinzufügen - + Remove Yellow Counter Gelbe Marke entfernen - + Set Yellow Counters... Gelbe Marken setzen... - - + + Add Red Counter Rote Marke hinzufügen - - + + Remove Red Counter Rote Marke entfernen - - + + Set Red Counters... Rote Marken setzen... - + Add Life Counter Lebensmarke hinzufügen - + Remove Life Counter Lebensmarke entfernen - + Set Life Counters... Lebensmarken setzen... - + Add White Counter Weiße Marke hinzufügen - + Remove White Counter Weiße Marke entfernen - + Set White Counters... Weiße Marken setzen... - + Add Blue Counter Blaue Marke hinzufügen - + Remove Blue Counter Blaue Marke entfernen - + Set Blue Counters... Blaue Marken setzen... - + Add Black Counter Schwarze Marke hinzufügen - + Remove Black Counter Schwarze Marke entfernen - + Set Black Counters... Schwarze Marken setzen... - + Add Colorless Counter Farblose Marke hinzufügen - + Remove Colorless Counter Farblose Marke entfernen - + Set Colorless Counters... Farblose Marken setzen... - + Add Other Counter Andere Marke hinzufügen - + Remove Other Counter Andere Marke entfernen - + Set Other Counters... Andere Marke setzen... - + Add Power (+1/+0) Stärke hinzufügen (+1/+0) - + Remove Power (-1/-0) Stärke entfernen (-1/-0) - + Move Toughness to Power (+1/-1) Widerstandskraft nach Stärke verschieben (+1/-1) - + Add Toughness (+0/+1) Widerstandskraft hinzufügen (+0/+1) - + Remove Toughness (-0/-1) Widerstandskraft entfernen (-0/-1) - + Move Power to Toughness (-1/+1) Stärke nach Widerstandskraft verschieben (-1/+1) - + Add Power and Toughness (+1/+1) Stärke und Widerstandskraft hinzufügen (+1/+1) - + Remove Power and Toughness (-1/-1) Stärke und Widerstandskraft entfernen (-1/-1) - + Set Power and Toughness... Stärke und Widerstandskraft setzen... - + Reset Power and Toughness Stärke und Widerstandskraft zurücksetzen - + Untap Enttappen - + Upkeep Versorgung - + Draw Ziehen - + First Main Phase Erste Hauptphase - + Start Combat Beginn des Kampfes - + Attack Angriff - + Block Blocken - + Damage Schaden - + End Combat Ende des Kampfes - + Second Main Phase Zweite Hauptphase - + End Endsegment - + Next Phase Nächste Phase - + Next Phase Action Nächste Phasenaktion - + Next Turn Nächster Zug - + + Hide Card in Reveal Window + Karte im Enthüllungsfenster verstecken + + + Tap / Untap Card Tappe / Enttappe Karte - + Untap All Alle enttappen - + Toggle Untap Enttappen umschalten - + Turn Card Over Karte herumdrehen - + Peek Card Karte ansehen - + Play Card Karte spielen - + Attach Card... Karte anlegen... - + Unattach Card Karte lösen - + Clone Card Karte klonen - + Create Token... Spielstein erstellen... - + Create All Related Tokens Alle zugehörigen Spielsteine erstellen - + Create Another Token Einen weiter Spielstein erstellen - + Set Annotation... Notiz setzen... - - + + Select All Cards in Zone + Alle Karten in der Zone auswählen + + + + Select All Cards in Row + Alle Karten in der Reihe auswählen + + + + Select All Cards in Column + Alle Karten in der Spalte auswählen + + + + Bottom of Library Untere Karte der Bibliothek - - - - + + + + Exile Exil - - - - + + + + Graveyard Friedhof - - + + Hand Hand - - + + Top of Library Obere Karte der Bibliothek - - - + + + Battlefield, Face Down Spielfeld, verdeckt - + Battlefield Spielfeld - + Library Bibliothek - + Sideboard Sideboard - + Top Cards of Library Oberste Karten der Bibliothek - + + Bottom Cards of Library + Unterste Karten der Bibliothek + + + Close Recent View Schließe letzte Ansicht - - + + Stack Stapel - - + + Graveyard (Multiple) Friedhof (mehrere) - - + + Exile (Multiple) Exil (mehrere) - + Stack Until Found Stapel bis gefunden - + Draw Bottom Card Unterste Karte ziehen - + Draw Multiple Cards from Bottom... Mehrere Karten von unten ziehen... - + Draw Arrow... Pfeil zeichnen... - + Remove Local Arrows Lokale Pfeile entfernen - + Leave Game Spiel verlassen - + Concede Aufgeben - + Roll Dice... Würfeln... - + Shuffle Library Bibliothek mischen - + + Shuffle Top Cards of Library + + + + + Shuffle Bottom Cards of Library + + + + Mulligan Mulligan - + Draw a Card Eine Karte ziehen - + Draw Multiple Cards... Mehrere Karten ziehen... - + Undo Draw Ziehen rückgängig machen - + Always Reveal Top Card Oberste Karte aufgedeckt lassen - + Always Look At Top Card Oberste Karte immer betrachten - + Rotate View Clockwise Ansicht im Uhrzeigersinn drehen - + Rotate View Counterclockwise Ansicht gegen den Uhrzeigersinn drehen - + Unfocus Text Box Textboxfokus aufheben - + Focus Chat Chat fokussieren - + Clear Chat Chat leeren - + Refresh Aktualisieren - + + Skip Forward + Vorspulen + + + + Skip Backward + Zurückspulen + + + + Skip Forward by a lot + Sehr weit vorspulen + + + + Skip Backward by a lot + Sehr weit zurückspulen + + + Play/Pause Abspielen/Pausieren - + Toggle Fast Forward Schnelldurchlauf umschalten + + + Visual Deck Storage + Visuelle Deckablage + + + + Deck Storage + Deckablage + + + + Server + Server + + + + Account + Benutzerkonto + + + + Administration + Administration + + + + Logs + Logs + \ No newline at end of file diff --git a/cockatrice/translations/cockatrice_el.ts b/cockatrice/translations/cockatrice_el.ts index aaf1a1176..3f658051d 100644 --- a/cockatrice/translations/cockatrice_el.ts +++ b/cockatrice/translations/cockatrice_el.ts @@ -2,7 +2,7 @@ AbstractCounter - + &Set counter... &Ρύθμιση μετρητή... @@ -10,95 +10,247 @@ AbstractCounterDialog - + Set counter Ρύθμιση μετρητή - + New value for counter '%1': Νέα τιμή για τον μετρητή '%1' + + AbstractDlgDeckTextEdit + + + &Refresh + + + + + Parse Set Name and Number (if available) + + + + + AbstractTabDeckEditor + + + Open in new tab + + + + + Are you sure? + + + + + The decklist has been modified. +Do you want to save the changes? + + + + + + + + + + + Error + + + + + Could not open deck at %1 + + + + + Could not save remote deck + + + + + + The deck could not be saved. +Please check that the directory is writable and try again. + + + + + Save deck + + + + + The deck could not be saved. + + + + + There are no cards in your deck to be exported + + + + + No deck was selected to be exported. + + + + + AdminNotesDialog + + + Update Notes + + + + + Admin Notes for %1 + + + + + AllZonesCardAmountWidget + + + Mainboard + + + + + Sideboard + + + AppearanceSettingsPage - + Error Σφάλμα - + Could not create themes directory at '%1'. - + Theme settings Ρυθμίσεις θέματος - + Current theme: Τρέχον θέμα: - + Open themes folder Άνοιγμα φακέλου θεμάτων - + + Menu settings + + + + + Show keyboard shortcuts in right-click menus + + + + Card rendering Απόδοση κάρτας - + Display card names on cards having a picture Εμφανίστε τα ονόματα καρτών σε κάρτες με εικόνα - + + Auto-Rotate cards with sideways layout + + + + + Override all card art with personal set preference (Pre-ProviderID change behavior) [Requires Client restart] + + + + + Bump sets that the deck contains cards from to the top in the printing selector + + + + Scale cards on mouse over Αύξηση της κλίμακας των καρτών με την επιλογή του ποντικιού - + + Use rounded card corners + + + + Minimum overlap percentage of cards on the stack and in vertical hand - + + Maximum initial height for card view window: + + + + + + rows + + + + + Maximum expanded height for card view window: + + + + Hand layout Διάταξη χειρός - + Display hand horizontally (wastes space) Εμφάνιση χεριού οριζόντια (σπατάλη χώρου) - + Enable left justification Ενεργοποιήστε την αριστερή αιτιολόγηση - + Table grid layout Διάταξη πλέγματος πίνακα - + Invert vertical coordinate Ανατροπή κάθετης συντεταγμένης - + Minimum player count for multi-column layout: Ελάχιστος αριθμός παικτών για διάταξη πολλαπλών στηλών: - + Maximum font size for information displayed on cards: Μέγιστο μέγεθος γραμματοσειράς για πληροφορίες που εμφανίζονται στις κάρτες: @@ -106,112 +258,112 @@ BanDialog - + ban &user name απαγόρευση και όνομα χρήστη - + ban &IP address απαγόρευση & διεύθυνση IP - + ban client I&D απαγόρευση εξυπηρετητή I&D - + Ban type Τύπος απαγόρευσης - + &permanent ban &μόνιμη απαγόρευση - + &temporary ban &προσωρινή απαγόρευση - + &Days: &Μέρες: - + &Hours: &Ώρες: - + &Minutes: &Λεπτά: - + Duration of the ban Διάρκεια της απαγόρευσης - + Please enter the reason for the ban. This is only saved for moderators and cannot be seen by the banned person. Παρακαλώ καταχωρίστε τον λόγο της απαγόρευσης. Αυτό αποθηκεύεται μόνο για τους επόπτες και δεν μπορεί να το δει το αποκλεισμένο άτομο. - + Please enter the reason for the ban that will be visible to the banned person. Παρακαλώ καταχωρίστε τον λόγο της απαγόρευσης που θα είναι ορατή στο αποκλεισμένο άτομο. - + Redact all messages from this user in all rooms - + &OK &Εντάξει - + &Cancel &Άκυρο - + Ban user from server Απαγόρευση χρήστη από τον διακομιστή - - - - + + + + Error Σφάλμα - + You have to select a name-based, IP-based, clientId based, or some combination of the three to place a ban. Για την απαγόρευση πρέπει να επιλέξετε ένα όνομα, μία IP, ένα clientId, ή κάποιο συνδυασμό των τριών. - + You must have a value in the name ban when selecting the name ban checkbox. Πρέπει να έχετε μια τιμή στην απαγόρευση ονόματος όταν επιλέγετε το πλαίσιο ελέγχου απαγόρευσης ονόματος. - + You must have a value in the ip ban when selecting the ip ban checkbox. Πρέπει να έχετε μια τιμή στην απαγόρευση ip όταν επιλέγετε το πλαίσιο ελέγχου απαγόρευσης ip. - + You must have a value in the clientid ban when selecting the clientid ban checkbox. Πρέπει να έχετε μια τιμή στην απαγόρευση clientid όταν επιλέγετε το πλαίσιο ελέγχου απαγόρευσης clientid. @@ -219,22 +371,22 @@ This is only saved for moderators and cannot be seen by the banned person. BetaReleaseChannel - - Beta Releases + + Beta - + No reply received from the release update server. - + Invalid reply received from the release update server. - + No reply received from the file update server. @@ -242,32 +394,32 @@ This is only saved for moderators and cannot be seen by the banned person. CardDatabaseModel - + Name Όνομα - + Sets Σετς - + Mana cost Το κόστος του Μάνα - + Card type Είδος κάρτας - + P/T P/T - + Color(s) Χρώμα(τα) @@ -360,325 +512,621 @@ This is only saved for moderators and cannot be seen by the banned person. - CardFrame + CardInfoFrameWidget - + Image - Εικόνα + - + Description - Περιγραφή + - + Both - Και τα δύο + + + + + View transformation + - CardInfoText + CardInfoPictureWidget - + + View related cards + + + + + Add card to deck + + + + + Mainboard + + + + + Sideboard + + + + + CardInfoTextWidget + + Name: - Όνομα: + - + Related cards: - Σχετικές κάρτες: + - + Unknown card: - Άγνωστη κάρτα: + CardItem - + &Move to &Μετακίνηση σε - + &Power / toughness &Επίθεση / άμυνα + + CardSizeWidget + + + Card Size + + + CardZone - + their hand nominative το χέρι τους - + %1's hand nominative %1's χέρι - + their library look at zone η βιβλιοθήκη τους - + %1's library look at zone %1's βιβλιοθήκη - + of their library top cards of zone, της βιβλιοθήκης τους - + of %1's library top cards of zone - + their library reveal zone η βιβλιοθήκη τους - + %1's library reveal zone - + their library shuffle η βιβλιοθήκη τους - + %1's library shuffle - + their library nominative η βιβλιοθήκη τους - + %1's library nominative - + their graveyard nominative το νεκροταφείο τους - + %1's graveyard nominative - + their exile nominative η εξορία τους - + %1's exile nominative - + their sideboard look at zone το sideboard τους - + %1's sideboard look at zone - + their sideboard nominative το sideboard τους - + %1's sideboard nominative %1's sideboard + + DeckEditorCardInfoDockWidget + + + Card Info + + + + + DeckEditorDatabaseDisplayWidget + + + Search by card name (or search expressions) + + + + + Add to Deck + + + + + Add to Sideboard + + + + + Select Printing + + + + + Show on EDHREC (Commander) + + + + + Show on EDHREC (Card) + + + + + Show Related cards + + + + + Add card to &maindeck + + + + + Add card to &sideboard + + + + + DeckEditorDeckDockWidget + + + Banner Card + + + + + Select Printing + + + + + Deck + + + + + Deck &name: + + + + + &Comments: + + + + + Hash: + + + + + &Increment number + + + + + &Decrement number + + + + + &Remove row + + + + + Swap card to/from sideboard + + + + + DeckEditorFilterDockWidget + + + Filters + + + + + &Clear all filters + + + + + Delete selected + + + + + DeckEditorMenu + + + &Deck Editor + + + + + &New deck + + + + + &Load deck... + + + + + Load recent deck... + + + + + Clear + + + + + &Save deck + + + + + Save deck &as... + + + + + Load deck from cl&ipboard... + + + + + Edit deck in clipboard + + + + + + Annotated + + + + + + Not Annotated + + + + + Save deck to clipboard + + + + + Annotated (No set info) + + + + + Not Annotated (No set info) + + + + + &Print deck... + + + + + &Send deck to online service + + + + + Create decklist (decklist.org) + + + + + Create decklist (decklist.xyz) + + + + + Analyze deck (deckstats.net) + + + + + Analyze deck (tappedout.net) + + + + + &Close + + + + + DeckEditorPrintingSelectorDockWidget + + + Printing Selector + + + DeckEditorSettingsPage - - + + Update Spoilers - - + + Success Επιτυχία - + Download URLs have been reset. - + Downloaded card pictures have been reset. - + Error Σφάλμα - + One or more downloaded card pictures could not be cleared. - + Add URL - - + + URL: - - + + Edit URL - + + Network Cache Size: + + + + + Redirect Cache TTL: + + + + + How long cached redirects for urls are valid for. + + + + + Picture Cache Size: + + + + Add New URL - + Remove URL - + + Day(s) + + + + Updating... - + Choose path - + URL Download Priority - + Spoilers - + Download Spoilers Automatically - + Spoiler Location: - + Last Change - + Spoilers download automatically on launch - + Press the button to manually update without relaunching - + Do not close settings until manual update is complete - + Download card pictures on the fly - + How to add a custom URL - + Delete Downloaded Images - + Reset Download URLs - - Downloaded images directory size: - - - - + On-disk cache for downloaded pictures - - Picture cache size: - - - - + In-memory cache for pictures not currently on screen @@ -686,16 +1134,218 @@ This is only saved for moderators and cannot be seen by the banned person. DeckListModel - + + Count + + + + + Set + + + + Number Αριθμός - + + Provider ID + + + + Card Κάρτα + + DeckLoader + + + Common deck formats (%1) + + + + + All files (*.*) + + + + + DeckPreviewColorIdentityFilterWidget + + + Mode: Exact Match + + + + + Mode: Includes + + + + + DeckPreviewDeckTagsDisplayWidget + + + Edit tags ... + + + + + DeckPreviewTagDialog + + + Deck Tags Manager + + + + + Manage your deck tags. Check or uncheck tags as needed, or add new ones: + + + + + Add a new tag (e.g., Aggro️) + + + + + Add Tag + + + + + Filter tags... + + + + + OK + + + + + Cancel + + + + + Invalid Input + + + + + Tag name cannot be empty! + + + + + Duplicate Tag + + + + + This tag already exists. + + + + + DeckPreviewWidget + + + Banner Card + + + + + Open in deck editor + + + + + Edit Tags + + + + + Rename Deck + + + + + Save Deck to Clipboard + + + + + Annotated + + + + + Annotated (No set info) + + + + + Not Annotated + + + + + Not Annotated (No set info) + + + + + Rename File + + + + + Delete File + + + + + Set Banner Card + + + + + + New name: + + + + + + Error + + + + + Rename failed + + + + + Delete file + + + + + Are you sure you want to delete the selected file? + + + + + Delete failed + + + DeckStatsInterface @@ -713,181 +1363,217 @@ This is only saved for moderators and cannot be seen by the banned person. DeckViewContainer - + Load deck... Φόρτωση deck... - + Load remote deck... Φόρτωση απομακρυσμένου deck... - + + Unload deck + Unload deck... + + + + Ready to start - + + Force start + + + + Sideboard unlocked - + Sideboard locked - - Load deck - Φόρτωση deck - - - + Error Σφάλμα - + The selected file could not be loaded. Δεν ήταν δυνατή η φόρτωση του επιλεγμένου αρχείου. + + DialogConvertDeckToCodFormat + + + Deck Format Conversion + + + + + You tried to add a tag to a .txt format deck. + Tags can only be added to .cod format decks. + Do you want to convert the deck to the .cod format? + + + + + Remember and automatically apply choice in the future + + + DlgConnect - + Downloading... - + Known Hosts Γνωστοί Hosts - + + Delete the currently selected saved server + + + + Refresh the server list with known public servers - + New Host Νέος Host - + Name: Όνομα: - + &Host: &Host: - + &Port: &Θύρα: - + Player &name: Όνομα &παίχτη: - + P&assword: Κωδικός: - + &Save password &Αποθήκευση Κωδικού - + A&uto connect Α&υτόματη σύνδεση - + Automatically connect to the most recent login when Cockatrice opens Συνδεθείτε αυτόματα στην πιο πρόσφατη σύνδεση όταν ανοίγει το Cockatrice - + If you have any trouble connecting or registering then contact the server staff for help! - - + + Webpage - + Reset Password - + + Forgot password? + + + + &Connect - + Server Διακομιστής - + Login Σύνδεση - + Server Contact - + Connect to Server - + Server URL - + Communication Port - + Unique Server Name - + Connection Warning Προειδοποίηση σύνδεσης - + You need to name your new connection profile. Πρέπει να ονομάσετε το νέο προφίλ σύνδεσης. - + Connect Warning Προειδοποίηση σύνδεσης - + The player name can't be empty. Το όνομα παίκτη δεν μπορεί να είναι άδειο. @@ -970,27 +1656,37 @@ This is only saved for moderators and cannot be seen by the banned person.Θεατές - + + Starting life total: + + + + + Game setup options + + + + &Clear &Καθάρισε - + Create game Δημιουργία παιχνιδιού - + Game information Πληροφορίες παιχνιδιού - + Error Σφάλμα - + Server error. Σφάλμα Διακομιστή. @@ -998,92 +1694,92 @@ This is only saved for moderators and cannot be seen by the banned person. DlgCreateToken - + &Name: &Όνομα: - + Token Token - + C&olor: Χ&ρώμα: - + white άσπρο - + blue μπλε - + black μαύρο - + red κόκκινο - + green πράσινο - + multicolor πολύχρωμο - + colorless άχρωμο - + &P/T: &P/T: - + &Annotation: &Σχόλιο: - + &Destroy token when it leaves the table &κατέστρεψε το token όταν φύγει από το τραπέζι - + Token data Στοιχεία token - + Show &all tokens Εμφάνισε &όλα τα tokens - + Show tokens from this &deck Εμφάνιση tokens από αυτό το &deck - + Choose token from list Επέλεξε token από τη λίστα - + Create token Δημιουργία token @@ -1129,6 +1825,24 @@ To remove your current avatar, confirm without choosing a new image. Επιλέχθηκε μη έγκυρη εικόνα. + + DlgEditDeckInClipboard + + + Edit deck in clipboard + + + + + Error + + + + + Invalid deck list. + + + DlgEditPassword @@ -1171,93 +1885,93 @@ To remove your current avatar, confirm without choosing a new image. DlgEditTokens - + &Name: &Όνομα: - + C&olor: Χ&ρώμα: - + white άσπρο - + blue μπλε - + black μαύρο - + red κόκκινο - + green πράσινο - + multicolor πολύχρωμο - + colorless άχρωμο - + &P/T: &P/T: - + &Annotation: &Σχόλιο: - + Token data Στοιχεία token - - + + Add token Προσθήκη token - + Remove token Αφαίρεση token - + Edit custom tokens - + Please enter the name of the token: Παρακαλώ εισάγετε το όνομα του token: - + Error Σφάλμα - + The chosen name conflicts with an existing card or token. Make sure to enable the 'Token' set in the "Manage sets" dialog to display them correctly. @@ -1294,132 +2008,132 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgFilterGames - + no limit - + 5 minutes - + 10 minutes - + 30 minutes - + 1 hour - + 2 hours - - Show '&buddies only' games - Εμφάνιση παιχνιδιών "Μόνο φίλοι" - - - - Show &full games + + Hide 'buddies only' games - - Show games &that have started + + Hide full games - - Show &password protected games - Εμφάνιση παιχνιδιών με &κωδικό πρόσβασης - - - - Hide '&ignored user' games + + Hide games that have started - + + Hide password protected games + + + + + Hide 'ignored user' games + + + + &Newer than: - + Game &description: Παιχνίδι και &περιγραφή: - + &Creator name: &Όνομα δημιουργού: - + General - + &Game types &Είδη παιχνιδιού - + at &least: το &λιγότερο: - + at &most: το &περισσότερο: - + Maximum player count Μέγιστος αριθμός παικτών - + Restrictions Περιορισμοί - + Show games only if &spectators can watch - + Show spectator password p&rotected games - + Show only if spectators can ch&at - + Show only if spectators can see &hands - + Spectators - + Filter games Φίλτρο παιχνιδιών @@ -1603,26 +2317,27 @@ Make sure to enable the 'Token' set in the "Manage sets" dia - DlgLoadDeckFromClipboard + DlgLoadDeck - - &Refresh + + Load Deck + + + DlgLoadDeckFromClipboard - + Load deck from clipboard Φόρτωση deck από το clipboard - - + Error Σφάλμα - - + Invalid deck list. Μη έγκυρο deck list @@ -1630,11 +2345,49 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgLoadRemoteDeck - + Load deck Φόρτωση deck + + DlgMoveTopCardsUntil + + + Card name (or search expressions): + + + + + Number of hits: + + + + + Auto play hits + + + + + Put top cards on stack until... + + + + + No cards matching the search expression exists in the card database. Proceed anyways? + + + + + Cockatrice + + + + + Invalid filter + + + DlgRegister @@ -1748,12 +2501,12 @@ Your email will be used to verify your account. DlgSettings - + Unknown Error loading card database - + Your card database is invalid. Cockatrice may not function correctly with an invalid database @@ -1764,7 +2517,7 @@ Would you like to change your database location setting? - + Your card database version is too old. This can cause problems loading card information or images @@ -1781,7 +2534,7 @@ Would you like to change your database location setting? Θα θέλατε να αλλάξετε τη ρυθμισμένη θέση της βάσης δεδομένων σας; - + Your card database did not finish loading Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues with your cards.xml attached @@ -1790,21 +2543,21 @@ Would you like to change your database location setting? - + File Error loading your card database. Would you like to change your database location setting? - + Your card database was loaded but contains no cards. Would you like to change your database location setting? - + Unknown card database load status Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues @@ -1813,59 +2566,59 @@ Would you like to change your database location setting? - - - + + + Error Σφάλμα - + The path to your deck directory is invalid. Would you like to go back and set the correct path? - + The path to your card pictures directory is invalid. Would you like to go back and set the correct path? - + Settings Ρυθμίσεις - + General Γενικά - + Appearance Εμφάνιση - + User Interface Διεπαφή Χρήστη - + Card Sources - + Chat - + Sound Ήχος - + Shortcuts Συντομεύσεις @@ -1891,164 +2644,168 @@ Would you like to change your database location setting? DlgUpdate - + Current release channel Τρέχον κανάλι έκδοσης - + Reinstall Επανεγκατάσταση - + Cancel Download Ακύρωση Κατεβάσματος - + Open Download Page Άνοιγμα Σελίδας Κατεβάσματος - + Check for Client Updates - - - - + + + + Error Σφάλμα - + Cockatrice was not built with SSL support, therefore you cannot download updates automatically! Please visit the download page to update manually. - - Downloading update... + + Downloading update: %1 + Downloading update... - + Checking for updates... - + Finished checking for updates Ο έλεγχος για ενημερώσεις ολοκληρώθηκε - + No Update Available Καμία Διαθέσιμη Ενημέρωση - + Cockatrice is up to date! Το Cockatrice είναι πλήρως ενημερωμένο! - + You are already running the latest version available in the chosen release channel. Ήδη τρέχετε την πιό πρόσφατη έκδοση διαθέσιμη σε αυτό το κανάλι. - + Current version Τρέχουσα έκδοση - + Selected release channel Επιλεγμένο κανάλι έκδοσης - - + + Update Available Υπάρχει Διαθέσιμη Ενημέρωση - - + + A new version of Cockatrice is available! Μία νέα έκδοση του Cockatrice είναι διαθέσιμη! - - + + New version Νέα έκδοση - - + + Released - - + + Changelog Αρχείο Αλλαγών - + Do you want to update now? Θέλετε να ενημερώσετε τώρα; - - Unfortunately there are no download packages available for your operating system. -You may have to build from source yourself. + + Unfortunately, the automatic updater failed to find a compatible download. +You may have to manually download the new version. + Unfortunately there are no download packages available for your operating system. +You may have to build from source yourself. - - Please check the download page manually and visit the wiki for instructions on compiling. + + Please check the <a href="%1">releases page</a> on our Github and download the build for your system. + Please check the download page manually and visit the wiki for instructions on compiling. - - - + + + Update Error - + An error occurred while checking for updates: - + An error occurred while downloading an update: - + Installing... - + Cockatrice is unable to open the installer. - + Try to update manually by closing Cockatrice and running the installer. - + Download location @@ -2066,6 +2823,14 @@ You may have to build from source yourself. + + EdhrecCommanderResponseCommanderDetailsDisplayWidget + + + Salt: + + + FilterBuilder @@ -2077,105 +2842,120 @@ You may have to build from source yourself. GameSelector - - - - - - - - - + + + + + + + + + Error Σφάλμα - + Please join the appropriate room first. - + Wrong password. Λάθος κωδικός. - + Spectators are not allowed in this game. - + The game is already full. - + The game does not exist any more. - + This game is only open to registered users. - + This game is only open to its creator's buddies. - + You are being ignored by the creator of this game. - + + Join Game + + + + + Spectate Game + + + + + Game Information + + + + Join game - + Password: Κωδικός: - + Please join the respective room first. - + &Filter games - + C&lear filter Καθάρισμα Φίλτρου - + C&reate Δημιουργία - + &Join Είσοδος - + J&oin as spectator Είσοδος ως θεατής - + Games shown: %1 / %2 - + Games @@ -2183,100 +2963,100 @@ You may have to build from source yourself. GamesModel - + >1 day - + %1%2 hr short age in hours - + new - + %1%2 min short age in minutes - + password κωδικός - + buddies only μόνο φίλοι - + reg. users only μόνο εγγεγραμμένοι χρήστες - - + + can chat μπορούν να συνομιλήσουν - + see hands βλέπουν τα χέρια - + can see hands μπορούν να δούν τα χέρια - + not allowed δεν επιτρέπεται - + Room Δωμάτιο - + Age Ηλικία - + Description Περιγραφή - + Creator Δημιουργός - + Type Τύπος - + Restrictions Περιορισμοί - + Players Παίκτες - + Spectators Θεατές @@ -2284,92 +3064,102 @@ You may have to build from source yourself. GeneralSettingsPage - + Reset all paths - + All paths have been reset - - + + Choose path Επιλέξτε θέση - + Personal settings - + Language: Γλώσσα: - + Paths (editing disabled in portable mode) - + Paths - + + How to help with translations + + + + Decks directory: Φάκελος deck: - + Replays directory: Φάκελος replay: - + Pictures directory: Φάκελος εικόνων: - + Card database: Βάση δεδομένων καρτών - + Custom database directory: - + Token database: Βάση δεδομένων token: - + Update channel - + + Check for client updates on startup + + + + Notify if a feature supported by the server is missing in my client - + Automatically run Oracle when running a new version of Cockatrice - + Show tips on startup @@ -2377,711 +3167,722 @@ You may have to build from source yourself. MainWindow - - + + The server has reached its maximum user capacity, please check back later. - + There are too many concurrent connections from your address. - + Banned by moderator - + Expected end time: %1 - + This ban lasts indefinitely. - + Scheduled server shutdown. - - + + Invalid username. - + You have been logged out due to logging in at another location. - + Connection closed - + The server has terminated your connection. Reason: %1 - + The server is going to be restarted in %n minute(s). All running games will be lost. Reason for shutdown: %1 - + Scheduled server shutdown - - + + Success - + Registration accepted. Will now login. - + Account activation accepted. Will now login. - + Number of players - + Please enter the number of players. - - + + Player %1 - + Load replay - + About Cockatrice - + Version Έκδοση - + Cockatrice Webpage - + Project Manager: - + Past Project Managers: - + Developers: - + Our Developers - + Help Develop! - + Translators: - + Our Translators Οι μεταφραστές μας - + Help Translate! - + Support: Υποστήριξη: - + Report an Issue Αναφορά Προβλήματος - + Troubleshooting Αντιμετώπιση Προβλημάτων - + F.A.Q. Συχνές Ερωτήσεις - - - - - - - - - - - - - - - + + + + + + + + + + + + + - - - + + + + + Error Σφάλμα - + Server timeout - + Failed Login Αποτυχημένη Σύνδεση - + Your client seems to be missing features this server requires for connection. - + To update your client, go to 'Help -> Check for Client Updates'. - + Incorrect username or password. Please check your authentication information and try again. - + There is already an active session using this user name. Please close that session first and re-login. - - + + You are banned until %1. Είστε αποκλεισμένος μέχρι %1. - - + + You are banned indefinitely. Είστε αποκλεισμένος επ' αόριστον. - + This server requires user registration. Do you want to register now? Ο σέρβερ απαιτεί εγγραφή. Θέλετε να εγγραφείτε τώρα; - + This server requires client IDs. Your client is either failing to generate an ID or you are running a modified client. Please close and reopen your client to try again. - + An internal error has occurred, please close and reopen Cockatrice before trying again. If the error persists, ensure you are running the latest version of the software and if needed contact the software developers. - + Account activation Ενεργοποίηση λογαριασμού - + Your account has not been activated yet. You need to provide the activation token received in the activation email. - + Server Full Σέρβερ Πλήρης - + Unknown login error: %1 Άγνωστο σφάλμα σύνδεσης: %1 - - + + This usually means that your client version is out of date, and the server sent a reply your client doesn't understand. - + Your username must respect these rules: - + is %1 - %2 characters long - + can %1 contain lowercase characters - - - - + + + + NOT - + can %1 contain uppercase characters - + can %1 contain numeric characters - + can contain the following punctuation: %1 - + first character can %1 be a punctuation mark - + no unacceptable language as specified by these server rules: note that the following lines will not be translated - + can not contain any of the following words: %1 - + can not match any of the following expressions: %1 - + You may only use A-Z, a-z, 0-9, _, ., and - in your username. - - - - - - + + + + + + Registration denied - + Registration is currently disabled on this server - + There is already an existing account with the same user name. - + It's mandatory to specify a valid email address when registering. - + It appears you are attempting to register a new account on this server yet you already have an account registered with the email provided. This server restricts the number of accounts a user can register per address. Please contact the server operator for further assistance or to obtain your credential information. - + Password too short. Ο κωδικός είναι πολύ μικρός. - + Registration failed for a technical problem on the server. - + Unknown registration error: %1 - + Account activation failed - + Socket error: %1 - + You are trying to connect to an obsolete server. Please downgrade your Cockatrice version or connect to a suitable server. Local version is %1, remote version is %2. - + Your Cockatrice client is obsolete. Please update your Cockatrice version. Local version is %1, remote version is %2. - + Connecting to %1... - + Registering to %1 as %2... - + Disconnected - + Connected, logging in at %1 - - - + + + Requesting forgotten password to %1 as %2... - + &Connect... Σύνδεση... - + &Disconnect Αποσύνδεση - + Start &local game... Έναρξη τοπικού παιχνιδιού... - + &Watch replay... - - &Deck editor - Επεξεργασία Deck - - - + &Full screen Πλήρης οθόνη - + &Register to server... Εγγραφή σε σέρβερ... - + &Restore password... - + &Settings... Ρυθμίσεις... - + &Exit Έξοδος - + A&ctions - + &Cockatrice &Cockatrice - + C&ard Database Βάση δεδομένων καρτών - + &Manage sets... - + Edit custom &tokens... - + Open custom image folder - + Open custom sets folder - + Add custom sets/cards - + + Reload card database + + + + + Tabs + + + + &Help Βοήθεια - + &About Cockatrice Σχετικά με το Cockatrice - + &Tip of the Day - + Check for Client Updates Έλεγχος για ενημερώσεις - + Check for Card Updates... - + View &Debug Log - + + Open Settings Folder + + + + Show/Hide - + New Version - + Congratulations on updating to Cockatrice %1! Oracle will now launch to update your card database. - + Cockatrice installed - + Congratulations on installing Cockatrice %1! Oracle will now launch to install the initial card database. - + Card database Βάση δεδομένων καρτών - + Cockatrice is unable to load the card database. Do you want to update your card database now? If unsure or first time user, choose "Yes" - - + + Yes Ναι - - + + No Όχι - + Open settings Άνοιγμα ρυθμίσεων - + New sets found Βρέθηκαν νέα σετ - + %n new set(s) found in the card database Set code(s): %1 Do you want to enable it/them? - + View sets Εμφάνιση σετ - + Welcome Καλωσήρθατε - + Hi! It seems like you're running this version of Cockatrice for the first time. All the sets in the card database have been enabled. Read more about changing the set order or disabling specific sets and consequent effects in the "Manage Sets" dialog. - - + + Information Πληροφορίες - + A card database update is already running. - + Unable to run the card database updater: - - failed to start. - απέτυχε να ξεκινήσει. - - - - crashed. - κράσαρε. - - - - timed out. + + Failed to start. The file might be missing, or permissions might be incorrect. - - write error. - σφάλμα εγγραφής. - - - - read error. - σφάλμα ανάγνωσης. - - - - unknown error. - άγνωστο σφάλμα. - - - - The card database updater exited with an error: %1 + + The process crashed some time after starting successfully. - + + Timed out. The process took too long to respond. The last waitFor...() function timed out. + + + + + An error occurred when attempting to write to the process. For example, the process may not be running, or it may have closed its input channel. + + + + + An error occurred when attempting to read from the process. For example, the process may not be running. + + + + + Unknown error occurred. + + + + + The card database updater exited with an error: +%1 + + + + This server supports additional features that your client doesn't have. This is most likely not a problem, but this message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -3089,54 +3890,54 @@ To update your client, go to Help -> Check for Updates. - - - - - + + + + + Load sets/cards Φόρτωση σετ/καρτών - + Selected file cannot be found. Το επιλεγμένο αρχείο δε βρέθηκε. - + You can only import XML databases at this time. - + The new sets/cards have been added successfully. Cockatrice will now reload the card database. - + Sets/cards failed to import. - - - + + + Reset Password - + Your password has been reset successfully, you can now log in using the new credentials. - + Failed to reset user account password, please contact the server operator to reset your password. - + Activation request received, please check your email for an activation token. @@ -3410,179 +4211,190 @@ Cockatrice will now reload the card database. - %1 is looking at the top %3 card(s) %2. + %1 is looking at the %4 %3 card(s) %2. + %1 is looking at the top %3 card(s) %2. top card for singular, top %3 cards for plural - + + bottom + + + + + top + + + + %1 turns %2 face-down. - + %1 turns %2 face-up. - + The game has been closed. Το παιχνίδι έχει κλείσει - + The game has started. Το παιχνίδι ξεκίνησε. - + %1 has joined the game. - + %1 is now watching the game. Ο/Η %1 παρακολουθεί το παιχνίδι. - + You have been kicked out of the game. Σας απέβαλαν από το παιχνίδι. - + %1 has left the game (%2). - + %1 is not watching the game any more (%2). - + %1 is not ready to start the game any more. - + %1 shuffles their deck and draws a new hand of %2 card(s). - + %1 shuffles their deck and draws a new hand. - + You are watching a replay of game #%1. - + %1 is ready to start the game. - + cards an unknown amount of cards - + %1 card(s) a card for singular, %1 cards for plural - + %1 lends %2 to %3. - + %1 reveals %2 to %3. - + %1 reveals %2. - + %1 randomly reveals %2%3 to %4. - + %1 randomly reveals %2%3. - + %1 peeks at face down card #%2. - + %1 peeks at face down card #%2: %3. - + %1 reveals %2%3 to %4. - + %1 reveals %2%3. - + %1 reversed turn order, now it's %2. - + reversed - + normal - + Heads - + Tails - + %1 flipped a coin. It landed as %2. - + %1 rolls a %2 with a %3-sided die. - + %1 flips %2 coins. There are %3 heads and %4 tails. - + %1 rolls a %2-sided dice %3 times: %4. @@ -3702,17 +4514,17 @@ Cockatrice will now reload the card database. - + %1 unattaches %2. - + %1 undoes their last draw. - + %1 undoes their last draw (%2). @@ -3720,110 +4532,110 @@ Cockatrice will now reload the card database. MessagesSettingsPage - + Word1 Word2 Word3 - + Add New Message - + Edit Message - + Remove Message - + Add message - - + + Message: - + Edit message - + Chat settings - + Custom alert words - + Enable chat mentions - + Enable mention completer - + In-game message macros - + How to use in-game message macros - + Ignore chat room messages sent by unregistered users - + Ignore private messages sent by unregistered users - - + + Invert text color - + Enable desktop notifications for private messages - + Enable desktop notification for mentions - + Enable room message history on join - - + + (Color is hexadecimal) - + Separate words with a space, alphanumeric characters only @@ -3881,6 +4693,24 @@ Cockatrice will now reload the card database. + + OverlapControlWidget + + + Cards to overlap: + + + + + Overlap percentage: + + + + + Overlap direction: + + + Phase @@ -3947,57 +4777,57 @@ Cockatrice will now reload the card database. PhasesToolbar - + Untap step - + Upkeep step - + Draw step - + First main phase - + Beginning of combat step - + Declare attackers step - + Declare blockers step - + Combat damage step - + End of combat step - + Second main phase - + End of turn step @@ -4005,7 +4835,7 @@ Cockatrice will now reload the card database. PictureLoader - + en code for scryfall's language property, not available for all languages @@ -4014,647 +4844,781 @@ Cockatrice will now reload the card database. Player - + Reveal top cards of library - - - - - - - - - + + + + + + + + + + + + Number of cards: (max. %1) - + &View graveyard - + &View exile - + Player "%1" - - - - + + + + &Graveyard - - - - + + + + &Exile - + &Move hand to... - - - + + + &Top of library - - - + + + &Bottom of library - + &Move graveyard to... - - - - + + + + &Hand - + &Move exile to... - + &View library - + &View hand - + View &top cards of library... - + Reveal &library to... - + &Always reveal top card - + &View sideboard - + &Draw card - + D&raw cards... - + &Undo last draw - + Take &mulligan - - &Shuffle - - - - + Play top card &face down - + Move top card to grave&yard - + Move top card to e&xile - + Move top cards to &graveyard... - + Move top cards to &exile... - + Put top card on &bottom - + + View bottom cards of library... + + + + Lend library to... - + + Shuffle + + + + Put top cards on stack &until... Take top cards &until... - + + Shuffle top cards... + + + + + Shuffle bottom cards... + + + + &Reveal hand to... - + Reveal r&andom card to... - + Reveal random card to... - + &Sideboard - + &Library - + &Counters - + &Untap all permanents - + R&oll die... - + &Create token... - + C&reate another token - + Cr&eate predefined token - - + + &All players - + S&ay - + + &Select All + + + + + S&elect Row + + + + + S&elect Column + + + + &Play - + &Hide - + Play &Face Down - + &Tap / Untap Turn sideways or back again - + Toggle &normal untapping - + T&urn Over Turn face up/face down - + &Peek at card face - + &Clone - + Attac&h to card... - + Unattac&h - + &Draw arrow... - + &Increase power - + &Decrease power - + I&ncrease toughness - + D&ecrease toughness - + In&crease power and toughness - + Dec&rease power and toughness - + Increase power and decrease toughness - + Decrease power and increase toughness - + Set &power and toughness... - + Reset p&ower and toughness - + &Set annotation... - + Red - + Yellow - + Green - + &Add counter (%1) - + &Remove counter (%1) - + &Set counters (%1)... - + &Top of library in random order - + X cards from the top of library... - + &Bottom of library in random order - + View top cards of library - + + View bottom cards of library + + + + + Shuffle top cards of library + + + + + Shuffle bottom cards of library + + + + Which position should this card be placed: - + (max. %1) - + Draw hand - + 0 and lower are in comparison to current hand size - + Draw cards - - + + Number: - + Move top cards to grave - + Reveal &top cards to... - + &Top of library... - + &Bottom of library... - + &Always look at top card - + &Open deck in deck editor - + &Play top card - + &Draw bottom card - + D&raw bottom cards... - + &Play bottom card - + Play bottom card &face down - + Move bottom card to grave&yard - + Move bottom card to e&xile - + Move bottom cards to &graveyard... - + Move bottom cards to &exile... - + Put bottom card on &top - + Selec&ted cards - + Move top cards to exile - + Move bottom cards to grave - + Move bottom cards to exile - + Draw bottom cards - - + + C&reate another %1 token - + Create tokens - + Place card X cards from top of library - + Change power/toughness - + Change stats to: - + Set annotation - + Please enter the new annotation: - + Set counters - + Re&veal to... - + View related cards - + Token: - + All tokens + + PrintingSelector + + + Display Navigation Buttons + + + + + <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. + + + + + PrintingSelectorCardOverlayWidget + + + Preference + + + + + Pin Printing + + + + + Unpin Printing + + + + + Show Related cards + + + + + PrintingSelectorCardSearchWidget + + + Search by set name or set code + + + + + PrintingSelectorCardSelectionWidget + + + Previous Card in Deck + + + + + Next Card in Deck + + + + + PrintingSelectorCardSortingWidget + + + Alphabetical + + + + + Preference + + + + + Release Date + + + + + + Descending + + + + + Ascending + + + QMenuBar - + Services - + Hide %1 - + Hide Others - + Show All - + Preferences... - + Quit %1 - + About %1 @@ -4662,38 +5626,32 @@ Cockatrice will now reload the card database. QObject - + Cockatrice card database (*.xml) - - + All files (*.*) - + Cockatrice replays (*.cor) - - Common deck formats (*.cod *.dec *.dek *.txt *.mwDeck) - - - - + Maindeck - + Sideboard - + Tokens @@ -4701,92 +5659,92 @@ Cockatrice will now reload the card database. QPlatformTheme - + OK - + Save - + Save All - + Open - + &Yes - + Yes to &All - + &No - + N&o to All - + Abort - + Retry - + Ignore - + Close - + Cancel - + Discard - + Help - + Apply - + Reset - + Restore Defaults @@ -4794,17 +5752,17 @@ Cockatrice will now reload the card database. RemoteDeckList_TreeModel - + Name - + ID - + Upload time @@ -4812,32 +5770,32 @@ Cockatrice will now reload the card database. RemoteReplayList_TreeModel - + ID - + Name - + Players - + Keep - + Time started - + Duration (sec) @@ -4883,32 +5841,32 @@ Cockatrice will now reload the card database. SequenceEdit - + Choose an action from the table - + Hit the key/combination of keys you want to set for this action - - Shortcut already in use - - - - + Invalid key - + + Shortcut already in use by: + + + + Clear - + Restore default @@ -4944,72 +5902,80 @@ Cockatrice will now reload the card database. ShortcutSettingsPage - - + + Restore all default shortcuts - + Do you really want to restore all default shortcuts? - + Clear all default shortcuts - + Do you really want to clear all shortcuts? - - Action - - - - - Shortcut - - - - + Section: - + Action: - + Shortcut: - + How to set custom shortcuts - + Clear all shortcuts + + + Search by shortcut name + + + + + ShortcutTreeView + + + Action + + + + + Shortcut + + ShortcutsSettings - + Your configuration file contained invalid shortcuts. Please check your shortcut settings! - + The following shortcuts have been set to default: @@ -5018,17 +5984,17 @@ Please check your shortcut settings! ShutdownDialog - + &Reason for shutdown: - + &Time until shutdown (minutes): - + Shut down server @@ -5036,27 +6002,27 @@ Please check your shortcut settings! SoundSettingsPage - + Enable &sounds - + Current sounds theme: - + Test system sound engine - + Sound settings - + Master volume @@ -5064,48 +6030,48 @@ Please check your shortcut settings! SpoilerBackgroundUpdater - + Spoilers season has ended - + Deleting spoiler.xml. Please run Oracle - - + + Spoilers download failed - + No internet connection - + Error Σφάλμα - + Spoilers already up to date - + No new spoilers added - + Spoilers have been updated! - + Last change: @@ -5113,635 +6079,593 @@ Please check your shortcut settings! StableReleaseChannel - - Stable Releases + + Default - + No reply received from the release update server. - + Invalid reply received from the release update server. - + No reply received from the tag update server. - + Invalid reply received from the tag update server. + + TabAccount + + + Add to Buddy List + + + + + Add to Ignore List + + + + + Account + + + TabAdmin - + Administration - + Update server &message - + &Shut down server - + &Reload configuration - + Server administration functions - + + Server moderator functions + + + + + Replay ID + + + + + Grant Replay Access + + + + + Username to Activate + + + + + Force Activate User + + + + &Unlock functions - + &Lock functions + + + + Success + + + + + Replay access granted + + + + + + + + + Error + + + + + Unable to grant replay access. Replay ID invalid + + + + + Unable to grant replay access. Internal error + + + + + User successfully activated + + + + + Unable to activate user. Username invalid + + + + + Unable to activate user. User already active + + + + + Unable to activate user. Internal error + + TabDeckEditor - - Search by card name (or search expressions) - Search by card name - - - - - Add to Deck - - - - - Add to Sideboard - - - - - Show Related cards - - - - - &Clear all filters - - - - - Delete selected - - - - - Deck &name: - - - - - &Comments: - - - - - Hash: - - - - - &New deck - - - - - &Load deck... - - - - - &Save deck - - - - - Save deck &as... - - - - - Load deck from cl&ipboard... - - - - - Save deck to clipboard - - - - - Annotated - - - - - Not Annotated - - - - - &Print deck... - - - - - &Send deck to online service - - - - - Create decklist (decklist.org) - - - - - Analyze deck (deckstats.net) - - - - - Analyze deck (tappedout.net) - - - - - &Close - - - - - Add card to &maindeck - - - - - Add card to &sideboard - - - - - &Remove row - - - - - &Increment number - - - - - &Decrement number - - - - - &Deck Editor - - - - - + Card Info - - + Deck - - + Filters - + &View - - - + + Printing + + + + + + + Visible - - - + + + + Floating - + Reset layout - + Deck: %1 - - - Are you sure? - - - - - The decklist has been modified. -Do you want to save the changes? - - - - - Load deck - - - - - - - - - - Error - Σφάλμα - - - - The deck could not be saved. - - - - - Could not save remote deck - - - - - - The deck could not be saved. -Please check that the directory is writable and try again. - - - - - Save deck - - - - - There are no cards in your deck to be exported - - - - - No deck was selected to be saved. - - TabDeckStorage - - Deck storage - - - - + Local file system - + Server deck storage - - + + Open in deck editor - + + Rename deck or folder + + + + Upload deck - + Download deck - - + + + + New folder - - + + Delete - - + + Open decks folder + + + + + Rename local folder + + + + + Rename local file + + + + + New name: + + + + + + + Error Σφάλμα - + + Rename failed + + + + + Invalid deck file - + Enter deck name - + This decklist does not have a name. Please enter a name: - + Unnamed deck - + Failed to upload deck to server - + Delete local file - - - - Are you sure you want to delete "%1"? + + Are you sure you want to delete the selected files? - + + Delete remote decks + + + + + Are you sure you want to delete the selected decks? + + + + + Name of new folder: - - Delete remote folder + + Deck Storage + + + + + TabDeckStorageVisual + + + Visual Deck Storage - - Delete remote deck + + Error + + + + + Could not open deck at %1 + + + + + TabEdhRec + + + EDHREC: TabGame - - - + + + Replay - - + + Game - - + + Player List - - + + Card Info - - + + Messages - - + + Replay Timeline - + &Phases - + &Game - + Next &phase - + Next phase with &action - + Next &turn - + Reverse turn order - + &Remove all local arrows - + Rotate View Cl&ockwise - + Rotate View Co&unterclockwise - + Game &information - + + Un&concede + + + + &Concede - + &Leave game - + C&lose replay - + &Focus Chat - + &Say: - + &View - - - - + + + + Visible - - - - + + + + Floating - + Reset layout - + Concede - + Are you sure you want to concede this game? - + Unconcede - + You have already conceded. Do you want to return to this game? - + Leave game - + Are you sure you want to leave this game? - + You are flooding the game. Please wait a couple of seconds. - + A player has joined game #%1 - + %1 has joined the game - + kicked by game host or moderator - + player left the game - + player disconnected from server - + reason unknown - + You have been kicked out of the game. @@ -5754,152 +6678,152 @@ Please enter a name: - - - + + + Time;SenderName;SenderIP;Message;TargetID;TargetName - + Room Logs - + Game Logs - + Chat Logs - - + + Error Σφάλμα - + You must select at least one filter. - + You have to select a valid number of days to locate. - + Username: - + IP Address: - + Game Name: - + GameID: - + Message: - + Main Room - + Game Room - + Private Chat - + Past X Days: - + Today - + Last Hour - + Maximum Results: - + At least one filter is required. The more information you put in, the more specific your results will be. - + Get User Logs - + Clear Filters - + Filters - + Log Locations - + Date Range - + Maximum Results - - + + Message History - + Failed to collect message history information. - + There are no messages for the selected filters. @@ -5922,22 +6846,22 @@ The more information you put in, the more specific your results will be. - + This user is ignoring you, they cannot see your messages in main chat and you cannot join their games. - + Private message from - + %1 has left the server. - + %1 has joined the server. @@ -5945,117 +6869,153 @@ The more information you put in, the more specific your results will be. TabReplays - - Game replays - - - - + Local file system - + Server replay storage - - + + Watch replay - - + + Rename + + + + + + New folder + + + + + Delete - + + Open replays folder + + + + Download replay - + Toggle expiration lock - + + Rename local folder + + + + + Rename local file + + + + + New name: + + + + + Error + + + + + Rename failed + + + + + Name of new folder: + + + + Delete local file - - Are you sure you want to delete "%1"? + + Are you sure you want to delete the selected files? - - Downloading Replays + + Are you sure you want to delete the selected replays? - - Folder download is not yet supported. Please download replays individually. - - - - + Delete remote replay - - Are you sure you want to delete the replay of game %1? + + Game Replays TabRoom - + &Say: - + Chat - + &Room - + &Leave room - + &Clear chat - + Chat Settings... - + mentioned you. - + Click to view - + You are flooding the chat. Please wait a couple of seconds. @@ -6068,30 +7028,30 @@ The more information you put in, the more specific your results will be. - - - - + + + + Error Σφάλμα - + Failed to join the server room: it doesn't exist on the server. - + The server thinks you are in the server room but your client is unable to display it. Try restarting your client. - + You do not have the required permission to join this server room. - + Failed to join the server room due to an unknown error: %1. @@ -6099,32 +7059,72 @@ The more information you put in, the more specific your results will be. TabSupervisor - + + Deck Editor + + + + + &Visual Deck Storage + + + + + Server + + + + + Account + + + + + Deck Storage + + + + + Game Replays + + + + + Administration + + + + + Logs + + + + Are you sure? - + There are still open games. Are you sure you want to quit? - + Click to view - + Your buddy %1 has signed on! - + Unknown Event - + The server has sent you a message that your client does not understand. This message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -6132,61 +7132,43 @@ To update your client, go to Help -> Check for Updates. - + Idle Timeout - + You are about to be logged out due to inactivity. - + Promotion - + You have been promoted. Please log out and back in for changes to take effect. - + Warned - + You have received a warning due to %1. Please refrain from engaging in this activity or further actions may be taken against you. If you have any questions, please private message a moderator. - + You have received the following message from the server. (custom messages like these could be untranslated) - - TabUserLists - - - Account - - - - - Add to Buddy List - - - - - Add to Ignore List - - - TappedOutInterface @@ -6270,173 +7252,184 @@ Please refrain from engaging in this activity or further actions may be taken ag UserContextMenu - + User &details - + Private &chat - + Show this user's &games - + Add to &buddy list - + Remove from &buddy list - + Add to &ignore list - + Remove from &ignore list - + Kick from &game - + Warn user - + View user's war&n history - + Ban from &server - + View user's &ban history - + &Promote user to moderator - + Dem&ote user from moderator - + Promote user to &judge - + Demote user from judge - + + View admin notes + + + + %1's games - - - + + + Ban History - + Ban Time;Moderator;Ban Length;Ban Reason;Visible Reason - + User has never been banned. - + Failed to collect ban information. - - - + + + Warning History - + Warning Time;Moderator;User Name;Reason - + User has never been warned. - + Failed to collect warning information. - - + + Failed to get admin notes. + + + + + Success - + Successfully promoted user. - + Successfully demoted user. - - + + + Failed - + Failed to promote user. - + Failed to demote user. - + Copy hash to clipboard - + Remove this user's messages @@ -6444,173 +7437,173 @@ Please refrain from engaging in this activity or further actions may be taken ag UserInfoBox - + Location: - + Account Age: - + Edit - + Change password - + Change avatar - + Administrator - + Moderator - + Registered user - - + + Unregistered user - + Judge - + Unknown - + The entered password does not match your account. - - - + + + Information - + User information updated. - - - - - - - - - - + + + + + + + + + + Error Σφάλμα - + User Information - + Real Name: - + User Level: - + %n Year(s), amount of years (only shown if more than 0) - + %10%n Day(s) %20 amount of years (if more than 0), amount of days, date in local short format - + Enter Password Εισάγετε κωδικό - + Password verification is required in order to change your email address - - - + + + An error occurred while trying to update your user information. - + This server does not permit you to update your user informations. - + Password changed. - + This server does not permit you to change your password. - + The new password is too short. - + The old password is incorrect. - + Avatar updated. - + This server does not permit you to update your avatar. - + An error occured while trying to updater your avatar. @@ -6618,124 +7611,251 @@ Please refrain from engaging in this activity or further actions may be taken ag UserInterfaceSettingsPage - + General interface settings - + &Double-click cards to play them (instead of single-click) - + + &Clicking plays all selected cards (instead of just the clicked card) + + + + &Play all nonlands onto the stack (not the battlefield) by default - + + Close card view window when last card is removed + + + + Annotate card text on tokens - + Use tear-off menus, allowing right click menus to persist on screen - + Notifications settings - + Enable notifications in taskbar - + Notify in the taskbar for game events while you are spectating - + Notify in the taskbar when users in your buddy list connect - + Animation settings - + &Tap/untap animation + + + Deck editor/storage settings + + + + + Open deck in new tab by default + + + + + Prompt before converting .txt decks to .cod format + + + + + Always convert if not prompted + + + + + Use visual deck storage in game lobby + + + + + Replay settings + + + + + Buffer time for backwards skip via shortcut: + + - UserList + UserListWidget - + Users connected to server: %1 - + Users in this room: %1 - + Buddies online: %1 / %2 - + Ignored users online: %1 / %2 + + VisualDeckStorageFolderDisplayWidget + + + Deck Storage + + + + + VisualDeckStorageSearchWidget + + + Search by filename + + + + + VisualDeckStorageSortWidget + + + Sort Alphabetically (Deck Name) + + + + + Sort Alphabetically (Filename) + + + + + Sort by Last Modified + + + + + Sort by Last Loaded + + + + + VisualDeckStorageWidget + + + Loading database ... + + + + + Show Folders + + + + + Show Tag Filter + + + + + Show Tags On Deck Previews + + + + + Show Banner Card Selection Option + + + + + Include Folder Names in Search + + + + + Draw unused Color Identities + + + + + Unused Color Identities Opacity + + + WarningDialog - + Which warning would you like to send? - + Redact all messages from this user in all rooms - + &OK - + &Cancel - + Warn user for misconduct - - + + Error Σφάλμα - + User name to send a warning to can not be blank, please specify a user to warn. - + Warning to use can not be blank, please select a valid warning to send. @@ -6743,128 +7863,133 @@ Please refrain from engaging in this activity or further actions may be taken ag WndSets - + Move selected set to the top - + Move selected set up - + Move selected set down - + Move selected set to the bottom - + Search by set name, code, or type - + Default order - + Restore original art priority order - + Enable all sets - + Disable all sets - + Enable selected set(s) - + Disable selected set(s) - + Deck Editor - + Use CTRL+A to select all sets in the view. - + Only cards in enabled sets will appear in the card list of the deck editor. - + Image priority is decided in the following order: - + first the CUSTOM Folder (%1), then the Enabled Sets in this dialog (Top to Bottom) %1 is a link to the wiki - + Card Art - + How to use custom card art - + Hints - + Note - + Sorting by column allows you to find a set while not changing set priority. - + To enable ordering again, click the column header until this message disappears. - + Use the current sorting as the set priority instead - + Sorts the set priority using the same column - + + Include online-only (Arena) cards [requires restart] + + + + Manage sets @@ -6872,22 +7997,67 @@ Please refrain from engaging in this activity or further actions may be taken ag ZoneViewWidget - - sort by name + + Ungrouped - - sort by type + + Group by Type - + + Group by Mana Value + + + + + Group by Color + + + + + Unsorted + + + + + Sort by Name + + + + + Sort by Type + + + + + Sort by Mana Cost + + + + + Sort by Colors + + + + + Sort by P/T + + + + + Sort by Set + + + + shuffle when closing - + pile view @@ -6895,7 +8065,7 @@ Please refrain from engaging in this activity or further actions may be taken ag i18n - + English Ελληνικά (Greek) @@ -6903,12 +8073,12 @@ Please refrain from engaging in this activity or further actions may be taken ag main - + Connect on startup - + Debug to file @@ -6916,786 +8086,908 @@ Please refrain from engaging in this activity or further actions may be taken ag shortcutsTab - + Main Window - - + + Deck Editor - + Game Lobby - + Card Counters - + Player Counters - + Power and Toughness - + Game Phases - + Playing Area - + Move Selected Card - + View - + Move Top Card - + Move Bottom Card - + Gameplay - + Drawing - + Chat Room - + Game Window - + Load Deck from Clipboard - + + Replays - + + Tabs + + + + Check for Card Updates... - + Connect... - + Disconnect - + Exit - + Full screen - + Register... - + Settings... - + Start a Local Game... - + Watch Replay... - - Analyze Deck + + Analyze Deck (deckstats.net) + Analyze Deck - - Clear All Filters - - - - - Clear Selected Filter - - - - - Close + + Analyze Deck (tappedout.net) - Remove Card + Clear All Filters + Clear Selected Filter + + + + + Close + + + + + Remove Card + + + + Manage Sets... - + Edit Custom Tokens... - - Export Deck - - - - - Add Card - - - - - Load Deck... - - - - - Load Deck from Clipboard... + + Export Deck (decklist.org) - New Deck + Export Deck (decklist.xyz) - - Open Custom Pictures Folder + + Add Card - - Print Deck... + + Load Deck... + Load Deck from Clipboard... + + + + + Edit Deck in Clipboard, Annotated + + + + + Edit Deck in Clipboard + + + + + New Deck + + + + + Open Custom Pictures Folder + + + + + Print Deck... + + + + Delete Card - - + + Reset Layout - + Save Deck - + Save Deck as... - + Save Deck to Clipboard, Annotated - - Save Deck to Clipboard - - - - - Load Local Deck... - - - - - Load Remote Deck... - - - - - Set Ready to Start - - - - - Toggle Sideboard Lock - - - - - - Add Green Counter - - - - - - Remove Green Counter - - - - - - Set Green Counters... + + Save Deck to Clipboard, Annotated (No Set Info) + Save Deck to Clipboard + + + + + Save Deck to Clipboard (No Set Info) + + + + + Load Local Deck... + + + + + Load Remote Deck... + + + + + Set Ready to Start + + + + + Toggle Sideboard Lock + + + + + + Add Green Counter + + + + + + Remove Green Counter + + + + + + Set Green Counters... + + + + Add Yellow Counter - + Remove Yellow Counter - + Set Yellow Counters... - - + + Add Red Counter - - + + Remove Red Counter - - + + Set Red Counters... - + Add Life Counter - + Remove Life Counter - + Set Life Counters... - + Add White Counter - + Remove White Counter - + Set White Counters... - + Add Blue Counter - + Remove Blue Counter - + Set Blue Counters... - + Add Black Counter - + Remove Black Counter - + Set Black Counters... - + Add Colorless Counter - + Remove Colorless Counter - + Set Colorless Counters... - + Add Other Counter - + Remove Other Counter - + Set Other Counters... - + Add Power (+1/+0) - + Remove Power (-1/-0) - + Move Toughness to Power (+1/-1) - + Add Toughness (+0/+1) - + Remove Toughness (-0/-1) - + Move Power to Toughness (-1/+1) - + Add Power and Toughness (+1/+1) - + Remove Power and Toughness (-1/-1) - + Set Power and Toughness... - + Reset Power and Toughness - + Untap - + Upkeep - + Draw - + First Main Phase - + Start Combat - + Attack - + Block - + Damage - + End Combat - + Second Main Phase - + End - + Next Phase - + Next Phase Action - + Next Turn - + + Hide Card in Reveal Window + + + + Tap / Untap Card - + Untap All - + Toggle Untap - + Turn Card Over - + Peek Card - + Play Card - + Attach Card... - + Unattach Card - + Clone Card - + Create Token... - + Create All Related Tokens - + Create Another Token - + Set Annotation... - - + + Select All Cards in Zone + + + + + Select All Cards in Row + + + + + Select All Cards in Column + + + + + Bottom of Library - - - - + + + + Exile - - - - + + + + Graveyard - - + + Hand - - + + Top of Library - - - + + + Battlefield, Face Down - + Battlefield - + Library - + Sideboard - + Top Cards of Library - + + Bottom Cards of Library + + + + Close Recent View - - + + Stack - - + + Graveyard (Multiple) - - + + Exile (Multiple) - + Stack Until Found - + Draw Bottom Card - + Draw Multiple Cards from Bottom... - + Draw Arrow... - + Remove Local Arrows - + Leave Game - + Concede - + Roll Dice... - + Shuffle Library - + + Shuffle Top Cards of Library + + + + + Shuffle Bottom Cards of Library + + + + Mulligan - + Draw a Card - + Draw Multiple Cards... - + Undo Draw - + Always Reveal Top Card - + Always Look At Top Card - + Rotate View Clockwise - + Rotate View Counterclockwise - + Unfocus Text Box - + Focus Chat - + Clear Chat - + Refresh - + + Skip Forward + + + + + Skip Backward + + + + + Skip Forward by a lot + + + + + Skip Backward by a lot + + + + Play/Pause - + Toggle Fast Forward + + + Visual Deck Storage + + + + + Deck Storage + + + + + Server + + + + + Account + + + + + Administration + + + + + Logs + + \ No newline at end of file diff --git a/cockatrice/translations/cockatrice_en@pirate.ts b/cockatrice/translations/cockatrice_en@pirate.ts index 5fdcd7d07..370271c77 100644 --- a/cockatrice/translations/cockatrice_en@pirate.ts +++ b/cockatrice/translations/cockatrice_en@pirate.ts @@ -2,7 +2,7 @@ AbstractCounter - + &Set counter... &Set piece o' eight... @@ -10,95 +10,247 @@ AbstractCounterDialog - + Set counter Set piece o' eight - + New value for counter '%1': Freshly formed value for piece o' eight '%1': - AppearanceSettingsPage + AbstractDlgDeckTextEdit - + + &Refresh + + + + + Parse Set Name and Number (if available) + + + + + AbstractTabDeckEditor + + + Open in new tab + + + + + Are you sure? + + + + + The decklist has been modified. +Do you want to save the changes? + + + + + + + + + + Error - + + Could not open deck at %1 + + + + + Could not save remote deck + + + + + + The deck could not be saved. +Please check that the directory is writable and try again. + + + + + Save deck + + + + + The deck could not be saved. + + + + + There are no cards in your deck to be exported + + + + + No deck was selected to be exported. + + + + + AdminNotesDialog + + + Update Notes + + + + + Admin Notes for %1 + + + + + AllZonesCardAmountWidget + + + Mainboard + + + + + Sideboard + + + + + AppearanceSettingsPage + + + Error + + + + Could not create themes directory at '%1'. - + Theme settings Theme settin's - + Current theme: - + Open themes folder - + + Menu settings + + + + + Show keyboard shortcuts in right-click menus + + + + Card rendering C-arrr-d Scribblin' - + Display card names on cards having a picture - + + Auto-Rotate cards with sideways layout + + + + + Override all card art with personal set preference (Pre-ProviderID change behavior) [Requires Client restart] + + + + + Bump sets that the deck contains cards from to the top in the printing selector + + + + Scale cards on mouse over - + + Use rounded card corners + + + + Minimum overlap percentage of cards on the stack and in vertical hand - + + Maximum initial height for card view window: + + + + + + rows + + + + + Maximum expanded height for card view window: + + + + Hand layout - + Display hand horizontally (wastes space) - + Enable left justification - + Table grid layout - + Invert vertical coordinate - + Minimum player count for multi-column layout: - + Maximum font size for information displayed on cards: @@ -106,112 +258,112 @@ BanDialog - + ban &user name Curse da landlubber's name - + ban &IP address Curse da landlubbers port o call - + ban client I&D Curse da landlubber's Jolly Roger - + Ban type - + &permanent ban - + &temporary ban - + &Days: - + &Hours: - + &Minutes: - + Duration of the ban - + Please enter the reason for the ban. This is only saved for moderators and cannot be seen by the banned person. Me hearty, enter t' reason for t' ban. This be only saved for captains and cannot be seen by t' brutally banned scallywag. - + Please enter the reason for the ban that will be visible to the banned person. Me hearty, enter t' reason for t' ban that be visible to t' brutally banned scallywag. - + Redact all messages from this user in all rooms - + &OK Aye - + &Cancel Belay that - + Ban user from server Banish the scurvy dog from the ship! - - - - + + + + Error Cap'n? Thar be a problem - + You have to select a name-based, IP-based, clientId based, or some combination of the three to place a ban. - + You must have a value in the name ban when selecting the name ban checkbox. - + You must have a value in the ip ban when selecting the ip ban checkbox. - + You must have a value in the clientid ban when selecting the clientid ban checkbox. @@ -219,22 +371,22 @@ This be only saved for captains and cannot be seen by t' brutally banned sc BetaReleaseChannel - - Beta Releases + + Beta - + No reply received from the release update server. - + Invalid reply received from the release update server. - + No reply received from the file update server. @@ -242,32 +394,32 @@ This be only saved for captains and cannot be seen by t' brutally banned sc CardDatabaseModel - + Name Name - + Sets Fleets - + Mana cost Magic tax - + Card type Type 'o magic - + P/T P/T - + Color(s) @@ -360,37 +512,65 @@ This be only saved for captains and cannot be seen by t' brutally banned sc - CardFrame + CardInfoFrameWidget - + Image - Pic + - + Description - + Both + + + View transformation + + - CardInfoText + CardInfoPictureWidget - - Name: - Name: + + View related cards + - + + Add card to deck + + + + + Mainboard + + + + + Sideboard + + + + + CardInfoTextWidget + + + Name: + + + + Related cards: - + Unknown card: @@ -398,287 +578,555 @@ This be only saved for captains and cannot be seen by t' brutally banned sc CardItem - + &Move to - + &Power / toughness &Power / toughness + + CardSizeWidget + + + Card Size + + + CardZone - + their hand nominative - + %1's hand nominative - + their library look at zone - + %1's library look at zone - + of their library top cards of zone, - + of %1's library top cards of zone - + their library reveal zone - + %1's library reveal zone - + their library shuffle - + %1's library shuffle - + their library nominative - + %1's library nominative - + their graveyard nominative - + %1's graveyard nominative - + their exile nominative - + %1's exile nominative - - - their sideboard - look at zone - - - - - %1's sideboard - look at zone - - - - - their sideboard - nominative - - + their sideboard + look at zone + + + + + %1's sideboard + look at zone + + + + + their sideboard + nominative + + + + %1's sideboard nominative + + DeckEditorCardInfoDockWidget + + + Card Info + + + + + DeckEditorDatabaseDisplayWidget + + + Search by card name (or search expressions) + + + + + Add to Deck + + + + + Add to Sideboard + + + + + Select Printing + + + + + Show on EDHREC (Commander) + + + + + Show on EDHREC (Card) + + + + + Show Related cards + + + + + Add card to &maindeck + + + + + Add card to &sideboard + + + + + DeckEditorDeckDockWidget + + + Banner Card + + + + + Select Printing + + + + + Deck + + + + + Deck &name: + + + + + &Comments: + + + + + Hash: + + + + + &Increment number + + + + + &Decrement number + + + + + &Remove row + + + + + Swap card to/from sideboard + + + + + DeckEditorFilterDockWidget + + + Filters + + + + + &Clear all filters + + + + + Delete selected + + + + + DeckEditorMenu + + + &Deck Editor + + + + + &New deck + + + + + &Load deck... + + + + + Load recent deck... + + + + + Clear + + + + + &Save deck + + + + + Save deck &as... + + + + + Load deck from cl&ipboard... + + + + + Edit deck in clipboard + + + + + + Annotated + + + + + + Not Annotated + + + + + Save deck to clipboard + + + + + Annotated (No set info) + + + + + Not Annotated (No set info) + + + + + &Print deck... + + + + + &Send deck to online service + + + + + Create decklist (decklist.org) + + + + + Create decklist (decklist.xyz) + + + + + Analyze deck (deckstats.net) + + + + + Analyze deck (tappedout.net) + + + + + &Close + + + + + DeckEditorPrintingSelectorDockWidget + + + Printing Selector + + + DeckEditorSettingsPage - - + + Update Spoilers - - + + Success - + Download URLs have been reset. - + Downloaded card pictures have been reset. - + Error - + One or more downloaded card pictures could not be cleared. - + Add URL - - + + URL: - - + + Edit URL - + + Network Cache Size: + + + + + Redirect Cache TTL: + + + + + How long cached redirects for urls are valid for. + + + + + Picture Cache Size: + + + + Add New URL - + Remove URL - + + Day(s) + + + + Updating... - + Choose path Map yer scurvy way - + URL Download Priority - + Spoilers - + Download Spoilers Automatically - + Spoiler Location: - + Last Change - + Spoilers download automatically on launch - + Press the button to manually update without relaunching - + Do not close settings until manual update is complete - + Download card pictures on the fly - + How to add a custom URL - + Delete Downloaded Images - + Reset Download URLs - - Downloaded images directory size: - - - - + On-disk cache for downloaded pictures - - Picture cache size: - - - - + In-memory cache for pictures not currently on screen @@ -686,16 +1134,218 @@ This be only saved for captains and cannot be seen by t' brutally banned sc DeckListModel - + + Count + + + + + Set + + + + Number - + + Provider ID + + + + Card + + DeckLoader + + + Common deck formats (%1) + + + + + All files (*.*) + + + + + DeckPreviewColorIdentityFilterWidget + + + Mode: Exact Match + + + + + Mode: Includes + + + + + DeckPreviewDeckTagsDisplayWidget + + + Edit tags ... + + + + + DeckPreviewTagDialog + + + Deck Tags Manager + + + + + Manage your deck tags. Check or uncheck tags as needed, or add new ones: + + + + + Add a new tag (e.g., Aggro️) + + + + + Add Tag + + + + + Filter tags... + + + + + OK + + + + + Cancel + + + + + Invalid Input + + + + + Tag name cannot be empty! + + + + + Duplicate Tag + + + + + This tag already exists. + + + + + DeckPreviewWidget + + + Banner Card + + + + + Open in deck editor + + + + + Edit Tags + + + + + Rename Deck + + + + + Save Deck to Clipboard + + + + + Annotated + + + + + Annotated (No set info) + + + + + Not Annotated + + + + + Not Annotated (No set info) + + + + + Rename File + + + + + Delete File + + + + + Set Banner Card + + + + + + New name: + + + + + + Error + + + + + Rename failed + + + + + Delete file + + + + + Are you sure you want to delete the selected file? + + + + + Delete failed + + + DeckStatsInterface @@ -713,181 +1363,217 @@ This be only saved for captains and cannot be seen by t' brutally banned sc DeckViewContainer - + Load deck... - + Load remote deck... - + + Unload deck + Unload deck... + + + + Ready to start - + + Force start + + + + Sideboard unlocked - + Sideboard locked - - Load deck - Find a flagship - - - + Error Cap'n? Thar be a problem - + The selected file could not be loaded. + + DialogConvertDeckToCodFormat + + + Deck Format Conversion + + + + + You tried to add a tag to a .txt format deck. + Tags can only be added to .cod format decks. + Do you want to convert the deck to the .cod format? + + + + + Remember and automatically apply choice in the future + + + DlgConnect - + Downloading... - + Known Hosts - + + Delete the currently selected saved server + + + + Refresh the server list with known public servers - + New Host - + Name: - + &Host: &Cap'n - + &Port: - + Player &name: Matey &name: - + P&assword: &A stupendously secret code: - + &Save password Record t' &stupendously secret code - + A&uto connect - + Automatically connect to the most recent login when Cockatrice opens - + If you have any trouble connecting or registering then contact the server staff for help! - - + + Webpage - + Reset Password - + + Forgot password? + + + + &Connect - + Server - + Login - + Server Contact - + Connect to Server - + Server URL - + Communication Port - + Unique Server Name - + Connection Warning - + You need to name your new connection profile. - + Connect Warning - + The player name can't be empty. @@ -970,27 +1656,37 @@ This be only saved for captains and cannot be seen by t' brutally banned sc Landlubbers - + + Starting life total: + + + + + Game setup options + + + + &Clear - + Create game - + Game information - + Error Cap'n? Thar be a problem - + Server error. Cap'n? Thar be a problem wi' t' ship. @@ -998,92 +1694,92 @@ This be only saved for captains and cannot be seen by t' brutally banned sc DlgCreateToken - + &Name: &Name: - + Token - + C&olor: C&olor - + white pure white - + blue sea blue - + black deathly black - + red blood red - + green emerald green - + multicolor multicolor - + colorless colorless - + &P/T: &P/T: - + &Annotation: - + &Destroy token when it leaves the table - + Token data - + Show &all tokens - + Show tokens from this &deck - + Choose token from list - + Create token @@ -1128,6 +1824,24 @@ To remove your current avatar, confirm without choosing a new image. Invalid pic be picked! + + DlgEditDeckInClipboard + + + Edit deck in clipboard + + + + + Error + + + + + Invalid deck list. + + + DlgEditPassword @@ -1170,93 +1884,93 @@ To remove your current avatar, confirm without choosing a new image. DlgEditTokens - + &Name: &Name: - + C&olor: C&olor - + white pure white - + blue sea blue - + black deathly black - + red blood red - + green emerald green - + multicolor multicolor - + colorless colorless - + &P/T: &P/T: - + &Annotation: - + Token data - - + + Add token - + Remove token - + Edit custom tokens - + Please enter the name of the token: - + Error Cap'n? Thar be a problem - + The chosen name conflicts with an existing card or token. Make sure to enable the 'Token' set in the "Manage sets" dialog to display them correctly. @@ -1293,132 +2007,132 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgFilterGames - + no limit - + 5 minutes - + 10 minutes - + 30 minutes - + 1 hour - + 2 hours - - Show '&buddies only' games + + Hide 'buddies only' games - - Show &full games + + Hide full games - - Show games &that have started + + Hide games that have started - - Show &password protected games + + Hide password protected games - - Hide '&ignored user' games + + Hide 'ignored user' games - + &Newer than: - + Game &description: - + &Creator name: &Cap'n name: - + General - + &Game types - + at &least: - + at &most: - + Maximum player count - + Restrictions - + Show games only if &spectators can watch - + Show spectator password p&rotected games - + Show only if spectators can ch&at - + Show only if spectators can see &hands - + Spectators - + Filter games @@ -1602,26 +2316,27 @@ Make sure to enable the 'Token' set in the "Manage sets" dia - DlgLoadDeckFromClipboard + DlgLoadDeck - - &Refresh + + Load Deck + + + DlgLoadDeckFromClipboard - + Load deck from clipboard Find a flagship from t' clipplank - - + Error Cap'n? Thar be a problem - - + Invalid deck list. Invalid flagship list. @@ -1629,11 +2344,49 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgLoadRemoteDeck - + Load deck Find a flagship + + DlgMoveTopCardsUntil + + + Card name (or search expressions): + + + + + Number of hits: + + + + + Auto play hits + + + + + Put top cards on stack until... + + + + + No cards matching the search expression exists in the card database. Proceed anyways? + + + + + Cockatrice + + + + + Invalid filter + + + DlgRegister @@ -1747,12 +2500,12 @@ Your email will be used to verify your account. DlgSettings - + Unknown Error loading card database Cap'n? Thar be a unknown problem wi' t' dock - + Your card database is invalid. Cockatrice may not function correctly with an invalid database @@ -1763,7 +2516,7 @@ Would you like to change your database location setting? - + Your card database version is too old. This can cause problems loading card information or images @@ -1774,7 +2527,7 @@ Would you like to change your database location setting? - + Your card database did not finish loading Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues with your cards.xml attached @@ -1783,21 +2536,21 @@ Would you like to change your database location setting? - + File Error loading your card database. Would you like to change your database location setting? - + Your card database was loaded but contains no cards. Would you like to change your database location setting? - + Unknown card database load status Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues @@ -1806,59 +2559,59 @@ Would you like to change your database location setting? - - - + + + Error Cap'n? Thar be a problem - + The path to your deck directory is invalid. Would you like to go back and set the correct path? - + The path to your card pictures directory is invalid. Would you like to go back and set the correct path? - + Settings Cap'n's orders - + General - + Appearance - + User Interface - + Card Sources - + Chat - + Sound - + Shortcuts @@ -1884,164 +2637,168 @@ Would you like to change your database location setting? DlgUpdate - + Current release channel - + Reinstall - + Cancel Download - + Open Download Page - + Check for Client Updates - - - - + + + + Error - + Cockatrice was not built with SSL support, therefore you cannot download updates automatically! Please visit the download page to update manually. - - Downloading update... + + Downloading update: %1 + Downloading update... - + Checking for updates... - + Finished checking for updates - + No Update Available - + Cockatrice is up to date! - + You are already running the latest version available in the chosen release channel. - + Current version - + Selected release channel - - + + Update Available - - - - A new version of Cockatrice is available! - - - - - - New version - - - Released + A new version of Cockatrice is available! + + + + + + New version - Changelog + Released + + Changelog + + + + Do you want to update now? - - Unfortunately there are no download packages available for your operating system. -You may have to build from source yourself. + + Unfortunately, the automatic updater failed to find a compatible download. +You may have to manually download the new version. + Unfortunately there are no download packages available for your operating system. +You may have to build from source yourself. - - Please check the download page manually and visit the wiki for instructions on compiling. + + Please check the <a href="%1">releases page</a> on our Github and download the build for your system. + Please check the download page manually and visit the wiki for instructions on compiling. - - - + + + Update Error - + An error occurred while checking for updates: - + An error occurred while downloading an update: - + Installing... - + Cockatrice is unable to open the installer. - + Try to update manually by closing Cockatrice and running the installer. - + Download location @@ -2059,6 +2816,14 @@ You may have to build from source yourself. + + EdhrecCommanderResponseCommanderDetailsDisplayWidget + + + Salt: + + + FilterBuilder @@ -2070,105 +2835,120 @@ You may have to build from source yourself. GameSelector - - - - - - - - - + + + + + + + + + Error Cap'n? Thar be a problem - + Please join the appropriate room first. - + Wrong password. Ye don't know the stupendously secret code! - + Spectators are not allowed in this game. Liliy-livered landlubbers - get off me ship! - + The game is already full. T' ship be full now. - + The game does not exist any more. T' ship be vanishin'! - + This game is only open to registered users. - + This game is only open to its creator's buddies. T' ship be only open for the cap'n's first mates. - + You are being ignored by the creator of this game. T' cap'n be ignorin' ye. - + + Join Game + + + + + Spectate Game + + + + + Game Information + + + + Join game Board ship - + Password: Stupendously secret code: - + Please join the respective room first. - + &Filter games - + C&lear filter - + C&reate - + &Join - + J&oin as spectator B&oard as landlubber - + Games shown: %1 / %2 - + Games Ships @@ -2176,100 +2956,100 @@ You may have to build from source yourself. GamesModel - + >1 day - + %1%2 hr short age in hours - + new - + %1%2 min short age in minutes - + password stupendously secret code - + buddies only first mates only - + reg. users only - - + + can chat - + see hands - + can see hands - + not allowed - + Room - + Age - + Description - + Creator Cap'n - + Type - + Restrictions - + Players Mateys - + Spectators Landlubbers @@ -2277,92 +3057,102 @@ You may have to build from source yourself. GeneralSettingsPage - + Reset all paths - + All paths have been reset - - + + Choose path - + Personal settings Cap'n's orders for t' cap'n - + Language: - + Paths (editing disabled in portable mode) - + Paths - + + How to help with translations + + + + Decks directory: - + Replays directory: - + Pictures directory: - + Card database: Dock: - + Custom database directory: - + Token database: - + Update channel - + + Check for client updates on startup + + + + Notify if a feature supported by the server is missing in my client - + Automatically run Oracle when running a new version of Cockatrice - + Show tips on startup @@ -2370,711 +3160,722 @@ You may have to build from source yourself. MainWindow - - + + The server has reached its maximum user capacity, please check back later. - + There are too many concurrent connections from your address. - + Banned by moderator Cap'n's thrown ye outta tha ship! - + Expected end time: %1 - + This ban lasts indefinitely. - + Scheduled server shutdown. - - + + Invalid username. Yarrr, that ain't ye name ye scurvy landlubber! - + You have been logged out due to logging in at another location. - + Connection closed - + The server has terminated your connection. Reason: %1 - + The server is going to be restarted in %n minute(s). All running games will be lost. Reason for shutdown: %1 - + Scheduled server shutdown - - + + Success - + Registration accepted. Will now login. - + Account activation accepted. Will now login. - + Number of players Number o' mateys - + Please enter the number of players. Me hearty, enter t' number o' mateys - - + + Player %1 Matey %1 - + Load replay - + About Cockatrice - + Version - + Cockatrice Webpage - + Project Manager: - + Past Project Managers: - + Developers: - + Our Developers - + Help Develop! - + Translators: - + Our Translators - + Help Translate! - + Support: - + Report an Issue - + Troubleshooting - + F.A.Q. - - - - - - - - - - - - - - - + + + + + + + + + + + + + - - - + + + + + Error Cap'n? Thar be a problem - + Server timeout T' ship be timin' out! - + Failed Login - + Your client seems to be missing features this server requires for connection. - + To update your client, go to 'Help -> Check for Client Updates'. - + Incorrect username or password. Please check your authentication information and try again. - + There is already an active session using this user name. Please close that session first and re-login. - - + + You are banned until %1. - - + + You are banned indefinitely. - + This server requires user registration. Do you want to register now? - + This server requires client IDs. Your client is either failing to generate an ID or you are running a modified client. Please close and reopen your client to try again. - + An internal error has occurred, please close and reopen Cockatrice before trying again. If the error persists, ensure you are running the latest version of the software and if needed contact the software developers. - + Account activation - + Your account has not been activated yet. You need to provide the activation token received in the activation email. - + Server Full - + Unknown login error: %1 - - + + This usually means that your client version is out of date, and the server sent a reply your client doesn't understand. - + Your username must respect these rules: - + is %1 - %2 characters long - + can %1 contain lowercase characters - - - - + + + + NOT - + can %1 contain uppercase characters - + can %1 contain numeric characters - + can contain the following punctuation: %1 - + first character can %1 be a punctuation mark - + no unacceptable language as specified by these server rules: note that the following lines will not be translated - + can not contain any of the following words: %1 - + can not match any of the following expressions: %1 - + You may only use A-Z, a-z, 0-9, _, ., and - in your username. - - - - - - + + + + + + Registration denied - + Registration is currently disabled on this server - + There is already an existing account with the same user name. - + It's mandatory to specify a valid email address when registering. - + It appears you are attempting to register a new account on this server yet you already have an account registered with the email provided. This server restricts the number of accounts a user can register per address. Please contact the server operator for further assistance or to obtain your credential information. - + Password too short. The code be too piddly to be stupendously secret. - + Registration failed for a technical problem on the server. - + Unknown registration error: %1 - + Account activation failed - + Socket error: %1 - + You are trying to connect to an obsolete server. Please downgrade your Cockatrice version or connect to a suitable server. Local version is %1, remote version is %2. - + Your Cockatrice client is obsolete. Please update your Cockatrice version. Local version is %1, remote version is %2. - + Connecting to %1... Boarding %1 - + Registering to %1 as %2... - + Disconnected Disembarked - + Connected, logging in at %1 - - - + + + Requesting forgotten password to %1 as %2... - + &Connect... &Board ye ship... - + &Disconnect &Disembark - + Start &local game... Built &local ship - + &Watch replay... - - &Deck editor - &Dock - - - + &Full screen - + &Register to server... - + &Restore password... - + &Settings... Cap'n'&s orders - + &Exit Dis&embark - + A&ctions - + &Cockatrice - + C&ard Database - + &Manage sets... - + Edit custom &tokens... - + Open custom image folder - + Open custom sets folder - + Add custom sets/cards - + + Reload card database + + + + + Tabs + + + + &Help - + &About Cockatrice - + &Tip of the Day - + Check for Client Updates - + Check for Card Updates... - + View &Debug Log - + + Open Settings Folder + + + + Show/Hide - + New Version - + Congratulations on updating to Cockatrice %1! Oracle will now launch to update your card database. - + Cockatrice installed - + Congratulations on installing Cockatrice %1! Oracle will now launch to install the initial card database. - + Card database - + Cockatrice is unable to load the card database. Do you want to update your card database now? If unsure or first time user, choose "Yes" - - + + Yes AYE! - - + + No NAY! - + Open settings - + New sets found - + %n new set(s) found in the card database Set code(s): %1 Do you want to enable it/them? - + View sets - + Welcome - + Hi! It seems like you're running this version of Cockatrice for the first time. All the sets in the card database have been enabled. Read more about changing the set order or disabling specific sets and consequent effects in the "Manage Sets" dialog. - - + + Information - + A card database update is already running. A dock update be runnin' already! - + Unable to run the card database updater: Cap'n! We can't run the dock updater: - - failed to start. + + Failed to start. The file might be missing, or permissions might be incorrect. - - crashed. + + The process crashed some time after starting successfully. - - timed out. - be timin' out! + + Timed out. The process took too long to respond. The last waitFor...() function timed out. + - - write error. - Cap'n? Thar be a writin' problem wi' t' ship. + + An error occurred when attempting to write to the process. For example, the process may not be running, or it may have closed its input channel. + - - read error. - Cap'n? Thar be a readin' problem wi' t' ship. + + An error occurred when attempting to read from the process. For example, the process may not be running. + - - unknown error. - Cap'n? Thar be a unknown problem wi' t' ship. + + Unknown error occurred. + - - The card database updater exited with an error: %1 - The dock updater sunk. She be havin' a problem: %1 + + The card database updater exited with an error: +%1 + - + This server supports additional features that your client doesn't have. This is most likely not a problem, but this message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -3082,54 +3883,54 @@ To update your client, go to Help -> Check for Updates. - - - - - + + + + + Load sets/cards - + Selected file cannot be found. - + You can only import XML databases at this time. - + The new sets/cards have been added successfully. Cockatrice will now reload the card database. - + Sets/cards failed to import. - - - + + + Reset Password - + Your password has been reset successfully, you can now log in using the new credentials. - + Failed to reset user account password, please contact the server operator to reset your password. - + Activation request received, please check your email for an activation token. @@ -3403,179 +4204,190 @@ Cockatrice will now reload the card database. - %1 is looking at the top %3 card(s) %2. + %1 is looking at the %4 %3 card(s) %2. + %1 is looking at the top %3 card(s) %2. top card for singular, top %3 cards for plural - + + bottom + + + + + top + + + + %1 turns %2 face-down. - + %1 turns %2 face-up. - + The game has been closed. T' ship has been sunk! - + The game has started. - + %1 has joined the game. - + %1 is now watching the game. - + You have been kicked out of the game. - + %1 has left the game (%2). - + %1 is not watching the game any more (%2). - + %1 is not ready to start the game any more. - + %1 shuffles their deck and draws a new hand of %2 card(s). - + %1 shuffles their deck and draws a new hand. - + You are watching a replay of game #%1. - + %1 is ready to start the game. Yer mate %1 wants a duel! - + cards an unknown amount of cards - + %1 card(s) a card for singular, %1 cards for plural - + %1 lends %2 to %3. - + %1 reveals %2 to %3. - + %1 reveals %2. - + %1 randomly reveals %2%3 to %4. - + %1 randomly reveals %2%3. - + %1 peeks at face down card #%2. - + %1 peeks at face down card #%2: %3. - + %1 reveals %2%3 to %4. - + %1 reveals %2%3. - + %1 reversed turn order, now it's %2. - + reversed - + normal - + Heads - + Tails - + %1 flipped a coin. It landed as %2. - + %1 rolls a %2 with a %3-sided die. - + %1 flips %2 coins. There are %3 heads and %4 tails. - + %1 rolls a %2-sided dice %3 times: %4. @@ -3695,17 +4507,17 @@ Cockatrice will now reload the card database. - + %1 unattaches %2. - + %1 undoes their last draw. - + %1 undoes their last draw (%2). @@ -3713,110 +4525,110 @@ Cockatrice will now reload the card database. MessagesSettingsPage - + Word1 Word2 Word3 - + Add New Message - + Edit Message - + Remove Message - + Add message - - + + Message: - + Edit message - + Chat settings Cap'n's orders for t' chat - + Custom alert words - + Enable chat mentions - + Enable mention completer - + In-game message macros - + How to use in-game message macros - + Ignore chat room messages sent by unregistered users - + Ignore private messages sent by unregistered users - - + + Invert text color - + Enable desktop notifications for private messages - + Enable desktop notification for mentions - + Enable room message history on join - - + + (Color is hexadecimal) - + Separate words with a space, alphanumeric characters only @@ -3874,6 +4686,24 @@ Cockatrice will now reload the card database. + + OverlapControlWidget + + + Cards to overlap: + + + + + Overlap percentage: + + + + + Overlap direction: + + + Phase @@ -3940,57 +4770,57 @@ Cockatrice will now reload the card database. PhasesToolbar - + Untap step Step where ye be untappin' - + Upkeep step Step where ye be upkeepin' - + Draw step Step where ye be drawin' - + First main phase First main phase - + Beginning of combat step Step where ye begin fightin' - + Declare attackers step Step where ye be orderin' ye men to board - + Declare blockers step Step where ye be orderin' ye men to repel boarders - + Combat damage step Step where people get hurt - + End of combat step Step where ye stop fightin' - + Second main phase Second main phase - + End of turn step Step where ye be endin' ye turn @@ -3998,7 +4828,7 @@ Cockatrice will now reload the card database. PictureLoader - + en code for scryfall's language property, not available for all languages @@ -4007,647 +4837,781 @@ Cockatrice will now reload the card database. Player - + Reveal top cards of library - - - - - - - - - + + + + + + + + + + + + Number of cards: (max. %1) - + &View graveyard &View Davy Jones' Locker - + &View exile - + Player "%1" Matey "%1" - - - - + + + + &Graveyard Davy Jones' Locker - - - - + + + + &Exile - + &Move hand to... - - - + + + &Top of library - - - + + + &Bottom of library - + &Move graveyard to... - - - - + + + + &Hand &Hand - + &Move exile to... - + &View library &View library - + &View hand - + View &top cards of library... View &top cards o' library... - + Reveal &library to... - + &Always reveal top card - + &View sideboard &View sideplank - + &Draw card &Draw card - + D&raw cards... - + &Undo last draw - + Take &mulligan - - &Shuffle - - - - + Play top card &face down - + Move top card to grave&yard - + Move top card to e&xile - + Move top cards to &graveyard... Move top cards to Davy Jones' Locker - + Move top cards to &exile... - + Put top card on &bottom - + + View bottom cards of library... + + + + Lend library to... - + + Shuffle + + + + Put top cards on stack &until... Take top cards &until... - + + Shuffle top cards... + + + + + Shuffle bottom cards... + + + + &Reveal hand to... &Reveal hand to... - + Reveal r&andom card to... - + Reveal random card to... - + &Sideboard &Sideplank - + &Library &Library - + &Counters Pie&ces o' eight - + &Untap all permanents - + R&oll die... - + &Create token... - + C&reate another token - + Cr&eate predefined token - - + + &All players - + S&ay - + + &Select All + + + + + S&elect Row + + + + + S&elect Column + + + + &Play - + &Hide - + Play &Face Down - + &Tap / Untap Turn sideways or back again - + Toggle &normal untapping - + T&urn Over Turn face up/face down - + &Peek at card face - + &Clone - + Attac&h to card... - + Unattac&h - + &Draw arrow... - + &Increase power - + &Decrease power - + I&ncrease toughness - + D&ecrease toughness - + In&crease power and toughness - + Dec&rease power and toughness - + Increase power and decrease toughness - + Decrease power and increase toughness - + Set &power and toughness... - + Reset p&ower and toughness - + &Set annotation... - + Red Blood red - + Yellow Fool's gold - + Green Emerald green - + &Add counter (%1) - + &Remove counter (%1) - + &Set counters (%1)... - + &Top of library in random order - + X cards from the top of library... - + &Bottom of library in random order - + View top cards of library View top cards o' library - + + View bottom cards of library + + + + + Shuffle top cards of library + + + + + Shuffle bottom cards of library + + + + Which position should this card be placed: - + (max. %1) - + Draw hand - + 0 and lower are in comparison to current hand size - + Draw cards - - + + Number: - + Move top cards to grave - + Reveal &top cards to... - + &Top of library... - + &Bottom of library... - + &Always look at top card - + &Open deck in deck editor - + &Play top card - + &Draw bottom card - + D&raw bottom cards... - + &Play bottom card - + Play bottom card &face down - + Move bottom card to grave&yard - + Move bottom card to e&xile - + Move bottom cards to &graveyard... - + Move bottom cards to &exile... - + Put bottom card on &top - + Selec&ted cards - + Move top cards to exile - + Move bottom cards to grave - + Move bottom cards to exile - + Draw bottom cards - - + + C&reate another %1 token - + Create tokens - + Place card X cards from top of library - + Change power/toughness - + Change stats to: - + Set annotation - + Please enter the new annotation: - + Set counters Set pieces o' eight - + Re&veal to... - + View related cards - + Token: - + All tokens + + PrintingSelector + + + Display Navigation Buttons + + + + + <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. + + + + + PrintingSelectorCardOverlayWidget + + + Preference + + + + + Pin Printing + + + + + Unpin Printing + + + + + Show Related cards + + + + + PrintingSelectorCardSearchWidget + + + Search by set name or set code + + + + + PrintingSelectorCardSelectionWidget + + + Previous Card in Deck + + + + + Next Card in Deck + + + + + PrintingSelectorCardSortingWidget + + + Alphabetical + + + + + Preference + + + + + Release Date + + + + + + Descending + + + + + Ascending + + + QMenuBar - + Services - + Hide %1 - + Hide Others - + Show All - + Preferences... - + Quit %1 - + About %1 @@ -4655,38 +5619,32 @@ Cockatrice will now reload the card database. QObject - + Cockatrice card database (*.xml) - - + All files (*.*) - + Cockatrice replays (*.cor) - - Common deck formats (*.cod *.dec *.dek *.txt *.mwDeck) - - - - + Maindeck - + Sideboard - + Tokens @@ -4694,92 +5652,92 @@ Cockatrice will now reload the card database. QPlatformTheme - + OK - + Save - + Save All - + Open - + &Yes - + Yes to &All - + &No - + N&o to All - + Abort - + Retry - + Ignore - + Close - + Cancel - + Discard - + Help - + Apply - + Reset - + Restore Defaults @@ -4787,17 +5745,17 @@ Cockatrice will now reload the card database. RemoteDeckList_TreeModel - + Name Name - + ID - + Upload time @@ -4805,32 +5763,32 @@ Cockatrice will now reload the card database. RemoteReplayList_TreeModel - + ID - + Name Name - + Players - + Keep - + Time started - + Duration (sec) @@ -4876,32 +5834,32 @@ Cockatrice will now reload the card database. SequenceEdit - + Choose an action from the table - + Hit the key/combination of keys you want to set for this action - - Shortcut already in use - - - - + Invalid key - + + Shortcut already in use by: + + + + Clear - + Restore default @@ -4937,72 +5895,80 @@ Cockatrice will now reload the card database. ShortcutSettingsPage - - + + Restore all default shortcuts - + Do you really want to restore all default shortcuts? - + Clear all default shortcuts - + Do you really want to clear all shortcuts? - - Action - - - - - Shortcut - - - - + Section: - + Action: - + Shortcut: - + How to set custom shortcuts - + Clear all shortcuts + + + Search by shortcut name + + + + + ShortcutTreeView + + + Action + + + + + Shortcut + + ShortcutsSettings - + Your configuration file contained invalid shortcuts. Please check your shortcut settings! - + The following shortcuts have been set to default: @@ -5011,17 +5977,17 @@ Please check your shortcut settings! ShutdownDialog - + &Reason for shutdown: - + &Time until shutdown (minutes): - + Shut down server @@ -5029,27 +5995,27 @@ Please check your shortcut settings! SoundSettingsPage - + Enable &sounds - + Current sounds theme: - + Test system sound engine - + Sound settings Cap'n's orders for t' shanties - + Master volume @@ -5057,48 +6023,48 @@ Please check your shortcut settings! SpoilerBackgroundUpdater - + Spoilers season has ended - + Deleting spoiler.xml. Please run Oracle - - + + Spoilers download failed - + No internet connection - + Error - + Spoilers already up to date - + No new spoilers added - + Spoilers have been updated! - + Last change: @@ -5106,635 +6072,593 @@ Please check your shortcut settings! StableReleaseChannel - - Stable Releases + + Default - + No reply received from the release update server. - + Invalid reply received from the release update server. - + No reply received from the tag update server. - + Invalid reply received from the tag update server. + + TabAccount + + + Add to Buddy List + + + + + Add to Ignore List + + + + + Account + + + TabAdmin - + Administration - + Update server &message - + &Shut down server - + &Reload configuration - + Server administration functions - + + Server moderator functions + + + + + Replay ID + + + + + Grant Replay Access + + + + + Username to Activate + + + + + Force Activate User + + + + &Unlock functions - + &Lock functions + + + + Success + + + + + Replay access granted + + + + + + + + + Error + + + + + Unable to grant replay access. Replay ID invalid + + + + + Unable to grant replay access. Internal error + + + + + User successfully activated + + + + + Unable to activate user. Username invalid + + + + + Unable to activate user. User already active + + + + + Unable to activate user. Internal error + + TabDeckEditor - - Search by card name (or search expressions) - Search by card name - - - - - Add to Deck - - - - - Add to Sideboard - - - - - Show Related cards - - - - - &Clear all filters - - - - - Delete selected - - - - - Deck &name: - Flagship &name: - - - - &Comments: - - - - - Hash: - - - - - &New deck - Freshly formed flagship - - - - &Load deck... - Find a flagship... - - - - &Save deck - Record flag&ship - - - - Save deck &as... - Record flagship &as... - - - - Load deck from cl&ipboard... - Find a flagship from t' cl&ipplank - - - - Save deck to clipboard - - - - - Annotated - - - - - Not Annotated - - - - - &Print deck... - &Print flagship... - - - - &Send deck to online service - - - - - Create decklist (decklist.org) - - - - - Analyze deck (deckstats.net) - - - - - Analyze deck (tappedout.net) - - - - - &Close - - - - - Add card to &maindeck - Add card to t' &mainplank - - - - Add card to &sideboard - - - - - &Remove row - - - - - &Increment number - - - - - &Decrement number - - - - - &Deck Editor - &Dock - - - - + Card Info - - + Deck - - + Filters - + &View - - - + + Printing + + + + + + + Visible - - - + + + + Floating - + Reset layout - + Deck: %1 Flagship: %1 - - - Are you sure? - - - - - The decklist has been modified. -Do you want to save the changes? - - - - - Load deck - Find a flagship - - - - - - - - - Error - Cap'n? Thar be a problem - - - - The deck could not be saved. - T' flagship be failin' to be recordin'! - - - - Could not save remote deck - - - - - - The deck could not be saved. -Please check that the directory is writable and try again. - - - - - Save deck - Record flagship - - - - There are no cards in your deck to be exported - - - - - No deck was selected to be saved. - - TabDeckStorage - - Deck storage - Flagship port - - - + Local file system - + Server deck storage O'er ship's flagship port - - + + Open in deck editor Open in dock - + + Rename deck or folder + + + + Upload deck - + Download deck - - + + + + New folder - - + + Delete - - + + Open decks folder + + + + + Rename local folder + + + + + Rename local file + + + + + New name: + + + + + + + Error - + + Rename failed + + + + + Invalid deck file - + Enter deck name Enter flagship name - + This decklist does not have a name. Please enter a name: - + Unnamed deck Unnamed flagship - + Failed to upload deck to server - + Delete local file - - - - Are you sure you want to delete "%1"? + + Are you sure you want to delete the selected files? - + + Delete remote decks + + + + + Are you sure you want to delete the selected decks? + + + + + Name of new folder: - - Delete remote folder + + Deck Storage + + + + + TabDeckStorageVisual + + + Visual Deck Storage - - Delete remote deck + + Error + + + + + Could not open deck at %1 + + + + + TabEdhRec + + + EDHREC: TabGame - - - + + + Replay - - + + Game - - + + Player List - - + + Card Info - - + + Messages - - + + Replay Timeline - + &Phases - + &Game - + Next &phase - + Next phase with &action - + Next &turn - + Reverse turn order - + &Remove all local arrows - + Rotate View Cl&ockwise - + Rotate View Co&unterclockwise - + Game &information - + + Un&concede + + + + &Concede - + &Leave game - + C&lose replay - + &Focus Chat - + &Say: - + &View - - - - + + + + Visible - - - - + + + + Floating - + Reset layout - + Concede - + Are you sure you want to concede this game? - + Unconcede - + You have already conceded. Do you want to return to this game? - + Leave game - + Are you sure you want to leave this game? - + You are flooding the game. Please wait a couple of seconds. - + A player has joined game #%1 - + %1 has joined the game - + kicked by game host or moderator - + player left the game - + player disconnected from server - + reason unknown - + You have been kicked out of the game. @@ -5747,152 +6671,152 @@ Please enter a name: - - - + + + Time;SenderName;SenderIP;Message;TargetID;TargetName - + Room Logs - + Game Logs - + Chat Logs - - + + Error - + You must select at least one filter. - + You have to select a valid number of days to locate. - + Username: - + IP Address: - + Game Name: - + GameID: - + Message: - + Main Room - + Game Room - + Private Chat - + Past X Days: - + Today - + Last Hour - + Maximum Results: - + At least one filter is required. The more information you put in, the more specific your results will be. - + Get User Logs - + Clear Filters - + Filters - + Log Locations - + Date Range - + Maximum Results - - + + Message History - + Failed to collect message history information. - + There are no messages for the selected filters. @@ -5915,22 +6839,22 @@ The more information you put in, the more specific your results will be. - + This user is ignoring you, they cannot see your messages in main chat and you cannot join their games. - + Private message from - + %1 has left the server. - + %1 has joined the server. @@ -5938,117 +6862,153 @@ The more information you put in, the more specific your results will be. TabReplays - - Game replays - - - - + Local file system - + Server replay storage - - + + Watch replay - - + + Rename + + + + + + New folder + + + + + Delete - + + Open replays folder + + + + Download replay - + Toggle expiration lock - + + Rename local folder + + + + + Rename local file + + + + + New name: + + + + + Error + + + + + Rename failed + + + + + Name of new folder: + + + + Delete local file - - Are you sure you want to delete "%1"? + + Are you sure you want to delete the selected files? - - Downloading Replays + + Are you sure you want to delete the selected replays? - - Folder download is not yet supported. Please download replays individually. - - - - + Delete remote replay - - Are you sure you want to delete the replay of game %1? + + Game Replays TabRoom - + &Say: - + Chat - + &Room - + &Leave room - + &Clear chat - + Chat Settings... Cap'n's orders for t' chat... - + mentioned you. - + Click to view - + You are flooding the chat. Please wait a couple of seconds. @@ -6061,30 +7021,30 @@ The more information you put in, the more specific your results will be. - - - - + + + + Error - + Failed to join the server room: it doesn't exist on the server. - + The server thinks you are in the server room but your client is unable to display it. Try restarting your client. - + You do not have the required permission to join this server room. - + Failed to join the server room due to an unknown error: %1. @@ -6092,32 +7052,72 @@ The more information you put in, the more specific your results will be. TabSupervisor - + + Deck Editor + + + + + &Visual Deck Storage + + + + + Server + + + + + Account + + + + + Deck Storage + + + + + Game Replays + + + + + Administration + + + + + Logs + + + + Are you sure? - + There are still open games. Are you sure you want to quit? - + Click to view - + Your buddy %1 has signed on! - + Unknown Event - + The server has sent you a message that your client does not understand. This message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -6125,61 +7125,43 @@ To update your client, go to Help -> Check for Updates. - + Idle Timeout - + You are about to be logged out due to inactivity. - + Promotion - + You have been promoted. Please log out and back in for changes to take effect. - + Warned - + You have received a warning due to %1. Please refrain from engaging in this activity or further actions may be taken against you. If you have any questions, please private message a moderator. - + You have received the following message from the server. (custom messages like these could be untranslated) - - TabUserLists - - - Account - - - - - Add to Buddy List - - - - - Add to Ignore List - - - TappedOutInterface @@ -6263,173 +7245,184 @@ Please refrain from engaging in this activity or further actions may be taken ag UserContextMenu - + User &details - + Private &chat - + Show this user's &games - + Add to &buddy list - + Remove from &buddy list - + Add to &ignore list - + Remove from &ignore list - + Kick from &game - + Warn user - + View user's war&n history - + Ban from &server - + View user's &ban history - + &Promote user to moderator - + Dem&ote user from moderator - + Promote user to &judge - + Demote user from judge - + + View admin notes + + + + %1's games - - - + + + Ban History - + Ban Time;Moderator;Ban Length;Ban Reason;Visible Reason - + User has never been banned. - + Failed to collect ban information. - - - + + + Warning History - + Warning Time;Moderator;User Name;Reason - + User has never been warned. - + Failed to collect warning information. - - + + Failed to get admin notes. + + + + + Success - + Successfully promoted user. - + Successfully demoted user. - - + + + Failed - + Failed to promote user. - + Failed to demote user. - + Copy hash to clipboard - + Remove this user's messages @@ -6437,173 +7430,173 @@ Please refrain from engaging in this activity or further actions may be taken ag UserInfoBox - + Location: - + Account Age: - + Edit - + Change password - + Change avatar - + Administrator - + Moderator - + Registered user - - + + Unregistered user - + Judge - + Unknown - + The entered password does not match your account. - - - + + + Information - + User information updated. - - - - - - - - - - + + + + + + + + + + Error - + User Information - + Real Name: - + User Level: - + %n Year(s), amount of years (only shown if more than 0) - + %10%n Day(s) %20 amount of years (if more than 0), amount of days, date in local short format - + Enter Password - + Password verification is required in order to change your email address - - - + + + An error occurred while trying to update your user information. - + This server does not permit you to update your user informations. - + Password changed. - + This server does not permit you to change your password. - + The new password is too short. - + The old password is incorrect. - + Avatar updated. - + This server does not permit you to update your avatar. - + An error occured while trying to updater your avatar. @@ -6611,124 +7604,251 @@ Please refrain from engaging in this activity or further actions may be taken ag UserInterfaceSettingsPage - + General interface settings - + &Double-click cards to play them (instead of single-click) - + + &Clicking plays all selected cards (instead of just the clicked card) + + + + &Play all nonlands onto the stack (not the battlefield) by default - + + Close card view window when last card is removed + + + + Annotate card text on tokens - + Use tear-off menus, allowing right click menus to persist on screen - + Notifications settings - + Enable notifications in taskbar - + Notify in the taskbar for game events while you are spectating - + Notify in the taskbar when users in your buddy list connect - + Animation settings Cap'n's orders for t' things that be movin' - + &Tap/untap animation + + + Deck editor/storage settings + + + + + Open deck in new tab by default + + + + + Prompt before converting .txt decks to .cod format + + + + + Always convert if not prompted + + + + + Use visual deck storage in game lobby + + + + + Replay settings + + + + + Buffer time for backwards skip via shortcut: + + - UserList + UserListWidget - + Users connected to server: %1 - + Users in this room: %1 - + Buddies online: %1 / %2 - + Ignored users online: %1 / %2 + + VisualDeckStorageFolderDisplayWidget + + + Deck Storage + + + + + VisualDeckStorageSearchWidget + + + Search by filename + + + + + VisualDeckStorageSortWidget + + + Sort Alphabetically (Deck Name) + + + + + Sort Alphabetically (Filename) + + + + + Sort by Last Modified + + + + + Sort by Last Loaded + + + + + VisualDeckStorageWidget + + + Loading database ... + + + + + Show Folders + + + + + Show Tag Filter + + + + + Show Tags On Deck Previews + + + + + Show Banner Card Selection Option + + + + + Include Folder Names in Search + + + + + Draw unused Color Identities + + + + + Unused Color Identities Opacity + + + WarningDialog - + Which warning would you like to send? - + Redact all messages from this user in all rooms - + &OK - + &Cancel - + Warn user for misconduct - - + + Error - + User name to send a warning to can not be blank, please specify a user to warn. - + Warning to use can not be blank, please select a valid warning to send. @@ -6736,128 +7856,133 @@ Please refrain from engaging in this activity or further actions may be taken ag WndSets - + Move selected set to the top Move selected fleet to the top - + Move selected set up Move selected fleet up - + Move selected set down Move selected fleet down - + Move selected set to the bottom Move selected fleet to the bottom - + Search by set name, code, or type - + Default order - + Restore original art priority order - + Enable all sets Enable all fleets - + Disable all sets Disable all fleets - + Enable selected set(s) - + Disable selected set(s) - + Deck Editor - + Use CTRL+A to select all sets in the view. - + Only cards in enabled sets will appear in the card list of the deck editor. - + Image priority is decided in the following order: - + first the CUSTOM Folder (%1), then the Enabled Sets in this dialog (Top to Bottom) %1 is a link to the wiki - + Card Art - + How to use custom card art - + Hints - + Note - + Sorting by column allows you to find a set while not changing set priority. - + To enable ordering again, click the column header until this message disappears. - + Use the current sorting as the set priority instead - + Sorts the set priority using the same column - + + Include online-only (Arena) cards [requires restart] + + + + Manage sets @@ -6865,22 +7990,67 @@ Please refrain from engaging in this activity or further actions may be taken ag ZoneViewWidget - - sort by name + + Ungrouped - - sort by type + + Group by Type - + + Group by Mana Value + + + + + Group by Color + + + + + Unsorted + + + + + Sort by Name + + + + + Sort by Type + + + + + Sort by Mana Cost + + + + + Sort by Colors + + + + + Sort by P/T + + + + + Sort by Set + + + + shuffle when closing - + pile view @@ -6888,7 +8058,7 @@ Please refrain from engaging in this activity or further actions may be taken ag i18n - + English English, arr! (Pirate English) @@ -6896,12 +8066,12 @@ Please refrain from engaging in this activity or further actions may be taken ag main - + Connect on startup - + Debug to file @@ -6909,786 +8079,908 @@ Please refrain from engaging in this activity or further actions may be taken ag shortcutsTab - + Main Window - - + + Deck Editor - + Game Lobby - + Card Counters - + Player Counters - + Power and Toughness - + Game Phases - + Playing Area - + Move Selected Card - + View - + Move Top Card - + Move Bottom Card - + Gameplay - + Drawing - + Chat Room - + Game Window - + Load Deck from Clipboard - + + Replays - + + Tabs + + + + Check for Card Updates... - + Connect... - + Disconnect - + Exit - + Full screen - + Register... - + Settings... - + Start a Local Game... - + Watch Replay... - - Analyze Deck + + Analyze Deck (deckstats.net) + Analyze Deck - - Clear All Filters - - - - - Clear Selected Filter - - - - - Close + + Analyze Deck (tappedout.net) - Remove Card + Clear All Filters + Clear Selected Filter + + + + + Close + + + + + Remove Card + + + + Manage Sets... - + Edit Custom Tokens... - - Export Deck - - - - - Add Card - - - - - Load Deck... - - - - - Load Deck from Clipboard... + + Export Deck (decklist.org) - New Deck + Export Deck (decklist.xyz) - - Open Custom Pictures Folder + + Add Card - - Print Deck... + + Load Deck... + Load Deck from Clipboard... + + + + + Edit Deck in Clipboard, Annotated + + + + + Edit Deck in Clipboard + + + + + New Deck + + + + + Open Custom Pictures Folder + + + + + Print Deck... + + + + Delete Card - - + + Reset Layout - + Save Deck - + Save Deck as... - + Save Deck to Clipboard, Annotated - - Save Deck to Clipboard - - - - - Load Local Deck... - - - - - Load Remote Deck... - - - - - Set Ready to Start - - - - - Toggle Sideboard Lock - - - - - - Add Green Counter - - - - - - Remove Green Counter - - - - - - Set Green Counters... + + Save Deck to Clipboard, Annotated (No Set Info) + Save Deck to Clipboard + + + + + Save Deck to Clipboard (No Set Info) + + + + + Load Local Deck... + + + + + Load Remote Deck... + + + + + Set Ready to Start + + + + + Toggle Sideboard Lock + + + + + + Add Green Counter + + + + + + Remove Green Counter + + + + + + Set Green Counters... + + + + Add Yellow Counter - + Remove Yellow Counter - + Set Yellow Counters... - - + + Add Red Counter - - + + Remove Red Counter - - + + Set Red Counters... - + Add Life Counter - + Remove Life Counter - + Set Life Counters... - + Add White Counter - + Remove White Counter - + Set White Counters... - + Add Blue Counter - + Remove Blue Counter - + Set Blue Counters... - + Add Black Counter - + Remove Black Counter - + Set Black Counters... - + Add Colorless Counter - + Remove Colorless Counter - + Set Colorless Counters... - + Add Other Counter - + Remove Other Counter - + Set Other Counters... - + Add Power (+1/+0) - + Remove Power (-1/-0) - + Move Toughness to Power (+1/-1) - + Add Toughness (+0/+1) - + Remove Toughness (-0/-1) - + Move Power to Toughness (-1/+1) - + Add Power and Toughness (+1/+1) - + Remove Power and Toughness (-1/-1) - + Set Power and Toughness... - + Reset Power and Toughness - + Untap - + Upkeep - + Draw - + First Main Phase - + Start Combat - + Attack - + Block - + Damage - + End Combat - + Second Main Phase - + End - + Next Phase - + Next Phase Action - + Next Turn - + + Hide Card in Reveal Window + + + + Tap / Untap Card - + Untap All - + Toggle Untap - + Turn Card Over - + Peek Card - + Play Card - + Attach Card... - + Unattach Card - + Clone Card - + Create Token... - + Create All Related Tokens - + Create Another Token - + Set Annotation... - - + + Select All Cards in Zone + + + + + Select All Cards in Row + + + + + Select All Cards in Column + + + + + Bottom of Library - - - - + + + + Exile - - - - + + + + Graveyard - - + + Hand - - + + Top of Library - - - + + + Battlefield, Face Down - + Battlefield - + Library - + Sideboard - + Top Cards of Library - + + Bottom Cards of Library + + + + Close Recent View - - + + Stack - - + + Graveyard (Multiple) - - + + Exile (Multiple) - + Stack Until Found - + Draw Bottom Card - + Draw Multiple Cards from Bottom... - + Draw Arrow... - + Remove Local Arrows - + Leave Game - + Concede Parley - + Roll Dice... - + Shuffle Library - + + Shuffle Top Cards of Library + + + + + Shuffle Bottom Cards of Library + + + + Mulligan - + Draw a Card - + Draw Multiple Cards... - + Undo Draw - + Always Reveal Top Card - + Always Look At Top Card - + Rotate View Clockwise - + Rotate View Counterclockwise - + Unfocus Text Box - + Focus Chat - + Clear Chat - + Refresh - + + Skip Forward + + + + + Skip Backward + + + + + Skip Forward by a lot + + + + + Skip Backward by a lot + + + + Play/Pause - + Toggle Fast Forward + + + Visual Deck Storage + + + + + Deck Storage + + + + + Server + + + + + Account + + + + + Administration + + + + + Logs + + \ No newline at end of file diff --git a/cockatrice/translations/cockatrice_en_US.ts b/cockatrice/translations/cockatrice_en_US.ts index 6b1a77ab3..0723a8130 100644 --- a/cockatrice/translations/cockatrice_en_US.ts +++ b/cockatrice/translations/cockatrice_en_US.ts @@ -2,7 +2,7 @@ AbstractCounter - + &Set counter... &Set counter... @@ -10,95 +10,247 @@ AbstractCounterDialog - + Set counter Set counter - + New value for counter '%1': New value for counter '%1': + + AbstractDlgDeckTextEdit + + + &Refresh + + + + + Parse Set Name and Number (if available) + + + + + AbstractTabDeckEditor + + + Open in new tab + + + + + Are you sure? + + + + + The decklist has been modified. +Do you want to save the changes? + + + + + + + + + + + Error + + + + + Could not open deck at %1 + + + + + Could not save remote deck + + + + + + The deck could not be saved. +Please check that the directory is writable and try again. + + + + + Save deck + + + + + The deck could not be saved. + + + + + There are no cards in your deck to be exported + + + + + No deck was selected to be exported. + + + + + AdminNotesDialog + + + Update Notes + Update Notes + + + + Admin Notes for %1 + Admin Notes for %1 + + + + AllZonesCardAmountWidget + + + Mainboard + Mainboard + + + + Sideboard + Sideboard + + AppearanceSettingsPage - + Error Error - + Could not create themes directory at '%1'. Could not create themes directory at '%1'. - + Theme settings Theme settings - + Current theme: Current theme: - + Open themes folder Open themes folder - + + Menu settings + Menu settings + + + + Show keyboard shortcuts in right-click menus + Show keyboard shortcuts in right-click menus + + + Card rendering Card rendering - + Display card names on cards having a picture Display card names on cards having a picture - + + Auto-Rotate cards with sideways layout + Auto-Rotate cards with sideways layout + + + + Override all card art with personal set preference (Pre-ProviderID change behavior) [Requires Client restart] + Override all card art with personal set preference (Pre-ProviderID change behavior) [Requires Client restart] + + + + Bump sets that the deck contains cards from to the top in the printing selector + Bump sets that the deck contains cards from to the top in the printing selector + + + Scale cards on mouse over Scale cards on mouse over - + + Use rounded card corners + + + + Minimum overlap percentage of cards on the stack and in vertical hand Minimum overlap percentage of cards on the stack and in vertical hand - + + Maximum initial height for card view window: + Maximum initial height for card view window: + + + + + rows + rows + + + + Maximum expanded height for card view window: + + + + Hand layout Hand layout - + Display hand horizontally (wastes space) Display hand horizontally (wastes space) - + Enable left justification Enable left justification - + Table grid layout Table grid layout - + Invert vertical coordinate Invert vertical coordinate - + Minimum player count for multi-column layout: Minimum player count for multi-column layout: - + Maximum font size for information displayed on cards: Maximum font size for information displayed on cards: @@ -106,112 +258,112 @@ BanDialog - + ban &user name ban &user name - + ban &IP address ban &IP address - + ban client I&D ban client I&D - + Ban type Ban type - + &permanent ban &permanent ban - + &temporary ban &temporary ban - + &Days: &Days: - + &Hours: &Hours: - + &Minutes: &Minutes: - + Duration of the ban Duration of the ban - + Please enter the reason for the ban. This is only saved for moderators and cannot be seen by the banned person. Please enter the reason for the ban. This is only saved for moderators and cannot be seen by the banned person. - + Please enter the reason for the ban that will be visible to the banned person. Please enter the reason for the ban that will be visible to the banned person. - + Redact all messages from this user in all rooms Redact all messages from this user in all rooms - + &OK &OK - + &Cancel &Cancel - + Ban user from server Ban user from server - - - - + + + + Error Error - + You have to select a name-based, IP-based, clientId based, or some combination of the three to place a ban. You have to select a name-based, IP-based, clientId based, or some combination of the three to place a ban. - + You must have a value in the name ban when selecting the name ban checkbox. You must have a value in the name ban when selecting the name ban checkbox. - + You must have a value in the ip ban when selecting the ip ban checkbox. You must have a value in the ip ban when selecting the ip ban checkbox. - + You must have a value in the clientid ban when selecting the clientid ban checkbox. You must have a value in the clientid ban when selecting the clientid ban checkbox. @@ -219,22 +371,22 @@ This is only saved for moderators and cannot be seen by the banned person. BetaReleaseChannel - - Beta Releases - Beta Releases + + Beta + Beta - + No reply received from the release update server. No reply received from the release update server. - + Invalid reply received from the release update server. Invalid reply received from the release update server. - + No reply received from the file update server. No reply received from the file update server. @@ -242,32 +394,32 @@ This is only saved for moderators and cannot be seen by the banned person. CardDatabaseModel - + Name Name - + Sets Sets - + Mana cost Mana cost - + Card type Card type - + P/T P/T - + Color(s) Color(s) @@ -360,37 +512,65 @@ This is only saved for moderators and cannot be seen by the banned person. - CardFrame + CardInfoFrameWidget - + Image Image - + Description Description - + Both Both + + + View transformation + + - CardInfoText + CardInfoPictureWidget - + + View related cards + View related cards + + + + Add card to deck + + + + + Mainboard + + + + + Sideboard + + + + + CardInfoTextWidget + + Name: Name: - + Related cards: Related cards: - + Unknown card: Unknown card: @@ -398,287 +578,555 @@ This is only saved for moderators and cannot be seen by the banned person. CardItem - + &Move to &Move to - + &Power / toughness &Power / toughness + + CardSizeWidget + + + Card Size + Card Size + + CardZone - + their hand nominative their hand - + %1's hand nominative %1's hand - + their library look at zone their library - + %1's library look at zone %1's library - + of their library top cards of zone, of their library - + of %1's library top cards of zone of %1's library - + their library reveal zone their library - + %1's library reveal zone %1's library - + their library shuffle their library - + %1's library shuffle %1's library - + their library nominative their library - + %1's library nominative %1's library - + their graveyard nominative their graveyard - + %1's graveyard nominative %1's graveyard - + their exile nominative their exile - + %1's exile nominative %1's exile - + their sideboard look at zone their sideboard - + %1's sideboard look at zone %1's sideboard - + their sideboard nominative their sideboard - + %1's sideboard nominative %1's sideboard + + DeckEditorCardInfoDockWidget + + + Card Info + + + + + DeckEditorDatabaseDisplayWidget + + + Search by card name (or search expressions) + + + + + Add to Deck + + + + + Add to Sideboard + + + + + Select Printing + + + + + Show on EDHREC (Commander) + + + + + Show on EDHREC (Card) + + + + + Show Related cards + + + + + Add card to &maindeck + + + + + Add card to &sideboard + + + + + DeckEditorDeckDockWidget + + + Banner Card + + + + + Select Printing + + + + + Deck + + + + + Deck &name: + + + + + &Comments: + + + + + Hash: + + + + + &Increment number + + + + + &Decrement number + + + + + &Remove row + + + + + Swap card to/from sideboard + + + + + DeckEditorFilterDockWidget + + + Filters + + + + + &Clear all filters + + + + + Delete selected + + + + + DeckEditorMenu + + + &Deck Editor + + + + + &New deck + + + + + &Load deck... + + + + + Load recent deck... + + + + + Clear + + + + + &Save deck + + + + + Save deck &as... + + + + + Load deck from cl&ipboard... + + + + + Edit deck in clipboard + + + + + + Annotated + + + + + + Not Annotated + + + + + Save deck to clipboard + + + + + Annotated (No set info) + + + + + Not Annotated (No set info) + + + + + &Print deck... + + + + + &Send deck to online service + + + + + Create decklist (decklist.org) + + + + + Create decklist (decklist.xyz) + + + + + Analyze deck (deckstats.net) + + + + + Analyze deck (tappedout.net) + + + + + &Close + + + + + DeckEditorPrintingSelectorDockWidget + + + Printing Selector + + + DeckEditorSettingsPage - - + + Update Spoilers Update Spoilers - - + + Success Success - + Download URLs have been reset. Download URLs have been reset. - + Downloaded card pictures have been reset. Downloaded card pictures have been reset. - + Error Error - + One or more downloaded card pictures could not be cleared. One or more downloaded card pictures could not be cleared. - + Add URL Add URL - - + + URL: URL: - - + + Edit URL Edit URL - + + Network Cache Size: + Network Cache Size: + + + + Redirect Cache TTL: + Redirect Cache TTL: + + + + How long cached redirects for urls are valid for. + How long cached redirects for urls are valid for. + + + + Picture Cache Size: + Picture Cache Size: + + + Add New URL Add New URL - + Remove URL Remove URL - + + Day(s) + Day(s) + + + Updating... Updating... - + Choose path Choose path - + URL Download Priority URL Download Priority - + Spoilers Spoilers - + Download Spoilers Automatically Download Spoilers Automatically - + Spoiler Location: Spoiler Location: - + Last Change Last Change - + Spoilers download automatically on launch Spoilers download automatically on launch - + Press the button to manually update without relaunching Press the button to manually update without relaunching - + Do not close settings until manual update is complete Do not close settings until manual update is complete - + Download card pictures on the fly Download card pictures on the fly - + How to add a custom URL How to add a custom URL - + Delete Downloaded Images Delete Downloaded Images - + Reset Download URLs Reset Download URLs - - Downloaded images directory size: - Downloaded images directory size: - - - + On-disk cache for downloaded pictures On-disk cache for downloaded pictures - - Picture cache size: - Picture cache size: - - - + In-memory cache for pictures not currently on screen In-memory cache for pictures not currently on screen @@ -686,16 +1134,218 @@ This is only saved for moderators and cannot be seen by the banned person. DeckListModel - + + Count + Count + + + + Set + Set + + + Number Number - + + Provider ID + Provider ID + + + Card Card + + DeckLoader + + + Common deck formats (%1) + + + + + All files (*.*) + + + + + DeckPreviewColorIdentityFilterWidget + + + Mode: Exact Match + + + + + Mode: Includes + + + + + DeckPreviewDeckTagsDisplayWidget + + + Edit tags ... + + + + + DeckPreviewTagDialog + + + Deck Tags Manager + + + + + Manage your deck tags. Check or uncheck tags as needed, or add new ones: + + + + + Add a new tag (e.g., Aggro️) + + + + + Add Tag + + + + + Filter tags... + + + + + OK + + + + + Cancel + + + + + Invalid Input + + + + + Tag name cannot be empty! + + + + + Duplicate Tag + + + + + This tag already exists. + + + + + DeckPreviewWidget + + + Banner Card + + + + + Open in deck editor + + + + + Edit Tags + + + + + Rename Deck + + + + + Save Deck to Clipboard + + + + + Annotated + + + + + Annotated (No set info) + + + + + Not Annotated + + + + + Not Annotated (No set info) + + + + + Rename File + + + + + Delete File + + + + + Set Banner Card + + + + + + New name: + + + + + + Error + + + + + Rename failed + + + + + Delete file + + + + + Are you sure you want to delete the selected file? + + + + + Delete failed + + + DeckStatsInterface @@ -713,181 +1363,217 @@ This is only saved for moderators and cannot be seen by the banned person. DeckViewContainer - + Load deck... Load deck... - + Load remote deck... Load remote deck... - + + Unload deck + Unload deck... + + + + Ready to start Ready to start - + + Force start + Force start + + + Sideboard unlocked Sideboard unlocked - + Sideboard locked Sideboard locked - - Load deck - Load deck - - - + Error Error - + The selected file could not be loaded. The selected file could not be loaded. + + DialogConvertDeckToCodFormat + + + Deck Format Conversion + + + + + You tried to add a tag to a .txt format deck. + Tags can only be added to .cod format decks. + Do you want to convert the deck to the .cod format? + + + + + Remember and automatically apply choice in the future + + + DlgConnect - + Downloading... Downloading... - + Known Hosts Known Hosts - + + Delete the currently selected saved server + Delete the currently selected saved server + + + Refresh the server list with known public servers Refresh the server list with known public servers - + New Host New Host - + Name: Name: - + &Host: &Host: - + &Port: &Port: - + Player &name: Player &name: - + P&assword: P&assword: - + &Save password &Save password - + A&uto connect A&uto connect - + Automatically connect to the most recent login when Cockatrice opens Automatically connect to the most recent login when Cockatrice opens - + If you have any trouble connecting or registering then contact the server staff for help! If you have any trouble connecting or registering then contact the server staff for help! - - + + Webpage Webpage - + Reset Password Reset Password - + + Forgot password? + Forgot password? + + + &Connect &Connect - + Server Server - + Login Login - + Server Contact Server Contact - + Connect to Server Connect to Server - + Server URL Server URL - + Communication Port Communication Port - + Unique Server Name Unique Server Name - + Connection Warning Connection Warning - + You need to name your new connection profile. You need to name your new connection profile. - + Connect Warning Connect Warning - + The player name can't be empty. The player name can't be empty. @@ -970,27 +1656,37 @@ This is only saved for moderators and cannot be seen by the banned person.Spectators - + + Starting life total: + Starting life total: + + + + Game setup options + Game setup options + + + &Clear &Clear - + Create game Create game - + Game information Game information - + Error Error - + Server error. Server error. @@ -998,92 +1694,92 @@ This is only saved for moderators and cannot be seen by the banned person. DlgCreateToken - + &Name: &Name: - + Token Token - + C&olor: C&olor: - + white white - + blue blue - + black black - + red red - + green green - + multicolor multicolor - + colorless colorless - + &P/T: &P/T: - + &Annotation: &Annotation: - + &Destroy token when it leaves the table &Destroy token when it leaves the table - + Token data Token data - + Show &all tokens Show &all tokens - + Show tokens from this &deck Show tokens from this &deck - + Choose token from list Choose token from list - + Create token Create token @@ -1129,6 +1825,24 @@ To remove your current avatar, confirm without choosing a new image.Invalid image chosen. + + DlgEditDeckInClipboard + + + Edit deck in clipboard + + + + + Error + + + + + Invalid deck list. + + + DlgEditPassword @@ -1171,93 +1885,93 @@ To remove your current avatar, confirm without choosing a new image. DlgEditTokens - + &Name: &Name: - + C&olor: C&olor: - + white white - + blue blue - + black black - + red red - + green green - + multicolor multicolor - + colorless colorless - + &P/T: &P/T: - + &Annotation: &Annotation: - + Token data Token data - - + + Add token Add token - + Remove token Remove token - + Edit custom tokens Edit custom tokens - + Please enter the name of the token: Please enter the name of the token: - + Error Error - + The chosen name conflicts with an existing card or token. Make sure to enable the 'Token' set in the "Manage sets" dialog to display them correctly. The chosen name conflicts with an existing card or token. @@ -1295,132 +2009,132 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgFilterGames - + no limit no limit - + 5 minutes 5 minutes - + 10 minutes 10 minutes - + 30 minutes 30 minutes - + 1 hour 1 hour - + 2 hours 2 hours - - Show '&buddies only' games - Show '&buddies only' games + + Hide 'buddies only' games + - - Show &full games - Show &full games + + Hide full games + - - Show games &that have started - Show games &that have started + + Hide games that have started + - - Show &password protected games - Show &password protected games + + Hide password protected games + - - Hide '&ignored user' games - Hide '&ignored user' games + + Hide 'ignored user' games + - + &Newer than: &Newer than: - + Game &description: Game &description: - + &Creator name: &Creator name: - + General General - + &Game types &Game types - + at &least: at &least: - + at &most: at &most: - + Maximum player count Maximum player count - + Restrictions Restrictions - + Show games only if &spectators can watch Show games only if &spectators can watch - + Show spectator password p&rotected games Show spectator password p&rotected games - + Show only if spectators can ch&at Show only if spectators can ch&at - + Show only if spectators can see &hands Show only if spectators can see &hands - + Spectators Spectators - + Filter games Filter games @@ -1603,27 +2317,28 @@ Make sure to enable the 'Token' set in the "Manage sets" dia The passwords do not match. + + DlgLoadDeck + + + Load Deck + Load Deck + + DlgLoadDeckFromClipboard - - &Refresh - &Refresh - - - + Load deck from clipboard Load deck from clipboard - - + Error Error - - + Invalid deck list. Invalid deck list. @@ -1631,11 +2346,49 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgLoadRemoteDeck - + Load deck Load deck + + DlgMoveTopCardsUntil + + + Card name (or search expressions): + Card name (or search expressions): + + + + Number of hits: + Number of hits: + + + + Auto play hits + Auto play hits + + + + Put top cards on stack until... + Put top cards on stack until... + + + + No cards matching the search expression exists in the card database. Proceed anyways? + No cards matching the search expression exists in the card database. Proceed anyways? + + + + Cockatrice + Cockatrice + + + + Invalid filter + Invalid filter + + DlgRegister @@ -1750,12 +2503,12 @@ Your email will be used to verify your account. DlgSettings - + Unknown Error loading card database Unknown Error loading card database - + Your card database is invalid. Cockatrice may not function correctly with an invalid database @@ -1772,7 +2525,7 @@ You may need to rerun oracle to update your card database. Would you like to change your database location setting? - + Your card database version is too old. This can cause problems loading card information or images @@ -1789,7 +2542,7 @@ Usually this can be fixed by rerunning oracle to to update your card database. Would you like to change your database location setting? - + Your card database did not finish loading Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues with your cards.xml attached @@ -1802,7 +2555,7 @@ Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues with you Would you like to change your database location setting? - + File Error loading your card database. Would you like to change your database location setting? @@ -1811,7 +2564,7 @@ Would you like to change your database location setting? Would you like to change your database location setting? - + Your card database was loaded but contains no cards. Would you like to change your database location setting? @@ -1820,7 +2573,7 @@ Would you like to change your database location setting? Would you like to change your database location setting? - + Unknown card database load status Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues @@ -1833,59 +2586,59 @@ Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues Would you like to change your database location setting? - - - + + + Error Error - + The path to your deck directory is invalid. Would you like to go back and set the correct path? The path to your deck directory is invalid. Would you like to go back and set the correct path? - + The path to your card pictures directory is invalid. Would you like to go back and set the correct path? The path to your card pictures directory is invalid. Would you like to go back and set the correct path? - + Settings Settings - + General General - + Appearance Appearance - + User Interface User Interface - + Card Sources Card Sources - + Chat Chat - + Sound Sound - + Shortcuts Shortcuts @@ -1911,166 +2664,169 @@ Would you like to change your database location setting? DlgUpdate - + Current release channel Current release channel - + Reinstall Reinstall - + Cancel Download Cancel Download - + Open Download Page Open Download Page - + Check for Client Updates Check for Client Updates - - - - + + + + Error Error - + Cockatrice was not built with SSL support, therefore you cannot download updates automatically! Please visit the download page to update manually. Cockatrice was not built with SSL support, therefore you cannot download updates automatically! Please visit the download page to update manually. - - Downloading update... - Downloading update... + + Downloading update: %1 + Downloading update... + - + Checking for updates... Checking for updates... - + Finished checking for updates Finished checking for updates - + No Update Available No Update Available - + Cockatrice is up to date! Cockatrice is up to date! - + You are already running the latest version available in the chosen release channel. You are already running the latest version available in the chosen release channel. - + Current version Current version - + Selected release channel Selected release channel - - + + Update Available Update Available - - + + A new version of Cockatrice is available! A new version of Cockatrice is available! - - + + New version New version - - + + Released Released - - + + Changelog Changelog - + Do you want to update now? Do you want to update now? - - Unfortunately there are no download packages available for your operating system. -You may have to build from source yourself. - Unfortunately there are no download packages available for your operating system. -You may have to build from source yourself. + + Unfortunately, the automatic updater failed to find a compatible download. +You may have to manually download the new version. + Unfortunately there are no download packages available for your operating system. +You may have to build from source yourself. + - - Please check the download page manually and visit the wiki for instructions on compiling. - Please check the download page manually and visit the wiki for instructions on compiling. + + Please check the <a href="%1">releases page</a> on our Github and download the build for your system. + Please check the download page manually and visit the wiki for instructions on compiling. + - - - + + + Update Error Update Error - + An error occurred while checking for updates: An error occurred while checking for updates: - + An error occurred while downloading an update: An error occurred while downloading an update: - + Installing... Installing... - + Cockatrice is unable to open the installer. Cockatrice is unable to open the installer. - + Try to update manually by closing Cockatrice and running the installer. Try to update manually by closing Cockatrice and running the installer. - + Download location Download location @@ -2088,6 +2844,14 @@ You may have to build from source yourself. Debug Log + + EdhrecCommanderResponseCommanderDetailsDisplayWidget + + + Salt: + + + FilterBuilder @@ -2099,105 +2863,120 @@ You may have to build from source yourself. GameSelector - - - - - - - - - + + + + + + + + + Error Error - + Please join the appropriate room first. Please join the appropriate room first. - + Wrong password. Wrong password. - + Spectators are not allowed in this game. Spectators are not allowed in this game. - + The game is already full. The game is already full. - + The game does not exist any more. The game does not exist any more. - + This game is only open to registered users. This game is only open to registered users. - + This game is only open to its creator's buddies. This game is only open to its creator's buddies. - + You are being ignored by the creator of this game. You are being ignored by the creator of this game. - + + Join Game + + + + + Spectate Game + + + + + Game Information + + + + Join game Join game - + Password: Password: - + Please join the respective room first. Please join the respective room first. - + &Filter games &Filter games - + C&lear filter C&lear filter - + C&reate C&reate - + &Join &Join - + J&oin as spectator J&oin as spectator - + Games shown: %1 / %2 Games shown: %1 / %2 - + Games Games @@ -2205,100 +2984,100 @@ You may have to build from source yourself. GamesModel - + >1 day >1 day - + %1%2 hr short age in hours %1%2 hr%1%2 hr - + new new - + %1%2 min short age in minutes %1%2 min%1%2 min - + password password - + buddies only buddies only - + reg. users only reg. users only - - + + can chat can chat - + see hands see hands - + can see hands can see hands - + not allowed not allowed - + Room Room - + Age Age - + Description Description - + Creator Creator - + Type Type - + Restrictions Restrictions - + Players Players - + Spectators Spectators @@ -2306,92 +3085,102 @@ You may have to build from source yourself. GeneralSettingsPage - + Reset all paths Reset all paths - + All paths have been reset All paths have been reset - - + + Choose path Choose path - + Personal settings Personal settings - + Language: Language: - + Paths (editing disabled in portable mode) Paths (editing disabled in portable mode) - + Paths Paths - + + How to help with translations + + + + Decks directory: Decks directory: - + Replays directory: Replays directory: - + Pictures directory: Pictures directory: - + Card database: Card database: - + Custom database directory: Custom database directory: - + Token database: Token database: - + Update channel Update channel - + + Check for client updates on startup + Check for client updates on startup + + + Notify if a feature supported by the server is missing in my client Notify if a feature supported by the server is missing in my client - + Automatically run Oracle when running a new version of Cockatrice Automatically run Oracle when running a new version of Cockatrice - + Show tips on startup Show tips on startup @@ -2399,61 +3188,61 @@ You may have to build from source yourself. MainWindow - - + + The server has reached its maximum user capacity, please check back later. The server has reached its maximum user capacity, please check back later. - + There are too many concurrent connections from your address. There are too many concurrent connections from your address. - + Banned by moderator Banned by moderator - + Expected end time: %1 Expected end time: %1 - + This ban lasts indefinitely. This ban lasts indefinitely. - + Scheduled server shutdown. Scheduled server shutdown. - - + + Invalid username. Invalid username. - + You have been logged out due to logging in at another location. You have been logged out due to logging in at another location. - + Connection closed Connection closed - + The server has terminated your connection. Reason: %1 The server has terminated your connection. Reason: %1 - + The server is going to be restarted in %n minute(s). All running games will be lost. Reason for shutdown: %1 @@ -2464,558 +3253,568 @@ All running games will be lost. Reason for shutdown: %1 - + Scheduled server shutdown Scheduled server shutdown - - + + Success Success - + Registration accepted. Will now login. Registration accepted. Will now login. - + Account activation accepted. Will now login. Account activation accepted. Will now login. - + Number of players Number of players - + Please enter the number of players. Please enter the number of players. - - + + Player %1 Player %1 - + Load replay Load replay - + About Cockatrice About Cockatrice - + Version Version - + Cockatrice Webpage Cockatrice Webpage - + Project Manager: Project Manager: - + Past Project Managers: Past Project Managers: - + Developers: Developers: - + Our Developers Our Developers - + Help Develop! Help Develop! - + Translators: Translators: - + Our Translators Our Translators - + Help Translate! Help Translate! - + Support: Support: - + Report an Issue Report an Issue - + Troubleshooting Troubleshooting - + F.A.Q. F.A.Q. - - - - - - - - - - - - - - - + + + + + + + + + + + + + - - - + + + + + Error Error - + Server timeout Server timeout - + Failed Login Failed Login - + Your client seems to be missing features this server requires for connection. Your client seems to be missing features this server requires for connection. - + To update your client, go to 'Help -> Check for Client Updates'. To update your client, go to 'Help -> Check for Client Updates'. - + Incorrect username or password. Please check your authentication information and try again. Incorrect username or password. Please check your authentication information and try again. - + There is already an active session using this user name. Please close that session first and re-login. There is already an active session using this user name. Please close that session first and re-login. - - + + You are banned until %1. You are banned until %1. - - + + You are banned indefinitely. You are banned indefinitely. - + This server requires user registration. Do you want to register now? This server requires user registration. Do you want to register now? - + This server requires client IDs. Your client is either failing to generate an ID or you are running a modified client. Please close and reopen your client to try again. This server requires client IDs. Your client is either failing to generate an ID or you are running a modified client. Please close and reopen your client to try again. - + An internal error has occurred, please close and reopen Cockatrice before trying again. If the error persists, ensure you are running the latest version of the software and if needed contact the software developers. An internal error has occurred, please close and reopen Cockatrice before trying again. If the error persists, ensure you are running the latest version of the software and if needed contact the software developers. - + Account activation Account activation - + Your account has not been activated yet. You need to provide the activation token received in the activation email. Your account has not been activated yet. You need to provide the activation token received in the activation email. - + Server Full Server Full - + Unknown login error: %1 Unknown login error: %1 - - + + This usually means that your client version is out of date, and the server sent a reply your client doesn't understand. This usually means that your client version is out of date, and the server sent a reply your client doesn't understand. - + Your username must respect these rules: Your username must respect these rules: - + is %1 - %2 characters long is %1 - %2 characters long - + can %1 contain lowercase characters can %1 contain lowercase characters - - - - + + + + NOT NOT - + can %1 contain uppercase characters can %1 contain uppercase characters - + can %1 contain numeric characters can %1 contain numeric characters - + can contain the following punctuation: %1 can contain the following punctuation: %1 - + first character can %1 be a punctuation mark first character can %1 be a punctuation mark - + no unacceptable language as specified by these server rules: note that the following lines will not be translated no unacceptable language as specified by these server rules: - + can not contain any of the following words: %1 can not contain any of the following words: %1 - + can not match any of the following expressions: %1 can not match any of the following expressions: %1 - + You may only use A-Z, a-z, 0-9, _, ., and - in your username. You may only use A-Z, a-z, 0-9, _, ., and - in your username. - - - - - - + + + + + + Registration denied Registration denied - + Registration is currently disabled on this server Registration is currently disabled on this server - + There is already an existing account with the same user name. There is already an existing account with the same user name. - + It's mandatory to specify a valid email address when registering. It's mandatory to specify a valid email address when registering. - + It appears you are attempting to register a new account on this server yet you already have an account registered with the email provided. This server restricts the number of accounts a user can register per address. Please contact the server operator for further assistance or to obtain your credential information. It appears you are attempting to register a new account on this server yet you already have an account registered with the email provided. This server restricts the number of accounts a user can register per address. Please contact the server operator for further assistance or to obtain your credential information. - + Password too short. Password too short. - + Registration failed for a technical problem on the server. Registration failed for a technical problem on the server. - + Unknown registration error: %1 Unknown registration error: %1 - + Account activation failed Account activation failed - + Socket error: %1 Socket error: %1 - + You are trying to connect to an obsolete server. Please downgrade your Cockatrice version or connect to a suitable server. Local version is %1, remote version is %2. You are trying to connect to an obsolete server. Please downgrade your Cockatrice version or connect to a suitable server. Local version is %1, remote version is %2. - + Your Cockatrice client is obsolete. Please update your Cockatrice version. Local version is %1, remote version is %2. Your Cockatrice client is obsolete. Please update your Cockatrice version. Local version is %1, remote version is %2. - + Connecting to %1... Connecting to %1... - + Registering to %1 as %2... Registering to %1 as %2... - + Disconnected Disconnected - + Connected, logging in at %1 Connected, logging in at %1 - - - + + + Requesting forgotten password to %1 as %2... Requesting forgotten password to %1 as %2... - + &Connect... &Connect... - + &Disconnect &Disconnect - + Start &local game... Start &local game... - + &Watch replay... &Watch replay... - - &Deck editor - &Deck editor - - - + &Full screen &Full screen - + &Register to server... &Register to server... - + &Restore password... &Restore password... - + &Settings... &Settings... - + &Exit &Exit - + A&ctions A&ctions - + &Cockatrice &Cockatrice - + C&ard Database C&ard Database - + &Manage sets... &Manage sets... - + Edit custom &tokens... Edit custom &tokens... - + Open custom image folder Open custom image folder - + Open custom sets folder Open custom sets folder - + Add custom sets/cards Add custom sets/cards - + + Reload card database + Reload card database + + + + Tabs + + + + &Help &Help - + &About Cockatrice &About Cockatrice - + &Tip of the Day &Tip of the Day - + Check for Client Updates Check for Client Updates - + Check for Card Updates... Check for Card Updates... - + View &Debug Log View &Debug Log - + + Open Settings Folder + Open Settings Folder + + + Show/Hide Show/Hide - + New Version New Version - + Congratulations on updating to Cockatrice %1! Oracle will now launch to update your card database. Congratulations on updating to Cockatrice %1! Oracle will now launch to update your card database. - + Cockatrice installed Cockatrice installed - + Congratulations on installing Cockatrice %1! Oracle will now launch to install the initial card database. Congratulations on installing Cockatrice %1! Oracle will now launch to install the initial card database. - + Card database Card database - + Cockatrice is unable to load the card database. Do you want to update your card database now? If unsure or first time user, choose "Yes" @@ -3024,29 +3823,29 @@ Do you want to update your card database now? If unsure or first time user, choose "Yes" - - + + Yes Yes - - + + No No - + Open settings Open settings - + New sets found New sets found - + %n new set(s) found in the card database Set code(s): %1 Do you want to enable it/them? @@ -3057,17 +3856,17 @@ Set codes: %1 Do you want to enable them? - + View sets View sets - + Welcome Welcome - + Hi! It seems like you're running this version of Cockatrice for the first time. All the sets in the card database have been enabled. Read more about changing the set order or disabling specific sets and consequent effects in the "Manage Sets" dialog. @@ -3076,58 +3875,59 @@ All the sets in the card database have been enabled. Read more about changing the set order or disabling specific sets and consequent effects in the "Manage Sets" dialog. - - + + Information Information - + A card database update is already running. A card database update is already running. - + Unable to run the card database updater: Unable to run the card database updater: - - failed to start. - failed to start. + + Failed to start. The file might be missing, or permissions might be incorrect. + - - crashed. - crashed. + + The process crashed some time after starting successfully. + - - timed out. - timed out. + + Timed out. The process took too long to respond. The last waitFor...() function timed out. + - - write error. - write error. + + An error occurred when attempting to write to the process. For example, the process may not be running, or it may have closed its input channel. + - - read error. - read error. + + An error occurred when attempting to read from the process. For example, the process may not be running. + - - unknown error. - unknown error. + + Unknown error occurred. + - - The card database updater exited with an error: %1 - The card database updater exited with an error: %1 + + The card database updater exited with an error: +%1 + - + This server supports additional features that your client doesn't have. This is most likely not a problem, but this message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -3138,55 +3938,55 @@ This is most likely not a problem, but this message might mean there is a new ve To update your client, go to Help -> Check for Updates. - - - - - + + + + + Load sets/cards Load sets/cards - + Selected file cannot be found. Selected file cannot be found. - + You can only import XML databases at this time. You can only import XML databases at this time. - + The new sets/cards have been added successfully. Cockatrice will now reload the card database. The new sets/cards have been added successfully. Cockatrice will now reload the card database. - + Sets/cards failed to import. Sets/cards failed to import. - - - + + + Reset Password Reset Password - + Your password has been reset successfully, you can now log in using the new credentials. Your password has been reset successfully, you can now log in using the new credentials. - + Failed to reset user account password, please contact the server operator to reset your password. Failed to reset user account password, please contact the server operator to reset your password. - + Activation request received, please check your email for an activation token. Activation request received, please check your email for an activation token. @@ -3460,179 +4260,190 @@ Cockatrice will now reload the card database. - %1 is looking at the top %3 card(s) %2. + %1 is looking at the %4 %3 card(s) %2. + %1 is looking at the top %3 card(s) %2. top card for singular, top %3 cards for plural - %1 is looking at the top card %2.%1 is looking at the top %3 cards %2. + %1 is looking at the %4 %3 card %2.%1 is looking at the %4 %3 cards %2. - + + bottom + bottom + + + + top + top + + + %1 turns %2 face-down. %1 turns %2 face-down. - + %1 turns %2 face-up. %1 turns %2 face-up. - + The game has been closed. The game has been closed. - + The game has started. The game has started. - + %1 has joined the game. %1 has joined the game. - + %1 is now watching the game. %1 is now watching the game. - + You have been kicked out of the game. You have been kicked out of the game. - + %1 has left the game (%2). %1 has left the game (%2). - + %1 is not watching the game any more (%2). %1 is not watching the game any more (%2). - + %1 is not ready to start the game any more. %1 is not ready to start the game any more. - + %1 shuffles their deck and draws a new hand of %2 card(s). %1 shuffles their deck and draws a card.%1 shuffles their deck and draws a new hand of %2 cards. - + %1 shuffles their deck and draws a new hand. %1 shuffles their deck and draws a new hand. - + You are watching a replay of game #%1. You are watching a replay of game #%1. - + %1 is ready to start the game. %1 is ready to start the game. - + cards an unknown amount of cards cards - + %1 card(s) a card for singular, %1 cards for plural one card%1 cards - + %1 lends %2 to %3. %1 lends %2 to %3. - + %1 reveals %2 to %3. %1 reveals %2 to %3. - + %1 reveals %2. %1 reveals %2. - + %1 randomly reveals %2%3 to %4. %1 randomly reveals %2%3 to %4. - + %1 randomly reveals %2%3. %1 randomly reveals %2%3. - + %1 peeks at face down card #%2. %1 peeks at face down card #%2. - + %1 peeks at face down card #%2: %3. %1 peeks at face down card #%2: %3. - + %1 reveals %2%3 to %4. %1 reveals %2%3 to %4. - + %1 reveals %2%3. %1 reveals %2%3. - + %1 reversed turn order, now it's %2. %1 reversed turn order, now it's %2. - + reversed reversed - + normal normal - + Heads Heads - + Tails Tails - + %1 flipped a coin. It landed as %2. %1 flipped a coin. It landed as %2. - + %1 rolls a %2 with a %3-sided die. %1 rolls a %2 with a %3-sided die. - + %1 flips %2 coins. There are %3 heads and %4 tails. %1 flips %2 coins. There are %3 heads and %4 tails. - + %1 rolls a %2-sided dice %3 times: %4. %1 rolls a %2-sided dice %3 times: %4. @@ -3752,17 +4563,17 @@ Cockatrice will now reload the card database. %1 shuffles cards %3 - %4 of %2. - + %1 unattaches %2. %1 unattaches %2. - + %1 undoes their last draw. %1 undoes their last draw. - + %1 undoes their last draw (%2). %1 undoes their last draw (%2). @@ -3770,110 +4581,110 @@ Cockatrice will now reload the card database. MessagesSettingsPage - + Word1 Word2 Word3 Word1 Word2 Word3 - + Add New Message Add New Message - + Edit Message Edit Message - + Remove Message Remove Message - + Add message Add message - - + + Message: Message: - + Edit message Edit message - + Chat settings Chat settings - + Custom alert words Custom alert words - + Enable chat mentions Enable chat mentions - + Enable mention completer Enable mention completer - + In-game message macros In-game message macros - + How to use in-game message macros How to use in-game message macros - + Ignore chat room messages sent by unregistered users Ignore chat room messages sent by unregistered users - + Ignore private messages sent by unregistered users Ignore private messages sent by unregistered users - - + + Invert text color Invert text color - + Enable desktop notifications for private messages Enable desktop notifications for private messages - + Enable desktop notification for mentions Enable desktop notification for mentions - + Enable room message history on join Enable room message history on join - - + + (Color is hexadecimal) (Color is hexadecimal) - + Separate words with a space, alphanumeric characters only Separate words with a space, alphanumeric characters only @@ -3931,6 +4742,24 @@ Cockatrice will now reload the card database. Color Identity + + OverlapControlWidget + + + Cards to overlap: + Cards to overlap: + + + + Overlap percentage: + Overlap percentage: + + + + Overlap direction: + Overlap direction: + + Phase @@ -3997,57 +4826,57 @@ Cockatrice will now reload the card database. PhasesToolbar - + Untap step Untap step - + Upkeep step Upkeep step - + Draw step Draw step - + First main phase First main phase - + Beginning of combat step Beginning of combat step - + Declare attackers step Declare attackers step - + Declare blockers step Declare blockers step - + Combat damage step Combat damage step - + End of combat step End of combat step - + Second main phase Second main phase - + End of turn step End of turn step @@ -4055,7 +4884,7 @@ Cockatrice will now reload the card database. PictureLoader - + en code for scryfall's language property, not available for all languages en @@ -4064,647 +4893,781 @@ Cockatrice will now reload the card database. Player - + Reveal top cards of library Reveal top cards of library - - - - - - - - - + + + + + + + + + + + + Number of cards: (max. %1) Number of cards: (max. %1) - + &View graveyard &View graveyard - + &View exile &View exile - + Player "%1" Player "%1" - - - - + + + + &Graveyard &Graveyard - - - - + + + + &Exile &Exile - + &Move hand to... &Move hand to... - - - + + + &Top of library &Top of library - - - + + + &Bottom of library &Bottom of library - + &Move graveyard to... &Move graveyard to... - - - - + + + + &Hand &Hand - + &Move exile to... &Move exile to... - + &View library &View library - + &View hand &View hand - + View &top cards of library... View &top cards of library... - + Reveal &library to... Reveal &library to... - + &Always reveal top card &Always reveal top card - + &View sideboard &View sideboard - + &Draw card &Draw card - + D&raw cards... D&raw cards... - + &Undo last draw &Undo last draw - + Take &mulligan Take &mulligan - - &Shuffle - &Shuffle - - - + Play top card &face down Play top card &face down - + Move top card to grave&yard Move top card to grave&yard - + Move top card to e&xile Move top card to e&xile - + Move top cards to &graveyard... Move top cards to &graveyard... - + Move top cards to &exile... Move top cards to &exile... - + Put top card on &bottom Put top card on &bottom - + + View bottom cards of library... + View bottom cards of library... + + + Lend library to... Lend library to... - + + Shuffle + + + + Put top cards on stack &until... Take top cards &until... Put top cards on stack &until... - + + Shuffle top cards... + + + + + Shuffle bottom cards... + + + + &Reveal hand to... &Reveal hand to... - + Reveal r&andom card to... Reveal r&andom card to... - + Reveal random card to... Reveal random card to... - + &Sideboard &Sideboard - + &Library &Library - + &Counters &Counters - + &Untap all permanents &Untap all permanents - + R&oll die... R&oll die... - + &Create token... &Create token... - + C&reate another token C&reate another token - + Cr&eate predefined token Cr&eate predefined token - - + + &All players &All players - + S&ay S&ay - + + &Select All + &Select All + + + + S&elect Row + S&elect Row + + + + S&elect Column + S&elect Column + + + &Play &Play - + &Hide &Hide - + Play &Face Down Play &Face Down - + &Tap / Untap Turn sideways or back again &Tap / Untap - + Toggle &normal untapping Toggle &normal untapping - + T&urn Over Turn face up/face down T&urn Over - + &Peek at card face &Peek at card face - + &Clone &Clone - + Attac&h to card... Attac&h to card... - + Unattac&h Unattac&h - + &Draw arrow... &Draw arrow... - + &Increase power &Increase power - + &Decrease power &Decrease power - + I&ncrease toughness I&ncrease toughness - + D&ecrease toughness D&ecrease toughness - + In&crease power and toughness In&crease power and toughness - + Dec&rease power and toughness Dec&rease power and toughness - + Increase power and decrease toughness Increase power and decrease toughness - + Decrease power and increase toughness Decrease power and increase toughness - + Set &power and toughness... Set &power and toughness... - + Reset p&ower and toughness Reset p&ower and toughness - + &Set annotation... &Set annotation... - + Red Red - + Yellow Yellow - + Green Green - + &Add counter (%1) &Add counter (%1) - + &Remove counter (%1) &Remove counter (%1) - + &Set counters (%1)... &Set counters (%1)... - + &Top of library in random order &Top of library in random order - + X cards from the top of library... X cards from the top of library... - + &Bottom of library in random order &Bottom of library in random order - + View top cards of library View top cards of library - + + View bottom cards of library + View bottom cards of library + + + + Shuffle top cards of library + + + + + Shuffle bottom cards of library + + + + Which position should this card be placed: Which position should this card be placed: - + (max. %1) (max. %1) - + Draw hand Draw hand - + 0 and lower are in comparison to current hand size 0 and lower are in comparison to current hand size - + Draw cards Draw cards - - + + Number: Number: - + Move top cards to grave Move top cards to grave - + Reveal &top cards to... Reveal &top cards to... - + &Top of library... &Top of library... - + &Bottom of library... &Bottom of library... - + &Always look at top card &Always look at top card - + &Open deck in deck editor &Open deck in deck editor - + &Play top card &Play top card - + &Draw bottom card &Draw bottom card - + D&raw bottom cards... D&raw bottom cards... - + &Play bottom card &Play bottom card - + Play bottom card &face down Play bottom card &face down - + Move bottom card to grave&yard Move bottom card to grave&yard - + Move bottom card to e&xile Move bottom card to e&xile - + Move bottom cards to &graveyard... Move bottom cards to &graveyard... - + Move bottom cards to &exile... Move bottom cards to &exile... - + Put bottom card on &top Put bottom card on &top - + Selec&ted cards Selec&ted cards - + Move top cards to exile Move top cards to exile - + Move bottom cards to grave Move bottom cards to grave - + Move bottom cards to exile Move bottom cards to exile - + Draw bottom cards Draw bottom cards - - + + C&reate another %1 token C&reate another %1 token - + Create tokens Create tokens - + Place card X cards from top of library Place card X cards from top of library - + Change power/toughness Change power/toughness - + Change stats to: Change stats to: - + Set annotation Set annotation - + Please enter the new annotation: Please enter the new annotation: - + Set counters Set counters - + Re&veal to... Re&veal to... - + View related cards View related cards - + Token: Token: - + All tokens All tokens + + PrintingSelector + + + Display Navigation Buttons + + + + + <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. + + + + + PrintingSelectorCardOverlayWidget + + + Preference + Preference + + + + Pin Printing + Pin Printing + + + + Unpin Printing + Unpin Printing + + + + Show Related cards + Show Related cards + + + + PrintingSelectorCardSearchWidget + + + Search by set name or set code + Search by set name or set code + + + + PrintingSelectorCardSelectionWidget + + + Previous Card in Deck + Previous Card in Deck + + + + Next Card in Deck + Next Card in Deck + + + + PrintingSelectorCardSortingWidget + + + Alphabetical + Alphabetical + + + + Preference + Preference + + + + Release Date + Release Date + + + + + Descending + Descending + + + + Ascending + Ascending + + QMenuBar - + Services Services - + Hide %1 Hide %1 - + Hide Others Hide Others - + Show All Show All - + Preferences... Preferences... - + Quit %1 Quit %1 - + About %1 About %1 @@ -4712,38 +5675,32 @@ Cockatrice will now reload the card database. QObject - + Cockatrice card database (*.xml) Cockatrice card database (*.xml) - - + All files (*.*) All files (*.*) - + Cockatrice replays (*.cor) Cockatrice replays (*.cor) - - Common deck formats (*.cod *.dec *.dek *.txt *.mwDeck) - Common deck formats (*.cod *.dec *.dek *.txt *.mwDeck) - - - + Maindeck Maindeck - + Sideboard Sideboard - + Tokens Tokens @@ -4751,92 +5708,92 @@ Cockatrice will now reload the card database. QPlatformTheme - + OK OK - + Save Save - + Save All Save All - + Open Open - + &Yes &Yes - + Yes to &All Yes to &All - + &No &No - + N&o to All N&o to All - + Abort Abort - + Retry Retry - + Ignore Ignore - + Close Close - + Cancel Cancel - + Discard Discard - + Help Help - + Apply Apply - + Reset Reset - + Restore Defaults Restore Defaults @@ -4844,17 +5801,17 @@ Cockatrice will now reload the card database. RemoteDeckList_TreeModel - + Name Name - + ID ID - + Upload time Upload time @@ -4862,32 +5819,32 @@ Cockatrice will now reload the card database. RemoteReplayList_TreeModel - + ID ID - + Name Name - + Players Players - + Keep Keep - + Time started Time started - + Duration (sec) Duration (sec) @@ -4933,32 +5890,32 @@ Cockatrice will now reload the card database. SequenceEdit - + Choose an action from the table Choose an action from the table - + Hit the key/combination of keys you want to set for this action Hit the key/combination of keys you want to set for this action - - Shortcut already in use - Shortcut already in use - - - + Invalid key Invalid key - + + Shortcut already in use by: + Shortcut already in use by: + + + Clear Clear - + Restore default Restore default @@ -4994,73 +5951,81 @@ Cockatrice will now reload the card database. ShortcutSettingsPage - - + + Restore all default shortcuts Restore all default shortcuts - + Do you really want to restore all default shortcuts? Do you really want to restore all default shortcuts? - + Clear all default shortcuts Clear all default shortcuts - + Do you really want to clear all shortcuts? Do you really want to clear all shortcuts? - - Action - Action - - - - Shortcut - Shortcut - - - + Section: Section: - + Action: Action: - + Shortcut: Shortcut: - + How to set custom shortcuts How to set custom shortcuts - + Clear all shortcuts Clear all shortcuts + + + Search by shortcut name + Search by shortcut name + + + + ShortcutTreeView + + + Action + Action + + + + Shortcut + Shortcut + ShortcutsSettings - + Your configuration file contained invalid shortcuts. Please check your shortcut settings! Your configuration file contained invalid shortcuts. Please check your shortcut settings! - + The following shortcuts have been set to default: The following shortcuts have been set to default: @@ -5070,17 +6035,17 @@ Please check your shortcut settings! ShutdownDialog - + &Reason for shutdown: &Reason for shutdown: - + &Time until shutdown (minutes): &Time until shutdown (minutes): - + Shut down server Shut down server @@ -5088,27 +6053,27 @@ Please check your shortcut settings! SoundSettingsPage - + Enable &sounds Enable &sounds - + Current sounds theme: Current sounds theme: - + Test system sound engine Test system sound engine - + Sound settings Sound settings - + Master volume Master volume @@ -5116,48 +6081,48 @@ Please check your shortcut settings! SpoilerBackgroundUpdater - + Spoilers season has ended Spoilers season has ended - + Deleting spoiler.xml. Please run Oracle Deleting spoiler.xml. Please run Oracle - - + + Spoilers download failed Spoilers download failed - + No internet connection No internet connection - + Error Error - + Spoilers already up to date Spoilers already up to date - + No new spoilers added No new spoilers added - + Spoilers have been updated! Spoilers have been updated! - + Last change: Last change: @@ -5165,638 +6130,594 @@ Please check your shortcut settings! StableReleaseChannel - - Stable Releases - Stable Releases + + Default + Default - + No reply received from the release update server. No reply received from the release update server. - + Invalid reply received from the release update server. Invalid reply received from the release update server. - + No reply received from the tag update server. No reply received from the tag update server. - + Invalid reply received from the tag update server. Invalid reply received from the tag update server. + + TabAccount + + + Add to Buddy List + + + + + Add to Ignore List + + + + + Account + + + TabAdmin - + Administration Administration - + Update server &message Update server &message - + &Shut down server &Shut down server - + &Reload configuration &Reload configuration - + Server administration functions Server administration functions - + + Server moderator functions + + + + + Replay ID + Replay ID + + + + Grant Replay Access + Grant Replay Access + + + + Username to Activate + Username to Activate + + + + Force Activate User + Force Activate User + + + &Unlock functions &Unlock functions - + &Lock functions &Lock functions + + + + Success + Success + + + + Replay access granted + Replay access granted + + + + + + + + Error + Error + + + + Unable to grant replay access. Replay ID invalid + Unable to grant replay access. Replay ID invalid + + + + Unable to grant replay access. Internal error + Unable to grant replay access. Internal error + + + + User successfully activated + User successfully activated + + + + Unable to activate user. Username invalid + Unable to activate user. Username invalid + + + + Unable to activate user. User already active + Unable to activate user. User already active + + + + Unable to activate user. Internal error + Unable to activate user. Internal error + TabDeckEditor - - Search by card name (or search expressions) - Search by card name - Search by card name (or search expressions) - - - - Add to Deck - Add to Deck - - - - Add to Sideboard - Add to Sideboard - - - - Show Related cards - Show Related cards - - - - &Clear all filters - &Clear all filters - - - - Delete selected - Delete selected - - - - Deck &name: - Deck &name: - - - - &Comments: - &Comments: - - - - Hash: - Hash: - - - - &New deck - &New deck - - - - &Load deck... - &Load deck... - - - - &Save deck - &Save deck - - - - Save deck &as... - Save deck &as... - - - - Load deck from cl&ipboard... - Load deck from cl&ipboard... - - - - Save deck to clipboard - Save deck to clipboard - - - - Annotated - Annotated - - - - Not Annotated - Not Annotated - - - - &Print deck... - &Print deck... - - - - &Send deck to online service - &Send deck to online service - - - - Create decklist (decklist.org) - Create decklist (decklist.org) - - - - Analyze deck (deckstats.net) - Analyze deck (deckstats.net) - - - - Analyze deck (tappedout.net) - Analyze deck (tappedout.net) - - - - &Close - &Close - - - - Add card to &maindeck - Add card to &maindeck - - - - Add card to &sideboard - Add card to &sideboard - - - - &Remove row - &Remove row - - - - &Increment number - &Increment number - - - - &Decrement number - &Decrement number - - - - &Deck Editor - &Deck Editor - - - - + Card Info Card Info - - + Deck Deck - - + Filters Filters - + &View &View - - - + + Printing + Printing + + + + + + Visible Visible - - - + + + + Floating Floating - + Reset layout Reset layout - + Deck: %1 Deck: %1 - - - Are you sure? - Are you sure? - - - - The decklist has been modified. -Do you want to save the changes? - The decklist has been modified. -Do you want to save the changes? - - - - Load deck - Load deck - - - - - - - - - Error - Error - - - - The deck could not be saved. - The deck could not be saved. - - - - Could not save remote deck - Could not save remote deck - - - - - The deck could not be saved. -Please check that the directory is writable and try again. - The deck could not be saved. -Please check that the directory is writable and try again. - - - - Save deck - Save deck - - - - There are no cards in your deck to be exported - There are no cards in your deck to be exported - - - - No deck was selected to be saved. - No deck was selected to be saved. - TabDeckStorage - - Deck storage - Deck storage - - - + Local file system Local file system - + Server deck storage Server deck storage - - + + Open in deck editor Open in deck editor - + + Rename deck or folder + + + + Upload deck Upload deck - + Download deck Download deck - - + + + + New folder New folder - - + + Delete Delete - - + + Open decks folder + Open decks folder + + + + Rename local folder + + + + + Rename local file + + + + + New name: + + + + + + + Error Error - + + Rename failed + + + + + Invalid deck file Invalid deck file - + Enter deck name Enter deck name - + This decklist does not have a name. Please enter a name: This decklist does not have a name. Please enter a name: - + Unnamed deck Unnamed deck - + Failed to upload deck to server Failed to upload deck to server - + Delete local file Delete local file - - - - Are you sure you want to delete "%1"? - Are you sure you want to delete "%1"? + + Are you sure you want to delete the selected files? + Are you sure you want to delete the selected files? - + + Delete remote decks + Delete remote decks + + + + Are you sure you want to delete the selected decks? + Are you sure you want to delete the selected decks? + + + + Name of new folder: Name of new folder: - - Delete remote folder - Delete remote folder + + Deck Storage + + + + + TabDeckStorageVisual + + + Visual Deck Storage + - - Delete remote deck - Delete remote deck + + Error + + + + + Could not open deck at %1 + + + + + TabEdhRec + + + EDHREC: + TabGame - - - + + + Replay Replay - - + + Game Game - - + + Player List Player List - - + + Card Info Card Info - - + + Messages Messages - - + + Replay Timeline Replay Timeline - + &Phases &Phases - + &Game &Game - + Next &phase Next &phase - + Next phase with &action Next phase with &action - + Next &turn Next &turn - + Reverse turn order Reverse turn order - + &Remove all local arrows &Remove all local arrows - + Rotate View Cl&ockwise Rotate View Cl&ockwise - + Rotate View Co&unterclockwise Rotate View Co&unterclockwise - + Game &information Game &information - + + Un&concede + Un&concede + + + &Concede &Concede - + &Leave game &Leave game - + C&lose replay C&lose replay - + &Focus Chat &Focus Chat - + &Say: &Say: - + &View &View - - - - + + + + Visible Visible - - - - + + + + Floating Floating - + Reset layout Reset layout - + Concede Concede - + Are you sure you want to concede this game? Are you sure you want to concede this game? - + Unconcede Unconcede - + You have already conceded. Do you want to return to this game? You have already conceded. Do you want to return to this game? - + Leave game Leave game - + Are you sure you want to leave this game? Are you sure you want to leave this game? - + You are flooding the game. Please wait a couple of seconds. You are flooding the game. Please wait a couple of seconds. - + A player has joined game #%1 A player has joined game #%1 - + %1 has joined the game %1 has joined the game - + kicked by game host or moderator kicked by game host or moderator - + player left the game player left the game - + player disconnected from server player disconnected from server - + reason unknown reason unknown - + You have been kicked out of the game. You have been kicked out of the game. @@ -5809,153 +6730,153 @@ Please enter a name: Logs - - - + + + Time;SenderName;SenderIP;Message;TargetID;TargetName Time;SenderName;SenderIP;Message;TargetID;TargetName - + Room Logs Room Logs - + Game Logs Game Logs - + Chat Logs Chat Logs - - + + Error Error - + You must select at least one filter. You must select at least one filter. - + You have to select a valid number of days to locate. You have to select a valid number of days to locate. - + Username: Username: - + IP Address: IP Address: - + Game Name: Game Name: - + GameID: GameID: - + Message: Message: - + Main Room Main Room - + Game Room Game Room - + Private Chat Private Chat - + Past X Days: Past X Days: - + Today Today - + Last Hour Last Hour - + Maximum Results: Maximum Results: - + At least one filter is required. The more information you put in, the more specific your results will be. At least one filter is required. The more information you put in, the more specific your results will be. - + Get User Logs Get User Logs - + Clear Filters Clear Filters - + Filters Filters - + Log Locations Log Locations - + Date Range Date Range - + Maximum Results Maximum Results - - + + Message History Message History - + Failed to collect message history information. Failed to collect message history information. - + There are no messages for the selected filters. There are no messages for the selected filters. @@ -5978,22 +6899,22 @@ The more information you put in, the more specific your results will be.%1 - Private chat - + This user is ignoring you, they cannot see your messages in main chat and you cannot join their games. This user is ignoring you, they cannot see your messages in main chat and you cannot join their games. - + Private message from Private message from - + %1 has left the server. %1 has left the server. - + %1 has joined the server. %1 has joined the server. @@ -6001,117 +6922,153 @@ The more information you put in, the more specific your results will be. TabReplays - - Game replays - Game replays - - - + Local file system Local file system - + Server replay storage Server replay storage - - + + Watch replay Watch replay - - + + Rename + Rename + + + + + New folder + New folder + + + + Delete Delete - + + Open replays folder + Open replays folder + + + Download replay Download replay - + Toggle expiration lock Toggle expiration lock - + + Rename local folder + Rename local folder + + + + Rename local file + Rename local file + + + + New name: + New name: + + + + Error + Error + + + + Rename failed + Rename failed + + + + Name of new folder: + Name of new folder: + + + Delete local file Delete local file - - Are you sure you want to delete "%1"? - Are you sure you want to delete "%1"? + + Are you sure you want to delete the selected files? + Are you sure you want to delete the selected files? - - Downloading Replays - Downloading Replays + + Are you sure you want to delete the selected replays? + Are you sure you want to delete the selected replays? - - Folder download is not yet supported. Please download replays individually. - Folder download is not yet supported. Please download replays individually. - - - + Delete remote replay Delete remote replay - - Are you sure you want to delete the replay of game %1? - Are you sure you want to delete the replay of game %1? + + Game Replays + TabRoom - + &Say: &Say: - + Chat Chat - + &Room &Room - + &Leave room &Leave room - + &Clear chat &Clear chat - + Chat Settings... Chat Settings... - + mentioned you. mentioned you. - + Click to view Click to view - + You are flooding the chat. Please wait a couple of seconds. You are flooding the chat. Please wait a couple of seconds. @@ -6124,30 +7081,30 @@ The more information you put in, the more specific your results will be.Server - - - - + + + + Error Error - + Failed to join the server room: it doesn't exist on the server. Failed to join the server room: it doesn't exist on the server. - + The server thinks you are in the server room but your client is unable to display it. Try restarting your client. The server thinks you are in the server room but your client is unable to display it. Try restarting your client. - + You do not have the required permission to join this server room. You do not have the required permission to join this server room. - + Failed to join the server room due to an unknown error: %1. Failed to join the server room due to an unknown error: %1. @@ -6155,32 +7112,72 @@ The more information you put in, the more specific your results will be. TabSupervisor - + + Deck Editor + + + + + &Visual Deck Storage + + + + + Server + + + + + Account + + + + + Deck Storage + + + + + Game Replays + + + + + Administration + + + + + Logs + + + + Are you sure? Are you sure? - + There are still open games. Are you sure you want to quit? There are still open games. Are you sure you want to quit? - + Click to view Click to view - + Your buddy %1 has signed on! Your buddy %1 has signed on! - + Unknown Event Unknown Event - + The server has sent you a message that your client does not understand. This message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -6191,63 +7188,45 @@ This message might mean there is a new version of Cockatrice available or this s To update your client, go to Help -> Check for Updates. - + Idle Timeout Idle Timeout - + You are about to be logged out due to inactivity. You are about to be logged out due to inactivity. - + Promotion Promotion - + You have been promoted. Please log out and back in for changes to take effect. You have been promoted. Please log out and back in for changes to take effect. - + Warned Warned - + You have received a warning due to %1. Please refrain from engaging in this activity or further actions may be taken against you. If you have any questions, please private message a moderator. You have received a warning due to %1. Please refrain from engaging in this activity or further actions may be taken against you. If you have any questions, please private message a moderator. - + You have received the following message from the server. (custom messages like these could be untranslated) You have received the following message from the server. (custom messages like these could be untranslated) - - TabUserLists - - - Account - Account - - - - Add to Buddy List - Add to Buddy List - - - - Add to Ignore List - Add to Ignore List - - TappedOutInterface @@ -6333,173 +7312,184 @@ Please refrain from engaging in this activity or further actions may be taken ag UserContextMenu - + User &details User &details - + Private &chat Private &chat - + Show this user's &games Show this user's &games - + Add to &buddy list Add to &buddy list - + Remove from &buddy list Remove from &buddy list - + Add to &ignore list Add to &ignore list - + Remove from &ignore list Remove from &ignore list - + Kick from &game Kick from &game - + Warn user Warn user - + View user's war&n history View user's war&n history - + Ban from &server Ban from &server - + View user's &ban history View user's &ban history - + &Promote user to moderator &Promote user to moderator - + Dem&ote user from moderator Dem&ote user from moderator - + Promote user to &judge Promote user to &judge - + Demote user from judge Demote user from judge - + + View admin notes + View admin notes + + + %1's games %1's games - - - + + + Ban History Ban History - + Ban Time;Moderator;Ban Length;Ban Reason;Visible Reason Ban Time;Moderator;Ban Length;Ban Reason;Visible Reason - + User has never been banned. User has never been banned. - + Failed to collect ban information. Failed to collect ban information. - - - + + + Warning History Warning History - + Warning Time;Moderator;User Name;Reason Warning Time;Moderator;User Name;Reason - + User has never been warned. User has never been warned. - + Failed to collect warning information. Failed to collect warning information. - - + + Failed to get admin notes. + Failed to get admin notes. + + + + Success Success - + Successfully promoted user. Successfully promoted user. - + Successfully demoted user. Successfully demoted user. - - + + + Failed Failed - + Failed to promote user. Failed to promote user. - + Failed to demote user. Failed to demote user. - + Copy hash to clipboard Copy hash to clipboard - + Remove this user's messages Remove this user's messages @@ -6507,173 +7497,173 @@ Please refrain from engaging in this activity or further actions may be taken ag UserInfoBox - + Location: Location: - + Account Age: Account Age: - + Edit Edit - + Change password Change password - + Change avatar Change avatar - + Administrator Administrator - + Moderator Moderator - + Registered user Registered user - - + + Unregistered user Unregistered user - + Judge Judge - + Unknown Unknown - + The entered password does not match your account. The entered password does not match your account. - - - + + + Information Information - + User information updated. User information updated. - - - - - - - - - - + + + + + + + + + + Error Error - + User Information User Information - + Real Name: Real Name: - + User Level: User Level: - + %n Year(s), amount of years (only shown if more than 0) %n Year, %n Years, - + %10%n Day(s) %20 amount of years (if more than 0), amount of days, date in local short format %10%n Day %20%10%n Days %20 - + Enter Password Enter Password - + Password verification is required in order to change your email address Password verification is required in order to change your email address - - - + + + An error occurred while trying to update your user information. An error occurred while trying to update your user information. - + This server does not permit you to update your user informations. This server does not permit you to update your user informations. - + Password changed. Password changed. - + This server does not permit you to change your password. This server does not permit you to change your password. - + The new password is too short. The new password is too short. - + The old password is incorrect. The old password is incorrect. - + Avatar updated. Avatar updated. - + This server does not permit you to update your avatar. This server does not permit you to update your avatar. - + An error occured while trying to updater your avatar. An error occured while trying to updater your avatar. @@ -6681,124 +7671,251 @@ Please refrain from engaging in this activity or further actions may be taken ag UserInterfaceSettingsPage - + General interface settings General interface settings - + &Double-click cards to play them (instead of single-click) &Double-click cards to play them (instead of single-click) - + + &Clicking plays all selected cards (instead of just the clicked card) + &Clicking plays all selected cards (instead of just the clicked card) + + + &Play all nonlands onto the stack (not the battlefield) by default &Play all nonlands onto the stack (not the battlefield) by default - + + Close card view window when last card is removed + + + + Annotate card text on tokens Annotate card text on tokens - + Use tear-off menus, allowing right click menus to persist on screen Use tear-off menus, allowing right click menus to persist on screen - + Notifications settings Notifications settings - + Enable notifications in taskbar Enable notifications in taskbar - + Notify in the taskbar for game events while you are spectating Notify in the taskbar for game events while you are spectating - + Notify in the taskbar when users in your buddy list connect Notify in the taskbar when users in your buddy list connect - + Animation settings Animation settings - + &Tap/untap animation &Tap/untap animation + + + Deck editor/storage settings + + + + + Open deck in new tab by default + Open deck in new tab by default + + + + Prompt before converting .txt decks to .cod format + + + + + Always convert if not prompted + + + + + Use visual deck storage in game lobby + + + + + Replay settings + Replay settings + + + + Buffer time for backwards skip via shortcut: + Buffer time for backwards skip via shortcut: + - UserList + UserListWidget - + Users connected to server: %1 - Users connected to server: %1 + - + Users in this room: %1 - Users in this room: %1 + - + Buddies online: %1 / %2 - Buddies online: %1 / %2 + - + Ignored users online: %1 / %2 - Ignored users online: %1 / %2 + + + + + VisualDeckStorageFolderDisplayWidget + + + Deck Storage + + + + + VisualDeckStorageSearchWidget + + + Search by filename + Search by filename + + + + VisualDeckStorageSortWidget + + + Sort Alphabetically (Deck Name) + + + + + Sort Alphabetically (Filename) + + + + + Sort by Last Modified + + + + + Sort by Last Loaded + + + + + VisualDeckStorageWidget + + + Loading database ... + + + + + Show Folders + + + + + Show Tag Filter + + + + + Show Tags On Deck Previews + + + + + Show Banner Card Selection Option + + + + + Include Folder Names in Search + + + + + Draw unused Color Identities + + + + + Unused Color Identities Opacity + WarningDialog - + Which warning would you like to send? Which warning would you like to send? - + Redact all messages from this user in all rooms Redact all messages from this user in all rooms - + &OK &OK - + &Cancel &Cancel - + Warn user for misconduct Warn user for misconduct - - + + Error Error - + User name to send a warning to can not be blank, please specify a user to warn. User name to send a warning to can not be blank, please specify a user to warn. - + Warning to use can not be blank, please select a valid warning to send. Warning to use can not be blank, please select a valid warning to send. @@ -6806,128 +7923,133 @@ Please refrain from engaging in this activity or further actions may be taken ag WndSets - + Move selected set to the top Move selected set to the top - + Move selected set up Move selected set up - + Move selected set down Move selected set down - + Move selected set to the bottom Move selected set to the bottom - + Search by set name, code, or type Search by set name, code, or type - + Default order Default order - + Restore original art priority order Restore original art priority order - + Enable all sets Enable all sets - + Disable all sets Disable all sets - + Enable selected set(s) Enable selected set(s) - + Disable selected set(s) Disable selected set(s) - + Deck Editor Deck Editor - + Use CTRL+A to select all sets in the view. Use CTRL+A to select all sets in the view. - + Only cards in enabled sets will appear in the card list of the deck editor. Only cards in enabled sets will appear in the card list of the deck editor. - + Image priority is decided in the following order: Image priority is decided in the following order: - + first the CUSTOM Folder (%1), then the Enabled Sets in this dialog (Top to Bottom) %1 is a link to the wiki first the CUSTOM Folder (%1), then the Enabled Sets in this dialog (Top to Bottom) - + Card Art Card Art - + How to use custom card art How to use custom card art - + Hints Hints - + Note Note - + Sorting by column allows you to find a set while not changing set priority. Sorting by column allows you to find a set while not changing set priority. - + To enable ordering again, click the column header until this message disappears. To enable ordering again, click the column header until this message disappears. - + Use the current sorting as the set priority instead Use the current sorting as the set priority instead - + Sorts the set priority using the same column Sorts the set priority using the same column - + + Include online-only (Arena) cards [requires restart] + + + + Manage sets Manage sets @@ -6935,22 +8057,67 @@ Please refrain from engaging in this activity or further actions may be taken ag ZoneViewWidget - - sort by name - sort by name + + Ungrouped + Ungrouped - - sort by type - sort by type + + Group by Type + Group by Type - + + Group by Mana Value + Group by Mana Value + + + + Group by Color + Group by Color + + + + Unsorted + Unsorted + + + + Sort by Name + Sort by Name + + + + Sort by Type + Sort by Type + + + + Sort by Mana Cost + Sort by Mana Cost + + + + Sort by Colors + Sort by Colors + + + + Sort by P/T + Sort by P/T + + + + Sort by Set + Sort by Set + + + shuffle when closing shuffle when closing - + pile view pile view @@ -6958,7 +8125,7 @@ Please refrain from engaging in this activity or further actions may be taken ag i18n - + English English @@ -6966,12 +8133,12 @@ Please refrain from engaging in this activity or further actions may be taken ag main - + Connect on startup Connect on startup - + Debug to file Debug to file @@ -6979,785 +8146,907 @@ Please refrain from engaging in this activity or further actions may be taken ag shortcutsTab - + Main Window Main Window - - + + Deck Editor Deck Editor - + Game Lobby Game Lobby - + Card Counters Card Counters - + Player Counters Player Counters - + Power and Toughness Power and Toughness - + Game Phases Game Phases - + Playing Area Playing Area - + Move Selected Card Move Selected Card - + View View - + Move Top Card Move Top Card - + Move Bottom Card Move Bottom Card - + Gameplay Gameplay - + Drawing Drawing - + Chat Room Chat Room - + Game Window Game Window - + Load Deck from Clipboard Load Deck from Clipboard - + + Replays + Replays + + + + Tabs - + Check for Card Updates... Check for Card Updates... - + Connect... Connect... - + Disconnect Disconnect - + Exit Exit - + Full screen Full screen - + Register... Register... - + Settings... Settings... - + Start a Local Game... Start a Local Game... - + Watch Replay... Watch Replay... - - Analyze Deck - Analyze Deck + + Analyze Deck (deckstats.net) + Analyze Deck + - + + Analyze Deck (tappedout.net) + + + + Clear All Filters Clear All Filters - + Clear Selected Filter Clear Selected Filter - + Close Close - + Remove Card Remove Card - + Manage Sets... Manage Sets... - + Edit Custom Tokens... Edit Custom Tokens... - - Export Deck - Export Deck + + Export Deck (decklist.org) + - + + Export Deck (decklist.xyz) + + + + Add Card Add Card - + Load Deck... Load Deck... - + Load Deck from Clipboard... Load Deck from Clipboard... - + + Edit Deck in Clipboard, Annotated + + + + + Edit Deck in Clipboard + + + + New Deck New Deck - + Open Custom Pictures Folder Open Custom Pictures Folder - + Print Deck... Print Deck... - + Delete Card Delete Card - - + + Reset Layout Reset Layout - + Save Deck Save Deck - + Save Deck as... Save Deck as... - + Save Deck to Clipboard, Annotated Save Deck to Clipboard, Annotated - + + Save Deck to Clipboard, Annotated (No Set Info) + + + + Save Deck to Clipboard Save Deck to Clipboard - + + Save Deck to Clipboard (No Set Info) + + + + Load Local Deck... Load Local Deck... - + Load Remote Deck... Load Remote Deck... - + Set Ready to Start Set Ready to Start - + Toggle Sideboard Lock Toggle Sideboard Lock - - + + Add Green Counter Add Green Counter - - + + Remove Green Counter Remove Green Counter - - + + Set Green Counters... Set Green Counters... - + Add Yellow Counter Add Yellow Counter - + Remove Yellow Counter Remove Yellow Counter - + Set Yellow Counters... Set Yellow Counters... - - + + Add Red Counter Add Red Counter - - + + Remove Red Counter Remove Red Counter - - + + Set Red Counters... Set Red Counters... - + Add Life Counter Add Life Counter - + Remove Life Counter Remove Life Counter - + Set Life Counters... Set Life Counters... - + Add White Counter Add White Counter - + Remove White Counter Remove White Counter - + Set White Counters... Set White Counters... - + Add Blue Counter Add Blue Counter - + Remove Blue Counter Remove Blue Counter - + Set Blue Counters... Set Blue Counters... - + Add Black Counter Add Black Counter - + Remove Black Counter Remove Black Counter - + Set Black Counters... Set Black Counters... - + Add Colorless Counter Add Colorless Counter - + Remove Colorless Counter Remove Colorless Counter - + Set Colorless Counters... Set Colorless Counters... - + Add Other Counter Add Other Counter - + Remove Other Counter Remove Other Counter - + Set Other Counters... Set Other Counters... - + Add Power (+1/+0) Add Power (+1/+0) - + Remove Power (-1/-0) Remove Power (-1/-0) - + Move Toughness to Power (+1/-1) Move Toughness to Power (+1/-1) - + Add Toughness (+0/+1) Add Toughness (+0/+1) - + Remove Toughness (-0/-1) Remove Toughness (-0/-1) - + Move Power to Toughness (-1/+1) Move Power to Toughness (-1/+1) - + Add Power and Toughness (+1/+1) Add Power and Toughness (+1/+1) - + Remove Power and Toughness (-1/-1) Remove Power and Toughness (-1/-1) - + Set Power and Toughness... Set Power and Toughness... - + Reset Power and Toughness Reset Power and Toughness - + Untap Untap - + Upkeep Upkeep - + Draw Draw - + First Main Phase First Main Phase - + Start Combat Start Combat - + Attack Attack - + Block Block - + Damage Damage - + End Combat End Combat - + Second Main Phase Second Main Phase - + End End - + Next Phase Next Phase - + Next Phase Action Next Phase Action - + Next Turn Next Turn - + + Hide Card in Reveal Window + Hide Card in Reveal Window + + + Tap / Untap Card Tap / Untap Card - + Untap All Untap All - + Toggle Untap Toggle Untap - + Turn Card Over Turn Card Over - + Peek Card Peek Card - + Play Card Play Card - + Attach Card... Attach Card... - + Unattach Card Unattach Card - + Clone Card Clone Card - + Create Token... Create Token... - + Create All Related Tokens Create All Related Tokens - + Create Another Token Create Another Token - + Set Annotation... Set Annotation... - - + + Select All Cards in Zone + Select All Cards in Zone + + + + Select All Cards in Row + Select All Cards in Row + + + + Select All Cards in Column + Select All Cards in Column + + + + Bottom of Library Bottom of Library - - - - + + + + Exile Exile - - - - + + + + Graveyard Graveyard - - + + Hand Hand - - + + Top of Library Top of Library - - - + + + Battlefield, Face Down Battlefield, Face Down - + Battlefield Battlefield - + Library Library - + Sideboard Sideboard - + Top Cards of Library Top Cards of Library - + + Bottom Cards of Library + Bottom Cards of Library + + + Close Recent View Close Recent View - - + + Stack Stack - - + + Graveyard (Multiple) Graveyard (Multiple) - - + + Exile (Multiple) Exile (Multiple) - + Stack Until Found Stack Until Found - + Draw Bottom Card Draw Bottom Card - + Draw Multiple Cards from Bottom... Draw Multiple Cards from Bottom... - + Draw Arrow... Draw Arrow... - + Remove Local Arrows Remove Local Arrows - + Leave Game Leave Game - + Concede Concede - + Roll Dice... Roll Dice... - + Shuffle Library Shuffle Library - + + Shuffle Top Cards of Library + + + + + Shuffle Bottom Cards of Library + + + + Mulligan Mulligan - + Draw a Card Draw a Card - + Draw Multiple Cards... Draw Multiple Cards... - + Undo Draw Undo Draw - + Always Reveal Top Card Always Reveal Top Card - + Always Look At Top Card Always Look At Top Card - + Rotate View Clockwise Rotate View Clockwise - + Rotate View Counterclockwise Rotate View Counterclockwise - + Unfocus Text Box Unfocus Text Box - + Focus Chat Focus Chat - + Clear Chat Clear Chat - + Refresh Refresh - + + Skip Forward + Skip Forward + + + + Skip Backward + Skip Backward + + + + Skip Forward by a lot + Skip Forward by a lot + + + + Skip Backward by a lot + Skip Backward by a lot + + + Play/Pause + Play/Pause + + + + Toggle Fast Forward + Toggle Fast Forward + + + + Visual Deck Storage - - Toggle Fast Forward + + Deck Storage + + + + + Server + + + + + Account + + + + + Administration + + + + + Logs diff --git a/cockatrice/translations/cockatrice_es.ts b/cockatrice/translations/cockatrice_es.ts index f4279f5f1..f2f206b4d 100644 --- a/cockatrice/translations/cockatrice_es.ts +++ b/cockatrice/translations/cockatrice_es.ts @@ -2,7 +2,7 @@ AbstractCounter - + &Set counter... &Establecer contador... @@ -10,95 +10,247 @@ AbstractCounterDialog - + Set counter Establecer contador - + New value for counter '%1': Nuevo valor para el contador '%1': + + AbstractDlgDeckTextEdit + + + &Refresh + + + + + Parse Set Name and Number (if available) + + + + + AbstractTabDeckEditor + + + Open in new tab + + + + + Are you sure? + + + + + The decklist has been modified. +Do you want to save the changes? + + + + + + + + + + + Error + + + + + Could not open deck at %1 + + + + + Could not save remote deck + + + + + + The deck could not be saved. +Please check that the directory is writable and try again. + + + + + Save deck + + + + + The deck could not be saved. + + + + + There are no cards in your deck to be exported + + + + + No deck was selected to be exported. + + + + + AdminNotesDialog + + + Update Notes + + + + + Admin Notes for %1 + + + + + AllZonesCardAmountWidget + + + Mainboard + + + + + Sideboard + + + AppearanceSettingsPage - + Error Error - + Could not create themes directory at '%1'. No se pudo crear directorio de temas en '%1'. - + Theme settings Preferencias del tema - + Current theme: Tema actual: - + Open themes folder Abrir carpeta de temas - + + Menu settings + + + + + Show keyboard shortcuts in right-click menus + + + + Card rendering Representación de las cartas - + Display card names on cards having a picture Mostrar nombre de las cartas en aquellas que tengan imagen - + + Auto-Rotate cards with sideways layout + + + + + Override all card art with personal set preference (Pre-ProviderID change behavior) [Requires Client restart] + + + + + Bump sets that the deck contains cards from to the top in the printing selector + + + + Scale cards on mouse over Cambiar tamaño de las cartas al pasar el ratón por encima - + + Use rounded card corners + + + + Minimum overlap percentage of cards on the stack and in vertical hand - + + Maximum initial height for card view window: + + + + + + rows + + + + + Maximum expanded height for card view window: + + + + Hand layout Disposición de la mano - + Display hand horizontally (wastes space) Mostrar la mano horizontalmente (desperdicia espacio) - + Enable left justification Habilitar justificación a la izquierda - + Table grid layout Diseño de la cuadrícula de la mesa - + Invert vertical coordinate Invertir coordenada vertical - + Minimum player count for multi-column layout: Número minimo de jugadores para usar la cuadrícula multicolumna: - + Maximum font size for information displayed on cards: Tamaño de fuente máxima para la información presentada en las cartas: @@ -106,112 +258,112 @@ BanDialog - + ban &user name banear &usuario - + ban &IP address banear dirección IP &IP - + ban client I&D banear por I&D de cliente - + Ban type Tipo de ban - + &permanent ban acceso prohibido &permanentemente - + &temporary ban acceso prohibido &temporalmente - + &Days: &Días: - + &Hours: &Horas: - + &Minutes: &Minutos: - + Duration of the ban Duración del ban - + Please enter the reason for the ban. This is only saved for moderators and cannot be seen by the banned person. Por favor, introduce el motivo de la restricción de acceso Se almacenará unicamente para moderadores y no podrá ser visto por la persona restringida. - + Please enter the reason for the ban that will be visible to the banned person. Por favor, introduce el motivo de la restricción que será visible para la persona restringida. - + Redact all messages from this user in all rooms Redactar todos los mensajes de este usuario en todas las salas - + &OK Aceptar (&O) - + &Cancel &Cancelar - + Ban user from server Banear usuario del servidor - - - - + + + + Error Error - + You have to select a name-based, IP-based, clientId based, or some combination of the three to place a ban. Debes seleccionar baneo por nombre, por IP, por id de cliente, o cualquier combinación de los tres. - + You must have a value in the name ban when selecting the name ban checkbox. Debes introducir un nombre cuando la casilla de baneo por nombre está activada. - + You must have a value in the ip ban when selecting the ip ban checkbox. Debes introducir una IP cuando la casilla de baneo por IP está activada. - + You must have a value in the clientid ban when selecting the clientid ban checkbox. Debes introducir un id de cliente cuando la casilla de baneo por id de cliente está activada. @@ -219,22 +371,22 @@ Se almacenará unicamente para moderadores y no podrá ser visto por la persona BetaReleaseChannel - - Beta Releases - Versiones Beta + + Beta + - + No reply received from the release update server. No se recibió respuesta del servidor de actualización. - + Invalid reply received from the release update server. Respuesta inválida del servidor de actualización. - + No reply received from the file update server. Sin respuesta por parte del servidor de actualización de archivos @@ -242,32 +394,32 @@ Se almacenará unicamente para moderadores y no podrá ser visto por la persona CardDatabaseModel - + Name Nombre - + Sets Ediciones - + Mana cost Coste de maná - + Card type Tipo de carta - + P/T F/R - + Color(s) Color(es) @@ -360,325 +512,621 @@ Se almacenará unicamente para moderadores y no podrá ser visto por la persona - CardFrame + CardInfoFrameWidget - + Image - Imagen + - + Description - Descripción + - + Both - Ambos + + + + + View transformation + - CardInfoText + CardInfoPictureWidget - + + View related cards + + + + + Add card to deck + + + + + Mainboard + + + + + Sideboard + + + + + CardInfoTextWidget + + Name: - Nombre: + - + Related cards: - Cartas relacionadas: + - + Unknown card: - Carta desconocida: + CardItem - + &Move to &Mover a - + &Power / toughness Fuerza / resistencia (&P) + + CardSizeWidget + + + Card Size + + + CardZone - + their hand nominative su mano - + %1's hand nominative mano de %1 - + their library look at zone su biblioteca - + %1's library look at zone biblioteca de %1 - + of their library top cards of zone, de su biblioteca - + of %1's library top cards of zone de la biblioteca de %1 - + their library reveal zone su biblioteca - + %1's library reveal zone biblioteca de %1 - + their library shuffle su biblioteca - + %1's library shuffle biblioteca de %1 - + their library nominative su biblioteca - + %1's library nominative biblioteca de %1 - + their graveyard nominative su cementerio - + %1's graveyard nominative cementerio de %1 - + their exile nominative su zona de exilio - + %1's exile nominative la zona de exilio de %1 - + their sideboard look at zone su banquillo - + %1's sideboard look at zone el banquillo de %1 - + their sideboard nominative su banquillo - + %1's sideboard nominative el banquillo de %1 + + DeckEditorCardInfoDockWidget + + + Card Info + + + + + DeckEditorDatabaseDisplayWidget + + + Search by card name (or search expressions) + + + + + Add to Deck + + + + + Add to Sideboard + + + + + Select Printing + + + + + Show on EDHREC (Commander) + + + + + Show on EDHREC (Card) + + + + + Show Related cards + + + + + Add card to &maindeck + + + + + Add card to &sideboard + + + + + DeckEditorDeckDockWidget + + + Banner Card + + + + + Select Printing + + + + + Deck + + + + + Deck &name: + + + + + &Comments: + + + + + Hash: + + + + + &Increment number + + + + + &Decrement number + + + + + &Remove row + + + + + Swap card to/from sideboard + + + + + DeckEditorFilterDockWidget + + + Filters + + + + + &Clear all filters + + + + + Delete selected + + + + + DeckEditorMenu + + + &Deck Editor + + + + + &New deck + + + + + &Load deck... + + + + + Load recent deck... + + + + + Clear + + + + + &Save deck + + + + + Save deck &as... + + + + + Load deck from cl&ipboard... + + + + + Edit deck in clipboard + + + + + + Annotated + + + + + + Not Annotated + + + + + Save deck to clipboard + + + + + Annotated (No set info) + + + + + Not Annotated (No set info) + + + + + &Print deck... + + + + + &Send deck to online service + + + + + Create decklist (decklist.org) + + + + + Create decklist (decklist.xyz) + + + + + Analyze deck (deckstats.net) + + + + + Analyze deck (tappedout.net) + + + + + &Close + + + + + DeckEditorPrintingSelectorDockWidget + + + Printing Selector + + + DeckEditorSettingsPage - - + + Update Spoilers Actualizar Spoilers - - + + Success Éxito - + Download URLs have been reset. Las URL de descarga se han reiniciado. - + Downloaded card pictures have been reset. Las imágenes de las cartas descargadas se han reiniciado. - + Error Error - + One or more downloaded card pictures could not be cleared. Una o más imágenes descargadas pueden no ser claras - + Add URL Añadir URL - - + + URL: URL: - - + + Edit URL Editar URL - + + Network Cache Size: + + + + + Redirect Cache TTL: + + + + + How long cached redirects for urls are valid for. + + + + + Picture Cache Size: + + + + Add New URL Añadir URL - + Remove URL Borrar URL - + + Day(s) + + + + Updating... Actualizando... - + Choose path Elegir camino - + URL Download Priority Prioridad de descarga de URL - + Spoilers Spoilers - + Download Spoilers Automatically Descargar Spoilers automáticamente - + Spoiler Location: Localización del Spoiler: - + Last Change Último cambio - + Spoilers download automatically on launch Spoilers se descargan automáticamente en lanzamiento - + Press the button to manually update without relaunching Presione el botón para actualizar manualmente sin reiniciar - + Do not close settings until manual update is complete No cierre la ventana de configuración hasta que la actualización manual este completa. - + Download card pictures on the fly Descargar imagenes de las cartas rapidamente - + How to add a custom URL Cómo agregar una URL personalizada - + Delete Downloaded Images Eliminar imágenes descargadas - + Reset Download URLs Reiniciar descarga de URLs - - Downloaded images directory size: - - - - + On-disk cache for downloaded pictures - - Picture cache size: - - - - + In-memory cache for pictures not currently on screen @@ -686,16 +1134,218 @@ Se almacenará unicamente para moderadores y no podrá ser visto por la persona DeckListModel - + + Count + + + + + Set + + + + Number Número - + + Provider ID + + + + Card Carta + + DeckLoader + + + Common deck formats (%1) + + + + + All files (*.*) + + + + + DeckPreviewColorIdentityFilterWidget + + + Mode: Exact Match + + + + + Mode: Includes + + + + + DeckPreviewDeckTagsDisplayWidget + + + Edit tags ... + + + + + DeckPreviewTagDialog + + + Deck Tags Manager + + + + + Manage your deck tags. Check or uncheck tags as needed, or add new ones: + + + + + Add a new tag (e.g., Aggro️) + + + + + Add Tag + + + + + Filter tags... + + + + + OK + + + + + Cancel + + + + + Invalid Input + + + + + Tag name cannot be empty! + + + + + Duplicate Tag + + + + + This tag already exists. + + + + + DeckPreviewWidget + + + Banner Card + + + + + Open in deck editor + + + + + Edit Tags + + + + + Rename Deck + + + + + Save Deck to Clipboard + + + + + Annotated + + + + + Annotated (No set info) + + + + + Not Annotated + + + + + Not Annotated (No set info) + + + + + Rename File + + + + + Delete File + + + + + Set Banner Card + + + + + + New name: + + + + + + Error + + + + + Rename failed + + + + + Delete file + + + + + Are you sure you want to delete the selected file? + + + + + Delete failed + + + DeckStatsInterface @@ -713,181 +1363,217 @@ Se almacenará unicamente para moderadores y no podrá ser visto por la persona DeckViewContainer - + Load deck... Cargar mazo... - + Load remote deck... Cargar mazo remoto... - + + Unload deck + Unload deck... + + + + Ready to start Listo para empezar - + + Force start + + + + Sideboard unlocked Banquillo desbloqueado - + Sideboard locked Banquillo bloqueado - - Load deck - Cargar mazo - - - + Error Error - + The selected file could not be loaded. El fichero seleccionado no pudo cargarse. + + DialogConvertDeckToCodFormat + + + Deck Format Conversion + + + + + You tried to add a tag to a .txt format deck. + Tags can only be added to .cod format decks. + Do you want to convert the deck to the .cod format? + + + + + Remember and automatically apply choice in the future + + + DlgConnect - + Downloading... Descargando... - + Known Hosts Hospedajes Conocidos - + + Delete the currently selected saved server + + + + Refresh the server list with known public servers Actualizar la lista con servidores públicos conocidos - + New Host Nueva dirección - + Name: Nombre: - + &Host: &Dirección: - + &Port: &Puerto: - + Player &name: &Nombre del jugador: - + P&assword: &Contraseña: - + &Save password &Guardar contraseña - + A&uto connect Conectarse a&utomáticamente - + Automatically connect to the most recent login when Cockatrice opens Conectar automáticamente al servidor más reciente cuando se inicie Cockatrice - + If you have any trouble connecting or registering then contact the server staff for help! ¡Si tiene algún problema en conectarse o registrarse puede contactar a nuestro equipo! - - + + Webpage Página web - + Reset Password Reiniciar Contraseña - + + Forgot password? + + + + &Connect &Conectar - + Server Servidor - + Login Iniciar sesión - + Server Contact Contacto del Servidor - + Connect to Server Conectar al Servidor - + Server URL dirección URL del servidor - + Communication Port Puerto de comunicación - + Unique Server Name Nombre único para el servidor - + Connection Warning Advertencia de Conexión - + You need to name your new connection profile. Necesita dar nombre a su nuevo perfil de conexión. - + Connect Warning Advertencia de conexión - + The player name can't be empty. El nombre del jugador no puede estar vacío. @@ -970,27 +1656,37 @@ Se almacenará unicamente para moderadores y no podrá ser visto por la persona Espectadores - + + Starting life total: + + + + + Game setup options + + + + &Clear Limpiar (&C) - + Create game Crear partida - + Game information Información de la partida - + Error Error - + Server error. Error del servidor. @@ -998,92 +1694,92 @@ Se almacenará unicamente para moderadores y no podrá ser visto por la persona DlgCreateToken - + &Name: &Nombre: - + Token Ficha - + C&olor: &Color: - + white blanco - + blue azul - + black negro - + red rojo - + green verde - + multicolor multicolor - + colorless incoloro - + &P/T: &F/R: - + &Annotation: &Anotación: - + &Destroy token when it leaves the table &Destruir la ficha cuando deje la mesa - + Token data Datos de la ficha - + Show &all tokens Mostrar &todas las fichas - + Show tokens from this &deck Mostrar todas las fichas de este &mazo - + Choose token from list Elegir ficha de la lista - + Create token Crear ficha @@ -1129,6 +1825,24 @@ Para eliminar tu avatar actual, confirma sin elegir una nueva imagen.La imagen seleccionada no es válida. + + DlgEditDeckInClipboard + + + Edit deck in clipboard + + + + + Error + + + + + Invalid deck list. + + + DlgEditPassword @@ -1171,93 +1885,93 @@ Para eliminar tu avatar actual, confirma sin elegir una nueva imagen. DlgEditTokens - + &Name: &Nombre: - + C&olor: &Color: - + white blanco - + blue azul - + black negro - + red rojo - + green verde - + multicolor multicolor - + colorless incoloro - + &P/T: &F/R: - + &Annotation: &Anotación: - + Token data Información de la ficha - - + + Add token Añadir ficha - + Remove token Eliminar ficha - + Edit custom tokens Editar fichas personalizadas - + Please enter the name of the token: Por favor, introduzca el nombre de la ficha: - + Error Error - + The chosen name conflicts with an existing card or token. Make sure to enable the 'Token' set in the "Manage sets" dialog to display them correctly. El nombre elegido ya existe y está siendo usado por una carta o ficha. @@ -1295,132 +2009,132 @@ Asegúrese de habilitar la edición 'Fichas' en la opción "Mante DlgFilterGames - + no limit sin límite - + 5 minutes 5 minutos - + 10 minutes 10 minutos - + 30 minutes 30 minutos - + 1 hour 1 hora - + 2 hours 2 horas - - Show '&buddies only' games - Muestra juegos de ' solo &amigos' + + Hide 'buddies only' games + - - Show &full games - Mostrar &partidas completas + + Hide full games + - - Show games &that have started - Mostrar partidas &ya comenzadas + + Hide games that have started + - - Show &password protected games - Mostrar partidas protegidas con contraseña + + Hide password protected games + - - Hide '&ignored user' games - Esconder juegos de '&ignorada usuario' + + Hide 'ignored user' games + - + &Newer than: &Más nuevo que: - + Game &description: &Descipción de la partida: - + &Creator name: &Nombre del creador: - + General General - + &Game types Tipos de &partida - + at &least: al &menos: - + at &most: &como máximo: - + Maximum player count Número máximo de jugadores - + Restrictions Restricciones - + Show games only if &spectators can watch Mostrar partidas solo si &espectadores pueden ver - + Show spectator password p&rotected games Mostrar contraseña de espectador para juegos p&rotegidos - + Show only if spectators can ch&at Mostrar solo si los espectadores pueden ha&blar - + Show only if spectators can see &hands Mostrar solo si los espectadores pueden ver &manos - + Spectators Espectadores - + Filter games Filtrar partidas @@ -1603,27 +2317,28 @@ Asegúrese de habilitar la edición 'Fichas' en la opción "Mante Las contraseñas no coinciden. + + DlgLoadDeck + + + Load Deck + + + DlgLoadDeckFromClipboard - - &Refresh - &Refrescar - - - + Load deck from clipboard Cargar mazo del portapapeles - - + Error Error - - + Invalid deck list. Lista de mazo inválida. @@ -1631,11 +2346,49 @@ Asegúrese de habilitar la edición 'Fichas' en la opción "Mante DlgLoadRemoteDeck - + Load deck Cargar mazo + + DlgMoveTopCardsUntil + + + Card name (or search expressions): + + + + + Number of hits: + + + + + Auto play hits + + + + + Put top cards on stack until... + + + + + No cards matching the search expression exists in the card database. Proceed anyways? + + + + + Cockatrice + + + + + Invalid filter + + + DlgRegister @@ -1750,12 +2503,12 @@ Tu correo electrónico se utilizará para verificar tu cuenta. DlgSettings - + Unknown Error loading card database Error desconocido al cargar la base de datos de cartas. - + Your card database is invalid. Cockatrice may not function correctly with an invalid database @@ -1772,7 +2525,7 @@ Podrías necesitar volver a ejecutar oracle para actualizar tu base de datos de ¿Quieres cambiar la ubicación de tu base de datos? - + Your card database version is too old. This can cause problems loading card information or images @@ -1789,7 +2542,7 @@ Normalmente esto se soluciona volviendo a ejecutar oracle para actualizar tu bas ¿Quieres cambiar la ubicación de tu base de datos? - + Your card database did not finish loading Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues with your cards.xml attached @@ -1802,7 +2555,7 @@ Por favor, envía un ticket en https://github.com/Cockatrice/Cockatrice/issues c ¿Te gustaría cambiar la configuración de la ubicación de tu base de datos? - + File Error loading your card database. Would you like to change your database location setting? @@ -1811,7 +2564,7 @@ Would you like to change your database location setting? ¿Quieres cambiar la ubicación de tu base de datos? - + Your card database was loaded but contains no cards. Would you like to change your database location setting? @@ -1820,7 +2573,7 @@ Would you like to change your database location setting? ¿Quieres cambiar la ubicación de tu base de datos? - + Unknown card database load status Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues @@ -1833,59 +2586,59 @@ Por favor, envía un ticket en https://github.com/Cockatrice/Cockatrice/issues ¿Te gustaría cambiar la configuración de la ubicación de tu base de datos? - - - + + + Error Error - + The path to your deck directory is invalid. Would you like to go back and set the correct path? La ruta a tu directorio de mazos no es válida. ¿Deseas volver y seleccionar la ruta correcta? - + The path to your card pictures directory is invalid. Would you like to go back and set the correct path? La ruta a tu directorio de imágenes de las cartas no es válida. ¿Deseas volver y seleccionar la ruta correcta? - + Settings Preferencias - + General General - + Appearance Apariencia - + User Interface Interfaz de usuario - + Card Sources Origen de la carta - + Chat Chat - + Sound Sonido - + Shortcuts Atajos de teclado @@ -1911,165 +2664,169 @@ Por favor, envía un ticket en https://github.com/Cockatrice/Cockatrice/issues DlgUpdate - + Current release channel Canal de lanzamiento actual - + Reinstall Reinstalar - + Cancel Download Cancelar Descarga - + Open Download Page Abrir página de descarga - + Check for Client Updates Comprobar actualizaciones del cliente - - - - + + + + Error Error - + Cockatrice was not built with SSL support, therefore you cannot download updates automatically! Please visit the download page to update manually. Cockatrice no fue creado con soporte para SSL, ¡por lo que no se pueden descargar actualizaciones automáticamente! Por favor, visita la página de descargas para actualizarlo manualmente. - - Downloading update... - Descargando actualización... + + Downloading update: %1 + Downloading update... + - + Checking for updates... Buscando actualizaciones... - + Finished checking for updates Comprobación de actualizaciones finalizada - + No Update Available Actualización no disponible - + Cockatrice is up to date! ¡Cockatrice se ha actualizado! - + You are already running the latest version available in the chosen release channel. Actualmente está ejecutando la última versión disponible en el canal de distribución. - + Current version Versión actual - + Selected release channel Seleccionado canal de lanzamiento - - + + Update Available Actualización disponible - - + + A new version of Cockatrice is available! ¡Una nueva version de Cockatrice está disponible! - - + + New version Nueva versión - - + + Released Lanzado - - + + Changelog Registro de cambios - + Do you want to update now? ¿Quieres actualizar ahora? - - Unfortunately there are no download packages available for your operating system. -You may have to build from source yourself. - Desafortunadamente no hay paquetes de descarga disponibles para su sistema operativo. Es posible que tenga que generar una versión propia a partir del código fuente. + + Unfortunately, the automatic updater failed to find a compatible download. +You may have to manually download the new version. + Unfortunately there are no download packages available for your operating system. +You may have to build from source yourself. + - - Please check the download page manually and visit the wiki for instructions on compiling. - Por favor revise la página de descargas manualmente y visite la wiki para instrucciones de como compilar. + + Please check the <a href="%1">releases page</a> on our Github and download the build for your system. + Please check the download page manually and visit the wiki for instructions on compiling. + - - - + + + Update Error Error en la actualización - + An error occurred while checking for updates: Ha ocurrido un error mientras se buscaban actualizaciones: - + An error occurred while downloading an update: Ha ocurrido un error mientras se descargaba una actualización: - + Installing... Instalando... - + Cockatrice is unable to open the installer. Cockatrice es incapaz de abrir el instalador. - + Try to update manually by closing Cockatrice and running the installer. Intente actualizar manualmente cerrando Cockatrice y ejecutando el instalador. - + Download location Ubicación de descarga @@ -2087,6 +2844,14 @@ You may have to build from source yourself. Registro de depuración + + EdhrecCommanderResponseCommanderDetailsDisplayWidget + + + Salt: + + + FilterBuilder @@ -2098,105 +2863,120 @@ You may have to build from source yourself. GameSelector - - - - - - - - - + + + + + + + + + Error Error - + Please join the appropriate room first. Por favor, entre a la sala adecuada primero. - + Wrong password. Contraseña incorrecta. - + Spectators are not allowed in this game. No se permiten espectadores en esta partida. - + The game is already full. La partida no tiene plazas libres. - + The game does not exist any more. La partida ya no existe. - + This game is only open to registered users. Esta partida está abierta sólo a usuarios registrados. - + This game is only open to its creator's buddies. Esta partida está abierta sólo a los amigos del creador. - + You are being ignored by the creator of this game. Estás siendo ignorado por el creador de la partida. - + + Join Game + + + + + Spectate Game + + + + + Game Information + + + + Join game Entrar en la partida - + Password: Contraseña: - + Please join the respective room first. Por favor, entre primero en la sala respectiva. - + &Filter games &Filtrar partidas - + C&lear filter &Limpiar filtro - + C&reate C&rear - + &Join Unirse (&J) - + J&oin as spectator Entrar como e&spectador - + Games shown: %1 / %2 Juegos mostrados: %1 / %2 - + Games Partidas @@ -2204,100 +2984,100 @@ You may have to build from source yourself. GamesModel - + >1 day >1 day - + %1%2 hr short age in hours %1%2 hora%1%2 horas%1%2 horas - + new nuevo - + %1%2 min short age in minutes %1%2 min%1%2 min%1%2 min - + password contraseña - + buddies only solo amigos - + reg. users only solo usuarios registrados - - + + can chat pueden usar el chat - + see hands ver manos - + can see hands puede ver manos - + not allowed no permitidos - + Room Sala - + Age Edad - + Description Descripción - + Creator Creador - + Type Tipo - + Restrictions Restricciones - + Players Jugadores - + Spectators Espectadores @@ -2305,92 +3085,102 @@ You may have to build from source yourself. GeneralSettingsPage - + Reset all paths Reiniciar todos los caminos - + All paths have been reset Todos los caminos se han reiniciado - - + + Choose path Elija ruta - + Personal settings Preferencias personales - + Language: Idioma: - + Paths (editing disabled in portable mode) Rutas (edición deshabilitada en modo portatil) - + Paths Rutas - + + How to help with translations + + + + Decks directory: Directorio de mazos: - + Replays directory: Directorio de repeticiones: - + Pictures directory: Directorio de imágenes: - + Card database: Base de datos de cartas: - + Custom database directory: Directorio de base de datos personalizado: - + Token database: Base de datos de tokens: - + Update channel Actualizar canal - + + Check for client updates on startup + + + + Notify if a feature supported by the server is missing in my client Notificar si hace falta en mi cliente alguna característica soportada por el servidor - + Automatically run Oracle when running a new version of Cockatrice Oracle se ejecutará automáticamente cuando se ejecuté una nueva versión de Cockatrice - + Show tips on startup Mostrar tips al inicio @@ -2398,61 +3188,61 @@ You may have to build from source yourself. MainWindow - - + + The server has reached its maximum user capacity, please check back later. Este servidor ha alcanzado su capacidad máxima de usuarios, por favor inténtalo más tarde. - + There are too many concurrent connections from your address. Hay demasiadas conexiones simultaneas desde tu dirección. - + Banned by moderator Baneado por el moderador - + Expected end time: %1 Tiempo de finalización estimado: %1 - + This ban lasts indefinitely. Este ban permanecerá indefinidamente. - + Scheduled server shutdown. Apagado programado del servidor. - - + + Invalid username. El nombre de usuario no es válido. - + You have been logged out due to logging in at another location. Se ha cerrado tu sesión debido a que se ha iniciado en otra ubicación. - + Connection closed Conexión cerrada - + The server has terminated your connection. Reason: %1 El servidor ha finalizado tu conexión. Motivo: %1 - + The server is going to be restarted in %n minute(s). All running games will be lost. Reason for shutdown: %1 @@ -2465,557 +3255,567 @@ Todas las partidas en curso se cerraran. Motivo para el apagado: %1 - + Scheduled server shutdown Apagado programado del servidor - - + + Success Éxito - + Registration accepted. Will now login. Registro aceptado. Iniciando sesión. - + Account activation accepted. Will now login. Activación de la cuenta aceptada. Iniciando sesión. - + Number of players Número de jugadores - + Please enter the number of players. Por favor, introduzca el número de jugadores. - - + + Player %1 Jugador %1 - + Load replay Cargar repetición - + About Cockatrice Acerca de Cockatrice - + Version Versión - + Cockatrice Webpage Página de Cockatrice - + Project Manager: Jefe de Proyecto: - + Past Project Managers: Antiguos Jefes de Proyecto: - + Developers: Desarrolladores: - + Our Developers Nuestros desarrolladores - + Help Develop! ¡Ayuda a desarrollar! - + Translators: Traductores: - + Our Translators Nuestros Traductores - + Help Translate! ¡Ayuda a traducir! - + Support: Soporte: - + Report an Issue Informa de un problema - + Troubleshooting Problemas frecuentes - + F.A.Q. Preguntas frecuentes - - - - - - - - - - - - - - - + + + + + + + + + + + + + - - - + + + + + Error Error - + Server timeout Tiempo de espera del servidor agotado - + Failed Login Inicio de sesión fallido - + Your client seems to be missing features this server requires for connection. Tu cliente parece no tener las características requeridas para la conexión de este servidor. - + To update your client, go to 'Help -> Check for Client Updates'. Para actualizar tu cliente, ir a 'Ayuda->Marcar para Actualizaciones del Cliente' - + Incorrect username or password. Please check your authentication information and try again. Nombre de usuario o contraseña incorrecto. Por favor, verifica tu información de autentificación y vuelve a intentarlo. - + There is already an active session using this user name. Please close that session first and re-login. Ya existe una sesión activa usando ese nombre de usuario. Por favor, cierra esa sesión primero y reintentalo. - - + + You are banned until %1. Estás baneado hasta %1. - - + + You are banned indefinitely. Estás baneado indefinidamente. - + This server requires user registration. Do you want to register now? Este servidor requiere el registro de usuarios. ¿Quieres registrarte ahora? - + This server requires client IDs. Your client is either failing to generate an ID or you are running a modified client. Please close and reopen your client to try again. Este servidor exige IDs de cliente. Tu cliente no está generando un ID o estás ejecutando un cliente modificado. Cierra y vuelve a abrir el cliente para intentarlo de nuevo. - + An internal error has occurred, please close and reopen Cockatrice before trying again. If the error persists, ensure you are running the latest version of the software and if needed contact the software developers. Ha ocurrido un error interno. Cierra y vuelve a abrir Cockatrice antes de intentar de nuevo. Si el error persiste, asegúrate de que estás ejecutando la versión más reciente del software y contacta a los desarrolladores del software si es necesario. - + Account activation Activación de cuenta - + Your account has not been activated yet. You need to provide the activation token received in the activation email. Tu cuenta aún no ha sido activada. Tienes que proporcionar el código recibido en el email de activación - + Server Full Servidor Lleno - + Unknown login error: %1 Error de inicio de sesión desconocido: %1 - - + + This usually means that your client version is out of date, and the server sent a reply your client doesn't understand. Esto suele ser debido a que tu versión del cliente está desfasada y el servidor ha enviado una respuesta que tu cliente no comprende. - + Your username must respect these rules: Tu nombre de usuario debe: - + is %1 - %2 characters long Tener de %1 a %2 caracteres de longitud. - + can %1 contain lowercase characters %1 Puede tener minúsculas. - - - - + + + + NOT NO - + can %1 contain uppercase characters %1 Puede tener mayúsculas. - + can %1 contain numeric characters %1 Puede tener caracteres númericos. - + can contain the following punctuation: %1 Puede tener los siguientes signos de puntuación: %1 - + first character can %1 be a punctuation mark El primer caracter %1 puede ser un signo de puntuación. - + no unacceptable language as specified by these server rules: note that the following lines will not be translated - + can not contain any of the following words: %1 No puede contener ninguna de las siguientes palabras: %1 - + can not match any of the following expressions: %1 No puede coincidir ninguna de las siguientes expresiones: %1 - + You may only use A-Z, a-z, 0-9, _, ., and - in your username. Sólo puedes usar A-Z, a-z, 0-9, _, ., y - en tu nombre de usuario. - - - - - - + + + + + + Registration denied Registro denegado - + Registration is currently disabled on this server El registro está actualmente deshabilitado en este servidor - + There is already an existing account with the same user name. Ya existe una cuenta con ese nombre. - + It's mandatory to specify a valid email address when registering. Es obligatorio especificar una dirección de correo válida al registrarse. - + It appears you are attempting to register a new account on this server yet you already have an account registered with the email provided. This server restricts the number of accounts a user can register per address. Please contact the server operator for further assistance or to obtain your credential information. Parece ser que usted está intentando registrar una cuenta nueva en este servidor; sin embargo usted ya tiene una cuetna registrada con el correo provisto. Este servidor restringe el número de cuentas que un usuario puede registrar por correo electrónico. Por favor contacte al operador del servidor para asistencia o para obtener la información de sus credenciales. - + Password too short. La contraseña es demasiado corta. - + Registration failed for a technical problem on the server. El registro falló debido a un problema técnico en el servidor. - + Unknown registration error: %1 Error de registro desconocido: %1 - + Account activation failed La activación de cuenta falló. - + Socket error: %1 Error del Socket: %1 - + You are trying to connect to an obsolete server. Please downgrade your Cockatrice version or connect to a suitable server. Local version is %1, remote version is %2. Estás intentando conectar a un servidor obsoleto. Por favor, usa una versión anterior de Cockatrice o conecta a un servidor apropiado. La versión local es %1, la versión remota es %2. - + Your Cockatrice client is obsolete. Please update your Cockatrice version. Local version is %1, remote version is %2. Tu cliente de Cockatrice está obsoleto. Por favor, actualiza tu versión de Cockatrice. La versión local es %1, la versión remota es %2. - + Connecting to %1... Conectando a %1... - + Registering to %1 as %2... Registrando en %1 como %2... - + Disconnected Desconectado - + Connected, logging in at %1 Conectado, hora de inicio de sesión: %1 - - - + + + Requesting forgotten password to %1 as %2... Solicitando contraseña olvidada de %1 como %2... - + &Connect... &Conectar... - + &Disconnect &Desconectar - + Start &local game... Empezar partida &local... - + &Watch replay... &Ver repetición... - - &Deck editor - Editor de &mazos - - - + &Full screen &Pantalla completa - + &Register to server... &Registrarse en el servidor... - + &Restore password... &Restaurar contraseña... - + &Settings... &Preferencias... - + &Exit &Salir - + A&ctions A&cciones - + &Cockatrice &Cockatrice - + C&ard Database Base de datos de c&artas - + &Manage sets... &Mantenedor de ediciones... - + Edit custom &tokens... Editar &fichas personalizadas... - + Open custom image folder Abrir carpeta de imágenes personalizadas - + Open custom sets folder Abrir carpeta de ediciones personalizadas - + Add custom sets/cards Agregar cartas/ediciones personalizadas - + + Reload card database + + + + + Tabs + + + + &Help A&yuda - + &About Cockatrice &Acerca de Cockatrice - + &Tip of the Day &Tip del Día - + Check for Client Updates Buscar Actualizaciones de Cliente - + Check for Card Updates... Comprobar actualizaciones de cartas... - + View &Debug Log Ver &registro de debugs - + + Open Settings Folder + + + + Show/Hide - + New Version Nueva Versión - + Congratulations on updating to Cockatrice %1! Oracle will now launch to update your card database. Felicitaciones se ha actualizado Cockatrice %1! Oracle actualizará tu base de datos de cartas. - + Cockatrice installed Cockatrice está instalado. - + Congratulations on installing Cockatrice %1! Oracle will now launch to install the initial card database. ¡Felicitaciones! Se ha instalado Cockatrice %1 Ahora se ejecutará Oracle para instalar la base de datos de cartas inicial. - + Card database Base de datos de cartas - + Cockatrice is unable to load the card database. Do you want to update your card database now? If unsure or first time user, choose "Yes" @@ -3024,29 +3824,29 @@ If unsure or first time user, choose "Yes" Si no está seguro o es su primera vez, seleccione "Sí" - - + + Yes Si - - + + No No - + Open settings Abrir preferencias - + New sets found Nuevas ediciones encontradas - + %n new set(s) found in the card database Set code(s): %1 Do you want to enable it/them? @@ -3059,17 +3859,17 @@ Códigos de sets: %1 ¿Quieres habilitarlos? - + View sets Ver ediciones - + Welcome Bienvenido/a - + Hi! It seems like you're running this version of Cockatrice for the first time. All the sets in the card database have been enabled. Read more about changing the set order or disabling specific sets and consequent effects in the "Manage Sets" dialog. @@ -3078,58 +3878,59 @@ Todas las ediciones en la base de datos de cartas han sido habilitadas. Aprenda más sobre cambiar el orden de la edición o deshabilitando ediciones especificas y otras preferencias en la opción "Mantenedor de ediciones". - - + + Information Información - + A card database update is already running. La actualización de la base de datos de cartas ya está en marcha. - + Unable to run the card database updater: Imposible iniciar el actualizador de la base de datos de cartas: - - failed to start. - falló al iniciar. + + Failed to start. The file might be missing, or permissions might be incorrect. + - - crashed. - falló. + + The process crashed some time after starting successfully. + - - timed out. - tiempo agotado. + + Timed out. The process took too long to respond. The last waitFor...() function timed out. + - - write error. - error de escritura. + + An error occurred when attempting to write to the process. For example, the process may not be running, or it may have closed its input channel. + - - read error. - error de lectura. + + An error occurred when attempting to read from the process. For example, the process may not be running. + - - unknown error. - error desconocido. + + Unknown error occurred. + - - The card database updater exited with an error: %1 - El actualizador de la base de datos de cartas terminó con el error: %1 + + The card database updater exited with an error: +%1 + - + This server supports additional features that your client doesn't have. This is most likely not a problem, but this message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -3139,55 +3940,55 @@ Esto no debería ser un problema, pero este mensaje podría significar que hay u Para actualizar tu cliente, ve a Ayuda -> Buscar Actualizaciones. - - - - - + + + + + Load sets/cards Cargar ediciones/cartas - + Selected file cannot be found. El archivo seleccionado no fue encontrado. - + You can only import XML databases at this time. Solo puedes importar bases de datos en XML en este momento. - + The new sets/cards have been added successfully. Cockatrice will now reload the card database. Las nuevas ediciones/cartas se han agregado correctamente. Cockatrice está recargando la base de datos de cartas. - + Sets/cards failed to import. Las ediciones/cartas fallaron al ser importadas. - - - + + + Reset Password Reiniciar contraseña - + Your password has been reset successfully, you can now log in using the new credentials. Tu contraseña se ha reiniciado con éxito, ahora puedes conectarte con las nuevas credenciales. - + Failed to reset user account password, please contact the server operator to reset your password. No se pudo reiniciar la contraseña, por favor contacte con el operador del servidor para reiniciar su contraseña. - + Activation request received, please check your email for an activation token. Petición de activación recibida, por favor comprueba tu email para el código de activación. @@ -3461,179 +4262,190 @@ Cockatrice está recargando la base de datos de cartas. - %1 is looking at the top %3 card(s) %2. + %1 is looking at the %4 %3 card(s) %2. + %1 is looking at the top %3 card(s) %2. top card for singular, top %3 cards for plural - %1 está mirando la primera carta %2.%1 está mirando las primeras %3 cartas %2.%1 está mirando las primeras %3 cartas %2. + - + + bottom + + + + + top + + + + %1 turns %2 face-down. %1 pone %2 boca abajo. - + %1 turns %2 face-up. %1 pone %2 boca arriba. - + The game has been closed. La partida ha sido cerrada. - + The game has started. La partida ha comenzado. - + %1 has joined the game. %1 se ha unido a la partida. - + %1 is now watching the game. %1 está ahora observando la partida. - + You have been kicked out of the game. Has sido expulsado de la partida. - + %1 has left the game (%2). %1 ha dejado la partida (%2). - + %1 is not watching the game any more (%2). %1 ya no está viendo el juego (%2). - + %1 is not ready to start the game any more. %1 está preparado/a para empezar la partida. - + %1 shuffles their deck and draws a new hand of %2 card(s). %1 barajea su mazo y roba una nueva mano de %2 carta(s).%1 barajea su mazo y roba una nueva mano de %2 carta(s)%1 barajea su mazo y roba una nueva mano de %2 carta(s) - + %1 shuffles their deck and draws a new hand. %1 Baraja su maso y robas una nueva mano. - + You are watching a replay of game #%1. Estás viendo una repetición de la partida #%1. - + %1 is ready to start the game. %1 está preparado/a para empezar la partida. - + cards an unknown amount of cards cartas - + %1 card(s) a card for singular, %1 cards for plural %1 carta(s)%1 cars%1 cars - + %1 lends %2 to %3. - + %1 reveals %2 to %3. %1 revela %2 a %3. - + %1 reveals %2. %1 revela %2. - + %1 randomly reveals %2%3 to %4. %1 revela al azar %2%3 a %4. - + %1 randomly reveals %2%3. %1 revela al azar %2%3. - + %1 peeks at face down card #%2. %1 está mirando la carta #%2 boca abajo. - + %1 peeks at face down card #%2: %3. %1 mira la carta boca abajo #%2: %3. - + %1 reveals %2%3 to %4. %1 revela %2%3 a %4. - + %1 reveals %2%3. %1 revela %2%3. - + %1 reversed turn order, now it's %2. %1 Orden del turno invertido, ahora es %2. - + reversed Invertido. - + normal Normal. - + Heads Cara - + Tails Cruz - + %1 flipped a coin. It landed as %2. %1 tira una moneda. Ha salido %2. - + %1 rolls a %2 with a %3-sided die. %1 sacó un %2 con un dado de %3 caras. - + %1 flips %2 coins. There are %3 heads and %4 tails. - + %1 rolls a %2-sided dice %3 times: %4. @@ -3754,17 +4566,17 @@ Cockatrice está recargando la base de datos de cartas. %1 barajea las cartas %3 - %4 de %2. - + %1 unattaches %2. %1 desanexa %2. - + %1 undoes their last draw. %1 deshace su último robo. - + %1 undoes their last draw (%2). %1 deshace su último robo (%2). @@ -3772,110 +4584,110 @@ Cockatrice está recargando la base de datos de cartas. MessagesSettingsPage - + Word1 Word2 Word3 Palabra1 Palabra2 Palabra3 - + Add New Message Añadir nuevo mensaje - + Edit Message Editar mensaje - + Remove Message Borrar mensaje - + Add message Añadir mensaje - - + + Message: Mensaje: - + Edit message Editar mensaje - + Chat settings Preferencias de Chat - + Custom alert words Palabras de alerta personalizadas - + Enable chat mentions Habilitar las menciones en el chat - + Enable mention completer Habilitar completado de menciones - + In-game message macros Macros para mensajes durante la partida - + How to use in-game message macros - + Ignore chat room messages sent by unregistered users Ignorar mensajes de sala de usuarios sin registrar - + Ignore private messages sent by unregistered users Ignorar mensajes privados de usuarios sin registrar - - + + Invert text color Invertir el color del texto - + Enable desktop notifications for private messages Habilitar notificaciones de escritorio para los mensajes privados - + Enable desktop notification for mentions Habilitar notificaciones de escritorio para menciones - + Enable room message history on join Habilitar historial de mensajes de sala al unirse - - + + (Color is hexadecimal) (El color es hexadecimal) - + Separate words with a space, alphanumeric characters only Separa las palabras con un espacio, solo caracteres alfanuméricos @@ -3933,6 +4745,24 @@ Cockatrice está recargando la base de datos de cartas. Identidad de color + + OverlapControlWidget + + + Cards to overlap: + + + + + Overlap percentage: + + + + + Overlap direction: + + + Phase @@ -3999,57 +4829,57 @@ Cockatrice está recargando la base de datos de cartas. PhasesToolbar - + Untap step Paso de enderezar - + Upkeep step Paso de mantenimiento - + Draw step Paso de robar - + First main phase Primera fase principal - + Beginning of combat step Paso de inicio del combate - + Declare attackers step Paso de declarar atacantes - + Declare blockers step Paso de declarar bloqueadores - + Combat damage step Paso de daño de combate - + End of combat step Paso de final del combate - + Second main phase Segunda fase principal - + End of turn step Paso final @@ -4057,7 +4887,7 @@ Cockatrice está recargando la base de datos de cartas. PictureLoader - + en code for scryfall's language property, not available for all languages es @@ -4066,647 +4896,781 @@ Cockatrice está recargando la base de datos de cartas. Player - + Reveal top cards of library Muestra las primeras cartas de la biblioteca - - - - - - - - - + + + + + + + + + + + + Number of cards: (max. %1) Número de cartas: (máx. %1) - + &View graveyard Ver &cementerio - + &View exile Ver &exilio - + Player "%1" Jugador "%1" - - - - + + + + &Graveyard &Cementerio - - - - + + + + &Exile &Exiliar - + &Move hand to... &Mover mano a... - - - + + + &Top of library &Parte superior de la biblioteca - - - + + + &Bottom of library &Fondo de la biblioteca - + &Move graveyard to... &Mover el cementerio a... - - - - + + + + &Hand &Mano - + &Move exile to... Mover las cartas exiliadas a... - + &View library &Ver biblioteca - + &View hand &Ver mano - + View &top cards of library... Ver cartas de la parte &superior de la biblioteca... - + Reveal &library to... Enseñar &biblioteca a... - + &Always reveal top card &Siempre revelar la carta superior - + &View sideboard Ver banquillo (&S) - + &Draw card &Robar carta - + D&raw cards... &Robar cartas... - + &Undo last draw &Deshacer último robo - + Take &mulligan Hacer &mulligan - - &Shuffle - &Barajar - - - + Play top card &face down Jugar la primera carta &boca abajo - + Move top card to grave&yard Mover la primera carta del &cementerio - + Move top card to e&xile Mover la carta superior al e&xilio - + Move top cards to &graveyard... Mover cartas de la parte s&uperior de la biblioteca al cementerio... - + Move top cards to &exile... Mover cartas superiores al &exilio... - + Put top card on &bottom Poner carta superior en la parte &inferior - + + View bottom cards of library... + + + + Lend library to... - + + Shuffle + + + + Put top cards on stack &until... Take top cards &until... - + + Shuffle top cards... + + + + + Shuffle bottom cards... + + + + &Reveal hand to... &Enseñar mano a... - + Reveal r&andom card to... Revelar una carta al &azar a... - + Reveal random card to... Revelar una carta al azar a... - + &Sideboard Banquillo (&S) - + &Library &Biblioteca - + &Counters &Contadores - + &Untap all permanents &Enderezar todos los permanentes - + R&oll die... &Lanzar dado... - + &Create token... Crear &Ficha... - + C&reate another token C&rear otra ficha - + Cr&eate predefined token Cr&ear ficha predefinida - - + + &All players &Todos los jugadores - + S&ay D&ecir - + + &Select All + + + + + S&elect Row + + + + + S&elect Column + + + + &Play &Jugar - + &Hide &Ocultar - + Play &Face Down Jugar boca abajo - + &Tap / Untap Turn sideways or back again &Girar / Enderezar - + Toggle &normal untapping Alternar enderezamiento &normal - + T&urn Over Turn face up/face down D%ar vuelta - + &Peek at card face &Mirar el dorso de la carta - + &Clone &Clonar - + Attac&h to card... Ane&xar a una carta... - + Unattac&h Desane&xar - + &Draw arrow... &Dibujar flecha... - + &Increase power &Incrementar fuerza - + &Decrease power &Decrementar fuerza - + I&ncrease toughness I&ncrementar resistencia - + D&ecrease toughness D&ecrementar resistencia - + In&crease power and toughness In&crementar fuerza y resistencia - + Dec&rease power and toughness Dec&rementar fuerza y resistencia - + Increase power and decrease toughness Incrementa fuerza y decrementar resistencia - + Decrease power and increase toughness Decrementar fuerza e incrementar resistencia - + Set &power and toughness... Establecer &fuerza y resistencia... - + Reset p&ower and toughness Reiniciar f&uerza y resistencia - + &Set annotation... E&scribir anotación... - + Red Rojo - + Yellow Amarillo - + Green Verde - + &Add counter (%1) &Añadir contador (%1) - + &Remove counter (%1) &Eliminar contador (%1) - + &Set counters (%1)... &Establecer contadores (%1) - + &Top of library in random order - + X cards from the top of library... X cartas desde la parte superior de su biblioteca - + &Bottom of library in random order &Al fondo de la librería en orden aleatorio - + View top cards of library Ver cartas de la parte superior de la biblioteca - + + View bottom cards of library + + + + + Shuffle top cards of library + + + + + Shuffle bottom cards of library + + + + Which position should this card be placed: En qué posición debería esta carta estar colocada: - + (max. %1) (max. %1) - + Draw hand Robar carta - + 0 and lower are in comparison to current hand size 0 o menos son en comparación con el tamaños de mano actual - + Draw cards Robar cartas - - + + Number: Número: - + Move top cards to grave Mover cartas superiores al cementerio - + Reveal &top cards to... Mostrar &cartas de la parte superior... - + &Top of library... &Parte superior de la biblioteca... - + &Bottom of library... &Fondo de la biblioteca... - + &Always look at top card Siempre mirar la carta superior - + &Open deck in deck editor Abrir baraja en el editor de barajas - + &Play top card Jugar carta de la parte superior - + &Draw bottom card Robar carta del fondo - + D&raw bottom cards... Robar cartas del fondo... - + &Play bottom card Jugar carta del fondo - + Play bottom card &face down Jugar carta del fondo boca abajo - + Move bottom card to grave&yard Mover carta del fondo al cementerio - + Move bottom card to e&xile Mover carta del fondo al exilio - + Move bottom cards to &graveyard... Mover cartas del fondo al cementerio... - + Move bottom cards to &exile... Mover cartas del fondo al exilio... - + Put bottom card on &top Poner carta del fondo en la parte superior - + Selec&ted cards Cartas seleccionadas - + Move top cards to exile Mover cartas superiores al exilio - + Move bottom cards to grave Mover cartas del fondo al cementerio - + Move bottom cards to exile Mover cartas del fondo al exilio - + Draw bottom cards Robar cartas del fondo - - + + C&reate another %1 token Crear otro token %1 - + Create tokens Crear fichas - + Place card X cards from top of library Pone carta X cartas desde la parte superior de la biblioteca - + Change power/toughness Cambiar fuerza/resistencia - + Change stats to: Cambiar estadísticas a: - + Set annotation Escribir anotación - + Please enter the new annotation: Por favor, introduza la nueva anotación: - + Set counters Establecer contadores - + Re&veal to... - + View related cards Vea cartas relacionadas - + Token: Ficha: - + All tokens Todas las fichas + + PrintingSelector + + + Display Navigation Buttons + + + + + <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. + + + + + PrintingSelectorCardOverlayWidget + + + Preference + + + + + Pin Printing + + + + + Unpin Printing + + + + + Show Related cards + + + + + PrintingSelectorCardSearchWidget + + + Search by set name or set code + + + + + PrintingSelectorCardSelectionWidget + + + Previous Card in Deck + + + + + Next Card in Deck + + + + + PrintingSelectorCardSortingWidget + + + Alphabetical + + + + + Preference + + + + + Release Date + + + + + + Descending + + + + + Ascending + + + QMenuBar - + Services Servicios - + Hide %1 Ocultar %1 - + Hide Others Ocultar otros - + Show All Mostrar todo - + Preferences... Preferencias... - + Quit %1 Salir %1 - + About %1 Sobre %1 @@ -4714,38 +5678,32 @@ Cockatrice está recargando la base de datos de cartas. QObject - + Cockatrice card database (*.xml) Base de datos de cartas de Cockatrice (*.xml) - - + All files (*.*) Todos los archivos (*.*) - + Cockatrice replays (*.cor) Repeticiones de Cockatrice (*.cor) - - Common deck formats (*.cod *.dec *.dek *.txt *.mwDeck) - Formatos de mazo comunes (*.cod *.dec *.dek *.txt *.mwDeck) - - - + Maindeck - + Sideboard - + Tokens @@ -4753,92 +5711,92 @@ Cockatrice está recargando la base de datos de cartas. QPlatformTheme - + OK OK - + Save Guardar - + Save All Guardar todo - + Open Abrir - + &Yes &Sí - + Yes to &All Sí a &Todo - + &No &No - + N&o to All N&o a todo - + Abort Abortar - + Retry Volver a intentar - + Ignore Ignorar - + Close Cerrar - + Cancel Cancelar - + Discard Descartar - + Help Ayuda - + Apply Aplicar - + Reset Reiniciar - + Restore Defaults Restaurar predeterminados @@ -4846,17 +5804,17 @@ Cockatrice está recargando la base de datos de cartas. RemoteDeckList_TreeModel - + Name Nombre - + ID ID - + Upload time Tiempo de subida @@ -4864,32 +5822,32 @@ Cockatrice está recargando la base de datos de cartas. RemoteReplayList_TreeModel - + ID ID - + Name Nombre - + Players Jugadores - + Keep Mantener - + Time started Tiempo de inicio - + Duration (sec) Duración (seg.) @@ -4935,32 +5893,32 @@ Cockatrice está recargando la base de datos de cartas. SequenceEdit - + Choose an action from the table - + Hit the key/combination of keys you want to set for this action - - Shortcut already in use - - - - + Invalid key - + + Shortcut already in use by: + + + + Clear - + Restore default @@ -4996,73 +5954,81 @@ Cockatrice está recargando la base de datos de cartas. ShortcutSettingsPage - - + + Restore all default shortcuts Restaurar atajos por defecto - + Do you really want to restore all default shortcuts? ¿Realmente quieres restablecer los atajos predeterminados? - + Clear all default shortcuts Limpiar todos los atajos predeterminados - + Do you really want to clear all shortcuts? ¿Realmente quieres limpiar los atajos predeterminados? - - Action - Acción - - - - Shortcut - Atajo - - - + Section: Sección: - + Action: Acción: - + Shortcut: Atajo: - + How to set custom shortcuts Cómo fijar atajos personalizados - + Clear all shortcuts + + + Search by shortcut name + + + + + ShortcutTreeView + + + Action + + + + + Shortcut + + ShortcutsSettings - + Your configuration file contained invalid shortcuts. Please check your shortcut settings! Su archivo de configuración contiene atajos inválidos. Por favor, revise su configuración de atajos. - + The following shortcuts have been set to default: Los siguientes atajos han sido ajustados a como eran por defecto: @@ -5071,17 +6037,17 @@ Por favor, revise su configuración de atajos. ShutdownDialog - + &Reason for shutdown: &Motivo para el apagado: - + &Time until shutdown (minutes): &Tiempo hasta el apagado (minutos): - + Shut down server Apagar servidor @@ -5089,27 +6055,27 @@ Por favor, revise su configuración de atajos. SoundSettingsPage - + Enable &sounds Activar &sonidos - + Current sounds theme: Tema de sonidos actual: - + Test system sound engine Probar el audio del sistema - + Sound settings Preferencias de sonido - + Master volume Volumen maestro @@ -5117,48 +6083,48 @@ Por favor, revise su configuración de atajos. SpoilerBackgroundUpdater - + Spoilers season has ended La temporada de Spoilers ha terminado - + Deleting spoiler.xml. Please run Oracle Eliminando spoiler.xml Por favor, ejecute Oracle - - + + Spoilers download failed Error al descargar Spoilers - + No internet connection No hay conexión a internet - + Error Error - + Spoilers already up to date Los spoilers ya están actualizados - + No new spoilers added No se han añadido nuevos spoilers - + Spoilers have been updated! Los Spoilers han sido actualizados! - + Last change: Último cambio: @@ -5166,638 +6132,594 @@ Por favor, revise su configuración de atajos. StableReleaseChannel - - Stable Releases - Versiones estables + + Default + - + No reply received from the release update server. No se recibe respuesta del servidor de lanzamiento de actualizaciones. - + Invalid reply received from the release update server. Respuesta invalida del servidor de lanzamiento de actualizaciones. - + No reply received from the tag update server. No se recibe respuesta del servidor de etiquetas de actualizaciones. - + Invalid reply received from the tag update server. Respuesta invalida del servidor de etiquetas de actualizaciones. + + TabAccount + + + Add to Buddy List + + + + + Add to Ignore List + + + + + Account + + + TabAdmin - + Administration Administración - + Update server &message Actualizar &mensaje del servidor - + &Shut down server &Apagar servidor - + &Reload configuration Volver a carga&r la configuración - + Server administration functions Funciones de administración del servidor - + + Server moderator functions + + + + + Replay ID + + + + + Grant Replay Access + + + + + Username to Activate + + + + + Force Activate User + + + + &Unlock functions &Desbloquear funciones - + &Lock functions &Bloquear funciones + + + + Success + + + + + Replay access granted + + + + + + + + + Error + + + + + Unable to grant replay access. Replay ID invalid + + + + + Unable to grant replay access. Internal error + + + + + User successfully activated + + + + + Unable to activate user. Username invalid + + + + + Unable to activate user. User already active + + + + + Unable to activate user. Internal error + + TabDeckEditor - - Search by card name (or search expressions) - Search by card name - - - - - Add to Deck - Añadir al Mazo - - - - Add to Sideboard - Añadir al Banquillo - - - - Show Related cards - Mostrar cartas Relacionadas - - - - &Clear all filters - &Limpiar filtros - - - - Delete selected - Borrar seleccionado - - - - Deck &name: - &Nombre del mazo: - - - - &Comments: - &Comentarios: - - - - Hash: - Hash: - - - - &New deck - &Nuevo mazo - - - - &Load deck... - &Cargar mazo... - - - - &Save deck - &Guardar mazo - - - - Save deck &as... - Guardar mazo &como... - - - - Load deck from cl&ipboard... - Cargar mazo del &portapapeles... - - - - Save deck to clipboard - Guardar mazo al portapapeles - - - - Annotated - Anotado - - - - Not Annotated - No Anotado - - - - &Print deck... - Im&primir mazo... - - - - &Send deck to online service - &Enviar mazo al servicio online - - - - Create decklist (decklist.org) - Crear lista del mazo (decklist.org) - - - - Analyze deck (deckstats.net) - Analizar mazo (deckstats.net) - - - - Analyze deck (tappedout.net) - Analizar mazo (tappedout.net) - - - - &Close - &Cerrar - - - - Add card to &maindeck - Añadir carta al &mazo principal - - - - Add card to &sideboard - Añadir carta al banquillo (&S) - - - - &Remove row - &Eliminar columna - - - - &Increment number - &Incrementar número - - - - &Decrement number - &Decrementar número - - - - &Deck Editor - &Editor de mazos - - - - + Card Info Información de la carta - - + Deck Mazo - - + Filters Filtros - + &View &Ver - - - + + Printing + + + + + + + Visible Visible - - - + + + + Floating Flotante - + Reset layout Reinicializar la disposición - + Deck: %1 Mazo: %1 - - - Are you sure? - ¿Estás seguro? - - - - The decklist has been modified. -Do you want to save the changes? - La lista del mazo ha sido modificada -¿Deseas guardar los cambios? - - - - Load deck - Cargar mazo - - - - - - - - - Error - Error - - - - The deck could not be saved. - El mazo no pudo ser guardado. - - - - Could not save remote deck - No se pudo guardar la baraja remota - - - - - The deck could not be saved. -Please check that the directory is writable and try again. - El mazo no pudo ser guardado -Por favor, comprueba que tienes permisos de escritura en el directorio e inténtalo de nuevo. - - - - Save deck - Guardar mazo - - - - There are no cards in your deck to be exported - No hay cartas en tu mazo para exportar - - - - No deck was selected to be saved. - Ningún mazo se ha seleccionado para ser guardado. - TabDeckStorage - - Deck storage - Almacenamiento de mazos - - - + Local file system Sistema de archivos local - + Server deck storage Almacen de mazos del servidor - - + + Open in deck editor Abrir en el editor de mazos - + + Rename deck or folder + + + + Upload deck Subir mazo - + Download deck Descargar mazo - - + + + + New folder Nueva carpeta - - + + Delete Borrar - - + + Open decks folder + + + + + Rename local folder + + + + + Rename local file + + + + + New name: + + + + + + + Error Error - + + Rename failed + + + + + Invalid deck file Archivo de baraja inválido - + Enter deck name Introduzca el nombre del mazo - + This decklist does not have a name. Please enter a name: Esta lista de mazo no tiene un nombre. Por favor, introduce un nombre: - + Unnamed deck Mazo sin nombre - + Failed to upload deck to server Fallo al subir baraja al servidor - + Delete local file Borrar fichero local - - - - Are you sure you want to delete "%1"? - ¿Estás seguro de que quieres borrar "%1"? + + Are you sure you want to delete the selected files? + - + + Delete remote decks + + + + + Are you sure you want to delete the selected decks? + + + + + Name of new folder: Nombre de la nueva carpeta: - - Delete remote folder - Borrar carpeta remota + + Deck Storage + + + + + TabDeckStorageVisual + + + Visual Deck Storage + - - Delete remote deck - Borrar mazo remoto + + Error + + + + + Could not open deck at %1 + + + + + TabEdhRec + + + EDHREC: + TabGame - - - + + + Replay Repetir - - + + Game Partida - - + + Player List Lista de jugadores - - + + Card Info Información de la carta - - + + Messages Mensajes - - + + Replay Timeline Historial de partidas - + &Phases &Fases - + &Game &Partida - + Next &phase Próxima &fase - + Next phase with &action Próxima fase con &acción - + Next &turn Próximo &turno - + Reverse turn order Orden del turno invertido. - + &Remove all local arrows &Retirar todas las flechas locales - + Rotate View Cl&ockwise Girar en sentido horario - + Rotate View Co&unterclockwise Girar en sentido antih&orario (&U) - + Game &information &Información de la partida - + + Un&concede + + + + &Concede &Conceder - + &Leave game &Abandonar la partida - + C&lose replay &Cerrar repetición - + &Focus Chat &Resaltar chat - + &Say: &Decir: - + &View &Ver - - - - + + + + Visible Visible - - - - + + + + Floating Flotante - + Reset layout Resetear la distribución - + Concede Conceder - + Are you sure you want to concede this game? ¿Estás seguro de que quieres conceder esta partida? - + Unconcede Revertir rendición - + You have already conceded. Do you want to return to this game? Ya te rendiste. ¿Quieres regresar a este juego? - + Leave game Abandonar la partida - + Are you sure you want to leave this game? ¿Estás seguro de que quieres abandonar la partida? - + You are flooding the game. Please wait a couple of seconds. Estás saturando la partida. Por favor, espera unos minutos. - + A player has joined game #%1 Un jugador se ha unido a la partida #%1 - + %1 has joined the game %1 se ha unido a la partida - + kicked by game host or moderator expulsado por el anfitrión del juego o moderador - + player left the game jugador ha dejado la partida - + player disconnected from server jugador se ha desconectado del servidor - + reason unknown razón desconocida - + You have been kicked out of the game. Has sido expulsado de la partida. @@ -5810,153 +6732,153 @@ Por favor, introduce un nombre: Historial - - - + + + Time;SenderName;SenderIP;Message;TargetID;TargetName Time;SenderName;SenderIP;Message;TargetID;TargetName - + Room Logs Registros de sala - + Game Logs Registros de partidas - + Chat Logs Registros de chat - - + + Error Error - + You must select at least one filter. Debes seleccionar como mínimo un filtro. - + You have to select a valid number of days to locate. Debes seleccionar un número de días válido para buscar. - + Username: Nombre de usuario: - + IP Address: Dirección IP: - + Game Name: Nombre de partida: - + GameID: ID de partida: - + Message: Mensaje: - + Main Room Sala Principal - + Game Room Sala de Partida - + Private Chat Chat privado - + Past X Days: Pasados X días: - + Today Hoy - + Last Hour Última hora - + Maximum Results: Resultados máximos: - + At least one filter is required. The more information you put in, the more specific your results will be. Se requiere un filtro como mínimo. Cuando más información introduzcas, más específicos serán los resultados. - + Get User Logs Ver registros de usuario - + Clear Filters Borrar filtros - + Filters Filtros - + Log Locations Localización de registro. - + Date Range Alcance de fecha - + Maximum Results Resultados máximos - - + + Message History Historial de mensajes - + Failed to collect message history information. Error al recopilar información del historial de mensajes. - + There are no messages for the selected filters. No hay mensajes con los filtros seleccionados @@ -5979,22 +6901,22 @@ Cuando más información introduzcas, más específicos serán los resultados.%1 - Chat privado - + This user is ignoring you, they cannot see your messages in main chat and you cannot join their games. El usuario te esta ignorando, los usuarios no podrán ver tus mensajes en el chat principal y no podrás unirte a sus partidas. - + Private message from Mensaje privado de - + %1 has left the server. %1 ha abandonado el servidor. - + %1 has joined the server. %1 se ha unido al servidor. @@ -6002,117 +6924,153 @@ Cuando más información introduzcas, más específicos serán los resultados. TabReplays - - Game replays - Repetición de partidas - - - + Local file system Sistema de archivos local - + Server replay storage Almacén de repeticiones del servidor - - + + Watch replay Ver repetición - - + + Rename + + + + + + New folder + + + + + Delete Borrar - + + Open replays folder + + + + Download replay Descargar repetición - + Toggle expiration lock Alternar expiración del bloqueo - + + Rename local folder + + + + + Rename local file + + + + + New name: + + + + + Error + + + + + Rename failed + + + + + Name of new folder: + + + + Delete local file Borrar fichero local - - Are you sure you want to delete "%1"? - ¿Estás seguro de que quieres borrar "%1"? + + Are you sure you want to delete the selected files? + - - Downloading Replays - Descargando Repeticiones + + Are you sure you want to delete the selected replays? + - - Folder download is not yet supported. Please download replays individually. - La descarga de carpetas aún no es compatible. Descarga las repeticiones individualmente. - - - + Delete remote replay Borrar repetición remota - - Are you sure you want to delete the replay of game %1? - ¿Estás seguro de que quieres borrar la repetición de la partida %1? + + Game Replays + TabRoom - + &Say: &Decir: - + Chat Chat - + &Room &Sala - + &Leave room &Dejar sala - + &Clear chat &Limpiar chat - + Chat Settings... Preferencias del Chat... - + mentioned you. te ha mencionado. - + Click to view Click para ver - + You are flooding the chat. Please wait a couple of seconds. Estás saturando el chat. Por favor, espera unos segundos. @@ -6125,30 +7083,30 @@ Cuando más información introduzcas, más específicos serán los resultados.Servidor - - - - + + + + Error Error - + Failed to join the server room: it doesn't exist on the server. Fallo al unirse a la sala del servidor: no existe en el servidor. - + The server thinks you are in the server room but your client is unable to display it. Try restarting your client. El servidor cree que estás en la sala del servidor pero tu cliente es incapaz de mostrarlo. Intenta reiniciar tu cliente. - + You do not have the required permission to join this server room. No tienes los permisos requeridos para unirte a esta sala del servidor. - + Failed to join the server room due to an unknown error: %1. Fallo al unirse a la sala del servidor debido a un error desconocido: %1. @@ -6156,32 +7114,72 @@ Cuando más información introduzcas, más específicos serán los resultados. TabSupervisor - + + Deck Editor + + + + + &Visual Deck Storage + + + + + Server + + + + + Account + + + + + Deck Storage + + + + + Game Replays + + + + + Administration + + + + + Logs + + + + Are you sure? ¿Estás seguro? - + There are still open games. Are you sure you want to quit? Todavía hay partidas abiertas. ¿Estás seguro/a de que quieres salir? - + Click to view Click para ver - + Your buddy %1 has signed on! Tu amigo %1 se ha conectado! - + Unknown Event Evento desconocido - + The server has sent you a message that your client does not understand. This message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -6191,63 +7189,45 @@ Este mensaje podría significar que hay una nueva versión de Cockatrice disponi Para actualizar tu cliente, ve a Ayuda -> Buscar Actualizaciones. - + Idle Timeout Tiempo de ausencia agotado - + You are about to be logged out due to inactivity. Va a ser desconectado por inactividad. - + Promotion Promoción - + You have been promoted. Please log out and back in for changes to take effect. Has sido promocionado. Por favor cierra la sesión y vuelve a iniciarla para que los cambios surtan efecto. - + Warned Advertido - + You have received a warning due to %1. Please refrain from engaging in this activity or further actions may be taken against you. If you have any questions, please private message a moderator. Has recibido una advertencia debido a %1 Por favor, absténgase de realizar de nuevo esta actividad o se tomarán medidas contra usted. Si tiene alguna pregunta, por favor envíe un mensaje privado a un moderador. - + You have received the following message from the server. (custom messages like these could be untranslated) Ha recibido los siguientes mensajes del servidor. (los mensajes como estos pueden aparecer en el idioma original) - - TabUserLists - - - Account - Cuenta - - - - Add to Buddy List - Añadir a la lista de &amigos - - - - Add to Ignore List - Añadir a la lista de &ignorados - - TappedOutInterface @@ -6331,173 +7311,184 @@ Por favor, absténgase de realizar de nuevo esta actividad o se tomarán medidas UserContextMenu - + User &details &Detalles del usuario - + Private &chat &Chat privado - + Show this user's &games Mostrar &partidas de este usuario - + Add to &buddy list Añadir a la lista de &amigos - + Remove from &buddy list Quitar de la lista de &amigos - + Add to &ignore list Añadir a la lista de &ignorados - + Remove from &ignore list Quitar de la lista de &ignorados - + Kick from &game Expulsar de la &partida - + Warn user Usuario advertido - + View user's war&n history Ver historial de advertencias del usuario - + Ban from &server Banear del &servidor - + View user's &ban history Ver historial historial de baneo del usuario. - + &Promote user to moderator &Promocionar usuario a moderador - + Dem&ote user from moderator Degradar usuario de m&oderador - + Promote user to &judge Usuario promovido a &juez - + Demote user from judge Degradar usuario de juez - + + View admin notes + + + + %1's games Partidas de %1 - - - + + + Ban History Historial de baneo - + Ban Time;Moderator;Ban Length;Ban Reason;Visible Reason Ban Time;Moderator;Ban Length;Ban Reason;Visible Reason - + User has never been banned. El usuario nunca ha sido baneado - + Failed to collect ban information. Error al recopilar informacion de baneos. - - - + + + Warning History Historial de advertencias - + Warning Time;Moderator;User Name;Reason Warning Time;Moderator;User Name;Reason - + User has never been warned. El usuario nunca ha sido advertido - + Failed to collect warning information. Error al recopilar información de advertencias. - - + + Failed to get admin notes. + + + + + Success Éxito - + Successfully promoted user. Usuario promocionado con éxito. - + Successfully demoted user. Usuario degradado con éxito. - - + + + Failed Fallo - + Failed to promote user. Fallo al promocionar usuario. - + Failed to demote user. Fallo al degradar usuario. - + Copy hash to clipboard Copiar al portapapeles - + Remove this user's messages Borrar mensajes de este usuario @@ -6505,173 +7496,173 @@ Por favor, absténgase de realizar de nuevo esta actividad o se tomarán medidas UserInfoBox - + Location: Localización: - + Account Age: Tiempo de la cuenta: - + Edit Editar - + Change password Cambiar contraseña - + Change avatar Cambiar avatar - + Administrator Administrador - + Moderator Moderador - + Registered user Usuario registrado - - + + Unregistered user Usuario no registrado - + Judge Juez - + Unknown Desconocido - + The entered password does not match your account. La contraseña introducida no coincide con tu cuenta - - - + + + Information Información - + User information updated. Información del usuario actualizada. - - - - - - - - - - + + + + + + + + + + Error Error - + User Information Información de usuario - + Real Name: Nombre real: - + User Level: Nivel de usuario: - + %n Year(s), amount of years (only shown if more than 0) %n año,%n años,%n año(s), - + %10%n Day(s) %20 amount of years (if more than 0), amount of days, date in local short format %10%n día %20%10%n días %20%10%n días %20 - + Enter Password Introducir contraseña - + Password verification is required in order to change your email address Se requiere verificación de la contraseña para cambiar tu dirección de correo electrónico - - - + + + An error occurred while trying to update your user information. Un error ha ocurrido mientras intentaba actualizarse tu información de usuario. - + This server does not permit you to update your user informations. Este servidor no permite actualizar su información de usuario. - + Password changed. Tu contraseña ha sido cambiada. - + This server does not permit you to change your password. Este servidor no permite cambiar tu contraseña. - + The new password is too short. La nueva contraseña es muy corta - + The old password is incorrect. La contraseña anterior no es correcta. - + Avatar updated. Avatar actualizado - + This server does not permit you to update your avatar. Este servidor no permite actualizar avatar. - + An error occured while trying to updater your avatar. Ocurrió un error mientras se actualizaba tu avatar. @@ -6679,124 +7670,251 @@ Por favor, absténgase de realizar de nuevo esta actividad o se tomarán medidas UserInterfaceSettingsPage - + General interface settings Preferencias generales de la interfaz - + &Double-click cards to play them (instead of single-click) &Doble click en las cartas para jugarlas (en lugar de un solo click) - + + &Clicking plays all selected cards (instead of just the clicked card) + + + + &Play all nonlands onto the stack (not the battlefield) by default &Jugar todas las cartas que no sean tierras en la pila (en lugar de en el campo de batalla) por defecto. - + + Close card view window when last card is removed + + + + Annotate card text on tokens Anotar en las fichas. - + Use tear-off menus, allowing right click menus to persist on screen Usar menus flotantes, permitir click derecho para anclar menus en la pantalla - + Notifications settings Ajustes de notificaciones - + Enable notifications in taskbar Habilitar notificaciones en la barra de tareas - + Notify in the taskbar for game events while you are spectating Notificar en la barra de tareas los eventos de la partida mientras estás como espectador - + Notify in the taskbar when users in your buddy list connect Recibir una notificación en la barra de tareas cuando alguien de tu lista de amigos se conecte - + Animation settings Preferencias de animación - + &Tap/untap animation Animación de &girar/enderezar + + + Deck editor/storage settings + + + + + Open deck in new tab by default + + + + + Prompt before converting .txt decks to .cod format + + + + + Always convert if not prompted + + + + + Use visual deck storage in game lobby + + + + + Replay settings + + + + + Buffer time for backwards skip via shortcut: + + - UserList + UserListWidget - + Users connected to server: %1 - Usuarios conectados al servidor: %1 + - + Users in this room: %1 - Usuarios en esta sala: %1 + - + Buddies online: %1 / %2 - Amigos online: %1 / %2 + - + Ignored users online: %1 / %2 - Usuarios ignorados online: %1 / %2 + + + + + VisualDeckStorageFolderDisplayWidget + + + Deck Storage + + + + + VisualDeckStorageSearchWidget + + + Search by filename + + + + + VisualDeckStorageSortWidget + + + Sort Alphabetically (Deck Name) + + + + + Sort Alphabetically (Filename) + + + + + Sort by Last Modified + + + + + Sort by Last Loaded + + + + + VisualDeckStorageWidget + + + Loading database ... + + + + + Show Folders + + + + + Show Tag Filter + + + + + Show Tags On Deck Previews + + + + + Show Banner Card Selection Option + + + + + Include Folder Names in Search + + + + + Draw unused Color Identities + + + + + Unused Color Identities Opacity + WarningDialog - + Which warning would you like to send? ¿Qué advertencia quieres enviar? - + Redact all messages from this user in all rooms Redactar todos los mensajes de este usuario en todas las salas - + &OK &OK - + &Cancel &Cancelar - + Warn user for misconduct Usuario advertido por conducta inadecuada - - + + Error Error - + User name to send a warning to can not be blank, please specify a user to warn. El nombre de usuario no puede quedarse en blanco, por favor especifique un usuario al que advertir. - + Warning to use can not be blank, please select a valid warning to send. La advertencia no puede quedarse en blanco, por favor seleccione una advertencia válida. @@ -6804,128 +7922,133 @@ Por favor, absténgase de realizar de nuevo esta actividad o se tomarán medidas WndSets - + Move selected set to the top Mover el set seleccionado al principio - + Move selected set up Mover arriba el set seleccionado - + Move selected set down Mover abajo el set seleccionado - + Move selected set to the bottom Mover el set seleccionado al final - + Search by set name, code, or type Buscar por nombre de edición, código, o tipo - + Default order Orden por defecto - + Restore original art priority order Restaurar orden predeterminado de arte original - + Enable all sets Seleccionar todos los sets - + Disable all sets Deseleccionar todos los sets - + Enable selected set(s) Habilitar set(s) seleccionados - + Disable selected set(s) Deshabilitar set(s) seleccionados - + Deck Editor Editor de mazo - + Use CTRL+A to select all sets in the view. Usa CTRL+A para seleccionar todas las colecciones aquí mostradas. - + Only cards in enabled sets will appear in the card list of the deck editor. Solo las cartas en las colecciones permitidas aparecerán en la lista de cartas del editor de mazos. - + Image priority is decided in the following order: La prioridad de las imágenes es decidida en el siguiente orden: - + first the CUSTOM Folder (%1), then the Enabled Sets in this dialog (Top to Bottom) %1 is a link to the wiki primero la Carpeta PERSONALIZADA (%1), luego las Ediciones Habilitadas en este mensaje (de Arriba a Abajo) - + Card Art Arte de carta - + How to use custom card art Cómo usar arte de carta personalizada. - + Hints Consejos - + Note Nota - + Sorting by column allows you to find a set while not changing set priority. Ordenar por columna te permite encontrar una edición sin cambiar la prioridad de edición. - + To enable ordering again, click the column header until this message disappears. Para habilitar el orden de nuevo, haga click en el encabezado de la columna hasta que este mensaje deje de aparecer. - + Use the current sorting as the set priority instead Usa el filtro actual como la prioridad de edición - + Sorts the set priority using the same column Ordena la prioridad de edición usando la misma columna - + + Include online-only (Arena) cards [requires restart] + + + + Manage sets Mantenedor de ediciones @@ -6933,22 +8056,67 @@ Por favor, absténgase de realizar de nuevo esta actividad o se tomarán medidas ZoneViewWidget - - sort by name - ordenar por nombre + + Ungrouped + - - sort by type - ordenar por tipo + + Group by Type + - + + Group by Mana Value + + + + + Group by Color + + + + + Unsorted + + + + + Sort by Name + + + + + Sort by Type + + + + + Sort by Mana Cost + + + + + Sort by Colors + + + + + Sort by P/T + + + + + Sort by Set + + + + shuffle when closing barajar al cerrar - + pile view vista de la pila @@ -6956,7 +8124,7 @@ Por favor, absténgase de realizar de nuevo esta actividad o se tomarán medidas i18n - + English Español (Spanish) @@ -6964,12 +8132,12 @@ Por favor, absténgase de realizar de nuevo esta actividad o se tomarán medidas main - + Connect on startup Conectar al inicio - + Debug to file Depurar a archivo @@ -6977,786 +8145,908 @@ Por favor, absténgase de realizar de nuevo esta actividad o se tomarán medidas shortcutsTab - + Main Window Ventana principal - - + + Deck Editor Editor de mazos - + Game Lobby Vestíbulo de juego - + Card Counters Contadores de carta - + Player Counters Contadores de jugador - + Power and Toughness Fuerza y resistencia - + Game Phases Fases del juego - + Playing Area Area de juego - + Move Selected Card Mover la carta seleccionada - + View Ver - + Move Top Card Mover la carta superior - + Move Bottom Card Mover la carta inferior - + Gameplay Gameplay - + Drawing Robar - + Chat Room Sala de chat - + Game Window Ventana de juego - + Load Deck from Clipboard Cargar deck desde el portapapeles - + + Replays - + + Tabs + + + + Check for Card Updates... Buscar actualizaciones de cartas ... - + Connect... Conectar... - + Disconnect Desconectar - + Exit Salir - + Full screen Pantalla completa - + Register... Registrar... - + Settings... Ajustes... - + Start a Local Game... Empezar una partida local... - + Watch Replay... Ver repetición... - - Analyze Deck - Analizar mazo + + Analyze Deck (deckstats.net) + Analyze Deck + - + + Analyze Deck (tappedout.net) + + + + Clear All Filters Eliminar filtros - + Clear Selected Filter Eleminar filtro seleccionado - + Close Cerrar - + Remove Card Quitar carta - + Manage Sets... Administrar ediciones - + Edit Custom Tokens... Editar fichas personalizadas - - Export Deck - Exportar mazo + + Export Deck (decklist.org) + - + + Export Deck (decklist.xyz) + + + + Add Card Añadir carta - + Load Deck... Cargar mazo... - + Load Deck from Clipboard... Cargar mazo desde el portapapeles - + + Edit Deck in Clipboard, Annotated + + + + + Edit Deck in Clipboard + + + + New Deck Nuevo mazo - + Open Custom Pictures Folder Abrir carpeta de imágenes personalizadas - + Print Deck... Imprimir mazo... - + Delete Card Borrar carta - - + + Reset Layout Restablecer disposición - + Save Deck Guardar mazo - + Save Deck as... Guarda mazo como... - + Save Deck to Clipboard, Annotated Guarda mazo al portapapeles, Comentado - + + Save Deck to Clipboard, Annotated (No Set Info) + + + + Save Deck to Clipboard Guardar mazo al portapapeles - + + Save Deck to Clipboard (No Set Info) + + + + Load Local Deck... Cargar mazo local... - + Load Remote Deck... Carga mazo remoto... - + Set Ready to Start Marcar listo para empezar - + Toggle Sideboard Lock Permitir bloqueo de banquillo - - + + Add Green Counter Añadir contador verde - - + + Remove Green Counter Retirar un contador verde - - + + Set Green Counters... Establecer contadores verdes... - + Add Yellow Counter Añadir contador amarillo - + Remove Yellow Counter Retirar un contador amarillo - + Set Yellow Counters... Establecer contadores amarillos... - - + + Add Red Counter Agregar contador rojo - - + + Remove Red Counter Retirar un contador rojo - - + + Set Red Counters... Establecer contadores rojos... - + Add Life Counter Añadir contador de vida - + Remove Life Counter Retirar un contador de vida - + Set Life Counters... Establecer contadores de vida... - + Add White Counter Agregar contador blanco - + Remove White Counter Retirar un contador blanco... - + Set White Counters... Establecer contadores blancos... - + Add Blue Counter Agregar contador azul - + Remove Blue Counter Retirar un contador azul... - + Set Blue Counters... Establecer contadores azules... - + Add Black Counter Agregar contador negro - + Remove Black Counter Quitar contador negro - + Set Black Counters... Establecer contadores negros... - + Add Colorless Counter Agregar contador incoloro - + Remove Colorless Counter Retirar contador incoloro - + Set Colorless Counters... Establecer contadores incoloros... - + Add Other Counter Añadir otro contador - + Remove Other Counter Borrar otro contador - + Set Other Counters... Colocar otros contadores... - + Add Power (+1/+0) Agregar fuerza (+1/+0) - + Remove Power (-1/-0) Reducir fuerza (-1/-0) - + Move Toughness to Power (+1/-1) Pasar de resistencia a fuerza (+1/-1) - + Add Toughness (+0/+1) Agregar resistencia (+0/+1) - + Remove Toughness (-0/-1) Reducir resistencia (-0/-1) - + Move Power to Toughness (-1/+1) Pasar de fuerza a resistencia (-1/+1) - + Add Power and Toughness (+1/+1) Agregar fuerza y resistencia (+1/+1) - + Remove Power and Toughness (-1/-1) Reducir fuerza y resistencia (-1/-1) - + Set Power and Toughness... Establecer fuerza y resistencia... - + Reset Power and Toughness Restablecer fuerza y resistencia - + Untap Enderezar - + Upkeep Mantenimiento - + Draw Robo - + First Main Phase Primera fase principal - + Start Combat Comenzar combate - + Attack Ataque - + Block Bloqueo - + Damage Daño - + End Combat Terminar combate - + Second Main Phase Segunda fase principal - + End Final - + Next Phase Próxima fase - + Next Phase Action Próxima fase de acción - + Next Turn Próximo turno - + + Hide Card in Reveal Window + + + + Tap / Untap Card Girar / Enderezar Carta - + Untap All Enderezar todas - + Toggle Untap Alternar enderezamiento - + Turn Card Over Girar carta - + Peek Card Mirar carta - + Play Card Jugar carta - + Attach Card... Anexar carta... - + Unattach Card Desanexar carta... - + Clone Card Clonar carta - + Create Token... Crear ficha... - + Create All Related Tokens Crear todas las fichas relacionadas - + Create Another Token Crear otra ficha - + Set Annotation... Escribir anotación... - - + + Select All Cards in Zone + + + + + Select All Cards in Row + + + + + Select All Cards in Column + + + + + Bottom of Library Parte inferior de la biblioteca - - - - + + + + Exile Exilio - - - - + + + + Graveyard Cementerio - - + + Hand Mano - - + + Top of Library Parte superior de la biblioteca - - - + + + Battlefield, Face Down Campo de batalla, boca abajo - + Battlefield Campo de batalla - + Library Biblioteca - + Sideboard Banquillo - + Top Cards of Library Carta de la parte superior de la biblioteca - + + Bottom Cards of Library + + + + Close Recent View Cerrar vista reciente - - + + Stack Apilar - - + + Graveyard (Multiple) Cementerio (Multiple) - - + + Exile (Multiple) Exiliar (Multiple) - + Stack Until Found - + Draw Bottom Card Robar carta de la parte inferior - + Draw Multiple Cards from Bottom... Robar múltiples cartas de la parte inferior... - + Draw Arrow... Dibujar flecha - + Remove Local Arrows Eliminar flechas - + Leave Game Abandonar partida - + Concede Conceder - + Roll Dice... Lanzar dado... - + Shuffle Library Barajar biblioteca - + + Shuffle Top Cards of Library + + + + + Shuffle Bottom Cards of Library + + + + Mulligan Mulligan - + Draw a Card Robar una carta - + Draw Multiple Cards... Robar múltiples cartas... - + Undo Draw Deshacer último robo - + Always Reveal Top Card Mostrar siempre la carta superior - + Always Look At Top Card Mirar siempre la carta superior - + Rotate View Clockwise Rotar vista en sentido horario - + Rotate View Counterclockwise Rotar vista en sentido antihorario - + Unfocus Text Box No resaltar la caja de texto - + Focus Chat Resaltar el chat - + Clear Chat Borrar chat - + Refresh Actualizar - + + Skip Forward + + + + + Skip Backward + + + + + Skip Forward by a lot + + + + + Skip Backward by a lot + + + + Play/Pause - + Toggle Fast Forward + + + Visual Deck Storage + + + + + Deck Storage + + + + + Server + + + + + Account + + + + + Administration + + + + + Logs + + \ No newline at end of file diff --git a/cockatrice/translations/cockatrice_et.ts b/cockatrice/translations/cockatrice_et.ts index 2e5f8cd1d..c4984fcce 100644 --- a/cockatrice/translations/cockatrice_et.ts +++ b/cockatrice/translations/cockatrice_et.ts @@ -2,7 +2,7 @@ AbstractCounter - + &Set counter... &Komplektide loendaja... @@ -10,95 +10,247 @@ AbstractCounterDialog - + Set counter - + New value for counter '%1': + + AbstractDlgDeckTextEdit + + + &Refresh + + + + + Parse Set Name and Number (if available) + + + + + AbstractTabDeckEditor + + + Open in new tab + + + + + Are you sure? + + + + + The decklist has been modified. +Do you want to save the changes? + + + + + + + + + + + Error + + + + + Could not open deck at %1 + + + + + Could not save remote deck + + + + + + The deck could not be saved. +Please check that the directory is writable and try again. + + + + + Save deck + + + + + The deck could not be saved. + + + + + There are no cards in your deck to be exported + + + + + No deck was selected to be exported. + + + + + AdminNotesDialog + + + Update Notes + + + + + Admin Notes for %1 + + + + + AllZonesCardAmountWidget + + + Mainboard + + + + + Sideboard + + + AppearanceSettingsPage - + Error - + Could not create themes directory at '%1'. - + Theme settings Teema valikud - + Current theme: Praegune teema: - + Open themes folder - + + Menu settings + + + + + Show keyboard shortcuts in right-click menus + + + + Card rendering Kaartide visualiseerimine - + Display card names on cards having a picture Kuva piltidega kaartidel kaardi nimi - + + Auto-Rotate cards with sideways layout + + + + + Override all card art with personal set preference (Pre-ProviderID change behavior) [Requires Client restart] + + + + + Bump sets that the deck contains cards from to the top in the printing selector + + + + Scale cards on mouse over Muuda kaartide mõõtkava hiire ülejooksul - + + Use rounded card corners + + + + Minimum overlap percentage of cards on the stack and in vertical hand - + + Maximum initial height for card view window: + + + + + + rows + + + + + Maximum expanded height for card view window: + + + + Hand layout Käe välimus - + Display hand horizontally (wastes space) Kuva käsi horisontaalselt (raiskab ruumi) - + Enable left justification Luba vasakvormistus - + Table grid layout Laua ruudustiku vormistus - + Invert vertical coordinate Põõra vertikaalne koordinaat pahupidi - + Minimum player count for multi-column layout: Väikseim mängijate kogus mitmetulbaliseks vormistuseks: - + Maximum font size for information displayed on cards: Kaartidel kujutatava teabe fondi suurim suurus: @@ -106,112 +258,112 @@ BanDialog - + ban &user name blokeeri &kasutajanimi - + ban &IP address blokeeri &IP-aadress - + ban client I&D blokeeri kliendi I&D - + Ban type Blokeerimise tüüp - + &permanent ban &igavene blokeering - + &temporary ban &ajutine blokeering - + &Days: &Päeva: - + &Hours: &Tundi: - + &Minutes: &Minutit - + Duration of the ban Blokeerimise kestvus - + Please enter the reason for the ban. This is only saved for moderators and cannot be seen by the banned person. Palun sisestage blokeerimise põhjus. See on mõeldud ainult moderaatoritele ja blokeeritav isik seda ei näe. - + Please enter the reason for the ban that will be visible to the banned person. Palun sisestage blokeerimise põhjus, mis on nähtav blokeeritavale. - + Redact all messages from this user in all rooms - + &OK &OK - + &Cancel &Tühista - + Ban user from server Blokeeri kasutaja serverist - - - - + + + + Error Viga - + You have to select a name-based, IP-based, clientId based, or some combination of the three to place a ban. Peate valima, kas nimepõhise, IP-põhise või klientidpõhise blokeeringu või mingi kombinatsiooni kolmest et blokeerida. - + You must have a value in the name ban when selecting the name ban checkbox. Sul peab olema väärtus nime blokeeringus kui valid nime blokeerimise. - + You must have a value in the ip ban when selecting the ip ban checkbox. Sul peab olema väärtus IP blokeeringus kui valid IP blokeerimise. - + You must have a value in the clientid ban when selecting the clientid ban checkbox. Sul peab olema väärtus klientid blokeeringus kui valid klientid blokeerimise. @@ -219,22 +371,22 @@ See on mõeldud ainult moderaatoritele ja blokeeritav isik seda ei näe. BetaReleaseChannel - - Beta Releases + + Beta - + No reply received from the release update server. - + Invalid reply received from the release update server. - + No reply received from the file update server. @@ -242,32 +394,32 @@ See on mõeldud ainult moderaatoritele ja blokeeritav isik seda ei näe. CardDatabaseModel - + Name Nimi - + Sets Komplektid - + Mana cost Manahind - + Card type Kaardi tüüp - + P/T T/V - + Color(s) Värv(id) @@ -360,37 +512,65 @@ See on mõeldud ainult moderaatoritele ja blokeeritav isik seda ei näe. - CardFrame + CardInfoFrameWidget - + Image - Pilt + - + Description - Kirjeldus + - + Both - Mõlemad + + + + + View transformation + - CardInfoText + CardInfoPictureWidget - - Name: - Nimi: + + View related cards + - + + Add card to deck + + + + + Mainboard + + + + + Sideboard + + + + + CardInfoTextWidget + + + Name: + + + + Related cards: - + Unknown card: @@ -398,287 +578,555 @@ See on mõeldud ainult moderaatoritele ja blokeeritav isik seda ei näe. CardItem - + &Move to &Liiguta asukohta - + &Power / toughness &Tugevus / vastupidavus + + CardSizeWidget + + + Card Size + + + CardZone - + their hand nominative Nende käsi - + %1's hand nominative %1 käsi - + their library look at zone nende kogu - + %1's library look at zone %1 kogu - + of their library top cards of zone, nende kogu - + of %1's library top cards of zone %1 kogu - + their library reveal zone nende kogu - + %1's library reveal zone %1 kogu - + their library shuffle nende kogu - + %1's library shuffle %1 kogu - + their library nominative nende kogu - + %1's library nominative %1 kogu - + their graveyard nominative nende surnuaed - + %1's graveyard nominative %1 surnuaed - + their exile nominative nende pagendus - + %1's exile nominative %1 pagendus - + their sideboard look at zone nende külgpakk - + %1's sideboard look at zone %1 külgpakk - + their sideboard nominative nende külgpakk - + %1's sideboard nominative %1 külgpakk + + DeckEditorCardInfoDockWidget + + + Card Info + + + + + DeckEditorDatabaseDisplayWidget + + + Search by card name (or search expressions) + + + + + Add to Deck + + + + + Add to Sideboard + + + + + Select Printing + + + + + Show on EDHREC (Commander) + + + + + Show on EDHREC (Card) + + + + + Show Related cards + + + + + Add card to &maindeck + + + + + Add card to &sideboard + + + + + DeckEditorDeckDockWidget + + + Banner Card + + + + + Select Printing + + + + + Deck + + + + + Deck &name: + + + + + &Comments: + + + + + Hash: + + + + + &Increment number + + + + + &Decrement number + + + + + &Remove row + + + + + Swap card to/from sideboard + + + + + DeckEditorFilterDockWidget + + + Filters + + + + + &Clear all filters + + + + + Delete selected + + + + + DeckEditorMenu + + + &Deck Editor + + + + + &New deck + + + + + &Load deck... + + + + + Load recent deck... + + + + + Clear + + + + + &Save deck + + + + + Save deck &as... + + + + + Load deck from cl&ipboard... + + + + + Edit deck in clipboard + + + + + + Annotated + + + + + + Not Annotated + + + + + Save deck to clipboard + + + + + Annotated (No set info) + + + + + Not Annotated (No set info) + + + + + &Print deck... + + + + + &Send deck to online service + + + + + Create decklist (decklist.org) + + + + + Create decklist (decklist.xyz) + + + + + Analyze deck (deckstats.net) + + + + + Analyze deck (tappedout.net) + + + + + &Close + + + + + DeckEditorPrintingSelectorDockWidget + + + Printing Selector + + + DeckEditorSettingsPage - - + + Update Spoilers - - + + Success - + Download URLs have been reset. - + Downloaded card pictures have been reset. - + Error - + One or more downloaded card pictures could not be cleared. - + Add URL - - + + URL: - - + + Edit URL - + + Network Cache Size: + + + + + Redirect Cache TTL: + + + + + How long cached redirects for urls are valid for. + + + + + Picture Cache Size: + + + + Add New URL - + Remove URL - + + Day(s) + + + + Updating... - + Choose path - + URL Download Priority - + Spoilers - + Download Spoilers Automatically - + Spoiler Location: - + Last Change - + Spoilers download automatically on launch - + Press the button to manually update without relaunching - + Do not close settings until manual update is complete - + Download card pictures on the fly - + How to add a custom URL - + Delete Downloaded Images - + Reset Download URLs - - Downloaded images directory size: - - - - + On-disk cache for downloaded pictures - - Picture cache size: - - - - + In-memory cache for pictures not currently on screen @@ -686,16 +1134,218 @@ See on mõeldud ainult moderaatoritele ja blokeeritav isik seda ei näe. DeckListModel - + + Count + + + + + Set + + + + Number Number - + + Provider ID + + + + Card Kaart + + DeckLoader + + + Common deck formats (%1) + + + + + All files (*.*) + + + + + DeckPreviewColorIdentityFilterWidget + + + Mode: Exact Match + + + + + Mode: Includes + + + + + DeckPreviewDeckTagsDisplayWidget + + + Edit tags ... + + + + + DeckPreviewTagDialog + + + Deck Tags Manager + + + + + Manage your deck tags. Check or uncheck tags as needed, or add new ones: + + + + + Add a new tag (e.g., Aggro️) + + + + + Add Tag + + + + + Filter tags... + + + + + OK + + + + + Cancel + + + + + Invalid Input + + + + + Tag name cannot be empty! + + + + + Duplicate Tag + + + + + This tag already exists. + + + + + DeckPreviewWidget + + + Banner Card + + + + + Open in deck editor + + + + + Edit Tags + + + + + Rename Deck + + + + + Save Deck to Clipboard + + + + + Annotated + + + + + Annotated (No set info) + + + + + Not Annotated + + + + + Not Annotated (No set info) + + + + + Rename File + + + + + Delete File + + + + + Set Banner Card + + + + + + New name: + + + + + + Error + + + + + Rename failed + + + + + Delete file + + + + + Are you sure you want to delete the selected file? + + + + + Delete failed + + + DeckStatsInterface @@ -713,181 +1363,217 @@ See on mõeldud ainult moderaatoritele ja blokeeritav isik seda ei näe. DeckViewContainer - + Load deck... Lae pakk... - + Load remote deck... Lae kaugpakk... - + + Unload deck + Unload deck... + + + + Ready to start - + + Force start + + + + Sideboard unlocked - + Sideboard locked - - Load deck - Lae pakk - - - + Error Viga - + The selected file could not be loaded. Valitud faili ei suudetud laadida. + + DialogConvertDeckToCodFormat + + + Deck Format Conversion + + + + + You tried to add a tag to a .txt format deck. + Tags can only be added to .cod format decks. + Do you want to convert the deck to the .cod format? + + + + + Remember and automatically apply choice in the future + + + DlgConnect - + Downloading... - + Known Hosts Teadaolevad hostid - + + Delete the currently selected saved server + + + + Refresh the server list with known public servers - + New Host Uus võõrustaja - + Name: Nimi: - + &Host: &Võõrustaja: - + &Port: &Port: - + Player &name: Mängija &nimi: - + P&assword: S&alasõna: - + &Save password &Salvesta salasõna - + A&uto connect Automaatne ühendamine - + Automatically connect to the most recent login when Cockatrice opens Automaatselt ühenda viimasele sisselogimisele kui Cockatrice avaneb - + If you have any trouble connecting or registering then contact the server staff for help! - - + + Webpage - + Reset Password - + + Forgot password? + + + + &Connect - + Server Server - + Login Sisselogimine - + Server Contact - + Connect to Server - + Server URL - + Communication Port - + Unique Server Name - + Connection Warning Ühenduse hoiatus - + You need to name your new connection profile. Peate oma uuele ühendusprofiilile valima nime. - + Connect Warning Ühendumisehoiatus - + The player name can't be empty. Mängija nimi ei saa olla tühi @@ -970,27 +1656,37 @@ See on mõeldud ainult moderaatoritele ja blokeeritav isik seda ei näe.Vaatlejad - + + Starting life total: + + + + + Game setup options + + + + &Clear &Tühjenda - + Create game Loo mäng - + Game information Mängu info - + Error Viga - + Server error. Serveri viga. @@ -998,92 +1694,92 @@ See on mõeldud ainult moderaatoritele ja blokeeritav isik seda ei näe. DlgCreateToken - + &Name: &Nimi: - + Token Märgistused - + C&olor: V&ärv: - + white valge - + blue sinine - + black must - + red punane - + green roheline - + multicolor mitmevärviline - + colorless värvitu - + &P/T: &T/V: - + &Annotation: &Märkus: - + &Destroy token when it leaves the table &Hävita märgistus kui see lahkub laualt - + Token data Märgistusinfo - + Show &all tokens Näita &kõik märgistused - + Show tokens from this &deck Näita märgistusi siit &pakist - + Choose token from list Vali märgistus nimekirjast - + Create token Loo märgistus @@ -1129,6 +1825,24 @@ Et eemaldada praegust avatari, kinnita ilma uue pildi valimata. Vigane pilt valitud. + + DlgEditDeckInClipboard + + + Edit deck in clipboard + + + + + Error + + + + + Invalid deck list. + + + DlgEditPassword @@ -1171,93 +1885,93 @@ Et eemaldada praegust avatari, kinnita ilma uue pildi valimata. DlgEditTokens - + &Name: &Nimi: - + C&olor: V&ärv: - + white valge - + blue sinine - + black must - + red punane - + green roheline - + multicolor mitmevärviline - + colorless värvitu - + &P/T: &T/V: - + &Annotation: &Märkus: - + Token data Märgistusandmed - - + + Add token Lisa märgistus - + Remove token Eemalda märgistus - + Edit custom tokens - + Please enter the name of the token: Sisenda märgistuse nimi: - + Error Viga - + The chosen name conflicts with an existing card or token. Make sure to enable the 'Token' set in the "Manage sets" dialog to display them correctly. @@ -1294,132 +2008,132 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgFilterGames - + no limit - + 5 minutes - + 10 minutes - + 30 minutes - + 1 hour - + 2 hours - - Show '&buddies only' games - Näita '&ainult sõbrad' mänge - - - - Show &full games + + Hide 'buddies only' games - - Show games &that have started + + Hide full games - - Show &password protected games - Näita &salasõnaga kaitstud mänge - - - - Hide '&ignored user' games + + Hide games that have started - + + Hide password protected games + + + + + Hide 'ignored user' games + + + + &Newer than: - + Game &description: Mängu &kirjeldus: - + &Creator name: %Looja nimi: - + General - + &Game types &Mängu tüübid - + at &least: &vähemalt: - + at &most: &enamjaolt: - + Maximum player count Mängijate ülempiir - + Restrictions Piirangud - + Show games only if &spectators can watch - + Show spectator password p&rotected games - + Show only if spectators can ch&at - + Show only if spectators can see &hands - + Spectators - + Filter games Filtreeri mänge @@ -1602,27 +2316,28 @@ Make sure to enable the 'Token' set in the "Manage sets" dia + + DlgLoadDeck + + + Load Deck + + + DlgLoadDeckFromClipboard - - &Refresh - &Värskenda - - - + Load deck from clipboard Lae pakk lõikelaualt - - + Error Viga - - + Invalid deck list. Vigane pakilist: @@ -1630,11 +2345,49 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgLoadRemoteDeck - + Load deck Lae pakk + + DlgMoveTopCardsUntil + + + Card name (or search expressions): + + + + + Number of hits: + + + + + Auto play hits + + + + + Put top cards on stack until... + + + + + No cards matching the search expression exists in the card database. Proceed anyways? + + + + + Cockatrice + + + + + Invalid filter + + + DlgRegister @@ -1748,12 +2501,12 @@ Your email will be used to verify your account. DlgSettings - + Unknown Error loading card database Ebateatud kaardi laadimis andmebaas - + Your card database is invalid. Cockatrice may not function correctly with an invalid database @@ -1770,7 +2523,7 @@ Sul võib vaja olla oraaklit uuesti jooksutada et uuendada kaardi andmebaasi. Kas sa tahaksid muuta andmebaasi asukoha seadmeid? - + Your card database version is too old. This can cause problems loading card information or images @@ -1787,7 +2540,7 @@ Tavaliselt seda saab parantada oraakli uuesti jooksutamisega et uuentada kaartid Kas sa tahad muuta andmebaasi asukoha valikuid? - + Your card database did not finish loading Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues with your cards.xml attached @@ -1796,7 +2549,7 @@ Would you like to change your database location setting? - + File Error loading your card database. Would you like to change your database location setting? @@ -1805,7 +2558,7 @@ Would you like to change your database location setting? Kaas sa tahaks muuta andmebaasi asukoha valikuid? - + Your card database was loaded but contains no cards. Would you like to change your database location setting? @@ -1814,7 +2567,7 @@ Would you like to change your database location setting? Kas sa tahaks muuta enda andmebaasi asukoha valikuid? - + Unknown card database load status Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues @@ -1823,59 +2576,59 @@ Would you like to change your database location setting? - - - + + + Error Viga - + The path to your deck directory is invalid. Would you like to go back and set the correct path? Tee paki kataloogi on vigane. Kas sa tahaks tagasi minna ja seada õige tee? - + The path to your card pictures directory is invalid. Would you like to go back and set the correct path? Tee kaartide piltidele on vigane. Kas sa tahaks tagasi minna ja seada õige tee? - + Settings Valikud - + General Üldine - + Appearance Välimus - + User Interface Kasutajaliides - + Card Sources - + Chat Jutustamine - + Sound Hääl - + Shortcuts Lühiteed @@ -1901,164 +2654,168 @@ Would you like to change your database location setting? DlgUpdate - + Current release channel - + Reinstall - + Cancel Download - + Open Download Page Ava allalaadimisleht - + Check for Client Updates - - - - + + + + Error Viga - + Cockatrice was not built with SSL support, therefore you cannot download updates automatically! Please visit the download page to update manually. - - Downloading update... - Lae uuendus alla... + + Downloading update: %1 + Downloading update... + - + Checking for updates... Uuenduste otsimine... - + Finished checking for updates - + No Update Available - + Cockatrice is up to date! - + You are already running the latest version available in the chosen release channel. - + Current version - + Selected release channel - - + + Update Available - - - - A new version of Cockatrice is available! - - - - - - New version - - - Released + A new version of Cockatrice is available! + + + + + + New version - Changelog + Released + + Changelog + + + + Do you want to update now? - - Unfortunately there are no download packages available for your operating system. -You may have to build from source yourself. + + Unfortunately, the automatic updater failed to find a compatible download. +You may have to manually download the new version. + Unfortunately there are no download packages available for your operating system. +You may have to build from source yourself. - - Please check the download page manually and visit the wiki for instructions on compiling. + + Please check the <a href="%1">releases page</a> on our Github and download the build for your system. + Please check the download page manually and visit the wiki for instructions on compiling. - - - + + + Update Error Uuendusviga - + An error occurred while checking for updates: - + An error occurred while downloading an update: - + Installing... Paigaldamine... - + Cockatrice is unable to open the installer. - + Try to update manually by closing Cockatrice and running the installer. - + Download location @@ -2076,6 +2833,14 @@ You may have to build from source yourself. Putukaeemalduslogi + + EdhrecCommanderResponseCommanderDetailsDisplayWidget + + + Salt: + + + FilterBuilder @@ -2087,105 +2852,120 @@ You may have to build from source yourself. GameSelector - - - - - - - - - + + + + + + + + + Error Viga - + Please join the appropriate room first. Palun ühine sobiva toaga enne. - + Wrong password. Vale salasõna. - + Spectators are not allowed in this game. Vaatlejad ei ole selles mängus lubatud. - + The game is already full. Mäng on juba täis. - + The game does not exist any more. - + This game is only open to registered users. - + This game is only open to its creator's buddies. - + You are being ignored by the creator of this game. - + + Join Game + + + + + Spectate Game + + + + + Game Information + + + + Join game - + Password: - + Please join the respective room first. - + &Filter games - + C&lear filter - + C&reate - + &Join - + J&oin as spectator - + Games shown: %1 / %2 - + Games @@ -2193,100 +2973,100 @@ You may have to build from source yourself. GamesModel - + >1 day - + %1%2 hr short age in hours - + new - + %1%2 min short age in minutes - + password - + buddies only - + reg. users only - - + + can chat - + see hands - + can see hands - + not allowed - + Room - + Age - + Description - + Creator - + Type - + Restrictions - + Players - + Spectators @@ -2294,92 +3074,102 @@ You may have to build from source yourself. GeneralSettingsPage - + Reset all paths - + All paths have been reset - - + + Choose path - + Personal settings - + Language: - + Paths (editing disabled in portable mode) - + Paths - + + How to help with translations + + + + Decks directory: - + Replays directory: - + Pictures directory: - + Card database: - + Custom database directory: - + Token database: - + Update channel - + + Check for client updates on startup + + + + Notify if a feature supported by the server is missing in my client - + Automatically run Oracle when running a new version of Cockatrice - + Show tips on startup @@ -2387,711 +3177,722 @@ You may have to build from source yourself. MainWindow - - + + The server has reached its maximum user capacity, please check back later. - + There are too many concurrent connections from your address. - + Banned by moderator - + Expected end time: %1 - + This ban lasts indefinitely. - + Scheduled server shutdown. - - + + Invalid username. - + You have been logged out due to logging in at another location. - + Connection closed - + The server has terminated your connection. Reason: %1 - + The server is going to be restarted in %n minute(s). All running games will be lost. Reason for shutdown: %1 - + Scheduled server shutdown - - + + Success - + Registration accepted. Will now login. - + Account activation accepted. Will now login. - + Number of players - + Please enter the number of players. - - + + Player %1 - + Load replay - + About Cockatrice - + Version - + Cockatrice Webpage - + Project Manager: - + Past Project Managers: - + Developers: - + Our Developers - + Help Develop! - + Translators: - + Our Translators - + Help Translate! - + Support: - + Report an Issue - + Troubleshooting - + F.A.Q. - - - - - - - - - - - - - - - + + + + + + + + + + + + + - - - + + + + + Error - + Server timeout - + Failed Login - + Your client seems to be missing features this server requires for connection. - + To update your client, go to 'Help -> Check for Client Updates'. - + Incorrect username or password. Please check your authentication information and try again. - + There is already an active session using this user name. Please close that session first and re-login. - - + + You are banned until %1. - - + + You are banned indefinitely. - + This server requires user registration. Do you want to register now? - + This server requires client IDs. Your client is either failing to generate an ID or you are running a modified client. Please close and reopen your client to try again. - + An internal error has occurred, please close and reopen Cockatrice before trying again. If the error persists, ensure you are running the latest version of the software and if needed contact the software developers. - + Account activation - + Your account has not been activated yet. You need to provide the activation token received in the activation email. - + Server Full - + Unknown login error: %1 - - + + This usually means that your client version is out of date, and the server sent a reply your client doesn't understand. - + Your username must respect these rules: - + is %1 - %2 characters long - + can %1 contain lowercase characters - - - - + + + + NOT - + can %1 contain uppercase characters - + can %1 contain numeric characters - + can contain the following punctuation: %1 - + first character can %1 be a punctuation mark - + no unacceptable language as specified by these server rules: note that the following lines will not be translated - + can not contain any of the following words: %1 - + can not match any of the following expressions: %1 - + You may only use A-Z, a-z, 0-9, _, ., and - in your username. - - - - - - + + + + + + Registration denied - + Registration is currently disabled on this server - + There is already an existing account with the same user name. - + It's mandatory to specify a valid email address when registering. - + It appears you are attempting to register a new account on this server yet you already have an account registered with the email provided. This server restricts the number of accounts a user can register per address. Please contact the server operator for further assistance or to obtain your credential information. - + Password too short. - + Registration failed for a technical problem on the server. - + Unknown registration error: %1 - + Account activation failed - + Socket error: %1 - + You are trying to connect to an obsolete server. Please downgrade your Cockatrice version or connect to a suitable server. Local version is %1, remote version is %2. - + Your Cockatrice client is obsolete. Please update your Cockatrice version. Local version is %1, remote version is %2. - + Connecting to %1... - + Registering to %1 as %2... - + Disconnected - + Connected, logging in at %1 - - - + + + Requesting forgotten password to %1 as %2... - + &Connect... - + &Disconnect - + Start &local game... - + &Watch replay... - - &Deck editor - - - - + &Full screen - + &Register to server... - + &Restore password... - + &Settings... - + &Exit - + A&ctions - + &Cockatrice - + C&ard Database - + &Manage sets... - + Edit custom &tokens... - + Open custom image folder - + Open custom sets folder - + Add custom sets/cards - + + Reload card database + + + + + Tabs + + + + &Help - + &About Cockatrice - + &Tip of the Day - + Check for Client Updates - + Check for Card Updates... - + View &Debug Log - + + Open Settings Folder + + + + Show/Hide - + New Version - + Congratulations on updating to Cockatrice %1! Oracle will now launch to update your card database. - + Cockatrice installed - + Congratulations on installing Cockatrice %1! Oracle will now launch to install the initial card database. - + Card database - + Cockatrice is unable to load the card database. Do you want to update your card database now? If unsure or first time user, choose "Yes" - - + + Yes - - + + No - + Open settings - + New sets found - + %n new set(s) found in the card database Set code(s): %1 Do you want to enable it/them? - + View sets - + Welcome - + Hi! It seems like you're running this version of Cockatrice for the first time. All the sets in the card database have been enabled. Read more about changing the set order or disabling specific sets and consequent effects in the "Manage Sets" dialog. - - + + Information - + A card database update is already running. - + Unable to run the card database updater: - - failed to start. + + Failed to start. The file might be missing, or permissions might be incorrect. - - crashed. + + The process crashed some time after starting successfully. - - timed out. + + Timed out. The process took too long to respond. The last waitFor...() function timed out. - - write error. + + An error occurred when attempting to write to the process. For example, the process may not be running, or it may have closed its input channel. - - read error. + + An error occurred when attempting to read from the process. For example, the process may not be running. - - unknown error. + + Unknown error occurred. - - The card database updater exited with an error: %1 + + The card database updater exited with an error: +%1 - + This server supports additional features that your client doesn't have. This is most likely not a problem, but this message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -3099,54 +3900,54 @@ To update your client, go to Help -> Check for Updates. - - - - - + + + + + Load sets/cards - + Selected file cannot be found. - + You can only import XML databases at this time. - + The new sets/cards have been added successfully. Cockatrice will now reload the card database. - + Sets/cards failed to import. - - - + + + Reset Password - + Your password has been reset successfully, you can now log in using the new credentials. - + Failed to reset user account password, please contact the server operator to reset your password. - + Activation request received, please check your email for an activation token. @@ -3420,179 +4221,190 @@ Cockatrice will now reload the card database. - %1 is looking at the top %3 card(s) %2. + %1 is looking at the %4 %3 card(s) %2. + %1 is looking at the top %3 card(s) %2. top card for singular, top %3 cards for plural - + + bottom + + + + + top + + + + %1 turns %2 face-down. - + %1 turns %2 face-up. - + The game has been closed. - + The game has started. - + %1 has joined the game. - + %1 is now watching the game. - + You have been kicked out of the game. - + %1 has left the game (%2). - + %1 is not watching the game any more (%2). - + %1 is not ready to start the game any more. - + %1 shuffles their deck and draws a new hand of %2 card(s). - + %1 shuffles their deck and draws a new hand. - + You are watching a replay of game #%1. - + %1 is ready to start the game. - + cards an unknown amount of cards - + %1 card(s) a card for singular, %1 cards for plural - + %1 lends %2 to %3. - + %1 reveals %2 to %3. - + %1 reveals %2. - + %1 randomly reveals %2%3 to %4. - + %1 randomly reveals %2%3. - + %1 peeks at face down card #%2. - + %1 peeks at face down card #%2: %3. - + %1 reveals %2%3 to %4. - + %1 reveals %2%3. - + %1 reversed turn order, now it's %2. - + reversed - + normal - + Heads - + Tails - + %1 flipped a coin. It landed as %2. - + %1 rolls a %2 with a %3-sided die. - + %1 flips %2 coins. There are %3 heads and %4 tails. - + %1 rolls a %2-sided dice %3 times: %4. @@ -3712,17 +4524,17 @@ Cockatrice will now reload the card database. - + %1 unattaches %2. - + %1 undoes their last draw. - + %1 undoes their last draw (%2). @@ -3730,110 +4542,110 @@ Cockatrice will now reload the card database. MessagesSettingsPage - + Word1 Word2 Word3 - + Add New Message - + Edit Message - + Remove Message - + Add message - - + + Message: - + Edit message - + Chat settings - + Custom alert words - + Enable chat mentions - + Enable mention completer - + In-game message macros - + How to use in-game message macros - + Ignore chat room messages sent by unregistered users - + Ignore private messages sent by unregistered users - - + + Invert text color - + Enable desktop notifications for private messages - + Enable desktop notification for mentions - + Enable room message history on join - - + + (Color is hexadecimal) - + Separate words with a space, alphanumeric characters only @@ -3891,6 +4703,24 @@ Cockatrice will now reload the card database. + + OverlapControlWidget + + + Cards to overlap: + + + + + Overlap percentage: + + + + + Overlap direction: + + + Phase @@ -3957,57 +4787,57 @@ Cockatrice will now reload the card database. PhasesToolbar - + Untap step - + Upkeep step - + Draw step - + First main phase - + Beginning of combat step - + Declare attackers step - + Declare blockers step - + Combat damage step - + End of combat step - + Second main phase - + End of turn step @@ -4015,7 +4845,7 @@ Cockatrice will now reload the card database. PictureLoader - + en code for scryfall's language property, not available for all languages @@ -4024,647 +4854,781 @@ Cockatrice will now reload the card database. Player - + Reveal top cards of library - - - - - - - - - + + + + + + + + + + + + Number of cards: (max. %1) - + &View graveyard - + &View exile - + Player "%1" - - - - + + + + &Graveyard - - - - + + + + &Exile - + &Move hand to... - - - + + + &Top of library - - - + + + &Bottom of library - + &Move graveyard to... - - - - + + + + &Hand - + &Move exile to... - + &View library - + &View hand - + View &top cards of library... - + Reveal &library to... - + &Always reveal top card - + &View sideboard - + &Draw card - + D&raw cards... - + &Undo last draw - + Take &mulligan - - &Shuffle - - - - + Play top card &face down - + Move top card to grave&yard - + Move top card to e&xile - + Move top cards to &graveyard... - + Move top cards to &exile... - + Put top card on &bottom - + + View bottom cards of library... + + + + Lend library to... - + + Shuffle + + + + Put top cards on stack &until... Take top cards &until... - + + Shuffle top cards... + + + + + Shuffle bottom cards... + + + + &Reveal hand to... - + Reveal r&andom card to... - + Reveal random card to... - + &Sideboard - + &Library - + &Counters - + &Untap all permanents - + R&oll die... - + &Create token... - + C&reate another token - + Cr&eate predefined token - - + + &All players - + S&ay - + + &Select All + + + + + S&elect Row + + + + + S&elect Column + + + + &Play - + &Hide - + Play &Face Down - + &Tap / Untap Turn sideways or back again - + Toggle &normal untapping - + T&urn Over Turn face up/face down - + &Peek at card face - + &Clone - + Attac&h to card... - + Unattac&h - + &Draw arrow... - + &Increase power - + &Decrease power - + I&ncrease toughness - + D&ecrease toughness - + In&crease power and toughness - + Dec&rease power and toughness - + Increase power and decrease toughness - + Decrease power and increase toughness - + Set &power and toughness... - + Reset p&ower and toughness - + &Set annotation... - + Red - + Yellow - + Green - + &Add counter (%1) - + &Remove counter (%1) - + &Set counters (%1)... - + &Top of library in random order - + X cards from the top of library... - + &Bottom of library in random order - + View top cards of library - + + View bottom cards of library + + + + + Shuffle top cards of library + + + + + Shuffle bottom cards of library + + + + Which position should this card be placed: - + (max. %1) - + Draw hand - + 0 and lower are in comparison to current hand size - + Draw cards - - + + Number: - + Move top cards to grave - + Reveal &top cards to... - + &Top of library... - + &Bottom of library... - + &Always look at top card - + &Open deck in deck editor - + &Play top card - + &Draw bottom card - + D&raw bottom cards... - + &Play bottom card - + Play bottom card &face down - + Move bottom card to grave&yard - + Move bottom card to e&xile - + Move bottom cards to &graveyard... - + Move bottom cards to &exile... - + Put bottom card on &top - + Selec&ted cards - + Move top cards to exile - + Move bottom cards to grave - + Move bottom cards to exile - + Draw bottom cards - - + + C&reate another %1 token - + Create tokens - + Place card X cards from top of library - + Change power/toughness - + Change stats to: - + Set annotation - + Please enter the new annotation: - + Set counters - + Re&veal to... - + View related cards - + Token: - + All tokens + + PrintingSelector + + + Display Navigation Buttons + + + + + <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. + + + + + PrintingSelectorCardOverlayWidget + + + Preference + + + + + Pin Printing + + + + + Unpin Printing + + + + + Show Related cards + + + + + PrintingSelectorCardSearchWidget + + + Search by set name or set code + + + + + PrintingSelectorCardSelectionWidget + + + Previous Card in Deck + + + + + Next Card in Deck + + + + + PrintingSelectorCardSortingWidget + + + Alphabetical + + + + + Preference + + + + + Release Date + + + + + + Descending + + + + + Ascending + + + QMenuBar - + Services - + Hide %1 - + Hide Others - + Show All - + Preferences... - + Quit %1 - + About %1 @@ -4672,38 +5636,32 @@ Cockatrice will now reload the card database. QObject - + Cockatrice card database (*.xml) - - + All files (*.*) - + Cockatrice replays (*.cor) - - Common deck formats (*.cod *.dec *.dek *.txt *.mwDeck) - - - - + Maindeck - + Sideboard - + Tokens @@ -4711,92 +5669,92 @@ Cockatrice will now reload the card database. QPlatformTheme - + OK - + Save - + Save All - + Open - + &Yes - + Yes to &All - + &No - + N&o to All - + Abort - + Retry - + Ignore - + Close - + Cancel - + Discard - + Help - + Apply - + Reset - + Restore Defaults @@ -4804,17 +5762,17 @@ Cockatrice will now reload the card database. RemoteDeckList_TreeModel - + Name - + ID - + Upload time @@ -4822,32 +5780,32 @@ Cockatrice will now reload the card database. RemoteReplayList_TreeModel - + ID - + Name - + Players - + Keep - + Time started - + Duration (sec) @@ -4893,32 +5851,32 @@ Cockatrice will now reload the card database. SequenceEdit - + Choose an action from the table - + Hit the key/combination of keys you want to set for this action - - Shortcut already in use - - - - + Invalid key - + + Shortcut already in use by: + + + + Clear - + Restore default @@ -4954,72 +5912,80 @@ Cockatrice will now reload the card database. ShortcutSettingsPage - - + + Restore all default shortcuts - + Do you really want to restore all default shortcuts? - + Clear all default shortcuts - + Do you really want to clear all shortcuts? - - Action - - - - - Shortcut - - - - + Section: - + Action: - + Shortcut: - + How to set custom shortcuts - + Clear all shortcuts + + + Search by shortcut name + + + + + ShortcutTreeView + + + Action + + + + + Shortcut + + ShortcutsSettings - + Your configuration file contained invalid shortcuts. Please check your shortcut settings! - + The following shortcuts have been set to default: @@ -5028,17 +5994,17 @@ Please check your shortcut settings! ShutdownDialog - + &Reason for shutdown: - + &Time until shutdown (minutes): - + Shut down server @@ -5046,27 +6012,27 @@ Please check your shortcut settings! SoundSettingsPage - + Enable &sounds - + Current sounds theme: - + Test system sound engine - + Sound settings - + Master volume @@ -5074,48 +6040,48 @@ Please check your shortcut settings! SpoilerBackgroundUpdater - + Spoilers season has ended - + Deleting spoiler.xml. Please run Oracle - - + + Spoilers download failed - + No internet connection - + Error - + Spoilers already up to date - + No new spoilers added - + Spoilers have been updated! - + Last change: @@ -5123,635 +6089,593 @@ Please check your shortcut settings! StableReleaseChannel - - Stable Releases + + Default - + No reply received from the release update server. - + Invalid reply received from the release update server. - + No reply received from the tag update server. - + Invalid reply received from the tag update server. + + TabAccount + + + Add to Buddy List + + + + + Add to Ignore List + + + + + Account + + + TabAdmin - + Administration - + Update server &message - + &Shut down server - + &Reload configuration - + Server administration functions - + + Server moderator functions + + + + + Replay ID + + + + + Grant Replay Access + + + + + Username to Activate + + + + + Force Activate User + + + + &Unlock functions - + &Lock functions + + + + Success + + + + + Replay access granted + + + + + + + + + Error + + + + + Unable to grant replay access. Replay ID invalid + + + + + Unable to grant replay access. Internal error + + + + + User successfully activated + + + + + Unable to activate user. Username invalid + + + + + Unable to activate user. User already active + + + + + Unable to activate user. Internal error + + TabDeckEditor - - Search by card name (or search expressions) - Search by card name - - - - - Add to Deck - - - - - Add to Sideboard - - - - - Show Related cards - - - - - &Clear all filters - - - - - Delete selected - - - - - Deck &name: - - - - - &Comments: - - - - - Hash: - - - - - &New deck - - - - - &Load deck... - - - - - &Save deck - - - - - Save deck &as... - - - - - Load deck from cl&ipboard... - - - - - Save deck to clipboard - - - - - Annotated - - - - - Not Annotated - - - - - &Print deck... - - - - - &Send deck to online service - - - - - Create decklist (decklist.org) - - - - - Analyze deck (deckstats.net) - - - - - Analyze deck (tappedout.net) - - - - - &Close - - - - - Add card to &maindeck - - - - - Add card to &sideboard - - - - - &Remove row - - - - - &Increment number - - - - - &Decrement number - - - - - &Deck Editor - - - - - + Card Info - - + Deck - - + Filters - + &View - - - + + Printing + + + + + + + Visible - - - + + + + Floating - + Reset layout - + Deck: %1 - - - Are you sure? - - - - - The decklist has been modified. -Do you want to save the changes? - - - - - Load deck - - - - - - - - - - Error - - - - - The deck could not be saved. - - - - - Could not save remote deck - - - - - - The deck could not be saved. -Please check that the directory is writable and try again. - - - - - Save deck - - - - - There are no cards in your deck to be exported - - - - - No deck was selected to be saved. - - TabDeckStorage - - Deck storage - - - - + Local file system - + Server deck storage - - + + Open in deck editor - + + Rename deck or folder + + + + Upload deck - + Download deck - - + + + + New folder - - + + Delete - - + + Open decks folder + + + + + Rename local folder + + + + + Rename local file + + + + + New name: + + + + + + + Error - + + Rename failed + + + + + Invalid deck file - + Enter deck name - + This decklist does not have a name. Please enter a name: - + Unnamed deck - + Failed to upload deck to server - + Delete local file - - - - Are you sure you want to delete "%1"? + + Are you sure you want to delete the selected files? - + + Delete remote decks + + + + + Are you sure you want to delete the selected decks? + + + + + Name of new folder: - - Delete remote folder + + Deck Storage + + + + + TabDeckStorageVisual + + + Visual Deck Storage - - Delete remote deck + + Error + + + + + Could not open deck at %1 + + + + + TabEdhRec + + + EDHREC: TabGame - - - + + + Replay - - + + Game - - + + Player List - - + + Card Info - - + + Messages - - + + Replay Timeline - + &Phases - + &Game - + Next &phase - + Next phase with &action - + Next &turn - + Reverse turn order - + &Remove all local arrows - + Rotate View Cl&ockwise - + Rotate View Co&unterclockwise - + Game &information - + + Un&concede + + + + &Concede - + &Leave game - + C&lose replay - + &Focus Chat - + &Say: - + &View - - - - + + + + Visible - - - - + + + + Floating - + Reset layout - + Concede - + Are you sure you want to concede this game? - + Unconcede - + You have already conceded. Do you want to return to this game? - + Leave game - + Are you sure you want to leave this game? - + You are flooding the game. Please wait a couple of seconds. - + A player has joined game #%1 - + %1 has joined the game - + kicked by game host or moderator - + player left the game - + player disconnected from server - + reason unknown - + You have been kicked out of the game. @@ -5764,152 +6688,152 @@ Please enter a name: - - - + + + Time;SenderName;SenderIP;Message;TargetID;TargetName - + Room Logs - + Game Logs - + Chat Logs - - + + Error - + You must select at least one filter. - + You have to select a valid number of days to locate. - + Username: - + IP Address: - + Game Name: - + GameID: - + Message: - + Main Room - + Game Room - + Private Chat - + Past X Days: - + Today - + Last Hour - + Maximum Results: - + At least one filter is required. The more information you put in, the more specific your results will be. - + Get User Logs - + Clear Filters - + Filters - + Log Locations - + Date Range - + Maximum Results - - + + Message History - + Failed to collect message history information. - + There are no messages for the selected filters. @@ -5932,22 +6856,22 @@ The more information you put in, the more specific your results will be. - + This user is ignoring you, they cannot see your messages in main chat and you cannot join their games. - + Private message from - + %1 has left the server. - + %1 has joined the server. @@ -5955,117 +6879,153 @@ The more information you put in, the more specific your results will be. TabReplays - - Game replays - - - - + Local file system - + Server replay storage - - + + Watch replay - - + + Rename + + + + + + New folder + + + + + Delete - + + Open replays folder + + + + Download replay - + Toggle expiration lock - + + Rename local folder + + + + + Rename local file + + + + + New name: + + + + + Error + + + + + Rename failed + + + + + Name of new folder: + + + + Delete local file - - Are you sure you want to delete "%1"? + + Are you sure you want to delete the selected files? - - Downloading Replays + + Are you sure you want to delete the selected replays? - - Folder download is not yet supported. Please download replays individually. - - - - + Delete remote replay - - Are you sure you want to delete the replay of game %1? + + Game Replays TabRoom - + &Say: - + Chat - + &Room - + &Leave room - + &Clear chat - + Chat Settings... - + mentioned you. - + Click to view - + You are flooding the chat. Please wait a couple of seconds. @@ -6078,30 +7038,30 @@ The more information you put in, the more specific your results will be. - - - - + + + + Error - + Failed to join the server room: it doesn't exist on the server. - + The server thinks you are in the server room but your client is unable to display it. Try restarting your client. - + You do not have the required permission to join this server room. - + Failed to join the server room due to an unknown error: %1. @@ -6109,32 +7069,72 @@ The more information you put in, the more specific your results will be. TabSupervisor - + + Deck Editor + + + + + &Visual Deck Storage + + + + + Server + + + + + Account + + + + + Deck Storage + + + + + Game Replays + + + + + Administration + + + + + Logs + + + + Are you sure? - + There are still open games. Are you sure you want to quit? - + Click to view - + Your buddy %1 has signed on! - + Unknown Event - + The server has sent you a message that your client does not understand. This message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -6142,61 +7142,43 @@ To update your client, go to Help -> Check for Updates. - + Idle Timeout - + You are about to be logged out due to inactivity. - + Promotion - + You have been promoted. Please log out and back in for changes to take effect. - + Warned - + You have received a warning due to %1. Please refrain from engaging in this activity or further actions may be taken against you. If you have any questions, please private message a moderator. - + You have received the following message from the server. (custom messages like these could be untranslated) - - TabUserLists - - - Account - - - - - Add to Buddy List - - - - - Add to Ignore List - - - TappedOutInterface @@ -6280,173 +7262,184 @@ Please refrain from engaging in this activity or further actions may be taken ag UserContextMenu - + User &details - + Private &chat - + Show this user's &games - + Add to &buddy list - + Remove from &buddy list - + Add to &ignore list - + Remove from &ignore list - + Kick from &game - + Warn user - + View user's war&n history - + Ban from &server - + View user's &ban history - + &Promote user to moderator - + Dem&ote user from moderator - + Promote user to &judge - + Demote user from judge - + + View admin notes + + + + %1's games - - - + + + Ban History - + Ban Time;Moderator;Ban Length;Ban Reason;Visible Reason - + User has never been banned. - + Failed to collect ban information. - - - + + + Warning History - + Warning Time;Moderator;User Name;Reason - + User has never been warned. - + Failed to collect warning information. - - + + Failed to get admin notes. + + + + + Success - + Successfully promoted user. - + Successfully demoted user. - - + + + Failed - + Failed to promote user. - + Failed to demote user. - + Copy hash to clipboard - + Remove this user's messages @@ -6454,173 +7447,173 @@ Please refrain from engaging in this activity or further actions may be taken ag UserInfoBox - + Location: - + Account Age: - + Edit - + Change password - + Change avatar - + Administrator - + Moderator - + Registered user - - + + Unregistered user - + Judge - + Unknown - + The entered password does not match your account. - - - + + + Information - + User information updated. - - - - - - - - - - + + + + + + + + + + Error - + User Information - + Real Name: - + User Level: - + %n Year(s), amount of years (only shown if more than 0) - + %10%n Day(s) %20 amount of years (if more than 0), amount of days, date in local short format - + Enter Password - + Password verification is required in order to change your email address - - - + + + An error occurred while trying to update your user information. - + This server does not permit you to update your user informations. - + Password changed. - + This server does not permit you to change your password. - + The new password is too short. - + The old password is incorrect. - + Avatar updated. - + This server does not permit you to update your avatar. - + An error occured while trying to updater your avatar. @@ -6628,124 +7621,251 @@ Please refrain from engaging in this activity or further actions may be taken ag UserInterfaceSettingsPage - + General interface settings - + &Double-click cards to play them (instead of single-click) - + + &Clicking plays all selected cards (instead of just the clicked card) + + + + &Play all nonlands onto the stack (not the battlefield) by default - + + Close card view window when last card is removed + + + + Annotate card text on tokens - + Use tear-off menus, allowing right click menus to persist on screen - + Notifications settings - + Enable notifications in taskbar - + Notify in the taskbar for game events while you are spectating - + Notify in the taskbar when users in your buddy list connect - + Animation settings - + &Tap/untap animation + + + Deck editor/storage settings + + + + + Open deck in new tab by default + + + + + Prompt before converting .txt decks to .cod format + + + + + Always convert if not prompted + + + + + Use visual deck storage in game lobby + + + + + Replay settings + + + + + Buffer time for backwards skip via shortcut: + + - UserList + UserListWidget - + Users connected to server: %1 - + Users in this room: %1 - + Buddies online: %1 / %2 - + Ignored users online: %1 / %2 + + VisualDeckStorageFolderDisplayWidget + + + Deck Storage + + + + + VisualDeckStorageSearchWidget + + + Search by filename + + + + + VisualDeckStorageSortWidget + + + Sort Alphabetically (Deck Name) + + + + + Sort Alphabetically (Filename) + + + + + Sort by Last Modified + + + + + Sort by Last Loaded + + + + + VisualDeckStorageWidget + + + Loading database ... + + + + + Show Folders + + + + + Show Tag Filter + + + + + Show Tags On Deck Previews + + + + + Show Banner Card Selection Option + + + + + Include Folder Names in Search + + + + + Draw unused Color Identities + + + + + Unused Color Identities Opacity + + + WarningDialog - + Which warning would you like to send? - + Redact all messages from this user in all rooms - + &OK - + &Cancel - + Warn user for misconduct - - + + Error - + User name to send a warning to can not be blank, please specify a user to warn. - + Warning to use can not be blank, please select a valid warning to send. @@ -6753,128 +7873,133 @@ Please refrain from engaging in this activity or further actions may be taken ag WndSets - + Move selected set to the top - + Move selected set up - + Move selected set down - + Move selected set to the bottom - + Search by set name, code, or type - + Default order - + Restore original art priority order - + Enable all sets - + Disable all sets - + Enable selected set(s) - + Disable selected set(s) - + Deck Editor - + Use CTRL+A to select all sets in the view. - + Only cards in enabled sets will appear in the card list of the deck editor. - + Image priority is decided in the following order: - + first the CUSTOM Folder (%1), then the Enabled Sets in this dialog (Top to Bottom) %1 is a link to the wiki - + Card Art - + How to use custom card art - + Hints - + Note - + Sorting by column allows you to find a set while not changing set priority. - + To enable ordering again, click the column header until this message disappears. - + Use the current sorting as the set priority instead - + Sorts the set priority using the same column - + + Include online-only (Arena) cards [requires restart] + + + + Manage sets @@ -6882,22 +8007,67 @@ Please refrain from engaging in this activity or further actions may be taken ag ZoneViewWidget - - sort by name + + Ungrouped - - sort by type + + Group by Type - + + Group by Mana Value + + + + + Group by Color + + + + + Unsorted + + + + + Sort by Name + + + + + Sort by Type + + + + + Sort by Mana Cost + + + + + Sort by Colors + + + + + Sort by P/T + + + + + Sort by Set + + + + shuffle when closing - + pile view @@ -6905,7 +8075,7 @@ Please refrain from engaging in this activity or further actions may be taken ag i18n - + English Eesti Keel (Estonian) @@ -6913,12 +8083,12 @@ Please refrain from engaging in this activity or further actions may be taken ag main - + Connect on startup - + Debug to file @@ -6926,786 +8096,908 @@ Please refrain from engaging in this activity or further actions may be taken ag shortcutsTab - + Main Window - - + + Deck Editor - + Game Lobby - + Card Counters - + Player Counters - + Power and Toughness - + Game Phases - + Playing Area - + Move Selected Card - + View - + Move Top Card - + Move Bottom Card - + Gameplay - + Drawing - + Chat Room - + Game Window - + Load Deck from Clipboard - + + Replays - + + Tabs + + + + Check for Card Updates... - + Connect... - + Disconnect - + Exit - + Full screen - + Register... - + Settings... - + Start a Local Game... - + Watch Replay... - - Analyze Deck + + Analyze Deck (deckstats.net) + Analyze Deck - - Clear All Filters - - - - - Clear Selected Filter - - - - - Close + + Analyze Deck (tappedout.net) - Remove Card + Clear All Filters + Clear Selected Filter + + + + + Close + + + + + Remove Card + + + + Manage Sets... - + Edit Custom Tokens... - - Export Deck - - - - - Add Card - - - - - Load Deck... - - - - - Load Deck from Clipboard... + + Export Deck (decklist.org) - New Deck + Export Deck (decklist.xyz) - - Open Custom Pictures Folder + + Add Card - - Print Deck... + + Load Deck... + Load Deck from Clipboard... + + + + + Edit Deck in Clipboard, Annotated + + + + + Edit Deck in Clipboard + + + + + New Deck + + + + + Open Custom Pictures Folder + + + + + Print Deck... + + + + Delete Card - - + + Reset Layout - + Save Deck - + Save Deck as... - + Save Deck to Clipboard, Annotated - - Save Deck to Clipboard - - - - - Load Local Deck... - - - - - Load Remote Deck... - - - - - Set Ready to Start - - - - - Toggle Sideboard Lock - - - - - - Add Green Counter - - - - - - Remove Green Counter - - - - - - Set Green Counters... + + Save Deck to Clipboard, Annotated (No Set Info) + Save Deck to Clipboard + + + + + Save Deck to Clipboard (No Set Info) + + + + + Load Local Deck... + + + + + Load Remote Deck... + + + + + Set Ready to Start + + + + + Toggle Sideboard Lock + + + + + + Add Green Counter + + + + + + Remove Green Counter + + + + + + Set Green Counters... + + + + Add Yellow Counter - + Remove Yellow Counter - + Set Yellow Counters... - - + + Add Red Counter - - + + Remove Red Counter - - + + Set Red Counters... - + Add Life Counter - + Remove Life Counter - + Set Life Counters... - + Add White Counter - + Remove White Counter - + Set White Counters... - + Add Blue Counter - + Remove Blue Counter - + Set Blue Counters... - + Add Black Counter - + Remove Black Counter - + Set Black Counters... - + Add Colorless Counter - + Remove Colorless Counter - + Set Colorless Counters... - + Add Other Counter - + Remove Other Counter - + Set Other Counters... - + Add Power (+1/+0) - + Remove Power (-1/-0) - + Move Toughness to Power (+1/-1) - + Add Toughness (+0/+1) - + Remove Toughness (-0/-1) - + Move Power to Toughness (-1/+1) - + Add Power and Toughness (+1/+1) - + Remove Power and Toughness (-1/-1) - + Set Power and Toughness... - + Reset Power and Toughness - + Untap - + Upkeep - + Draw - + First Main Phase - + Start Combat - + Attack - + Block - + Damage - + End Combat - + Second Main Phase - + End - + Next Phase - + Next Phase Action - + Next Turn - + + Hide Card in Reveal Window + + + + Tap / Untap Card - + Untap All - + Toggle Untap - + Turn Card Over - + Peek Card - + Play Card - + Attach Card... - + Unattach Card - + Clone Card - + Create Token... - + Create All Related Tokens - + Create Another Token - + Set Annotation... - - + + Select All Cards in Zone + + + + + Select All Cards in Row + + + + + Select All Cards in Column + + + + + Bottom of Library - - - - + + + + Exile - - - - + + + + Graveyard - - + + Hand - - + + Top of Library - - - + + + Battlefield, Face Down - + Battlefield - + Library - + Sideboard - + Top Cards of Library - + + Bottom Cards of Library + + + + Close Recent View - - + + Stack - - + + Graveyard (Multiple) - - + + Exile (Multiple) - + Stack Until Found - + Draw Bottom Card - + Draw Multiple Cards from Bottom... - + Draw Arrow... - + Remove Local Arrows - + Leave Game - + Concede - + Roll Dice... - + Shuffle Library - + + Shuffle Top Cards of Library + + + + + Shuffle Bottom Cards of Library + + + + Mulligan - + Draw a Card - + Draw Multiple Cards... - + Undo Draw - + Always Reveal Top Card - + Always Look At Top Card - + Rotate View Clockwise - + Rotate View Counterclockwise - + Unfocus Text Box - + Focus Chat - + Clear Chat - + Refresh - + + Skip Forward + + + + + Skip Backward + + + + + Skip Forward by a lot + + + + + Skip Backward by a lot + + + + Play/Pause - + Toggle Fast Forward + + + Visual Deck Storage + + + + + Deck Storage + + + + + Server + + + + + Account + + + + + Administration + + + + + Logs + + \ No newline at end of file diff --git a/cockatrice/translations/cockatrice_fi.ts b/cockatrice/translations/cockatrice_fi.ts index 775cf55e5..a4afdfe6a 100644 --- a/cockatrice/translations/cockatrice_fi.ts +++ b/cockatrice/translations/cockatrice_fi.ts @@ -2,7 +2,7 @@ AbstractCounter - + &Set counter... &Lisää countteri... @@ -10,95 +10,247 @@ AbstractCounterDialog - + Set counter Lisää countteri - + New value for counter '%1': Countterin uusi arvo '%1': - AppearanceSettingsPage + AbstractDlgDeckTextEdit - + + &Refresh + + + + + Parse Set Name and Number (if available) + + + + + AbstractTabDeckEditor + + + Open in new tab + + + + + Are you sure? + + + + + The decklist has been modified. +Do you want to save the changes? + + + + + + + + + + Error - + + Could not open deck at %1 + + + + + Could not save remote deck + + + + + + The deck could not be saved. +Please check that the directory is writable and try again. + + + + + Save deck + + + + + The deck could not be saved. + + + + + There are no cards in your deck to be exported + + + + + No deck was selected to be exported. + + + + + AdminNotesDialog + + + Update Notes + + + + + Admin Notes for %1 + + + + + AllZonesCardAmountWidget + + + Mainboard + + + + + Sideboard + + + + + AppearanceSettingsPage + + + Error + + + + Could not create themes directory at '%1'. - + Theme settings Teeman asetukset - + Current theme: Nykyinen teema: - + Open themes folder - + + Menu settings + + + + + Show keyboard shortcuts in right-click menus + + + + Card rendering Kortin renderöinti - + Display card names on cards having a picture Näytä kortin nimi korteissa, joissa on kuva - + + Auto-Rotate cards with sideways layout + + + + + Override all card art with personal set preference (Pre-ProviderID change behavior) [Requires Client restart] + + + + + Bump sets that the deck contains cards from to the top in the printing selector + + + + Scale cards on mouse over Suurenna kortti kun hiiri on kortin päällä - + + Use rounded card corners + + + + Minimum overlap percentage of cards on the stack and in vertical hand - + + Maximum initial height for card view window: + + + + + + rows + + + + + Maximum expanded height for card view window: + + + + Hand layout Käden layout - + Display hand horizontally (wastes space) Näytä käsikortit vierekkäin vaakatasossa (vie enemmän tilaa) - + Enable left justification Järjestä vasemmalta - + Table grid layout Pöydän grid-asetelma - + Invert vertical coordinate Käänteiset pystykoordinaatit - + Minimum player count for multi-column layout: Pelaajien vähimmäismäärä moniosaisessa layoutissa: - + Maximum font size for information displayed on cards: Suurin fonttikoko korteissa näkyville infoteksteille: @@ -106,112 +258,112 @@ BanDialog - + ban &user name Bännää &käyttäjä - + ban &IP address Bännää &IP osoite - + ban client I&D Bännää client I&D - + Ban type Bännäyksen tyyppi - + &permanent ban Bännätty pysyvästi - + &temporary ban Bännätty väliaikaisesti - + &Days: Päivät: - + &Hours: Tunnit: - + &Minutes: Minuutit: - + Duration of the ban Bännin kesto - + Please enter the reason for the ban. This is only saved for moderators and cannot be seen by the banned person. Kerro bännäyksen syy. Tallennetaan moderaattoreita varten. Bännätty henkilö ei näe tätä. - + Please enter the reason for the ban that will be visible to the banned person. Kerro bännäyksen syy. Tämä näkyy bännätylle henkilölle. - + Redact all messages from this user in all rooms - + &OK &OK - + &Cancel &Peruuta - + Ban user from server Bännää pelaaja serveriltä - - - - + + + + Error Virhe - + You have to select a name-based, IP-based, clientId based, or some combination of the three to place a ban. Valitse nimi, IP -osoite, clientid tai joku näiden yhdistelmä asettaaksesi bännin. - + You must have a value in the name ban when selecting the name ban checkbox. Bännää nimi kentässä pitää olla arvo ennen valintaruudun valitsemista - + You must have a value in the ip ban when selecting the ip ban checkbox. Bännää ip kentässä pitää olla arvo ennen valintaruudun valitsemista. - + You must have a value in the clientid ban when selecting the clientid ban checkbox. Bännää ClientID kentässä pitää olla arvo ennen valintaruudun valitsemista. @@ -219,22 +371,22 @@ Tallennetaan moderaattoreita varten. Bännätty henkilö ei näe tätä. BetaReleaseChannel - - Beta Releases - Betajulkaisut + + Beta + - + No reply received from the release update server. Ei vastausta päivitysserveriltä. - + Invalid reply received from the release update server. Päivitysserveri vastasi virheellisesti - + No reply received from the file update server. Ei vastausta tiedoston päivitysserveriltä. @@ -242,32 +394,32 @@ Tallennetaan moderaattoreita varten. Bännätty henkilö ei näe tätä. CardDatabaseModel - + Name Nimi - + Sets Setit - + Mana cost Mana cost - + Card type Korttityyppi - + P/T P/T - + Color(s) Väri(t) @@ -360,325 +512,621 @@ Tallennetaan moderaattoreita varten. Bännätty henkilö ei näe tätä. - CardFrame + CardInfoFrameWidget - + Image - Kuva + - + Description - Kuvaus + - + Both - Molemmat + + + + + View transformation + - CardInfoText + CardInfoPictureWidget - + + View related cards + + + + + Add card to deck + + + + + Mainboard + + + + + Sideboard + + + + + CardInfoTextWidget + + Name: - Nimi: + - + Related cards: - Samankaltaiset kortit: + - + Unknown card: - Tuntematon kortti: + CardItem - + &Move to &Siirrä - + &Power / toughness &Power / toughness + + CardSizeWidget + + + Card Size + + + CardZone - + their hand nominative käsikorttinsa - + %1's hand nominative %1n käsi - + their library look at zone heidän pakkansa - + %1's library look at zone %1n pakka - + of their library top cards of zone, heidän pakastaan - + of %1's library top cards of zone %1n pakasta - + their library reveal zone heidän pakkansa - + %1's library reveal zone %1n pakka - + their library shuffle heidän pakkansa - + %1's library shuffle %1n pakka - + their library nominative heidän pakkansa - + %1's library nominative %1n pakka - + their graveyard nominative Heidän hautansa - + %1's graveyard nominative %1n hauta - + their exile nominative heidän exilensä - + %1's exile nominative %1n exile - + their sideboard look at zone heidän sideboardinsa - + %1's sideboard look at zone %1n sideboard - + their sideboard nominative heidän sideboardinsa - + %1's sideboard nominative %1n sideboard + + DeckEditorCardInfoDockWidget + + + Card Info + + + + + DeckEditorDatabaseDisplayWidget + + + Search by card name (or search expressions) + + + + + Add to Deck + + + + + Add to Sideboard + + + + + Select Printing + + + + + Show on EDHREC (Commander) + + + + + Show on EDHREC (Card) + + + + + Show Related cards + + + + + Add card to &maindeck + + + + + Add card to &sideboard + + + + + DeckEditorDeckDockWidget + + + Banner Card + + + + + Select Printing + + + + + Deck + + + + + Deck &name: + + + + + &Comments: + + + + + Hash: + + + + + &Increment number + + + + + &Decrement number + + + + + &Remove row + + + + + Swap card to/from sideboard + + + + + DeckEditorFilterDockWidget + + + Filters + + + + + &Clear all filters + + + + + Delete selected + + + + + DeckEditorMenu + + + &Deck Editor + + + + + &New deck + + + + + &Load deck... + + + + + Load recent deck... + + + + + Clear + + + + + &Save deck + + + + + Save deck &as... + + + + + Load deck from cl&ipboard... + + + + + Edit deck in clipboard + + + + + + Annotated + + + + + + Not Annotated + + + + + Save deck to clipboard + + + + + Annotated (No set info) + + + + + Not Annotated (No set info) + + + + + &Print deck... + + + + + &Send deck to online service + + + + + Create decklist (decklist.org) + + + + + Create decklist (decklist.xyz) + + + + + Analyze deck (deckstats.net) + + + + + Analyze deck (tappedout.net) + + + + + &Close + + + + + DeckEditorPrintingSelectorDockWidget + + + Printing Selector + + + DeckEditorSettingsPage - - + + Update Spoilers Päivitä spoilerit - - + + Success Onnistui - + Download URLs have been reset. Latauksen URL:t on resetoitu - + Downloaded card pictures have been reset. Ladattujen korttien kuvat on resetoitu. - + Error Virhe - + One or more downloaded card pictures could not be cleared. Ei voitu poistaa yhtä tai useampaa kortin kuvaa - + Add URL Lisää URL - - + + URL: URL: - - + + Edit URL Muokkaa URL - + + Network Cache Size: + + + + + Redirect Cache TTL: + + + + + How long cached redirects for urls are valid for. + + + + + Picture Cache Size: + + + + Add New URL - + Remove URL - + + Day(s) + + + + Updating... Päivitetään... - + Choose path Valitse polku - + URL Download Priority URL Latausprioriteetti - + Spoilers Spoilerit - + Download Spoilers Automatically Lataa spoilerit automaattisesti - + Spoiler Location: Spoilerien sijainti: - + Last Change Muokattu viimeksi - + Spoilers download automatically on launch Lataa spoilerit latauksen yhteydessä - + Press the button to manually update without relaunching Lataa spoilerit manuaalisesti ilman uudelleenkäynnistystä - + Do not close settings until manual update is complete Älä sulje asetuksia, ennen kuin manuaalinen päivitys on valmis - + Download card pictures on the fly Lataa korttikuvat kun niitä tarvitaan - + How to add a custom URL Miten lisään oman URL:n - + Delete Downloaded Images Poista ladatut kuvat - + Reset Download URLs Resetoi latausten URL:t - - Downloaded images directory size: - - - - + On-disk cache for downloaded pictures - - Picture cache size: - - - - + In-memory cache for pictures not currently on screen @@ -686,16 +1134,218 @@ Tallennetaan moderaattoreita varten. Bännätty henkilö ei näe tätä. DeckListModel - + + Count + + + + + Set + + + + Number Numero - + + Provider ID + + + + Card Kortti + + DeckLoader + + + Common deck formats (%1) + + + + + All files (*.*) + + + + + DeckPreviewColorIdentityFilterWidget + + + Mode: Exact Match + + + + + Mode: Includes + + + + + DeckPreviewDeckTagsDisplayWidget + + + Edit tags ... + + + + + DeckPreviewTagDialog + + + Deck Tags Manager + + + + + Manage your deck tags. Check or uncheck tags as needed, or add new ones: + + + + + Add a new tag (e.g., Aggro️) + + + + + Add Tag + + + + + Filter tags... + + + + + OK + + + + + Cancel + + + + + Invalid Input + + + + + Tag name cannot be empty! + + + + + Duplicate Tag + + + + + This tag already exists. + + + + + DeckPreviewWidget + + + Banner Card + + + + + Open in deck editor + + + + + Edit Tags + + + + + Rename Deck + + + + + Save Deck to Clipboard + + + + + Annotated + + + + + Annotated (No set info) + + + + + Not Annotated + + + + + Not Annotated (No set info) + + + + + Rename File + + + + + Delete File + + + + + Set Banner Card + + + + + + New name: + + + + + + Error + + + + + Rename failed + + + + + Delete file + + + + + Are you sure you want to delete the selected file? + + + + + Delete failed + + + DeckStatsInterface @@ -713,181 +1363,217 @@ Tallennetaan moderaattoreita varten. Bännätty henkilö ei näe tätä. DeckViewContainer - + Load deck... Lataa pakka... - + Load remote deck... Lataa remote pakka... - + + Unload deck + Unload deck... + + + + Ready to start - + + Force start + + + + Sideboard unlocked - + Sideboard locked - - Load deck - Lataa pakka - - - + Error Virhe - + The selected file could not be loaded. Valittua tiedostoa ei voi avata. + + DialogConvertDeckToCodFormat + + + Deck Format Conversion + + + + + You tried to add a tag to a .txt format deck. + Tags can only be added to .cod format decks. + Do you want to convert the deck to the .cod format? + + + + + Remember and automatically apply choice in the future + + + DlgConnect - + Downloading... Ladataan... - + Known Hosts Tunnetut Isännät - + + Delete the currently selected saved server + + + + Refresh the server list with known public servers Päivitä servereiden lista tunnetuilla julkisilla servereillä - + New Host Lisää Isäntä - + Name: Nimi: - + &Host: &Host: - + &Port: &Portti: - + Player &name: Pelaajan &nimi: - + P&assword: &Salasana: - + &Save password &Tallenna salasana - + A&uto connect Yhdistä &Automaattisesti - + Automatically connect to the most recent login when Cockatrice opens Kirjaudu sisään automaattisesti kun Cockatrice käynnistyy - + If you have any trouble connecting or registering then contact the server staff for help! Jos sinulla on ongelmia yhdistämisessä tai rekisteröinnissä ota yhteyttä serverin henkilökuntaan! - - + + Webpage Websivu - + Reset Password - + + Forgot password? + + + + &Connect &Yhdistä - + Server Serveri - + Login Kirjaudu sisään - + Server Contact Serverin yhteystiedot - + Connect to Server Yhdistä serveriin - + Server URL Serverin URL - + Communication Port Tietoliikenneportti - + Unique Server Name Uniikki serverin nimi - + Connection Warning Yhteysvaroitus - + You need to name your new connection profile. Anna nimi uudelle yhteysprofiilillesi - + Connect Warning Yhdistämisvaroitus - + The player name can't be empty. Pelaajan nimi ei voi olla tyhjä. @@ -970,27 +1656,37 @@ Tallennetaan moderaattoreita varten. Bännätty henkilö ei näe tätä. Katsojat - + + Starting life total: + + + + + Game setup options + + + + &Clear &Tyhjennä - + Create game Luo peli - + Game information Tietoa pelistä - + Error Virhe - + Server error. Serverivirhe. @@ -998,92 +1694,92 @@ Tallennetaan moderaattoreita varten. Bännätty henkilö ei näe tätä. DlgCreateToken - + &Name: &Nimi: - + Token Tokeni - + C&olor: &Väri - + white valkoinen - + blue sininen - + black musta - + red punainen - + green vihreä - + multicolor monivärinen - + colorless väritön - + &P/T: P/T: - + &Annotation: &Kommentti: - + &Destroy token when it leaves the table &Tuhoa tokeni kun se lähtee pöydästä - + Token data Tokenin data - + Show &all tokens Näytä &kaikki tokenit - + Show tokens from this &deck Näytä kaikki tämän &pakan tokenit - + Choose token from list Valitse tokeni listasta - + Create token Luo token @@ -1129,6 +1825,24 @@ Poistaaksesi nykyisen avatarisi: Hyväksy valitsematta uutta kuvaa.Kuva ei kelpaa. + + DlgEditDeckInClipboard + + + Edit deck in clipboard + + + + + Error + + + + + Invalid deck list. + + + DlgEditPassword @@ -1171,93 +1885,93 @@ Poistaaksesi nykyisen avatarisi: Hyväksy valitsematta uutta kuvaa. DlgEditTokens - + &Name: &Nimi: - + C&olor: &Väri - + white valkoinen - + blue sininen - + black musta - + red punainen - + green vihreä - + multicolor monivärinen - + colorless väritön - + &P/T: P/T: - + &Annotation: &Kommentti: - + Token data Tokenin data - - + + Add token Lisää token - + Remove token Poista token - + Edit custom tokens Muokkaa tokeneita - + Please enter the name of the token: Anna tokenin nimi: - + Error Virhe - + The chosen name conflicts with an existing card or token. Make sure to enable the 'Token' set in the "Manage sets" dialog to display them correctly. Nimi on ristiriidassa olemassaolevan kortin tai tokenin kanssa. @@ -1295,132 +2009,132 @@ Varmista että 'Token' setti on asetettu näkyväksi 'Muokkaa set DlgFilterGames - + no limit - + 5 minutes - + 10 minutes - + 30 minutes - + 1 hour - + 2 hours - - Show '&buddies only' games - Näytä pelkästään &kavereiden pelit - - - - Show &full games + + Hide 'buddies only' games - - Show games &that have started + + Hide full games - - Show &password protected games - Näytä &salasanasuojatut pelit - - - - Hide '&ignored user' games + + Hide games that have started - + + Hide password protected games + + + + + Hide 'ignored user' games + + + + &Newer than: - + Game &description: Pelin &kuvaus: - + &Creator name: &Tekijän nimi: - + General Yleinen - + &Game types &Pelimuodot - + at &least: &vähintään: - + at &most: &enintään: - + Maximum player count Suurin pelaajamäärä - + Restrictions Ehdot - + Show games only if &spectators can watch - + Show spectator password p&rotected games - + Show only if spectators can ch&at - + Show only if spectators can see &hands - + Spectators - + Filter games Suodata pelit @@ -1603,27 +2317,28 @@ Varmista että 'Token' setti on asetettu näkyväksi 'Muokkaa set Salasanat eivät täsmää + + DlgLoadDeck + + + Load Deck + + + DlgLoadDeckFromClipboard - - &Refresh - &Päivitä - - - + Load deck from clipboard Avaa pakka leikepöydältä - - + Error Virhe - - + Invalid deck list. Pakka ei ole kelvollinen. @@ -1631,11 +2346,49 @@ Varmista että 'Token' setti on asetettu näkyväksi 'Muokkaa set DlgLoadRemoteDeck - + Load deck Lataa pakka + + DlgMoveTopCardsUntil + + + Card name (or search expressions): + + + + + Number of hits: + + + + + Auto play hits + + + + + Put top cards on stack until... + + + + + No cards matching the search expression exists in the card database. Proceed anyways? + + + + + Cockatrice + + + + + Invalid filter + + + DlgRegister @@ -1749,12 +2502,12 @@ Your email will be used to verify your account. DlgSettings - + Unknown Error loading card database Tuntematon virhe ladattaessa korttitietokantaa - + Your card database is invalid. Cockatrice may not function correctly with an invalid database @@ -1771,7 +2524,7 @@ Voit joutua ajamaan Oraclen ja päivittämään korttitietokannan uudelleen Haluatko vaihtaa tietokantasi sijaintia? - + Your card database version is too old. This can cause problems loading card information or images @@ -1788,7 +2541,7 @@ Yleensä tämä voidaan korjata palaamalla oracleen päivittämään korttitieto Haluatko vaihtaa tietokantasi sijaintia? - + Your card database did not finish loading Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues with your cards.xml attached @@ -1797,7 +2550,7 @@ Would you like to change your database location setting? - + File Error loading your card database. Would you like to change your database location setting? @@ -1806,7 +2559,7 @@ Would you like to change your database location setting? Haluatko vaihtaa tietokantasi sijaintia? - + Your card database was loaded but contains no cards. Would you like to change your database location setting? @@ -1815,7 +2568,7 @@ Would you like to change your database location setting? Haluatko vaihtaa tietokantasi sijaintia? - + Unknown card database load status Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues @@ -1824,59 +2577,59 @@ Would you like to change your database location setting? - - - + + + Error Virhe - + The path to your deck directory is invalid. Would you like to go back and set the correct path? Pakkojen tiedostosijainti on väärä. Haluatko palata takaisin ja asettaa oikean sijainnin pakat -kansiolle? - + The path to your card pictures directory is invalid. Would you like to go back and set the correct path? Kuvien tiedostosijainti on väärä. Haluatko palata takaisin ja asettaa oikean sijainnin kuvat -kansiolle? - + Settings Asetukset - + General Yleinen - + Appearance Ulkonäkö - + User Interface Käyttöliittymä - + Card Sources Korttien lähteet - + Chat Chat - + Sound Äänet - + Shortcuts Pikanäppäimet @@ -1902,167 +2655,169 @@ Would you like to change your database location setting? DlgUpdate - + Current release channel Tämänhetkinen julkaisukanava - + Reinstall Asenna uudelleen - + Cancel Download Peruuta Lataus - + Open Download Page Avaa Lataussivu - + Check for Client Updates - - - - + + + + Error Virhe - + Cockatrice was not built with SSL support, therefore you cannot download updates automatically! Please visit the download page to update manually. Cockatricessä ei ole SSL tukea, joten päivityksiä ei voi ladata automaattisesti! Vieraile lataussivulla päivittääksesi ohjelman manuaalisesti. - - Downloading update... - Ladataan päivitystä... + + Downloading update: %1 + Downloading update... + - + Checking for updates... Etsitään päivityksiä... - + Finished checking for updates Päivitysten haku on päättynyt - + No Update Available Ei uusia päivityksiä - + Cockatrice is up to date! Cockatrice on ajan tasalla! - + You are already running the latest version available in the chosen release channel. Sinulla on käytössä viimeisin versio, joka on saatavilla valitussa julkaisukanavassa. - + Current version Nykyinen versio - + Selected release channel Valitse julkaisukanava - - + + Update Available Päivitys Saatavilla - - + + A new version of Cockatrice is available! Uusi versio Cockatricesta on saatavilla! - - + + New version Uusi versio - - + + Released Julkaistu - - + + Changelog Muutosloki - + Do you want to update now? Haluatko päivittää nyt? - - Unfortunately there are no download packages available for your operating system. -You may have to build from source yourself. - Valitettavasti käyttöjärjestelmällesi ei löydy latauspaketteja. - -Saatat itse joutua buildaamaan ohjelman lähdekoodista. + + Unfortunately, the automatic updater failed to find a compatible download. +You may have to manually download the new version. + Unfortunately there are no download packages available for your operating system. +You may have to build from source yourself. + - - Please check the download page manually and visit the wiki for instructions on compiling. - Tutustu lataussivuun manuaalisesti ja lue wikistä ohjeet valitusta varten. + + Please check the <a href="%1">releases page</a> on our Github and download the build for your system. + Please check the download page manually and visit the wiki for instructions on compiling. + - - - + + + Update Error Päivitysvirhe - + An error occurred while checking for updates: Virhe päivityksiä tarkistettaessa: - + An error occurred while downloading an update: Virhe tiedostoa ladatessa: - + Installing... Asennetaan... - + Cockatrice is unable to open the installer. Cockatrice ei voi avata asennustiedostoa. - + Try to update manually by closing Cockatrice and running the installer. Yritä asentaa manuaalisesti sulkemalla Cockatrice ja käynnistämällä asennusohjelma. - + Download location Latausten sijainti @@ -2080,6 +2835,14 @@ Saatat itse joutua buildaamaan ohjelman lähdekoodista. Debug loki + + EdhrecCommanderResponseCommanderDetailsDisplayWidget + + + Salt: + + + FilterBuilder @@ -2091,105 +2854,120 @@ Saatat itse joutua buildaamaan ohjelman lähdekoodista. GameSelector - - - - - - - - - + + + + + + + + + Error Virhe - + Please join the appropriate room first. Liity ensin oikeaan huoneeseen. - + Wrong password. Väärä salasana. - + Spectators are not allowed in this game. Tähän peliin ei päästetä katsojia. - + The game is already full. Peli on jo täynnä. - + The game does not exist any more. Peliä ei enää ole. - + This game is only open to registered users. Peli on tarkoitettu vain rekisteröityneille käyttäjille. - + This game is only open to its creator's buddies. Peli on tarkoitettu vain sen tekijän kavereille. - + You are being ignored by the creator of this game. Pelin luoja ei huomioi sinua. - + + Join Game + + + + + Spectate Game + + + + + Game Information + + + + Join game Liity peliin - + Password: Salasana: - + Please join the respective room first. Liity ensin asianmukaiseen huoneeseen. - + &Filter games &Suodata pelit - + C&lear filter &Poista suodattimet - + C&reate &Luo - + &Join &Liity - + J&oin as spectator Liity &katsojana - + Games shown: %1 / %2 - + Games Pelit @@ -2197,100 +2975,100 @@ Saatat itse joutua buildaamaan ohjelman lähdekoodista. GamesModel - + >1 day - + %1%2 hr short age in hours - + new - + %1%2 min short age in minutes - + password salasana - + buddies only vain kaverit - + reg. users only vain rekisteröityneet käyttäjät - - + + can chat voi jutella - + see hands katso käsikortit - + can see hands voi nähdä käsikortit - + not allowed ei sallittu - + Room Huone - + Age Ikä - + Description Kuvaus - + Creator Luoja - + Type Muoto - + Restrictions Ehdot - + Players Pelaajat - + Spectators Katsojat @@ -2298,92 +3076,102 @@ Saatat itse joutua buildaamaan ohjelman lähdekoodista. GeneralSettingsPage - + Reset all paths - + All paths have been reset - - + + Choose path Valitse polku - + Personal settings Henkilökohtaiset asetukset - + Language: Kieli: - + Paths (editing disabled in portable mode) Polut (editoiminen poistettu käytöstä kannettavassa versiossa) - + Paths Polut - + + How to help with translations + + + + Decks directory: Pakkojen kansio: - + Replays directory: Replay kansio: - + Pictures directory: Kuvakansio: - + Card database: Korttitietokanta: - + Custom database directory: - + Token database: Tokenitietokanta: - + Update channel Päivitykset - + + Check for client updates on startup + + + + Notify if a feature supported by the server is missing in my client Ilmoita jos ohjelmastani puuttuu jokin serverin tarjoama ominaisuus - + Automatically run Oracle when running a new version of Cockatrice Suorita Oracle automaattisesti kun Cockatricen uusi versio on asennettu - + Show tips on startup Näytä vinkit käynnistyksen yhteydessä @@ -2391,61 +3179,61 @@ Saatat itse joutua buildaamaan ohjelman lähdekoodista. MainWindow - - + + The server has reached its maximum user capacity, please check back later. Serveri on täynnä, yritä myöhemmin uudelleen. - + There are too many concurrent connections from your address. Liian monta samanaikaista yhteyttä osoitteestasi. - + Banned by moderator Bännätty moderaattorin toimesta - + Expected end time: %1 Arvioitu päättymisaika: %1 - + This ban lasts indefinitely. Bänni kestää loputtomasti. - + Scheduled server shutdown. Ajastettu serverin sammutus. - - + + Invalid username. Väärä käyttäjänimi. - + You have been logged out due to logging in at another location. Sinut kirjattiin ulos koska kirjauduit sisään toisesta paikasta. - + Connection closed Yhteys suljettu - + The server has terminated your connection. Reason: %1 Serveri katkaisi yhteyden. Syy: %1 - + The server is going to be restarted in %n minute(s). All running games will be lost. Reason for shutdown: %1 @@ -2456,658 +3244,669 @@ Kaikki pelit joudutaan lopettamaan Syy: %1 - + Scheduled server shutdown Ajastettu serverin sammutus. - - + + Success Onnistui - + Registration accepted. Will now login. Rekisteröinti hyväksytty Kirjautuu. - + Account activation accepted. Will now login. Tilin aktivointi hyväksytty. Kirjautuu. - + Number of players Pelaajien lukumäärä - + Please enter the number of players. Anna pelaajien lukumäärä. - - + + Player %1 Pelaaja %1 - + Load replay Lataa replay - + About Cockatrice Tieto sovelluksesta - + Version Versio - + Cockatrice Webpage Cockatricen Webbsivut - + Project Manager: Projektipäällikkö: - + Past Project Managers: Edelliset projektipäälliköt: - + Developers: Kehittäjät: - + Our Developers Kehittäjämme - + Help Develop! Auta kehittämään! - + Translators: Kääntäjät: - + Our Translators Kääntäjät - + Help Translate! Auta Kääntämään! - + Support: Tuki: - + Report an Issue Ilmoita puute - + Troubleshooting Vianhaku - + F.A.Q. F.A.Q. - - - - - - - - - - - - - - - + + + + + + + + + + + + + - - - + + + + + Error Virhe - + Server timeout Serverin aikakatkaisu - + Failed Login Kirjautuminen epäonnistui - + Your client seems to be missing features this server requires for connection. Sovelluksestasi puuttuu ominaisuuksia, joita serveri vaatii yhteyden muodostamiseksi. - + To update your client, go to 'Help -> Check for Client Updates'. Päivitä sovellus: 'Apua' -> 'Tarkasta onko päivityksiä'. - + Incorrect username or password. Please check your authentication information and try again. Väärä käyttäjänimi tai salasana. Tarkista todennustietosi ja yritä uudelleen. - + There is already an active session using this user name. Please close that session first and re-login. Tällä käyttäjänimellä on jo aktiivinen sessio käynnissä. Sulje se ensin ja kirjaudu sisään uudelleen - - + + You are banned until %1. Sinut on bännätty %1 saakka. - - + + You are banned indefinitely. Sinut on bännätty lopullisesti - + This server requires user registration. Do you want to register now? Serveri vaatii rekisteröitymisen. Haluatko rekisteröityä nyt? - + This server requires client IDs. Your client is either failing to generate an ID or you are running a modified client. Please close and reopen your client to try again. - + An internal error has occurred, please close and reopen Cockatrice before trying again. If the error persists, ensure you are running the latest version of the software and if needed contact the software developers. Sisäinen virhe! Sulje ja käynnistä Cockatrice uudelleen ennenkuin yrität uudestaan. Jos ongelma jatkuu, tarkasta että käytössäsi on sovelluksen uusin versio ja tarvittaessa ota yhteyttä sovelluksen kehittäjiin. - + Account activation Tilin aktivointi - + Your account has not been activated yet. You need to provide the activation token received in the activation email. Tiliäsi ei ole vielä aktivoitu Ole hyvä ja anna aktivointikoodi, jonka sait sähköpostiisi. - + Server Full Serveri on täynnä - + Unknown login error: %1 Tuntematon kirjautumisvirhe: %1 - - + + This usually means that your client version is out of date, and the server sent a reply your client doesn't understand. Yleensä tämä tarkoittaa, että sovelluksesi on päivittämättä ja serveri lähetti vastauksen, jota sovellus ei ymmärrä. - + Your username must respect these rules: Käyttäjänimesi täytyy olla näiden sääntöjen mukainen: - + is %1 - %2 characters long %1 - %2 merkkiä pitkä - + can %1 contain lowercase characters voi sisältää %1 pientä kirjainta - - - - + + + + NOT EI - + can %1 contain uppercase characters voi sisältää %1 isoa kirjainta - + can %1 contain numeric characters voi sisältää %1 numeroa - + can contain the following punctuation: %1 voi sisältää seuraavat merkit: %1 - + first character can %1 be a punctuation mark ensimmäinen kirjain ei voi olla %1 merkki - + no unacceptable language as specified by these server rules: note that the following lines will not be translated - + can not contain any of the following words: %1 ei voi sisältää näitä sanoja: %1 - + can not match any of the following expressions: %1 ei voi sisältää seuraavia asioita: %1 - + You may only use A-Z, a-z, 0-9, _, ., and - in your username. Voit käyttää käyttäjänimessä merkkejä A-Z, a-z, 0-9, _, ., ja - - - - - - - + + + + + + Registration denied Rekisteröinti evätty - + Registration is currently disabled on this server Tämä serveri ei ota tällä hetkellä vastaan rekisteröintejä - + There is already an existing account with the same user name. Käyttäjänimi on jo olemassa - + It's mandatory to specify a valid email address when registering. Rekisteröintiin on annettava toimiva sähköpostiosoite. - + It appears you are attempting to register a new account on this server yet you already have an account registered with the email provided. This server restricts the number of accounts a user can register per address. Please contact the server operator for further assistance or to obtain your credential information. Näyttää siltä että yrität rekisteröidä uutta tiliä tälle serverille, vaikka sinulla on tällä sähköpostilla jo olemassaoleva tili. Tämä serveri rajoittaa tilien määrää, jotka tulevat samasta osoitteesta. Ota yhteyttä serverin tukeen pyytääksesi apua tai saadaksesi kirjautumistietosi. - + Password too short. Salasana on liian lyhyt. - + Registration failed for a technical problem on the server. Rekisteröinti epäonnistui serverin vian vuoksi. - + Unknown registration error: %1 Tuntematon rekisteröintivirhe: %1 - + Account activation failed Tilin aktivointi epäonnistui - + Socket error: %1 Pistokevirhe: %1 - + You are trying to connect to an obsolete server. Please downgrade your Cockatrice version or connect to a suitable server. Local version is %1, remote version is %2. Yritit yhdistää vanhentuneeseen palvelimeen. Käytä vanhempaa versiota Cockatricesta tai yhdistä sopivaan palvelimeen. Paikallinen versio on %1, etäversio on %2. - + Your Cockatrice client is obsolete. Please update your Cockatrice version. Local version is %1, remote version is %2. Cockatrice-ohjelmasi on vanhentunut. Päivitä Cockatrice-versiosi. Paikallinen versio on %1, etäversio on %2. - + Connecting to %1... Yhdistetään: %1... - + Registering to %1 as %2... Rekisteröidytään %1:een %2:na - + Disconnected Yhteys katkaistu - + Connected, logging in at %1 Yhteys luotu, kirjaudutaan %1:n - - - + + + Requesting forgotten password to %1 as %2... - + &Connect... &Yhdistä... - + &Disconnect &Katkaise yhteys - + Start &local game... Aloita &paikallinen peli... - + &Watch replay... &Katso replay... - - &Deck editor - &Pakkaeditori - - - + &Full screen K&oko näyttö - + &Register to server... &Rekisteröidy serverille... - + &Restore password... - + &Settings... &Asetukset - + &Exit &Exit - + A&ctions &Toiminnot - + &Cockatrice &Cockatrice - + C&ard Database &Korttitietokanta - + &Manage sets... - + Edit custom &tokens... - + Open custom image folder Avaa mukautettu kuvakansio - + Open custom sets folder Avaa mukautettu setit -kansio - + Add custom sets/cards Lisää mukautettuja settejä/kortteja - + + Reload card database + + + + + Tabs + + + + &Help &Apua - + &About Cockatrice &Tietoa Cockatricesta - + &Tip of the Day &Päivän vinkki - + Check for Client Updates Etsi päivityksiä - + Check for Card Updates... - + View &Debug Log - + + Open Settings Folder + + + + Show/Hide - + New Version Uusi versio - + Congratulations on updating to Cockatrice %1! Oracle will now launch to update your card database. - + Cockatrice installed Cockatrice asennettu - + Congratulations on installing Cockatrice %1! Oracle will now launch to install the initial card database. - + Card database Korttitietokanta - + Cockatrice is unable to load the card database. Do you want to update your card database now? If unsure or first time user, choose "Yes" - - + + Yes Kyllä - - + + No Ei - + Open settings Avaa asetukset - + New sets found Löytyi uusia settejä! - + %n new set(s) found in the card database Set code(s): %1 Do you want to enable it/them? - + View sets Näytä setit - + Welcome Tervetuloa - + Hi! It seems like you're running this version of Cockatrice for the first time. All the sets in the card database have been enabled. Read more about changing the set order or disabling specific sets and consequent effects in the "Manage Sets" dialog. - - + + Information Tietoa - + A card database update is already running. Korttitietokannan päivitys on jo käynnissä - + Unable to run the card database updater: - - failed to start. - Käynnistyminen ei onnistunut. - - - - crashed. - Kaatui. - - - - timed out. - Aikakatkaisu. - - - - write error. - Kirjoitusvirhe. - - - - read error. - Lukuvirhe. - - - - unknown error. - Tuntematon virhe. - - - - The card database updater exited with an error: %1 + + Failed to start. The file might be missing, or permissions might be incorrect. - + + The process crashed some time after starting successfully. + + + + + Timed out. The process took too long to respond. The last waitFor...() function timed out. + + + + + An error occurred when attempting to write to the process. For example, the process may not be running, or it may have closed its input channel. + + + + + An error occurred when attempting to read from the process. For example, the process may not be running. + + + + + Unknown error occurred. + + + + + The card database updater exited with an error: +%1 + + + + This server supports additional features that your client doesn't have. This is most likely not a problem, but this message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -3115,54 +3914,54 @@ To update your client, go to Help -> Check for Updates. - - - - - + + + + + Load sets/cards Lataa setit/kortit - + Selected file cannot be found. - + You can only import XML databases at this time. - + The new sets/cards have been added successfully. Cockatrice will now reload the card database. - + Sets/cards failed to import. - - - + + + Reset Password - + Your password has been reset successfully, you can now log in using the new credentials. Salasanasi on muutettu, voit nyt kirjautua sisään uudella salasanallasi. - + Failed to reset user account password, please contact the server operator to reset your password. - + Activation request received, please check your email for an activation token. @@ -3436,179 +4235,190 @@ Cockatrice will now reload the card database. - %1 is looking at the top %3 card(s) %2. + %1 is looking at the %4 %3 card(s) %2. + %1 is looking at the top %3 card(s) %2. top card for singular, top %3 cards for plural - + + bottom + + + + + top + + + + %1 turns %2 face-down. - + %1 turns %2 face-up. - + The game has been closed. Peli on suljettu. - + The game has started. Peli alkaa. - + %1 has joined the game. %1 on liittynyt peliin. - + %1 is now watching the game. %1 seuraa peliä. - + You have been kicked out of the game. Sinut on kickattu pois pelistä. - + %1 has left the game (%2). %1 on lähtenyt pelistä. (%2). - + %1 is not watching the game any more (%2). %1 ei enää seuraa peliä (%2) - + %1 is not ready to start the game any more. %1 ei ole enää valmis aloittamaan peliä. - + %1 shuffles their deck and draws a new hand of %2 card(s). - + %1 shuffles their deck and draws a new hand. %1 sekoittaa pakkansa ja nostaa uuden käden. - + You are watching a replay of game #%1. Katsot pelin #%1 replayta. - + %1 is ready to start the game. %1 on valmis aloittamaan pelin. - + cards an unknown amount of cards - + %1 card(s) a card for singular, %1 cards for plural - + %1 lends %2 to %3. - + %1 reveals %2 to %3. - + %1 reveals %2. - + %1 randomly reveals %2%3 to %4. - + %1 randomly reveals %2%3. - + %1 peeks at face down card #%2. - + %1 peeks at face down card #%2: %3. - + %1 reveals %2%3 to %4. - + %1 reveals %2%3. - + %1 reversed turn order, now it's %2. %1 käänsi vuorojärjestyksen; nyt se on %2. - + reversed käänteinen - + normal normaali - + Heads Kruuna - + Tails Klaava - + %1 flipped a coin. It landed as %2. %1 heitti kolikkoa ja tulokseksi tuli %2. - + %1 rolls a %2 with a %3-sided die. %1 heitti tuloksen %2. %3 -sivuisella nopalla - + %1 flips %2 coins. There are %3 heads and %4 tails. - + %1 rolls a %2-sided dice %3 times: %4. @@ -3728,17 +4538,17 @@ Cockatrice will now reload the card database. - + %1 unattaches %2. %1 irrottaa kortin %2. - + %1 undoes their last draw. %1 peruuttaa viimeisimmän kortinnostonsa. - + %1 undoes their last draw (%2). %1 peruuttaa viimeisimmän kortinnostonsa. (%2). @@ -3746,110 +4556,110 @@ Cockatrice will now reload the card database. MessagesSettingsPage - + Word1 Word2 Word3 - + Add New Message - + Edit Message - + Remove Message - + Add message Lisää viesti - - + + Message: Viesti: - + Edit message - + Chat settings Keskusteluasetukset - + Custom alert words - + Enable chat mentions Salli keskustelumaininnat - + Enable mention completer - + In-game message macros Pelinsisäiset viestimakrot - + How to use in-game message macros - + Ignore chat room messages sent by unregistered users - + Ignore private messages sent by unregistered users - - + + Invert text color Käänteinen tekstin väri - + Enable desktop notifications for private messages - + Enable desktop notification for mentions - + Enable room message history on join - - + + (Color is hexadecimal) (Väri on heksadesimaaliluku) - + Separate words with a space, alphanumeric characters only @@ -3907,6 +4717,24 @@ Cockatrice will now reload the card database. + + OverlapControlWidget + + + Cards to overlap: + + + + + Overlap percentage: + + + + + Overlap direction: + + + Phase @@ -3973,57 +4801,57 @@ Cockatrice will now reload the card database. PhasesToolbar - + Untap step Untap-kohta - + Upkeep step Upkeep-kohta - + Draw step Nostokohta - + First main phase - + Beginning of combat step - + Declare attackers step - + Declare blockers step - + Combat damage step - + End of combat step - + Second main phase - + End of turn step @@ -4031,7 +4859,7 @@ Cockatrice will now reload the card database. PictureLoader - + en code for scryfall's language property, not available for all languages @@ -4040,647 +4868,781 @@ Cockatrice will now reload the card database. Player - + Reveal top cards of library - - - - - - - - - + + + + + + + + + + + + Number of cards: (max. %1) - + &View graveyard - + &View exile - + Player "%1" - - - - + + + + &Graveyard - - - - + + + + &Exile - + &Move hand to... - - - + + + &Top of library - - - + + + &Bottom of library - + &Move graveyard to... - - - - + + + + &Hand - + &Move exile to... - + &View library - + &View hand - + View &top cards of library... - + Reveal &library to... - + &Always reveal top card - + &View sideboard - + &Draw card - + D&raw cards... - + &Undo last draw - + Take &mulligan - - &Shuffle - - - - + Play top card &face down - + Move top card to grave&yard - + Move top card to e&xile - + Move top cards to &graveyard... - + Move top cards to &exile... - + Put top card on &bottom - + + View bottom cards of library... + + + + Lend library to... - + + Shuffle + + + + Put top cards on stack &until... Take top cards &until... - + + Shuffle top cards... + + + + + Shuffle bottom cards... + + + + &Reveal hand to... - + Reveal r&andom card to... - + Reveal random card to... - + &Sideboard - + &Library - + &Counters - + &Untap all permanents - + R&oll die... - + &Create token... - + C&reate another token - + Cr&eate predefined token - - + + &All players - + S&ay - + + &Select All + + + + + S&elect Row + + + + + S&elect Column + + + + &Play - + &Hide - + Play &Face Down - + &Tap / Untap Turn sideways or back again - + Toggle &normal untapping - + T&urn Over Turn face up/face down - + &Peek at card face - + &Clone - + Attac&h to card... - + Unattac&h - + &Draw arrow... - + &Increase power - + &Decrease power - + I&ncrease toughness - + D&ecrease toughness - + In&crease power and toughness - + Dec&rease power and toughness - + Increase power and decrease toughness - + Decrease power and increase toughness - + Set &power and toughness... - + Reset p&ower and toughness - + &Set annotation... - + Red Punainen - + Yellow keltainen - + Green Vihreä - + &Add counter (%1) - + &Remove counter (%1) - + &Set counters (%1)... - + &Top of library in random order - + X cards from the top of library... - + &Bottom of library in random order - + View top cards of library - + + View bottom cards of library + + + + + Shuffle top cards of library + + + + + Shuffle bottom cards of library + + + + Which position should this card be placed: - + (max. %1) - + Draw hand Nosta käsi - + 0 and lower are in comparison to current hand size 0 ja alempi on verraten nykyiseen käsikokoon - + Draw cards - - + + Number: - + Move top cards to grave - + Reveal &top cards to... - + &Top of library... - + &Bottom of library... - + &Always look at top card - + &Open deck in deck editor - + &Play top card - + &Draw bottom card - + D&raw bottom cards... - + &Play bottom card - + Play bottom card &face down - + Move bottom card to grave&yard - + Move bottom card to e&xile - + Move bottom cards to &graveyard... - + Move bottom cards to &exile... - + Put bottom card on &top - + Selec&ted cards - + Move top cards to exile - + Move bottom cards to grave - + Move bottom cards to exile - + Draw bottom cards - - + + C&reate another %1 token - + Create tokens - + Place card X cards from top of library - + Change power/toughness - + Change stats to: - + Set annotation - + Please enter the new annotation: - + Set counters - + Re&veal to... - + View related cards - + Token: - + All tokens + + PrintingSelector + + + Display Navigation Buttons + + + + + <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. + + + + + PrintingSelectorCardOverlayWidget + + + Preference + + + + + Pin Printing + + + + + Unpin Printing + + + + + Show Related cards + + + + + PrintingSelectorCardSearchWidget + + + Search by set name or set code + + + + + PrintingSelectorCardSelectionWidget + + + Previous Card in Deck + + + + + Next Card in Deck + + + + + PrintingSelectorCardSortingWidget + + + Alphabetical + + + + + Preference + + + + + Release Date + + + + + + Descending + + + + + Ascending + + + QMenuBar - + Services - + Hide %1 - + Hide Others - + Show All - + Preferences... - + Quit %1 - + About %1 @@ -4688,38 +5650,32 @@ Cockatrice will now reload the card database. QObject - + Cockatrice card database (*.xml) - - + All files (*.*) - + Cockatrice replays (*.cor) - - Common deck formats (*.cod *.dec *.dek *.txt *.mwDeck) - - - - + Maindeck - + Sideboard - + Tokens @@ -4727,92 +5683,92 @@ Cockatrice will now reload the card database. QPlatformTheme - + OK - + Save - + Save All - + Open - + &Yes - + Yes to &All - + &No - + N&o to All - + Abort - + Retry - + Ignore - + Close - + Cancel - + Discard - + Help - + Apply - + Reset - + Restore Defaults @@ -4820,17 +5776,17 @@ Cockatrice will now reload the card database. RemoteDeckList_TreeModel - + Name Nimi - + ID - + Upload time @@ -4838,32 +5794,32 @@ Cockatrice will now reload the card database. RemoteReplayList_TreeModel - + ID - + Name Nimi - + Players - + Keep - + Time started - + Duration (sec) @@ -4909,32 +5865,32 @@ Cockatrice will now reload the card database. SequenceEdit - + Choose an action from the table - + Hit the key/combination of keys you want to set for this action - - Shortcut already in use - - - - + Invalid key - + + Shortcut already in use by: + + + + Clear - + Restore default @@ -4970,72 +5926,80 @@ Cockatrice will now reload the card database. ShortcutSettingsPage - - + + Restore all default shortcuts - + Do you really want to restore all default shortcuts? - + Clear all default shortcuts - + Do you really want to clear all shortcuts? - - Action - - - - - Shortcut - - - - + Section: - + Action: - + Shortcut: - + How to set custom shortcuts - + Clear all shortcuts + + + Search by shortcut name + + + + + ShortcutTreeView + + + Action + + + + + Shortcut + + ShortcutsSettings - + Your configuration file contained invalid shortcuts. Please check your shortcut settings! - + The following shortcuts have been set to default: @@ -5044,17 +6008,17 @@ Please check your shortcut settings! ShutdownDialog - + &Reason for shutdown: - + &Time until shutdown (minutes): - + Shut down server @@ -5062,27 +6026,27 @@ Please check your shortcut settings! SoundSettingsPage - + Enable &sounds - + Current sounds theme: - + Test system sound engine - + Sound settings - + Master volume @@ -5090,48 +6054,48 @@ Please check your shortcut settings! SpoilerBackgroundUpdater - + Spoilers season has ended - + Deleting spoiler.xml. Please run Oracle - - + + Spoilers download failed - + No internet connection - + Error - + Spoilers already up to date - + No new spoilers added - + Spoilers have been updated! - + Last change: @@ -5139,635 +6103,593 @@ Please check your shortcut settings! StableReleaseChannel - - Stable Releases + + Default - + No reply received from the release update server. - + Invalid reply received from the release update server. - + No reply received from the tag update server. - + Invalid reply received from the tag update server. + + TabAccount + + + Add to Buddy List + + + + + Add to Ignore List + + + + + Account + + + TabAdmin - + Administration - + Update server &message - + &Shut down server - + &Reload configuration - + Server administration functions - + + Server moderator functions + + + + + Replay ID + + + + + Grant Replay Access + + + + + Username to Activate + + + + + Force Activate User + + + + &Unlock functions - + &Lock functions + + + + Success + + + + + Replay access granted + + + + + + + + + Error + + + + + Unable to grant replay access. Replay ID invalid + + + + + Unable to grant replay access. Internal error + + + + + User successfully activated + + + + + Unable to activate user. Username invalid + + + + + Unable to activate user. User already active + + + + + Unable to activate user. Internal error + + TabDeckEditor - - Search by card name (or search expressions) - Search by card name - - - - - Add to Deck - - - - - Add to Sideboard - - - - - Show Related cards - - - - - &Clear all filters - - - - - Delete selected - - - - - Deck &name: - - - - - &Comments: - - - - - Hash: - - - - - &New deck - - - - - &Load deck... - - - - - &Save deck - - - - - Save deck &as... - - - - - Load deck from cl&ipboard... - - - - - Save deck to clipboard - - - - - Annotated - - - - - Not Annotated - - - - - &Print deck... - - - - - &Send deck to online service - - - - - Create decklist (decklist.org) - - - - - Analyze deck (deckstats.net) - - - - - Analyze deck (tappedout.net) - - - - - &Close - - - - - Add card to &maindeck - - - - - Add card to &sideboard - - - - - &Remove row - - - - - &Increment number - - - - - &Decrement number - - - - - &Deck Editor - - - - - + Card Info - - + Deck - - + Filters - + &View - - - + + Printing + + + + + + + Visible - - - + + + + Floating - + Reset layout - + Deck: %1 - - - Are you sure? - - - - - The decklist has been modified. -Do you want to save the changes? - - - - - Load deck - - - - - - - - - - Error - Virhe - - - - The deck could not be saved. - - - - - Could not save remote deck - - - - - - The deck could not be saved. -Please check that the directory is writable and try again. - - - - - Save deck - - - - - There are no cards in your deck to be exported - - - - - No deck was selected to be saved. - - TabDeckStorage - - Deck storage - - - - + Local file system - + Server deck storage - - + + Open in deck editor - + + Rename deck or folder + + + + Upload deck - + Download deck - - + + + + New folder - - + + Delete - - + + Open decks folder + + + + + Rename local folder + + + + + Rename local file + + + + + New name: + + + + + + + Error - + + Rename failed + + + + + Invalid deck file - + Enter deck name - + This decklist does not have a name. Please enter a name: - + Unnamed deck - + Failed to upload deck to server - + Delete local file - - - - Are you sure you want to delete "%1"? + + Are you sure you want to delete the selected files? - + + Delete remote decks + + + + + Are you sure you want to delete the selected decks? + + + + + Name of new folder: - - Delete remote folder + + Deck Storage + + + + + TabDeckStorageVisual + + + Visual Deck Storage - - Delete remote deck + + Error + + + + + Could not open deck at %1 + + + + + TabEdhRec + + + EDHREC: TabGame - - - + + + Replay - - + + Game - - + + Player List - - + + Card Info - - + + Messages - - + + Replay Timeline - + &Phases - + &Game - + Next &phase - + Next phase with &action - + Next &turn - + Reverse turn order Käännä vuorojärjestys - + &Remove all local arrows - + Rotate View Cl&ockwise - + Rotate View Co&unterclockwise - + Game &information - + + Un&concede + + + + &Concede - + &Leave game - + C&lose replay - + &Focus Chat - + &Say: - + &View - - - - + + + + Visible - - - - + + + + Floating - + Reset layout - + Concede - + Are you sure you want to concede this game? - + Unconcede - + You have already conceded. Do you want to return to this game? - + Leave game Poistu pelistä - + Are you sure you want to leave this game? Haluatko varmasti poistua pelistä? - + You are flooding the game. Please wait a couple of seconds. - + A player has joined game #%1 Pelaaja on littynyt peliin #%1 - + %1 has joined the game - + kicked by game host or moderator Isäntä tai moderaattori poisti sinut pelistä - + player left the game - + player disconnected from server pelaaja katkaisi yhteyden serveriin - + reason unknown - + You have been kicked out of the game. @@ -5780,152 +6702,152 @@ Please enter a name: - - - + + + Time;SenderName;SenderIP;Message;TargetID;TargetName - + Room Logs - + Game Logs - + Chat Logs - - + + Error Virhe - + You must select at least one filter. - + You have to select a valid number of days to locate. - + Username: Käyttäjätunnus: - + IP Address: IP-osoite: - + Game Name: Pelin Nimi: - + GameID: Peli-ID: - + Message: Viesti: - + Main Room - + Game Room - + Private Chat - + Past X Days: - + Today Tänään - + Last Hour - + Maximum Results: - + At least one filter is required. The more information you put in, the more specific your results will be. - + Get User Logs - + Clear Filters - + Filters - + Log Locations - + Date Range - + Maximum Results - - + + Message History - + Failed to collect message history information. - + There are no messages for the selected filters. @@ -5948,22 +6870,22 @@ The more information you put in, the more specific your results will be. - + This user is ignoring you, they cannot see your messages in main chat and you cannot join their games. - + Private message from - + %1 has left the server. - + %1 has joined the server. @@ -5971,117 +6893,153 @@ The more information you put in, the more specific your results will be. TabReplays - - Game replays - - - - + Local file system - + Server replay storage - - + + Watch replay - - + + Rename + + + + + + New folder + + + + + Delete - + + Open replays folder + + + + Download replay - + Toggle expiration lock - + + Rename local folder + + + + + Rename local file + + + + + New name: + + + + + Error + + + + + Rename failed + + + + + Name of new folder: + + + + Delete local file - - Are you sure you want to delete "%1"? + + Are you sure you want to delete the selected files? - - Downloading Replays + + Are you sure you want to delete the selected replays? - - Folder download is not yet supported. Please download replays individually. - - - - + Delete remote replay - - Are you sure you want to delete the replay of game %1? + + Game Replays TabRoom - + &Say: - + Chat - + &Room - + &Leave room - + &Clear chat - + Chat Settings... - + mentioned you. - + Click to view - + You are flooding the chat. Please wait a couple of seconds. @@ -6094,30 +7052,30 @@ The more information you put in, the more specific your results will be. - - - - + + + + Error Virhe - + Failed to join the server room: it doesn't exist on the server. - + The server thinks you are in the server room but your client is unable to display it. Try restarting your client. - + You do not have the required permission to join this server room. - + Failed to join the server room due to an unknown error: %1. @@ -6125,32 +7083,72 @@ The more information you put in, the more specific your results will be. TabSupervisor - + + Deck Editor + + + + + &Visual Deck Storage + + + + + Server + + + + + Account + + + + + Deck Storage + + + + + Game Replays + + + + + Administration + + + + + Logs + + + + Are you sure? Oletko varma? - + There are still open games. Are you sure you want to quit? - + Click to view - + Your buddy %1 has signed on! - + Unknown Event Tuntematon Tapahtuma - + The server has sent you a message that your client does not understand. This message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -6158,61 +7156,43 @@ To update your client, go to Help -> Check for Updates. - + Idle Timeout - + You are about to be logged out due to inactivity. - + Promotion - + You have been promoted. Please log out and back in for changes to take effect. - + Warned - + You have received a warning due to %1. Please refrain from engaging in this activity or further actions may be taken against you. If you have any questions, please private message a moderator. - + You have received the following message from the server. (custom messages like these could be untranslated) - - TabUserLists - - - Account - Tili - - - - Add to Buddy List - Lisää Kaveriksi - - - - Add to Ignore List - - - TappedOutInterface @@ -6298,173 +7278,184 @@ Please refrain from engaging in this activity or further actions may be taken ag UserContextMenu - + User &details - + Private &chat - + Show this user's &games - + Add to &buddy list - + Remove from &buddy list - + Add to &ignore list - + Remove from &ignore list - + Kick from &game - + Warn user - + View user's war&n history - + Ban from &server - + View user's &ban history - + &Promote user to moderator - + Dem&ote user from moderator - + Promote user to &judge - + Demote user from judge - + + View admin notes + + + + %1's games - - - + + + Ban History - + Ban Time;Moderator;Ban Length;Ban Reason;Visible Reason - + User has never been banned. - + Failed to collect ban information. - - - + + + Warning History - + Warning Time;Moderator;User Name;Reason - + User has never been warned. - + Failed to collect warning information. - - + + Failed to get admin notes. + + + + + Success - + Successfully promoted user. - + Successfully demoted user. - - + + + Failed - + Failed to promote user. - + Failed to demote user. - + Copy hash to clipboard - + Remove this user's messages @@ -6472,173 +7463,173 @@ Please refrain from engaging in this activity or further actions may be taken ag UserInfoBox - + Location: - + Account Age: - + Edit Muokkaa - + Change password Vaihda Salasana - + Change avatar Vaihda avatar - + Administrator - + Moderator - + Registered user - - + + Unregistered user - + Judge - + Unknown - + The entered password does not match your account. - - - + + + Information - + User information updated. - - - - - - - - - - + + + + + + + + + + Error Virhe - + User Information - + Real Name: - + User Level: - + %n Year(s), amount of years (only shown if more than 0) - + %10%n Day(s) %20 amount of years (if more than 0), amount of days, date in local short format - + Enter Password - + Password verification is required in order to change your email address - - - + + + An error occurred while trying to update your user information. - + This server does not permit you to update your user informations. - + Password changed. - + This server does not permit you to change your password. - + The new password is too short. - + The old password is incorrect. - + Avatar updated. - + This server does not permit you to update your avatar. - + An error occured while trying to updater your avatar. @@ -6646,124 +7637,251 @@ Please refrain from engaging in this activity or further actions may be taken ag UserInterfaceSettingsPage - + General interface settings - + &Double-click cards to play them (instead of single-click) - + + &Clicking plays all selected cards (instead of just the clicked card) + + + + &Play all nonlands onto the stack (not the battlefield) by default - + + Close card view window when last card is removed + + + + Annotate card text on tokens - + Use tear-off menus, allowing right click menus to persist on screen - + Notifications settings Ilmoitusasetukset - + Enable notifications in taskbar Ota tehtäväpalkin ilmoitukset käyttöön - + Notify in the taskbar for game events while you are spectating - + Notify in the taskbar when users in your buddy list connect - + Animation settings Animaatioasetukset - + &Tap/untap animation + + + Deck editor/storage settings + + + + + Open deck in new tab by default + + + + + Prompt before converting .txt decks to .cod format + + + + + Always convert if not prompted + + + + + Use visual deck storage in game lobby + + + + + Replay settings + + + + + Buffer time for backwards skip via shortcut: + + - UserList + UserListWidget - + Users connected to server: %1 - Käyttäjiä serverillä: %1 + - + Users in this room: %1 - Käyttäkiä tässä huoneessa: %1 + - + Buddies online: %1 / %2 - + Ignored users online: %1 / %2 + + VisualDeckStorageFolderDisplayWidget + + + Deck Storage + + + + + VisualDeckStorageSearchWidget + + + Search by filename + + + + + VisualDeckStorageSortWidget + + + Sort Alphabetically (Deck Name) + + + + + Sort Alphabetically (Filename) + + + + + Sort by Last Modified + + + + + Sort by Last Loaded + + + + + VisualDeckStorageWidget + + + Loading database ... + + + + + Show Folders + + + + + Show Tag Filter + + + + + Show Tags On Deck Previews + + + + + Show Banner Card Selection Option + + + + + Include Folder Names in Search + + + + + Draw unused Color Identities + + + + + Unused Color Identities Opacity + + + WarningDialog - + Which warning would you like to send? - + Redact all messages from this user in all rooms - + &OK - + &Cancel - + Warn user for misconduct - - + + Error Virhe - + User name to send a warning to can not be blank, please specify a user to warn. - + Warning to use can not be blank, please select a valid warning to send. @@ -6771,128 +7889,133 @@ Please refrain from engaging in this activity or further actions may be taken ag WndSets - + Move selected set to the top - + Move selected set up - + Move selected set down - + Move selected set to the bottom - + Search by set name, code, or type - + Default order - + Restore original art priority order - + Enable all sets - + Disable all sets - + Enable selected set(s) - + Disable selected set(s) - + Deck Editor - + Use CTRL+A to select all sets in the view. - + Only cards in enabled sets will appear in the card list of the deck editor. - + Image priority is decided in the following order: - + first the CUSTOM Folder (%1), then the Enabled Sets in this dialog (Top to Bottom) %1 is a link to the wiki - + Card Art - + How to use custom card art - + Hints - + Note - + Sorting by column allows you to find a set while not changing set priority. - + To enable ordering again, click the column header until this message disappears. - + Use the current sorting as the set priority instead - + Sorts the set priority using the same column - + + Include online-only (Arena) cards [requires restart] + + + + Manage sets @@ -6900,22 +8023,67 @@ Please refrain from engaging in this activity or further actions may be taken ag ZoneViewWidget - - sort by name + + Ungrouped - - sort by type + + Group by Type - + + Group by Mana Value + + + + + Group by Color + + + + + Unsorted + + + + + Sort by Name + + + + + Sort by Type + + + + + Sort by Mana Cost + + + + + Sort by Colors + + + + + Sort by P/T + + + + + Sort by Set + + + + shuffle when closing - + pile view @@ -6923,7 +8091,7 @@ Please refrain from engaging in this activity or further actions may be taken ag i18n - + English Suomi (Finnish) @@ -6931,12 +8099,12 @@ Please refrain from engaging in this activity or further actions may be taken ag main - + Connect on startup - + Debug to file @@ -6944,786 +8112,908 @@ Please refrain from engaging in this activity or further actions may be taken ag shortcutsTab - + Main Window - - + + Deck Editor - + Game Lobby - + Card Counters - + Player Counters - + Power and Toughness - + Game Phases - + Playing Area - + Move Selected Card - + View - + Move Top Card - + Move Bottom Card - + Gameplay - + Drawing - + Chat Room - + Game Window - + Load Deck from Clipboard - + + Replays - + + Tabs + + + + Check for Card Updates... - + Connect... - + Disconnect Katkaise yhteys - + Exit - + Full screen - + Register... - + Settings... - + Start a Local Game... - + Watch Replay... - - Analyze Deck + + Analyze Deck (deckstats.net) + Analyze Deck - - Clear All Filters - - - - - Clear Selected Filter - - - - - Close + + Analyze Deck (tappedout.net) - Remove Card + Clear All Filters + Clear Selected Filter + + + + + Close + + + + + Remove Card + + + + Manage Sets... - + Edit Custom Tokens... - - Export Deck - - - - - Add Card - - - - - Load Deck... - - - - - Load Deck from Clipboard... + + Export Deck (decklist.org) - New Deck + Export Deck (decklist.xyz) - - Open Custom Pictures Folder + + Add Card - - Print Deck... + + Load Deck... + Load Deck from Clipboard... + + + + + Edit Deck in Clipboard, Annotated + + + + + Edit Deck in Clipboard + + + + + New Deck + + + + + Open Custom Pictures Folder + + + + + Print Deck... + + + + Delete Card - - + + Reset Layout - + Save Deck Tallenna Pakka - + Save Deck as... - + Save Deck to Clipboard, Annotated - - Save Deck to Clipboard - - - - - Load Local Deck... - - - - - Load Remote Deck... - - - - - Set Ready to Start - - - - - Toggle Sideboard Lock - - - - - - Add Green Counter - - - - - - Remove Green Counter - - - - - - Set Green Counters... + + Save Deck to Clipboard, Annotated (No Set Info) + Save Deck to Clipboard + + + + + Save Deck to Clipboard (No Set Info) + + + + + Load Local Deck... + + + + + Load Remote Deck... + + + + + Set Ready to Start + + + + + Toggle Sideboard Lock + + + + + + Add Green Counter + + + + + + Remove Green Counter + + + + + + Set Green Counters... + + + + Add Yellow Counter - + Remove Yellow Counter - + Set Yellow Counters... - - + + Add Red Counter - - + + Remove Red Counter - - + + Set Red Counters... - + Add Life Counter - + Remove Life Counter - + Set Life Counters... - + Add White Counter - + Remove White Counter - + Set White Counters... - + Add Blue Counter - + Remove Blue Counter - + Set Blue Counters... - + Add Black Counter - + Remove Black Counter - + Set Black Counters... - + Add Colorless Counter - + Remove Colorless Counter - + Set Colorless Counters... - + Add Other Counter - + Remove Other Counter - + Set Other Counters... - + Add Power (+1/+0) - + Remove Power (-1/-0) - + Move Toughness to Power (+1/-1) - + Add Toughness (+0/+1) - + Remove Toughness (-0/-1) - + Move Power to Toughness (-1/+1) - + Add Power and Toughness (+1/+1) - + Remove Power and Toughness (-1/-1) - + Set Power and Toughness... - + Reset Power and Toughness - + Untap - + Upkeep - + Draw - + First Main Phase - + Start Combat - + Attack - + Block - + Damage - + End Combat - + Second Main Phase - + End - + Next Phase - + Next Phase Action - + Next Turn - + + Hide Card in Reveal Window + + + + Tap / Untap Card - + Untap All - + Toggle Untap - + Turn Card Over - + Peek Card - + Play Card - + Attach Card... - + Unattach Card - + Clone Card - + Create Token... - + Create All Related Tokens - + Create Another Token - + Set Annotation... - - + + Select All Cards in Zone + + + + + Select All Cards in Row + + + + + Select All Cards in Column + + + + + Bottom of Library - - - - + + + + Exile - - - - + + + + Graveyard - - + + Hand - - + + Top of Library - - - + + + Battlefield, Face Down - + Battlefield - + Library - + Sideboard - + Top Cards of Library - + + Bottom Cards of Library + + + + Close Recent View - - + + Stack - - + + Graveyard (Multiple) - - + + Exile (Multiple) - + Stack Until Found - + Draw Bottom Card - + Draw Multiple Cards from Bottom... - + Draw Arrow... - + Remove Local Arrows - + Leave Game - + Concede - + Roll Dice... - + Shuffle Library - + + Shuffle Top Cards of Library + + + + + Shuffle Bottom Cards of Library + + + + Mulligan - + Draw a Card - + Draw Multiple Cards... - + Undo Draw - + Always Reveal Top Card - + Always Look At Top Card - + Rotate View Clockwise - + Rotate View Counterclockwise - + Unfocus Text Box - + Focus Chat - + Clear Chat - + Refresh - + + Skip Forward + + + + + Skip Backward + + + + + Skip Forward by a lot + + + + + Skip Backward by a lot + + + + Play/Pause - + Toggle Fast Forward + + + Visual Deck Storage + + + + + Deck Storage + + + + + Server + + + + + Account + + + + + Administration + + + + + Logs + + \ No newline at end of file diff --git a/cockatrice/translations/cockatrice_fr.ts b/cockatrice/translations/cockatrice_fr.ts index 116b3918d..faa2744a1 100644 --- a/cockatrice/translations/cockatrice_fr.ts +++ b/cockatrice/translations/cockatrice_fr.ts @@ -2,7 +2,7 @@ AbstractCounter - + &Set counter... Nombre de marqueur&s… @@ -10,95 +10,247 @@ AbstractCounterDialog - + Set counter Nombre de marqueurs - + New value for counter '%1': Nouvelle valeur des marqueurs « %1 » : + + AbstractDlgDeckTextEdit + + + &Refresh + + + + + Parse Set Name and Number (if available) + + + + + AbstractTabDeckEditor + + + Open in new tab + + + + + Are you sure? + + + + + The decklist has been modified. +Do you want to save the changes? + + + + + + + + + + + Error + + + + + Could not open deck at %1 + + + + + Could not save remote deck + + + + + + The deck could not be saved. +Please check that the directory is writable and try again. + + + + + Save deck + + + + + The deck could not be saved. + + + + + There are no cards in your deck to be exported + + + + + No deck was selected to be exported. + + + + + AdminNotesDialog + + + Update Notes + + + + + Admin Notes for %1 + + + + + AllZonesCardAmountWidget + + + Mainboard + + + + + Sideboard + + + AppearanceSettingsPage - + Error Erreur - + Could not create themes directory at '%1'. N'a pas pu créer le dossier Thèmes à '%1' - + Theme settings Paramètres du thème - + Current theme: Thème actuel : - + Open themes folder Ouvrir le dossier des thèmes - + + Menu settings + Options du Menu + + + + Show keyboard shortcuts in right-click menus + + + + Card rendering Rendu des cartes - + Display card names on cards having a picture Afficher les noms des cartes ayant une image - + + Auto-Rotate cards with sideways layout + + + + + Override all card art with personal set preference (Pre-ProviderID change behavior) [Requires Client restart] + + + + + Bump sets that the deck contains cards from to the top in the printing selector + + + + Scale cards on mouse over Agrandir les cartes lors du survol du curseur - + + Use rounded card corners + + + + Minimum overlap percentage of cards on the stack and in vertical hand - + + Maximum initial height for card view window: + + + + + + rows + + + + + Maximum expanded height for card view window: + + + + Hand layout Disposition de la main - + Display hand horizontally (wastes space) Afficher la main horizontalement (perte d'espace) - + Enable left justification Activer la justification à gauche - + Table grid layout Disposition en forme de grille - + Invert vertical coordinate Inverser la disposition du champ de bataille - + Minimum player count for multi-column layout: Nombre minimum de joueurs pour la disposition multi-colonnes : - + Maximum font size for information displayed on cards: Taille maximale de la police pour les informations affichées sur les cartes  : @@ -106,112 +258,112 @@ BanDialog - + ban &user name bannir &nom d'utilisateur - + ban &IP address bannir &adresse IP - + ban client I&D bannir &ID du client - + Ban type Type du bannissement - + &permanent ban &bannissement permanent - + &temporary ban bannissement &temporaire - + &Days: &Jours : - + &Hours: &Heures : - + &Minutes: &Minutes : - + Duration of the ban Durée du bannissement - + Please enter the reason for the ban. This is only saved for moderators and cannot be seen by the banned person. Veuillez expliquer la raison du bannissement. Cette information sera consultable uniquement par les modérateurs et ne sera pas visible par la personne bannie. - + Please enter the reason for the ban that will be visible to the banned person. Veuillez expliquer la raison du bannissement. Cette information sera visible par la personne bannie. - + Redact all messages from this user in all rooms Censurer tous les messages de cet utilisateur dans toutes les salles - + &OK &OK - + &Cancel &Annuler - + Ban user from server Bannir le joueur du serveur - - - - + + + + Error Erreur - + You have to select a name-based, IP-based, clientId based, or some combination of the three to place a ban. Vous devez choisir un bannissement à partir du nom, de l'adresse IP, de l'ID du client, ou une combinaison des trois. - + You must have a value in the name ban when selecting the name ban checkbox. Vous devez remplir le champ du nom du banni en le choisissant dans la liste. - + You must have a value in the ip ban when selecting the ip ban checkbox. Vous devez saisir l'adresse IP du banni en le choisissant dans la liste. - + You must have a value in the clientid ban when selecting the clientid ban checkbox. Vous devez saisir l'ID du client banni en le choisissant dans la liste. @@ -219,22 +371,22 @@ Cette information sera consultable uniquement par les modérateurs et ne sera pa BetaReleaseChannel - - Beta Releases - Versions bêta + + Beta + Beta - + No reply received from the release update server. Le serveur de mise à jour ne répond pas. - + Invalid reply received from the release update server. Réponse reçue du serveur de mise à jour de version invalide . - + No reply received from the file update server. Le serveur de mise à jour de fichier ne répond pas. @@ -242,32 +394,32 @@ Cette information sera consultable uniquement par les modérateurs et ne sera pa CardDatabaseModel - + Name Nom - + Sets Éditions - + Mana cost Coût de mana - + Card type Type de carte - + P/T F/E - + Color(s) Couleur(s) @@ -360,325 +512,621 @@ Cette information sera consultable uniquement par les modérateurs et ne sera pa - CardFrame + CardInfoFrameWidget - + Image Image - + Description Description - + Both - Les deux + + + + + View transformation + - CardInfoText + CardInfoPictureWidget - + + View related cards + + + + + Add card to deck + + + + + Mainboard + + + + + Sideboard + + + + + CardInfoTextWidget + + Name: - Nom : + Nom: - + Related cards: - Cartes associées : + - + Unknown card: - Carte inconnue : + CardItem - + &Move to &Déplacer vers - + &Power / toughness &Force / Endurance + + CardSizeWidget + + + Card Size + Taille de la Carte + + CardZone - + their hand nominative sa main - + %1's hand nominative main de %1 - + their library look at zone sa bibliothèque - + %1's library look at zone bibliothèque de %1 - + of their library top cards of zone, de sa bibliothèque - + of %1's library top cards of zone de la bibliothèque de %1 - + their library reveal zone sa bibliothèque - + %1's library reveal zone bibliothèque de %1 - + their library shuffle sa bibliothèque - + %1's library shuffle bibliothèque de %1 - + their library nominative sa bibliothèque - + %1's library nominative bibliothèque de %1 - + their graveyard nominative son cimetière - + %1's graveyard nominative le cimetière de %1 - + their exile nominative l'exil - + %1's exile nominative la zone d'exil de %1 - + their sideboard look at zone sa réserve - + %1's sideboard look at zone la réserve de %1 - + their sideboard nominative sa réserve - + %1's sideboard nominative la réserve de %1 + + DeckEditorCardInfoDockWidget + + + Card Info + + + + + DeckEditorDatabaseDisplayWidget + + + Search by card name (or search expressions) + + + + + Add to Deck + + + + + Add to Sideboard + + + + + Select Printing + + + + + Show on EDHREC (Commander) + + + + + Show on EDHREC (Card) + + + + + Show Related cards + + + + + Add card to &maindeck + + + + + Add card to &sideboard + + + + + DeckEditorDeckDockWidget + + + Banner Card + + + + + Select Printing + + + + + Deck + + + + + Deck &name: + + + + + &Comments: + + + + + Hash: + + + + + &Increment number + + + + + &Decrement number + + + + + &Remove row + + + + + Swap card to/from sideboard + + + + + DeckEditorFilterDockWidget + + + Filters + + + + + &Clear all filters + + + + + Delete selected + + + + + DeckEditorMenu + + + &Deck Editor + + + + + &New deck + + + + + &Load deck... + + + + + Load recent deck... + + + + + Clear + + + + + &Save deck + + + + + Save deck &as... + + + + + Load deck from cl&ipboard... + + + + + Edit deck in clipboard + + + + + + Annotated + + + + + + Not Annotated + + + + + Save deck to clipboard + + + + + Annotated (No set info) + + + + + Not Annotated (No set info) + + + + + &Print deck... + + + + + &Send deck to online service + + + + + Create decklist (decklist.org) + + + + + Create decklist (decklist.xyz) + + + + + Analyze deck (deckstats.net) + + + + + Analyze deck (tappedout.net) + + + + + &Close + + + + + DeckEditorPrintingSelectorDockWidget + + + Printing Selector + + + DeckEditorSettingsPage - - + + Update Spoilers Mettre à jour les spoilers - - + + Success Réussite - + Download URLs have been reset. Les URLs de téléchargement ont été réinitialisées. - + Downloaded card pictures have been reset. Les images des cartes téléchargées ont été supprimées. - + Error Erreur - + One or more downloaded card pictures could not be cleared. Une ou plusieurs images de cartes téléchargées n'ont pas pu être supprimées. - + Add URL Ajouter une URL - - + + URL: URL : - - + + Edit URL Modifier l'URL - + + Network Cache Size: + + + + + Redirect Cache TTL: + + + + + How long cached redirects for urls are valid for. + + + + + Picture Cache Size: + + + + Add New URL Ajouter Nouveau Lien - + Remove URL Effacer Lien - + + Day(s) + Jour(s) + + + Updating... Mise à jour... - + Choose path Choisir le chemin - + URL Download Priority URL de téléchargement prioritaire - + Spoilers Spoilers - + Download Spoilers Automatically Télécharger automatiquement les spoilers - + Spoiler Location: Emplacement des spoilers : - + Last Change Dernier changement - + Spoilers download automatically on launch Télécharger automatiquement les spoilers au lancement - + Press the button to manually update without relaunching Appuyez sur le bouton pour mettre à jour manuellement sans redémarrer. - + Do not close settings until manual update is complete Ne pas fermer la fenêtre des paramètres avant la mise à jour manuelle complète. - + Download card pictures on the fly Télécharger les images des cartes à la volée - + How to add a custom URL Comment ajouter une URL personnalisée - + Delete Downloaded Images Supprimer les images téléchargées - + Reset Download URLs Réinitialiser les URL de téléchargement - - Downloaded images directory size: - Taille du fichier d'image téléchargées - - - + On-disk cache for downloaded pictures Cache pour les images téléchargées - - Picture cache size: - Taille du cache des images - - - + In-memory cache for pictures not currently on screen Cache en mémoire pour les imges non affichées actuellement @@ -686,16 +1134,218 @@ Cette information sera consultable uniquement par les modérateurs et ne sera pa DeckListModel - + + Count + + + + + Set + + + + Number Nombre - + + Provider ID + + + + Card Carte + + DeckLoader + + + Common deck formats (%1) + + + + + All files (*.*) + + + + + DeckPreviewColorIdentityFilterWidget + + + Mode: Exact Match + + + + + Mode: Includes + + + + + DeckPreviewDeckTagsDisplayWidget + + + Edit tags ... + + + + + DeckPreviewTagDialog + + + Deck Tags Manager + + + + + Manage your deck tags. Check or uncheck tags as needed, or add new ones: + + + + + Add a new tag (e.g., Aggro️) + + + + + Add Tag + + + + + Filter tags... + + + + + OK + + + + + Cancel + + + + + Invalid Input + + + + + Tag name cannot be empty! + + + + + Duplicate Tag + + + + + This tag already exists. + + + + + DeckPreviewWidget + + + Banner Card + + + + + Open in deck editor + + + + + Edit Tags + + + + + Rename Deck + + + + + Save Deck to Clipboard + + + + + Annotated + + + + + Annotated (No set info) + + + + + Not Annotated + + + + + Not Annotated (No set info) + + + + + Rename File + + + + + Delete File + + + + + Set Banner Card + + + + + + New name: + + + + + + Error + + + + + Rename failed + + + + + Delete file + + + + + Are you sure you want to delete the selected file? + + + + + Delete failed + + + DeckStatsInterface @@ -713,181 +1363,217 @@ Cette information sera consultable uniquement par les modérateurs et ne sera pa DeckViewContainer - + Load deck... Charger un deck... - + Load remote deck... Charger un deck distant... - + + Unload deck + Unload deck... + + + + Ready to start Prêt à démarrer - + + Force start + + + + Sideboard unlocked Réserve déverrouillée - + Sideboard locked Réserve verrouillée - - Load deck - Charger un deck - - - + Error Erreur - + The selected file could not be loaded. Le fichier sélectionné n'a pas pu être chargé. + + DialogConvertDeckToCodFormat + + + Deck Format Conversion + + + + + You tried to add a tag to a .txt format deck. + Tags can only be added to .cod format decks. + Do you want to convert the deck to the .cod format? + + + + + Remember and automatically apply choice in the future + + + DlgConnect - + Downloading... Téléchargement - + Known Hosts Hôtes connus - + + Delete the currently selected saved server + + + + Refresh the server list with known public servers Actualiser la liste de serveurs avec des serveurs publics connus - + New Host Nouvel hôte - + Name: Nom  : - + &Host: &Hôte : - + &Port: &Port : - + Player &name: &Nom du joueur : - + P&assword: Mot de p&asse : - + &Save password &Se souvenir du mot de passe - + A&uto connect Connexion a&utomatique - + Automatically connect to the most recent login when Cockatrice opens Se connecter automatiquement avec les identifiants les plus récents à l'ouverture de Cockatrice - + If you have any trouble connecting or registering then contact the server staff for help! Si vous rencontrez des difficultés pour vous connecter ou vous inscrire, contactez le personnel du serveur pour obtenir de l'aide ! - - + + Webpage Page web - + Reset Password Réinitialiser le mot de masse - + + Forgot password? + + + + &Connect &Connecter - + Server Serveur - + Login Identifiants - + Server Contact Contact du serveur - + Connect to Server Se connecter au serveur - + Server URL URL du serveur - + Communication Port Port de communication - + Unique Server Name Nom de serveur unique - + Connection Warning Avertissement de connexion - + You need to name your new connection profile. Vous devez attribuer un nom à votre nouveau profil de connexion. - + Connect Warning Avertissement de connexion - + The player name can't be empty. Le nom du joueur ne peut être vide. @@ -970,27 +1656,37 @@ Cette information sera consultable uniquement par les modérateurs et ne sera pa Spectateurs - + + Starting life total: + + + + + Game setup options + + + + &Clear Effa&cer - + Create game Créer une partie - + Game information Informations sur la partie - + Error Erreur - + Server error. Erreur serveur. @@ -998,92 +1694,92 @@ Cette information sera consultable uniquement par les modérateurs et ne sera pa DlgCreateToken - + &Name: &Nom : - + Token Jeton - + C&olor: C&ouleur : - + white blanc - + blue bleu - + black noir - + red rouge - + green vert - + multicolor multicolore - + colorless incolore - + &P/T: &F/E : - + &Annotation: &Annotation : - + &Destroy token when it leaves the table &Detruire le jeton lorsqu'il quitte le champ de bataille - + Token data Données du jeton - + Show &all tokens Afficher &tous les jetons - + Show tokens from this &deck Afficher les jetons présents dans ce &deck - + Choose token from list Choisir un jeton dans la liste - + Create token Créer un jeton @@ -1129,6 +1825,24 @@ Pour enlever votre avatar actuel, confirmez sans choisir une nouvelle image.L'image choisie est invalide. + + DlgEditDeckInClipboard + + + Edit deck in clipboard + + + + + Error + + + + + Invalid deck list. + + + DlgEditPassword @@ -1171,93 +1885,93 @@ Pour enlever votre avatar actuel, confirmez sans choisir une nouvelle image. DlgEditTokens - + &Name: &Nom : - + C&olor: C&ouleur : - + white blanc - + blue bleu - + black noir - + red rouge - + green vert - + multicolor multicolore - + colorless incolore - + &P/T: &F/E : - + &Annotation: &Annotation : - + Token data Données du jeton - - + + Add token Ajouter un jeton - + Remove token Retirer le jeton - + Edit custom tokens Éditer les jetons personnalisés - + Please enter the name of the token: Entrez le nom du jeton : - + Error Erreur - + The chosen name conflicts with an existing card or token. Make sure to enable the 'Token' set in the "Manage sets" dialog to display them correctly. Le nom choisi est en conflit avec une carte ou un jeton existant. @@ -1295,132 +2009,132 @@ Assurez-vous d'activer l'édition « Fausse édition contenant les je DlgFilterGames - + no limit Sans limite - + 5 minutes 5 minutes - + 10 minutes 10 minutes - + 30 minutes 30 minutes - + 1 hour 1 heure - + 2 hours 2 heures - - Show '&buddies only' games - N'afficher que les parties « &amis uniquement » + + Hide 'buddies only' games + - - Show &full games - Montrer les parties pleines + + Hide full games + - - Show games &that have started - Montrer les parties en cours + + Hide games that have started + - - Show &password protected games - Afficher les parties &protégées par un mot de passe + + Hide password protected games + - - Hide '&ignored user' games - Cacher les parties des utilisateurs &ignorés + + Hide 'ignored user' games + - + &Newer than: &Créées avant : - + Game &description: &Description de la partie : - + &Creator name: Nom du &créateur : - + General Général - + &Game types Types de &partie - + at &least: au &moins : - + at &most: au &plus : - + Maximum player count Nombre de joueurs maximum - + Restrictions Restrictions - + Show games only if &spectators can watch Montrer les parties uniquement si les %spectateurs peuvent observer - + Show spectator password p&rotected games Montrer les parties où les spectateurs doivent ent&rer un mot de passe - + Show only if spectators can ch&at Montrer uniquement si les spectateurs peuvent p&arler - + Show only if spectators can see &hands Montrer uniquement si les spectateurs peuvent voir les &mains - + Spectators Spectateurs - + Filter games Flitrer les parties @@ -1603,27 +2317,28 @@ Assurez-vous d'activer l'édition « Fausse édition contenant les je Les mots de passe ne correspondent pas. + + DlgLoadDeck + + + Load Deck + + + DlgLoadDeckFromClipboard - - &Refresh - &Rafraîchir - - - + Load deck from clipboard Charger le deck depuis le presse-papier - - + Error Erreur - - + Invalid deck list. Liste de Deck invalide. @@ -1631,11 +2346,49 @@ Assurez-vous d'activer l'édition « Fausse édition contenant les je DlgLoadRemoteDeck - + Load deck Charger Deck + + DlgMoveTopCardsUntil + + + Card name (or search expressions): + + + + + Number of hits: + + + + + Auto play hits + + + + + Put top cards on stack until... + + + + + No cards matching the search expression exists in the card database. Proceed anyways? + + + + + Cockatrice + + + + + Invalid filter + + + DlgRegister @@ -1749,12 +2502,12 @@ Your email will be used to verify your account. DlgSettings - + Unknown Error loading card database Erreur inconnue lors du chargement de la base de données de cartes. - + Your card database is invalid. Cockatrice may not function correctly with an invalid database @@ -1771,7 +2524,7 @@ Vous devrez peut-être redémarrer Oracle pour mettre à jour votre base de donn Voulez-vous changer l'emplacement de votre base de données ? - + Your card database version is too old. This can cause problems loading card information or images @@ -1788,7 +2541,7 @@ Généralement, il suffit de redémarrer oracle pour mettre à jour votre base d Voulez-vous changer l'emplacement de votre base de données ? - + Your card database did not finish loading Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues with your cards.xml attached @@ -1801,7 +2554,7 @@ Veuillez ouvrir un ticket sur https://github.com/Cockatrice/Cockatrice/issues av Voulez-vous changer l'emplacement des données ? - + File Error loading your card database. Would you like to change your database location setting? @@ -1810,7 +2563,7 @@ Would you like to change your database location setting? Voulez-vous changer l'emplacement de votre base de données ? - + Your card database was loaded but contains no cards. Would you like to change your database location setting? @@ -1819,7 +2572,7 @@ Would you like to change your database location setting? Voulez-vous changer l'emplacement de votre base de données ? - + Unknown card database load status Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues @@ -1832,59 +2585,59 @@ Veuillez ouvrir un ticket sur https://github.com/Cockatrice/Cockatrice/issues Voulez-vous changer l'emplacement de votre base de données ? - - - + + + Error Erreur - + The path to your deck directory is invalid. Would you like to go back and set the correct path? Le chemin d'accès vers votre répertoire de decks est invalide. Voulez-vous redéfinir le chemin d'accès ? - + The path to your card pictures directory is invalid. Would you like to go back and set the correct path? Le chemin d'accès vers votre répertoire d'illustrations de cartes est invalide. Voulez-vous redéfinir le chemin d'accès ? - + Settings Paramètres - + General Général - + Appearance Apparence - + User Interface Interface utilisateur - + Card Sources Origine de carte - + Chat Chat - + Sound Son - + Shortcuts Raccourcis @@ -1910,166 +2663,169 @@ Voulez-vous changer l'emplacement de votre base de données ? DlgUpdate - + Current release channel Branche logicielle actuelle - + Reinstall Réinstaller - + Cancel Download Annuler le téléchargement - + Open Download Page Ouvrir la page de téléchargement - + Check for Client Updates Vérification des mises à jour - - - - + + + + Error Erreur - + Cockatrice was not built with SSL support, therefore you cannot download updates automatically! Please visit the download page to update manually. Cockatrice n'as pas été construit avec le support du SSL, donc il n'est pas possible de télécharger les mises à jour automatiquement. Veuillez s'il vous plaît visiter la page de téléchargement, pour mettre à jour manuellement. - - Downloading update... - Téléchargement de la mise à jour + + Downloading update: %1 + Downloading update... + - + Checking for updates... Vérification des mises à jour... - + Finished checking for updates Fin de la recherche des mises à jour - + No Update Available Pas de mises à jour disponibles - + Cockatrice is up to date! Cockatrice est à jour ! - + You are already running the latest version available in the chosen release channel. Vous avez déjà la dernière version correspondant à votre branche. - + Current version Version actuelle - + Selected release channel Branche logicielle sélectionnée - - + + Update Available Mise à jour disponible - - + + A new version of Cockatrice is available! Une nouvelle version de Cockatrice est disponible ! - - + + New version Nouvelle version - - + + Released Sortie - - + + Changelog Notes de version - + Do you want to update now? Voulez-vous mettre à jour maintenant ? - - Unfortunately there are no download packages available for your operating system. -You may have to build from source yourself. - Malheureusement, il n'y a pas de paquetage téléchargeable pour votre système d'exploitation. -Vous devez probablement compiler les sources vous-même. + + Unfortunately, the automatic updater failed to find a compatible download. +You may have to manually download the new version. + Unfortunately there are no download packages available for your operating system. +You may have to build from source yourself. + - - Please check the download page manually and visit the wiki for instructions on compiling. - Veuillez consulter la page de téléchargement et le wiki pour les instructions de compilation. + + Please check the <a href="%1">releases page</a> on our Github and download the build for your system. + Please check the download page manually and visit the wiki for instructions on compiling. + - - - + + + Update Error Erreur de mise à jour - + An error occurred while checking for updates: Une erreur est survenur pendant la vérification des mises à jour : - + An error occurred while downloading an update: Une erreur est survenue pendant le téléchargement de la mise à jour : - + Installing... Installation... - + Cockatrice is unable to open the installer. Cockatrice n'a pas pu ouvrir l'installateur. - + Try to update manually by closing Cockatrice and running the installer. Essayer de mettre à jour manuellement en fermant Cockatrice et en exécutant l'installateur. - + Download location Répertoire de téléchargement @@ -2087,6 +2843,14 @@ Vous devez probablement compiler les sources vous-même. Journal de debug + + EdhrecCommanderResponseCommanderDetailsDisplayWidget + + + Salt: + + + FilterBuilder @@ -2098,105 +2862,120 @@ Vous devez probablement compiler les sources vous-même. GameSelector - - - - - - - - - + + + + + + + + + Error Erreur - + Please join the appropriate room first. Veuillez d'abord rejoindre le bon salon. - + Wrong password. Mot de passe incorrect. - + Spectators are not allowed in this game. Les spectateurs ne sont pas autorisés dans cette partie. - + The game is already full. Cette partie est déjà complète. - + The game does not exist any more. La partie n'existe plus. - + This game is only open to registered users. Cette partie n'est accessible qu'aux joueurs enregistrés. - + This game is only open to its creator's buddies. Cette partie n'est accessible qu'aux amis de son créateur. - + You are being ignored by the creator of this game. Vous avez été ignoré par le créateur de la partie. - + + Join Game + + + + + Spectate Game + + + + + Game Information + + + + Join game Rejoindre partie - + Password: Mot de passe : - + Please join the respective room first. Veuillez d'abord rejoindre le bon salon. - + &Filter games &Filtrer les jeux - + C&lear filter Effacer les fi&ltres - + C&reate C&réer - + &Join Re&joindre - + J&oin as spectator Rej&oindre en tant que spectateur - + Games shown: %1 / %2 Parties affichées : %1 / %2 - + Games Parties @@ -2204,100 +2983,100 @@ Vous devez probablement compiler les sources vous-même. GamesModel - + >1 day > 1 jour - + %1%2 hr short age in hours %1%2 h%1%2 h%1%2 h - + new Nouveau - + %1%2 min short age in minutes %1%2 min%1%2 min%1%2 min - + password mot de passe - + buddies only amis uniquement - + reg. users only joueurs enregistrés uniquement - - + + can chat peut discuter - + see hands voir les mains - + can see hands peut voir les mains - + not allowed non autorisé - + Room Salon - + Age Âge - + Description Description - + Creator Créateur - + Type Type - + Restrictions Restrictions - + Players Joueurs - + Spectators Spectateurs @@ -2305,92 +3084,102 @@ Vous devez probablement compiler les sources vous-même. GeneralSettingsPage - + Reset all paths Réinitialiser tous les chemins - + All paths have been reset Les chemins ont été réinitialisés - - + + Choose path Choisir un chemin d'accès - + Personal settings Paramètres personnels - + Language: Langue : - + Paths (editing disabled in portable mode) Chemins (édition impossible en mode portable) - + Paths Chemins d’accès - + + How to help with translations + + + + Decks directory: Répertoire des decks : - + Replays directory: Répertoire des replays : - + Pictures directory: Répertoire des images : - + Card database: Base de données des cartes : - + Custom database directory: Répertoire de la base de données personnalisé - + Token database: Bases de données des jetons : - + Update channel Branche de la mise à jour : - + + Check for client updates on startup + + + + Notify if a feature supported by the server is missing in my client M'avertir si une fonctionnalité supportée par le serveur est manquante sur mon client. - + Automatically run Oracle when running a new version of Cockatrice Lancer Oracle automatiquement quand Cockatrice a été mis à jour. - + Show tips on startup Afficher les astuces au démarrage. @@ -2398,61 +3187,61 @@ Vous devez probablement compiler les sources vous-même. MainWindow - - + + The server has reached its maximum user capacity, please check back later. Le serveur a atteint sa capacité maximale de joueurs simultanés, veuillez réessayer plus tard. - + There are too many concurrent connections from your address. Il y a trop de connexions simultanées depuis votre ordinateur. - + Banned by moderator Banni par un modérateur - + Expected end time: %1 Fin théorique : %1 - + This ban lasts indefinitely. Banni définitivement. - + Scheduled server shutdown. Fermeture prévue du serveur. - - + + Invalid username. Nom d'utilisateur non valide. - + You have been logged out due to logging in at another location. Vous avez avez été déconnecté pour avoir changé d'endroit. - + Connection closed Connexion fermée - + The server has terminated your connection. Reason: %1 Le serveur a coupé votre connexion. Raison: %1 - + The server is going to be restarted in %n minute(s). All running games will be lost. Reason for shutdown: %1 @@ -2465,558 +3254,568 @@ Aucune partie en cours ne sera sauvegardée. Raison de la fermeture : %1 - + Scheduled server shutdown Fermeture prévue du serveur - - + + Success Réussite - + Registration accepted. Will now login. Enregistrement accepté. Connexion en cours. - + Account activation accepted. Will now login. Activation du compte acceptée. Connexion en cours. - + Number of players Nombre de joueurs - + Please enter the number of players. Entrez s'il vous plaît le nombre de joueurs. - - + + Player %1 Joueur %1 - + Load replay Charger replay - + About Cockatrice À propos de Cockatrice - + Version Version - + Cockatrice Webpage Page web de Cockatrice - + Project Manager: Chef de projet: - + Past Project Managers: Anciens chefs de projet: - + Developers: Développeurs : - + Our Developers Nos développeurs - + Help Develop! Aidez à développer ! - + Translators: Traducteurs : - + Our Translators Nos traducteurs - + Help Translate! Aidez à traduire ! - + Support: Assistance : - + Report an Issue Signaler un problème - + Troubleshooting Dépannage - + F.A.Q. F.À.Q. - - - - - - - - - - - - - - - + + + + + + + + + + + + + - - - + + + + + Error Erreur - + Server timeout Délai de la demande dépassé - + Failed Login Connexion échouée - + Your client seems to be missing features this server requires for connection. Il semble que certaines fonctionnalités nécessaires à la connexion à ce serveur sont absentes de votre client. - + To update your client, go to 'Help -> Check for Client Updates'. Pour mettre à jour Cockatrice, allez dans « Aide -> Vérifier les mises à jour' ». - + Incorrect username or password. Please check your authentication information and try again. Nom d'utilisateur ou mot de passe incorrect. Veuillez vérifier vos identifiants et réessayer. - + There is already an active session using this user name. Please close that session first and re-login. Il y a déjà une session ouverte avec le même nom d'utilisateur. Fermez cette session puis reconnectez vous. - - + + You are banned until %1. Vous êtes banni jusqu'au %1 - - + + You are banned indefinitely. Vous êtes banni indéfiniment. - + This server requires user registration. Do you want to register now? Vous devez être enregistré pour accéder à ce serveur. Voulez-vous vous enregistrer ? - + This server requires client IDs. Your client is either failing to generate an ID or you are running a modified client. Please close and reopen your client to try again. Ce serveur requiert un ID de client. Soit votre client a échoué lors de la génération d'un ID, soit vous utilisez un client modifié. Veuillez fermer et rouvrir votre client pour réessayer. - + An internal error has occurred, please close and reopen Cockatrice before trying again. If the error persists, ensure you are running the latest version of the software and if needed contact the software developers. Une erreur interne est survenue. Veuillez fermer et rouvrir Cockatrice avant de réessayer. Si l'erreur persiste, vérifiez que utilisez la dernière version du logiciel, ou contactez les développeurs si nécessaire. - + Account activation Activation du compte - + Your account has not been activated yet. You need to provide the activation token received in the activation email. Votre compte n'a pas encore été activé. Vous devez activer le jeton de l'activation reçu dans le mail d'activation. - + Server Full Serveur complet - + Unknown login error: %1 Erreur de connexion inconnue : %1 - - + + This usually means that your client version is out of date, and the server sent a reply your client doesn't understand. Cela veut généralement dire que votre client n'est plus à jour, et que le serveur a envoyé une réponse que votre client ne comprend pas. - + Your username must respect these rules: Votre nom d'utilisateur doit respecter ces règles : - + is %1 - %2 characters long est long de %1 - %2 caractères - + can %1 contain lowercase characters peut %1 contenir des caractères minuscules - - - - + + + + NOT PAS - + can %1 contain uppercase characters peut %1 contenir des caractères majuscules - + can %1 contain numeric characters peut %1 contenir des caractères numériques - + can contain the following punctuation: %1 peut contenir la ponctuation suivante : %1 - + first character can %1 be a punctuation mark le premier caractère peut %1 être une marque de ponctuation - + no unacceptable language as specified by these server rules: note that the following lines will not be translated Pas de langage inacceptable comme spécifié par ces règles du serveur: - + can not contain any of the following words: %1 ne peut contenir les mots suivants : %1 - + can not match any of the following expressions: %1 ne correspond à aucune des expressions suivantes : %1 - + You may only use A-Z, a-z, 0-9, _, ., and - in your username. Vous pouvez utiliser seulement A-Z, a-z, 0-9, _, ., et - dans votre nom d'utilisateur - - - - - - + + + + + + Registration denied Enregistrement refusé - + Registration is currently disabled on this server L'enregistrement est désactivé sur ce serveur - + There is already an existing account with the same user name. Il existe déjà un compte avec le même nom d'utilisateur. - + It's mandatory to specify a valid email address when registering. Il est obligatoire de spécifier une adresse e-mail valide lors de l'enregistrement. - + It appears you are attempting to register a new account on this server yet you already have an account registered with the email provided. This server restricts the number of accounts a user can register per address. Please contact the server operator for further assistance or to obtain your credential information. Il semble que vous essayez d'enregistrer un nouveau compte alors que vous en avez déjà un enregistré avec cet e-mail sur ce serveur. Ce serveur restreint le nombre de comptes par e-mail. Veuillez contacter l'administrateur du serveur pour obtenir vos identifiants ou plus d'informations. - + Password too short. Mot de passe trop court. - + Registration failed for a technical problem on the server. Enregistrement échoué lié à un problème technique du serveur. - + Unknown registration error: %1 Erreur d'enregistrement inconnue : %1 - + Account activation failed Activation du compte échouée - + Socket error: %1 Erreur de socket : %1 - + You are trying to connect to an obsolete server. Please downgrade your Cockatrice version or connect to a suitable server. Local version is %1, remote version is %2. Vous tentez de vous connecter à un serveur obsolète. Veuillez utiliser une ancienne version de Cockatrice, ou connectez-vous à un serveur approprié. La version locale est %1, la version distante est %2. - + Your Cockatrice client is obsolete. Please update your Cockatrice version. Local version is %1, remote version is %2. Votre client Cockatrice est obsolète. Veuillez mettre à jour votre version de Cockatrice. La version locale est %1, la nouvelle version est %2. - + Connecting to %1... Connexion à %1... - + Registering to %1 as %2... Enregistrement de %1 en tant que %2... - + Disconnected Déconnecté - + Connected, logging in at %1 Connecté, connexion à %1 - - - + + + Requesting forgotten password to %1 as %2... Envoi de la requête de mot de passe oublié à %1 en tant que %2... - + &Connect... &Connecter... - + &Disconnect &Déconnecter - + Start &local game... Démarrer une partie &locale... - + &Watch replay... &Regarder un replay... - - &Deck editor - Éditeur de &deck - - - + &Full screen &Plein écran - + &Register to server... S'enregistrer sur le serveur... - + &Restore password... &Restaurer le mot de passe... - + &Settings... &Paramètres... - + &Exit &Quitter - + A&ctions Actions - + &Cockatrice &Cockatrice - + C&ard Database B&ase de données de cartes - + &Manage sets... &Gérer les éditions... - + Edit custom &tokens... Éditer les &jetons personnalisés… - + Open custom image folder Ouvrir le dossier d'images personnalisées - + Open custom sets folder Ouvrir le dossier d'éditions personnalisées - + Add custom sets/cards Ajouter des éditions / cartes personnalisées - + + Reload card database + + + + + Tabs + + + + &Help A&ide - + &About Cockatrice À propos de Cock&atrice - + &Tip of the Day &Conseil du jour - + Check for Client Updates Vérifier les mises à jour - + Check for Card Updates... Vérifier les mises à jour des cartes… - + View &Debug Log Voir journal de &Debug - + + Open Settings Folder + + + + Show/Hide Montrer/Cacher - + New Version Nouvelle version - + Congratulations on updating to Cockatrice %1! Oracle will now launch to update your card database. Bravo pour avoir mis à jour Cockatrice %1 ! Oracle va maintenant se lancer pour mettre à jour votre base de données de cartes. - + Cockatrice installed Installation de Cockatrice terminée - + Congratulations on installing Cockatrice %1! Oracle will now launch to install the initial card database. Félicitations ! Vous venez d'installer Cockatrice %1 ! Oracle va maintenant se lancer pour installer la base de données de cartes initiale. - + Card database Base de données de cartes - + Cockatrice is unable to load the card database. Do you want to update your card database now? If unsure or first time user, choose "Yes" @@ -3025,29 +3824,29 @@ Voulez-vous la mettre à jour maintenant ? Si vous n’êtes pas sûr, ou si c'est la première fois que vous jouez, choisissez « Oui ». - - + + Yes Oui - - + + No Non - + Open settings Ouvrir les paramètres - + New sets found Nouvelles éditions détectées - + %n new set(s) found in the card database Set code(s): %1 Do you want to enable it/them? @@ -3060,17 +3859,17 @@ Code(s) d'édition(s) : %1 Voulez-vous l'(les) activer ? - + View sets Voir les éditions - + Welcome Bienvenue - + Hi! It seems like you're running this version of Cockatrice for the first time. All the sets in the card database have been enabled. Read more about changing the set order or disabling specific sets and consequent effects in the "Manage Sets" dialog. @@ -3079,58 +3878,59 @@ Toutes les éditions de la base de données de cartes ont été activées. Pour plus d'informations sur la modification de l'ordre des éditions, la désactivation d'éditions spécifiques, et leurs effets, consultez le menu « Gérer les éditions… ». - - + + Information Informations - + A card database update is already running. Une mise à jour de la base de données de cartes est déjà en cours. - + Unable to run the card database updater: Impossible de lancer la mise à jour de la base de données de cartes : - - failed to start. - n'a pas réussi à démarrer. + + Failed to start. The file might be missing, or permissions might be incorrect. + - - crashed. - a planté. + + The process crashed some time after starting successfully. + - - timed out. - s'est déconnecté. + + Timed out. The process took too long to respond. The last waitFor...() function timed out. + - - write error. - erreur d'écriture. + + An error occurred when attempting to write to the process. For example, the process may not be running, or it may have closed its input channel. + - - read error. - erreur de lecture. + + An error occurred when attempting to read from the process. For example, the process may not be running. + - - unknown error. - erreur inconnue. + + Unknown error occurred. + - - The card database updater exited with an error: %1 - L'outil de mise à jour de la base de données de cartes s'est arrêté avec l'erreur: %1 + + The card database updater exited with an error: +%1 + - + This server supports additional features that your client doesn't have. This is most likely not a problem, but this message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -3141,55 +3941,55 @@ Ce n'est probablement pas un problème, mais ce message peut signifier qu&a Pour mettre à jour votre client, allez dans « Aide -> Vérifier les mises à jour ». - - - - - + + + + + Load sets/cards Charger des éditions/cartes - + Selected file cannot be found. Le fichier sélectionné n'a pas pu être trouvé. - + You can only import XML databases at this time. Il n'est actuellement possible que d'importer des bases de données XML. - + The new sets/cards have been added successfully. Cockatrice will now reload the card database. Les nouvelles éditions/cartes ont été ajoutées avec succès. Cockactrice va maintenant recharger la base de données de cartes. - + Sets/cards failed to import. Échec de l'importation des éditions/cartes. - - - + + + Reset Password Réinitialiser le mot de passe - + Your password has been reset successfully, you can now log in using the new credentials. Votre mot de passe a été réinitialisé avec succès. Vous pouvez maintenant vous connecter en utilisant vos nouveaux identifiants. - + Failed to reset user account password, please contact the server operator to reset your password. Échec de la réinitialisation du mot de passe du compte. Veuillez contacter l'administrateur du serveur pour réinitialiser votre mot de passe. - + Activation request received, please check your email for an activation token. Demande d'activation reçue, vérifiez votre courrier électronique pour un jeton d'activation. @@ -3463,179 +4263,190 @@ Cockactrice va maintenant recharger la base de données de cartes. - %1 is looking at the top %3 card(s) %2. + %1 is looking at the %4 %3 card(s) %2. + %1 is looking at the top %3 card(s) %2. top card for singular, top %3 cards for plural - %1 regarde la %3 carte du dessus de sa bibliothèque %2.%1 regarde les %3 cartes du dessus de sa bibliothèque %2.%1 regarde la / les %3 carte(s) du dessus de sa bibliothèque %2. + - + + bottom + + + + + top + + + + %1 turns %2 face-down. %1 retourne %2 face cachée. - + %1 turns %2 face-up. %1 retourne %2 face visible. - + The game has been closed. La partie a été fermée. - + The game has started. La partie commence. - + %1 has joined the game. %1 a rejoint la partie. - + %1 is now watching the game. %1 est maintenant spectateur. - + You have been kicked out of the game. Vous avez été expulsé de la partie. - + %1 has left the game (%2). %1 a quitté la partie (%2). - + %1 is not watching the game any more (%2). %1 n'observe plus la partie (%2). - + %1 is not ready to start the game any more. %1 n'est plus prêt à démarrer la partie. - + %1 shuffles their deck and draws a new hand of %2 card(s). %1 mélange son deck et pioche une nouvelle main de %2 carte.%1 mélange sa bibliothèque et pioche une nouvelle main de %2 cartes.%1 mélange sa bibliothèque et pioche une nouvelle main de %2 carte(s). - + %1 shuffles their deck and draws a new hand. %1 mélange sa bibliothèque et pioche une nouvelle main. - + You are watching a replay of game #%1. Vous regardez un replay de la partie n° %1. - + %1 is ready to start the game. %1 est prêt à démarrer la partie. - + cards an unknown amount of cards cartes - + %1 card(s) a card for singular, %1 cards for plural %1 carte%1 cartes%1 carte(s) - + %1 lends %2 to %3. %1 prêtes %2 à %3. - + %1 reveals %2 to %3. %1 révèle %2 à %3. - + %1 reveals %2. %1 révèle %2. - + %1 randomly reveals %2%3 to %4. %1 révèle au hasard %2%3 à %4. - + %1 randomly reveals %2%3. %1 révèle au hasard %2%3. - + %1 peeks at face down card #%2. %1 regarde furtivement la carte face cachée n° %2. - + %1 peeks at face down card #%2: %3. %1 regarde furtivement la carte face cachée n° %2 : %3. - + %1 reveals %2%3 to %4. %1 révèle %2%3 à %4. - + %1 reveals %2%3. %1 révèle %2%3. - + %1 reversed turn order, now it's %2. %1 a inversé l'ordre des tours, c'est maintenant %2. - + reversed inversé - + normal normal - + Heads Face - + Tails Pile - + %1 flipped a coin. It landed as %2. %1 a lancé une pièce. Il a fait %2. - + %1 rolls a %2 with a %3-sided die. %1 a fait %2 avec un dé %3 faces. - + %1 flips %2 coins. There are %3 heads and %4 tails. %1 lances %2 pièces. Il y'a %3 faces et %4 piles. - + %1 rolls a %2-sided dice %3 times: %4. %1 lances un dé à %2-faces %3 fois: %4. @@ -3755,17 +4566,17 @@ Cockactrice va maintenant recharger la base de données de cartes. %1 mélange les cartes %3 - %4 de %2. - + %1 unattaches %2. %1 détache %2. - + %1 undoes their last draw. %1 annule sa dernière pioche. - + %1 undoes their last draw (%2). %1 annule sa dernière pioche (%2). @@ -3773,110 +4584,110 @@ Cockactrice va maintenant recharger la base de données de cartes. MessagesSettingsPage - + Word1 Word2 Word3 Mot1 Mot2 Mot3 - + Add New Message Ajouter un message - + Edit Message Éditer le message - + Remove Message Supprimer le message - + Add message Ajouter message - - + + Message: Message : - + Edit message Éditer le message - + Chat settings Paramètres du chat - + Custom alert words Mots d'alertes personnalisées - + Enable chat mentions Activer les mentions dans le chat - + Enable mention completer Activer les mentions dans le chat - + In-game message macros Macros de message en jeu - + How to use in-game message macros Comment utiliser les macro pour les messages en jeu - + Ignore chat room messages sent by unregistered users Ignorer les messages dans le chat principal envoyés par des joueurs non enregistrés. - + Ignore private messages sent by unregistered users Ignorer les messages privés envoyés par des joueurs non enregistrés. - - + + Invert text color Inverser la couleur du texte - + Enable desktop notifications for private messages Activer les notifications de bureau pour les messages privés. - + Enable desktop notification for mentions Activer les notifications de bureau pour les mentions. - + Enable room message history on join Activer l'historique des messages du chat principal dès la connexion - - + + (Color is hexadecimal) (La couleur est hexadécimale) - + Separate words with a space, alphanumeric characters only Séparer les mots avec un espace, seulement les caractères alphanumériques @@ -3934,6 +4745,24 @@ Cockactrice va maintenant recharger la base de données de cartes. Identité couleur + + OverlapControlWidget + + + Cards to overlap: + + + + + Overlap percentage: + + + + + Overlap direction: + + + Phase @@ -4000,57 +4829,57 @@ Cockactrice va maintenant recharger la base de données de cartes. PhasesToolbar - + Untap step Étape de dégagement - + Upkeep step Étape d'entretien - + Draw step Étape de pioche - + First main phase Première phase principale - + Beginning of combat step Étape de début de combat - + Declare attackers step Étape de déclaration des attaquants - + Declare blockers step Étape de déclaration des bloqueurs - + Combat damage step Étape d'assignation des blessures de combat - + End of combat step Étape de fin de combat - + Second main phase Seconde phase principale - + End of turn step Phase de fin de tour @@ -4058,7 +4887,7 @@ Cockactrice va maintenant recharger la base de données de cartes. PictureLoader - + en code for scryfall's language property, not available for all languages fr @@ -4067,647 +4896,781 @@ Cockactrice va maintenant recharger la base de données de cartes. Player - + Reveal top cards of library Révéler les cartes du dessus de la bibliothèque - - - - - - - - - + + + + + + + + + + + + Number of cards: (max. %1) Nombre de cartes : (max. %1) - + &View graveyard &Voir le cimetière - + &View exile Voir les cartes e&xilées - + Player "%1" Joueur « %1 » - - - - + + + + &Graveyard &Cimetière - - - - + + + + &Exile &Exil - + &Move hand to... Déplacer la &main vers… - - - + + + &Top of library &Dessus de la bibliothèque - - - + + + &Bottom of library Dessous de la &bibliothèque - + &Move graveyard to... Déplacer le cimetière vers... - - - - + + + + &Hand &Main - + &Move exile to... &Déplacer les cartes exilées vers... - + &View library &Voir la bibliothèque - + &View hand Voir &main - + View &top cards of library... Voir les cartes du &dessus de la bibliothèque... - + Reveal &library to... Révéler &la bibliothèque à... - + &Always reveal top card &Toujours révéler la carte du dessus - + &View sideboard &Voir la réserve - + &Draw card &Piocher une carte - + D&raw cards... P&iocher plusieurs cartes... - + &Undo last draw Annu&ler la dernière pioche - + Take &mulligan Faire un &mulligan - - &Shuffle - Mél&anger - - - + Play top card &face down Jouer la carte du dessus &face cachée - + Move top card to grave&yard Mettre la carte du dessus dans le cimetière - + Move top card to e&xile Exiler la carte du dessus - + Move top cards to &graveyard... Mettre les cartes du dessus dans le &cimetière... - + Move top cards to &exile... E&xiler les cartes du dessus... - + Put top card on &bottom Mettre la carte du dessus au-&dessous - + + View bottom cards of library... + + + + Lend library to... Prêter la bibliothèque à... - + + Shuffle + + + + Put top cards on stack &until... Take top cards &until... - + + Shuffle top cards... + + + + + Shuffle bottom cards... + + + + &Reveal hand to... &Révéler la main à... - + Reveal r&andom card to... Révéler une c&arte au hasard à... - + Reveal random card to... Révéler une carte au hasard à... - + &Sideboard Ré&serve - + &Library &Bibliothèque - + &Counters Mar&queurs - + &Untap all permanents Dé&gager tous les permanents - + R&oll die... Lancer un &dé... - + &Create token... &Créer un jeton... - + C&reate another token C&réer un autre jeton - + Cr&eate predefined token C&réer un jeton prédéfini - - + + &All players &Tous les joueurs - + S&ay D&ire - + + &Select All + &Sélectionner Tout + + + + S&elect Row + + + + + S&elect Column + + + + &Play &Jouer - + &Hide &Cacher - + Play &Face Down Jouer &face cachée - + &Tap / Untap Turn sideways or back again &Engager / dégager - + Toggle &normal untapping Activer / désactiver dégagement &normal - + T&urn Over Turn face up/face down Reto&urner - + &Peek at card face &Regarder furtivement la carte face cachée - + &Clone &Cloner - + Attac&h to card... Attac&her à une carte... - + Unattac&h Détac&her - + &Draw arrow... &Tracer une flèche... - + &Increase power &Augmenter la force - + &Decrease power &Diminuer la force - + I&ncrease toughness A&ugmenter l'endurance - + D&ecrease toughness D&iminuer l'endurance - + In&crease power and toughness Au&gmenter la force et l'endurance - + Dec&rease power and toughness Di&minuer la force et l'endurance - + Increase power and decrease toughness Augmenter la force et diminuer l'endurance - + Decrease power and increase toughness Diminuer la force et augmenter l'endurance - + Set &power and toughness... Changer la &force et l'endurance... - + Reset p&ower and toughness Réinitialiser la f&orce et l'endurance - + &Set annotation... &Annoter... - + Red Rouge - + Yellow Jaune - + Green Vert - + &Add counter (%1) &Ajouter un marqueur (%1) - + &Remove counter (%1) &Retirer un marqueur (%1) - + &Set counters (%1)... &Changer le nombre de marqueurs (%1)... - + &Top of library in random order - + X cards from the top of library... X carte(s) du dessus de la bibliothèque... - + &Bottom of library in random order &Dessous de la bibliothèque dans un ordre aléatoire - + View top cards of library Voir les cartes du dessus de la bibliothèque - + + View bottom cards of library + + + + + Shuffle top cards of library + + + + + Shuffle bottom cards of library + + + + Which position should this card be placed: Dans quel position cette carte sera placé - + (max. %1) (max. %1) - + Draw hand Piocher une main - + 0 and lower are in comparison to current hand size 0 et moins sont en comparaison avec la taille actuelle de la main - + Draw cards Piocher plusieurs cartes - - + + Number: Nombre : - + Move top cards to grave Mettre les cartes du dessus dans le cimetière - + Reveal &top cards to... Révéler les cartes du dessus à... - + &Top of library... &Dessus de la bibliothèque - + &Bottom of library... &Dessous de la bibliothèque - + &Always look at top card &Toujours révéler la carte du dessus - + &Open deck in deck editor &Ouvrir le deck dans l'éditeur - + &Play top card &Jouer la carte du dessus de la bibliothèque - + &Draw bottom card &Piocher la carte du dessous - + D&raw bottom cards... P&iocher la carte du dessous - + &Play bottom card &Jouer la carte du dessous - + Play bottom card &face down Jouer la carte du dessous &face cachée - + Move bottom card to grave&yard Mettre la carte du dessous dans le &cimetière - + Move bottom card to e&xile E&xiler les cartes du dessous - + Move bottom cards to &graveyard... Mettre les cartes du dessous dans le cime&tière... - + Move bottom cards to &exile... &Exiler les cartes du dessus... - + Put bottom card on &top Mettre la carte du dessous au-&dessus - + Selec&ted cards Cartes &sélectionnées - + Move top cards to exile Exiler les cartes du dessus - + Move bottom cards to grave Mettre les cartes du dessous dans le cimetière - + Move bottom cards to exile Exiler les cartes du dessus - + Draw bottom cards Piocher la carte du dessous - - + + C&reate another %1 token Créer un autre jeton %1 - + Create tokens Créer des jetons - + Place card X cards from top of library Mettre les X cartes du dessus de la bibliothèque - + Change power/toughness Changer la force et l'endurance - + Change stats to: Changer les stats à : - + Set annotation Annoter - + Please enter the new annotation: Veuillez entrer la nouvelle annotation : - + Set counters Nombre de marqueurs - + Re&veal to... Révéler à... - + View related cards Voir les cartes associées - + Token: Jeton : - + All tokens Tout les jetons + + PrintingSelector + + + Display Navigation Buttons + + + + + <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. + + + + + PrintingSelectorCardOverlayWidget + + + Preference + + + + + Pin Printing + + + + + Unpin Printing + + + + + Show Related cards + + + + + PrintingSelectorCardSearchWidget + + + Search by set name or set code + + + + + PrintingSelectorCardSelectionWidget + + + Previous Card in Deck + + + + + Next Card in Deck + + + + + PrintingSelectorCardSortingWidget + + + Alphabetical + Alphabétiquement + + + + Preference + Préférences + + + + Release Date + Date de Sortie + + + + + Descending + Descendant + + + + Ascending + Ascendant + + QMenuBar - + Services Services - + Hide %1 Cacher %1 - + Hide Others Cacher les autres - + Show All Montrer tout - + Preferences... Préférences... - + Quit %1 Quitter %1 - + About %1 À propos %1 @@ -4715,38 +5678,32 @@ Cockactrice va maintenant recharger la base de données de cartes. QObject - + Cockatrice card database (*.xml) Base de données de cartes Cockatrice (*.xml) - - + All files (*.*) Tous les fichiers (*.*) - + Cockatrice replays (*.cor) Replays Cockatrice (*.cor) - - Common deck formats (*.cod *.dec *.dek *.txt *.mwDeck) - Formats de deck communs (*.cod *.dec *.dek *.txt *.mwDeck) - - - + Maindeck Maindeck - + Sideboard Sideboard - + Tokens Jetons @@ -4754,92 +5711,92 @@ Cockactrice va maintenant recharger la base de données de cartes. QPlatformTheme - + OK OK - + Save Sauvegarder - + Save All Sauvegarder tout - + Open Ouvrir - + &Yes &Oui - + Yes to &All Dire oui à tout - + &No &Non - + N&o to All Dire non à tout - + Abort Abandonner - + Retry Réessayer - + Ignore Ignorer - + Close Fermer - + Cancel Annuler - + Discard Effacer - + Help Aide - + Apply Appliquer - + Reset Réinitialiser - + Restore Defaults Remettre à zéro @@ -4847,17 +5804,17 @@ Cockactrice va maintenant recharger la base de données de cartes. RemoteDeckList_TreeModel - + Name Nom - + ID ID - + Upload time Temps de chargement @@ -4865,32 +5822,32 @@ Cockactrice va maintenant recharger la base de données de cartes. RemoteReplayList_TreeModel - + ID ID - + Name Nom - + Players Joueurs - + Keep Garder - + Time started Le temps a commencé - + Duration (sec) Durée (sec) @@ -4936,34 +5893,34 @@ Cockactrice va maintenant recharger la base de données de cartes. SequenceEdit - + Choose an action from the table - + Hit the key/combination of keys you want to set for this action Choisissez la combinaison de touches que vous voulez utiliser pour cette action - - Shortcut already in use - Raccourci déjà utilisé - - - + Invalid key Touche invalide - + + Shortcut already in use by: + + + + Clear Effacer - + Restore default - + Options par défaut @@ -4997,72 +5954,80 @@ Cockactrice va maintenant recharger la base de données de cartes. ShortcutSettingsPage - - + + Restore all default shortcuts Réinitialiser tous les raccourcis - + Do you really want to restore all default shortcuts? Êtes-vous sûr de vouloir réinitialiser tous les raccourcis ? - + Clear all default shortcuts Effacer tous les raccourcis par défaut - + Do you really want to clear all shortcuts? Êtes-vous sûr de vouloir effacer tous les raccourcis ? - - Action - Action - - - - Shortcut - Raccourci - - - + Section: Section : - + Action: Action : - + Shortcut: Raccourci : - + How to set custom shortcuts Comment assigner des raccourcis personnalisés - + Clear all shortcuts + + + Search by shortcut name + + + + + ShortcutTreeView + + + Action + Action + + + + Shortcut + Raccourci + ShortcutsSettings - + Your configuration file contained invalid shortcuts. Please check your shortcut settings! Votre fichier de configuration contenait des raccourcis invalides. Merci de vérifier vos paramètres de raccourci ! - + The following shortcuts have been set to default: Les raccourcis suivants ont été définis par défaut: @@ -5072,17 +6037,17 @@ Please check your shortcut settings! ShutdownDialog - + &Reason for shutdown: &Raison de la fermeture : - + &Time until shutdown (minutes): &Temps avant la fermeture (minutes): - + Shut down server Fermer le serveur @@ -5090,27 +6055,27 @@ Please check your shortcut settings! SoundSettingsPage - + Enable &sounds Activer les &sons - + Current sounds theme: Thème sonore actuel: - + Test system sound engine Tester les effets sonores - + Sound settings Paramètres de son - + Master volume Volume principal @@ -5118,48 +6083,48 @@ Please check your shortcut settings! SpoilerBackgroundUpdater - + Spoilers season has ended La saison des spoilers est terminée - + Deleting spoiler.xml. Please run Oracle Suppression de spoiler.xml. S'il vous plaît exécuter Oracle - - + + Spoilers download failed Échec du téléchargement des spoilers - + No internet connection Pas de connexion Internet - + Error Erreur - + Spoilers already up to date Spoilers déjà à jour - + No new spoilers added Aucun nouveau spoilers ajouté - + Spoilers have been updated! Les spoilers ont été mis à jour! - + Last change: Dernier changement: @@ -5167,638 +6132,594 @@ Please check your shortcut settings! StableReleaseChannel - - Stable Releases - Version stable + + Default + - + No reply received from the release update server. Aucune réponse reçue de la part du serveur de mise à jour. - + Invalid reply received from the release update server. Réponse invalide reçue de la part du serveur de mise à jour. - + No reply received from the tag update server. Aucune réponse reçue de la part du serveur de mise à jour des tags. - + Invalid reply received from the tag update server. Réponse invalide reçue de la part du serveur de mise à jour des tags. + + TabAccount + + + Add to Buddy List + + + + + Add to Ignore List + + + + + Account + + + TabAdmin - + Administration Administration - + Update server &message Mettre à jour le &message du serveur - + &Shut down server &Fermer le serveur - + &Reload configuration &Recharger la configuration - + Server administration functions Fonctions d'administration du serveur - + + Server moderator functions + + + + + Replay ID + + + + + Grant Replay Access + + + + + Username to Activate + + + + + Force Activate User + + + + &Unlock functions &Débloquer fonctions - + &Lock functions &Bloquer fonctions + + + + Success + + + + + Replay access granted + + + + + + + + + Error + + + + + Unable to grant replay access. Replay ID invalid + + + + + Unable to grant replay access. Internal error + + + + + User successfully activated + + + + + Unable to activate user. Username invalid + + + + + Unable to activate user. User already active + + + + + Unable to activate user. Internal error + + TabDeckEditor - - Search by card name (or search expressions) - Search by card name - - - - - Add to Deck - Ajouter au deck - - - - Add to Sideboard - Ajouter à la réserve - - - - Show Related cards - Afficher les cartes associées - - - - &Clear all filters - &Effacer tous les filtres - - - - Delete selected - Enlever la sélection - - - - Deck &name: - &Nom du deck : - - - - &Comments: - &Commentaires : - - - - Hash: - Empreinte : - - - - &New deck - &Nouveau deck - - - - &Load deck... - Charger un deck... - - - - &Save deck - &Sauvegarder le deck - - - - Save deck &as... - S&auvegarder le deck sous... - - - - Load deck from cl&ipboard... - Charger un deck depuis le presse-pap&ier... - - - - Save deck to clipboard - Copier le deck dans le presse-papier - - - - Annotated - Avec annotations - - - - Not Annotated - Sans annotation - - - - &Print deck... - Im&primer le deck... - - - - &Send deck to online service - Envoyer le deck au service en &ligne - - - - Create decklist (decklist.org) - Créer une liste de deck (decklist.org) - - - - Analyze deck (deckstats.net) - Analyser le Deck (deckstats.net) - - - - Analyze deck (tappedout.net) - Analyser le deck (tappedout.net) - - - - &Close - &Fermer - - - - Add card to &maindeck - Ajouter la carte au &deck - - - - Add card to &sideboard - Ajouter carte à la ré&serve - - - - &Remove row - &Retirer la flèche - - - - &Increment number - Augmenter quant&ité - - - - &Decrement number - &Diminuer quantité - - - - &Deck Editor - &Éditeur de deck - - - - + Card Info Infos de la carte - - + Deck Bibliothèque - - + Filters Filtres - + &View &Voir - - - + + Printing + Imprimer + + + + + + Visible Visible - - - + + + + Floating Flottant - + Reset layout Réinitialiser l'interface - + Deck: %1 Deck : %1 - - - Are you sure? - Êtes-vous sûr ? - - - - The decklist has been modified. -Do you want to save the changes? - Le deck a été modifié. -Voulez-vous enregistrer les modifications ? - - - - Load deck - Charger deck - - - - - - - - - Error - Erreur - - - - The deck could not be saved. - Le deck n'a pas pu être enregistré. - - - - Could not save remote deck - N'a pas pu sauvegarder le deck distant - - - - - The deck could not be saved. -Please check that the directory is writable and try again. - Le deck n'a pas pu être enregistré. -Vérifiez que le répertoire ne soit pas en lecture seule et réessayez. - - - - Save deck - Sauvegarder le deck - - - - There are no cards in your deck to be exported - Il n'y a pas de cartes dans le deck à exporter - - - - No deck was selected to be saved. - Aucun deck n'a été sélectionné pour être sauvegardé. - TabDeckStorage - - Deck storage - Stockage de deck - - - + Local file system Système de fichier local - + Server deck storage Serveur de stockage de deck - - + + Open in deck editor Ouvrir dans l'éditeur de deck - + + Rename deck or folder + + + + Upload deck Envoyer deck - + Download deck Télécharger deck - - + + + + New folder Nouveau dossier - - + + Delete Supprimer - - + + Open decks folder + + + + + Rename local folder + + + + + Rename local file + + + + + New name: + + + + + + + Error Érreur - + + Rename failed + + + + + Invalid deck file Fichier de deck invalide - + Enter deck name Entrez le nom du deck - + This decklist does not have a name. Please enter a name: Ce deck n'a pas de nom. Veuillez entrer un nom: - + Unnamed deck Deck sans nom - + Failed to upload deck to server Erreur lors de l'importation du deck sur le serveur - + Delete local file Supprimer fichier local - - - - Are you sure you want to delete "%1"? - Êtes-vous sûr de vouloir supprimer « %1 » ? + + Are you sure you want to delete the selected files? + - + + Delete remote decks + + + + + Are you sure you want to delete the selected decks? + + + + + Name of new folder: Nom du nouveau dossier : - - Delete remote folder - Supprimer le dossier local + + Deck Storage + + + + + TabDeckStorageVisual + + + Visual Deck Storage + - - Delete remote deck - Supprimer le deck local + + Error + + + + + Could not open deck at %1 + + + + + TabEdhRec + + + EDHREC: + TabGame - - - + + + Replay Replay - - + + Game Partie - - + + Player List Liste des joueurs - - + + Card Info Infos de la carte - - + + Messages Messages - - + + Replay Timeline Historique des replays - + &Phases &Phases - + &Game &Partie - + Next &phase Étape &suivante - + Next phase with &action Étape suivante avec &action - + Next &turn &Tour suivant - + Reverse turn order Inverser l'ordre des tours - + &Remove all local arrows &Retirer toutes les flèches locales - + Rotate View Cl&ockwise Pivoter la vue dans le sens h&oraire - + Rotate View Co&unterclockwise Pivoter la vue dans le sens &anti-horaire - + Game &information &Informations sur la partie - + + Un&concede + + + + &Concede &Concéder - + &Leave game &Quitter la partie - + C&lose replay Fermer &le replay - + &Focus Chat &Focus sur le chat - + &Say: &Dire : - + &View &Voir - - - - + + + + Visible Visible - - - - + + + + Floating Flottant - + Reset layout Réinitialiser l'interface - + Concede Concéder - + Are you sure you want to concede this game? Êtes-vous sûr de vouloir concéder la partie ? - + Unconcede Rester dans la partie - + You have already conceded. Do you want to return to this game? Vous avez déjà concédé la partie. Voulez-vous la reprendre ? - + Leave game Quitter la partie - + Are you sure you want to leave this game? Êtes-vous sûr de vouloir quitter la partie ? - + You are flooding the game. Please wait a couple of seconds. Vous floodez la partie. Veuillez patienter quelques secondes. - + A player has joined game #%1 Un joueur a rejoint la partie #%1. - + %1 has joined the game %1 a rejoint la partie. - + kicked by game host or moderator Éjecté par l'hôte de la partie ou un modérateur - + player left the game joueur a quitté la partie - + player disconnected from server joueur déconnecté du serveur - + reason unknown raison inconnue - + You have been kicked out of the game. Vous avez été expulsé de la partie. @@ -5811,153 +6732,153 @@ Veuillez entrer un nom: Journaux - - - + + + Time;SenderName;SenderIP;Message;TargetID;TargetName Durée;Expéditeur;IPExpéditeur;Message;IDCible;NomCible - + Room Logs Journaux du salon - + Game Logs Journaux de la partie - + Chat Logs Journaux du chat - - + + Error Erreur - + You must select at least one filter. Vous devez sélectionner au moins un filtre. - + You have to select a valid number of days to locate. Vous devez sélectionner un nombre valide de jours à identifier. - + Username: Nom d'utilisateur: - + IP Address: Adresse IP : - + Game Name: Nom de la partie : - + GameID: ID de la partie : - + Message: Message : - + Main Room Salon principal - + Game Room Salon de jeu - + Private Chat Chat privé - + Past X Days: Les X derniers jours : - + Today Aujourd'hui - + Last Hour La dernière heure - + Maximum Results: Résultats maximum : - + At least one filter is required. The more information you put in, the more specific your results will be. Au moins un filtre est requis. Plus vous entrez d'informations, meilleurs seront les résultats. - + Get User Logs Obtenir les journaux de l'utilisateur - + Clear Filters Effacer les filtres - + Filters Filtres - + Log Locations Emplacement des journaux - + Date Range Intervalle de dates - + Maximum Results Résultats maximum - - + + Message History Historique des messages - + Failed to collect message history information. Impossible de récupérer l'historique des messages. - + There are no messages for the selected filters. Il n'y a pas de messages pour les filtres sélectionnés. @@ -5980,22 +6901,22 @@ Plus vous entrez d'informations, meilleurs seront les résultats.%1 - Chat privé - + This user is ignoring you, they cannot see your messages in main chat and you cannot join their games. Cet utilisateur vous ignore, il ne peut pas voir vos messages dans le chat principal et vous ne pouvez pas rejoindre ses parties. - + Private message from Message privé de - + %1 has left the server. %1 a quitté le serveur. - + %1 has joined the server. %1 a rejoint le serveur. @@ -6003,117 +6924,153 @@ Plus vous entrez d'informations, meilleurs seront les résultats. TabReplays - - Game replays - Replays de jeux - - - + Local file system Système de fichier local - + Server replay storage Stockage du serveur de replays - - + + Watch replay Voir replay - - + + Rename + + + + + + New folder + Nouveau fichier + + + + Delete Supprimer - + + Open replays folder + + + + Download replay Télécharger replay - + Toggle expiration lock Changer le verrouillage de l'expiration - + + Rename local folder + + + + + Rename local file + + + + + New name: + Nouveau nom: + + + + Error + + + + + Rename failed + + + + + Name of new folder: + Nom du nouveau fichier: + + + Delete local file Supprimer fichier local - - Are you sure you want to delete "%1"? - Êtes-vous sûr de vouloir supprimer « %1 » ? + + Are you sure you want to delete the selected files? + Êtes-vous certain de vouloir supprimer les fichiers sélectionnés? - - Downloading Replays - Télécharger des replays + + Are you sure you want to delete the selected replays? + Êtes-vous certain de vouloir supprimer cet enregistrement? - - Folder download is not yet supported. Please download replays individually. - Le téléchargement de dossier n'est pas encore pris en charge. Veuillez télécharger les replays individuellement - - - + Delete remote replay Supprimer le replay distant - - Are you sure you want to delete the replay of game %1? - Êtes-vous sûr de vouloir supprimer le replay de la partie %1 ? + + Game Replays + TabRoom - + &Say: &Dire : - + Chat Chat - + &Room &Salon - + &Leave room &Quitter le salon - + &Clear chat Effacer le &Chat - + Chat Settings... Paramètres du chat... - + mentioned you. vous a mentionné. - + Click to view Cliquer pour voir - + You are flooding the chat. Please wait a couple of seconds. Vous floodez le chat. Veuillez patienter quelques secondes. @@ -6126,30 +7083,30 @@ Plus vous entrez d'informations, meilleurs seront les résultats.Serveur - - - - + + + + Error Erreur - + Failed to join the server room: it doesn't exist on the server. Impossible de rejoindre le salon: il n'existe pas sur le serveur - + The server thinks you are in the server room but your client is unable to display it. Try restarting your client. Le serveur considère que vous êtes dans le salon mais votre client est incapable de l'afficher. Essayez en relançant votre client. - + You do not have the required permission to join this server room. Vous n'avez pas la permission requise pour rejoindre ce salon. - + Failed to join the server room due to an unknown error: %1. Impossible de rejoindre le salon à cause d'un erreur inconnue: %1 @@ -6157,32 +7114,72 @@ Plus vous entrez d'informations, meilleurs seront les résultats. TabSupervisor - + + Deck Editor + + + + + &Visual Deck Storage + + + + + Server + + + + + Account + + + + + Deck Storage + + + + + Game Replays + + + + + Administration + + + + + Logs + + + + Are you sure? Êtes-vous sûr ? - + There are still open games. Are you sure you want to quit? Il y a encore des parties en cours. Êtes-vous sûr de vouloir quitter ? - + Click to view Cliquer pour regarder - + Your buddy %1 has signed on! Votre ami %1 s'est inscrit ! - + Unknown Event Événement inconnu - + The server has sent you a message that your client does not understand. This message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -6193,63 +7190,45 @@ Ce message peut signifier qu'il y a une nouvelle version de Cockatrice disp Pour mettre à jour votre client, allez dans « Aide -> Vérifier les mises à jour ». - + Idle Timeout Temporisation inactive - + You are about to be logged out due to inactivity. Vous allez d'être déconnecté en raison de l'inactivité. - + Promotion Promotion - + You have been promoted. Please log out and back in for changes to take effect. Vous avez été promu modérateur. Veuillez vous reloguer pour que les changements prennent effet. - + Warned Averti - + You have received a warning due to %1. Please refrain from engaging in this activity or further actions may be taken against you. If you have any questions, please private message a moderator. You avez reçu un avertissement pour la raison : %1. Merci de ne pas recommencer ou d'autres mesures peuvent être prises contre vous. Si vous avez des questions, veuillez contacter un modérateur en privé. - + You have received the following message from the server. (custom messages like these could be untranslated) Vous avez reçu le message suivant du serveur. (il se peut que les messages personnalisés comme celui-ci ne soient pas traduits) - - TabUserLists - - - Account - Compte - - - - Add to Buddy List - Ajouter à la liste d'amis - - - - Add to Ignore List - Ajouter à la liste noire - - TappedOutInterface @@ -6335,173 +7314,184 @@ Merci de ne pas recommencer ou d'autres mesures peuvent être prises contre UserContextMenu - + User &details &Détails utilisateur - + Private &chat &Chat privé - + Show this user's &games Voir les parties du &joueur - + Add to &buddy list Ajouter à la liste d'&amis - + Remove from &buddy list Retirer de la liste d'&amis - + Add to &ignore list Ajouter à la l&iste noire - + Remove from &ignore list Retirer de la l&iste noire - + Kick from &game Exclure de la &partie - + Warn user Avertir le joueur - + View user's war&n history Voir l'historique des avertisseme&nts du joueur - + Ban from &server Bannir du &serveur - + View user's &ban history Voir l'historique de &bannissement du joueur - + &Promote user to moderator &Promouvoir d'utilisateur à modérateur - + Dem&ote user from moderator Rétr&ograder de modérateur à utilisateur - + Promote user to &judge Promouvoir utilisateur à &arbitre - + Demote user from judge Destituer arbitre - + + View admin notes + + + + %1's games Parties de %1 - - - + + + Ban History Historique des bannissements - + Ban Time;Moderator;Ban Length;Ban Reason;Visible Reason Heure de bannissement;Modérateur;Durée de bannissement;Raison du bannissement;Raison visible - + User has never been banned. Le joueur n'a jamais été banni. - + Failed to collect ban information. Impossible de collecter les informations de bannissement. - - - + + + Warning History Historique des avertissements - + Warning Time;Moderator;User Name;Reason Heure d'avertissement;Modérateur;Nom d'utilisateur;Raison - + User has never been warned. Le joueur n'a jamais reçu d'avertissement. - + Failed to collect warning information. Impossible de collecter les informations d'avertissements. - - + + Failed to get admin notes. + + + + + Success Succès - + Successfully promoted user. L'utilisateur a été promu avec succès. - + Successfully demoted user. L'utilisateur a été rétrogradé avec succès. - - + + + Failed Échec - + Failed to promote user. L'utilisateur n'a pas été promu. - + Failed to demote user. L'utilisateur n'a pas été rétrogradé. - + Copy hash to clipboard Copier le hash - + Remove this user's messages Supprimer les messages de cet utilisateur @@ -6509,173 +7499,173 @@ Merci de ne pas recommencer ou d'autres mesures peuvent être prises contre UserInfoBox - + Location: Localisation : - + Account Age: Ancienneté du compte : - + Edit Éditer - + Change password Changer le mot de passe - + Change avatar Changer l'avatar - + Administrator Administrateur - + Moderator Modérateur - + Registered user Joueur enregistré - - + + Unregistered user Joueur non enregistré - + Judge Arbitre - + Unknown Inconnu - + The entered password does not match your account. Le mot de passe est incorrect - - - + + + Information Information - + User information updated. Informations de l'utilisateur mises à jour. - - - - - - - - - - + + + + + + + + + + Error Erreur - + User Information Informations utilisateur - + Real Name: Nom réel : - + User Level: Rang : - + %n Year(s), amount of years (only shown if more than 0) %n année.%n années.%n année(s). - + %10%n Day(s) %20 amount of years (if more than 0), amount of days, date in local short format %10%n jour %20%10%n jours %20%10%n jour(s) %20 - + Enter Password Saisir le mot de passe - + Password verification is required in order to change your email address La vérification du mot de passe est requise pour changer votre adresse e-mail - - - + + + An error occurred while trying to update your user information. Une erreur est survenue lors de la mise à jour de vos informations personnelles. - + This server does not permit you to update your user informations. Ce serveur ne vous permet pas de mettre à jour vos informations personnelles. - + Password changed. Mot de passe changé. - + This server does not permit you to change your password. Ce serveur ne vous permet pas de changer votre mot de passe. - + The new password is too short. Le nouveau mot de passe est trop court. - + The old password is incorrect. L'ancien mot de passe est incorrect. - + Avatar updated. Avatar mis à jour. - + This server does not permit you to update your avatar. Ce serveur ne vous permet pas de mettre à jour votre avatar. - + An error occured while trying to updater your avatar. Une erreur est survenue lors de la mise à jour de votre avatar. @@ -6683,124 +7673,251 @@ Merci de ne pas recommencer ou d'autres mesures peuvent être prises contre UserInterfaceSettingsPage - + General interface settings Paramètres généraux de l'interface - + &Double-click cards to play them (instead of single-click) &Double cliquer sur les cartes pour les jouer (au lieu d'un simple clic) - + + &Clicking plays all selected cards (instead of just the clicked card) + + + + &Play all nonlands onto the stack (not the battlefield) by default &Jouer toutes les cartes non terrain dans la pile (pas sur le champ de bataille) par défaut - + + Close card view window when last card is removed + + + + Annotate card text on tokens Annoter le texte des cartes sur les jetons - + Use tear-off menus, allowing right click menus to persist on screen Utiliser les menus détachables, permettant les clics droits sur les rendre persistants - + Notifications settings Paramètres des notifications - + Enable notifications in taskbar Activer les notifications dans la barre des tâches - + Notify in the taskbar for game events while you are spectating Notifier dans la barre des tâches pour les évènements des parties quand vous y êtes spectateur - + Notify in the taskbar when users in your buddy list connect Notifier dans la barre des tâches quand un ami se connecte - + Animation settings Paramètres des animations - + &Tap/untap animation &Animation d'engagement / dégagement + + + Deck editor/storage settings + + + + + Open deck in new tab by default + + + + + Prompt before converting .txt decks to .cod format + + + + + Always convert if not prompted + + + + + Use visual deck storage in game lobby + + + + + Replay settings + + + + + Buffer time for backwards skip via shortcut: + + - UserList + UserListWidget - + Users connected to server: %1 - Joueurs connectés au serveur : %1 + - + Users in this room: %1 - Joueurs dans ce salon : %1 + - + Buddies online: %1 / %2 - Amis connectés : %1 / %2 + - + Ignored users online: %1 / %2 - Joueurs sur liste noire connectés : %1 / %2 + + + + + VisualDeckStorageFolderDisplayWidget + + + Deck Storage + + + + + VisualDeckStorageSearchWidget + + + Search by filename + + + + + VisualDeckStorageSortWidget + + + Sort Alphabetically (Deck Name) + + + + + Sort Alphabetically (Filename) + + + + + Sort by Last Modified + + + + + Sort by Last Loaded + + + + + VisualDeckStorageWidget + + + Loading database ... + + + + + Show Folders + + + + + Show Tag Filter + + + + + Show Tags On Deck Previews + + + + + Show Banner Card Selection Option + + + + + Include Folder Names in Search + + + + + Draw unused Color Identities + + + + + Unused Color Identities Opacity + WarningDialog - + Which warning would you like to send? Quel avertissement voudriez-vous envoyer ? - + Redact all messages from this user in all rooms Censurer tous les messages de cet utilisateur dans toutes les salles - + &OK &OK - + &Cancel &Annuler - + Warn user for misconduct Envoyer un avertissement au joueur pour mauvaise conduite - - + + Error Erreur - + User name to send a warning to can not be blank, please specify a user to warn. Le nom d'utilisateur du joueur à avertir ne peut pas être vide, merci d'entrer un joueur à avertir. - + Warning to use can not be blank, please select a valid warning to send. L'avertissement à utiliser ne peut pas être vide, merci de sélectionner un avertissement valide à envoyer. @@ -6808,128 +7925,133 @@ Merci de ne pas recommencer ou d'autres mesures peuvent être prises contre WndSets - + Move selected set to the top Déplacer l'édition sélectionnée tout en haut - + Move selected set up Déplacer l'édition sélectionnée vers le haut - + Move selected set down Déplacer l'édition sélectionnée vers le bas - + Move selected set to the bottom Déplacer l'édition sélectionnée tout en bas - + Search by set name, code, or type Rechercher par ensemble de nom, code ou type - + Default order Ordre par défaut - + Restore original art priority order Restaurer l'ordre original de priorité artistique - + Enable all sets Activer toutes les éditions - + Disable all sets Désactiver toutes les éditions - + Enable selected set(s) Activer l(es) édition(s) sélectionnée(s) - + Disable selected set(s) Désactiver l(es) éditions(s) sélectionné(s) - + Deck Editor Éditeur de deck - + Use CTRL+A to select all sets in the view. Utiliser Ctrl+A pour sélectionner toutes les éditions dans la vue actuelle. - + Only cards in enabled sets will appear in the card list of the deck editor. Seules les cartes des éditions activées apparaitront dans la liste de cartes de l'éditeur de deck. - + Image priority is decided in the following order: La priorité des images est décidé dans l'ordre suivant: - + first the CUSTOM Folder (%1), then the Enabled Sets in this dialog (Top to Bottom) %1 is a link to the wiki D'abord le dossier PERSONNALISÉ (%1), puis les éditions activées dans cette fenêtre (de haut en bas) - + Card Art Images des cartes - + How to use custom card art Comment utiliser une illustration personnalisée de carte - + Hints Astuces - + Note Note - + Sorting by column allows you to find a set while not changing set priority. Trier par colonne vous permet de trouver une édition sans changer la priorité des éditions. - + To enable ordering again, click the column header until this message disappears. Pour trier à nouveau, cliquez sur l'entête de la colonne jusqu'à ce que ce message disparaisse. - + Use the current sorting as the set priority instead Utiliser le tri actuel comme ordre de priorité des éditions - + Sorts the set priority using the same column Sortir la priorité sur la même colonne - + + Include online-only (Arena) cards [requires restart] + + + + Manage sets Gérer les éditions @@ -6937,22 +8059,67 @@ Merci de ne pas recommencer ou d'autres mesures peuvent être prises contre ZoneViewWidget - - sort by name - tri par nom + + Ungrouped + - - sort by type - tri par type + + Group by Type + Grouper par Types - + + Group by Mana Value + Grouper par Valeur de Mana + + + + Group by Color + Grouper par Couleurs + + + + Unsorted + + + + + Sort by Name + Trier par Nom + + + + Sort by Type + Trier par Type + + + + Sort by Mana Cost + Trier par Coût de Mana + + + + Sort by Colors + Trier par Couleurs + + + + Sort by P/T + + + + + Sort by Set + + + + shuffle when closing mélanger en quittant - + pile view vue de la pile @@ -6960,7 +8127,7 @@ Merci de ne pas recommencer ou d'autres mesures peuvent être prises contre i18n - + English Français (French) @@ -6968,12 +8135,12 @@ Merci de ne pas recommencer ou d'autres mesures peuvent être prises contre main - + Connect on startup Connecter au démarrage - + Debug to file Débuguer dans journal @@ -6981,786 +8148,908 @@ Merci de ne pas recommencer ou d'autres mesures peuvent être prises contre shortcutsTab - + Main Window Fenêtre principale - - + + Deck Editor Éditeur de deck - + Game Lobby Hall - + Card Counters Marqueurs sur la carte - + Player Counters Marqueurs sur le joueur - + Power and Toughness Force et Endurance - + Game Phases Phases de jeu - + Playing Area Zone de jeu - + Move Selected Card Déplacer la carte sélectionnée - + View Vue - + Move Top Card Déplacer la carte du dessus - + Move Bottom Card Déplacer la carte du dessous - + Gameplay Gameplay - + Drawing Piocher - + Chat Room Salon de discussion - + Game Window Fenêtre de jeu - + Load Deck from Clipboard Charger un deck depuis le presse-papier - + + Replays - + + Tabs + + + + Check for Card Updates... Vérifier les mises à jour des cartes… - + Connect... Se connecter… - + Disconnect Déconnecter - + Exit Quitter - + Full screen Plein écran - + Register... S'enregistrer… - + Settings... Paramètres… - + Start a Local Game... Démarrer une partie locale… - + Watch Replay... Regarder un replay… - - Analyze Deck - Analyser le deck + + Analyze Deck (deckstats.net) + Analyze Deck + - + + Analyze Deck (tappedout.net) + + + + Clear All Filters Effacer tous les filtres - + Clear Selected Filter Effacer le filtre sélectionné - + Close Fermer - + Remove Card Retirer la carte - + Manage Sets... Gérer les éditions… - + Edit Custom Tokens... Éditer les jetons personnalisés… - - Export Deck - Exporter le deck + + Export Deck (decklist.org) + - + + Export Deck (decklist.xyz) + + + + Add Card Ajouter une carte - + Load Deck... Charger un deck… - + Load Deck from Clipboard... Charger un deck depuis le presse-papier… - + + Edit Deck in Clipboard, Annotated + + + + + Edit Deck in Clipboard + + + + New Deck Nouveau deck - + Open Custom Pictures Folder Ouvrir le dossier d'images personnalisées - + Print Deck... Imprimer le deck… - + Delete Card Supprimer la carte - - + + Reset Layout Réinitialiser l'interface - + Save Deck Sauvegarder le deck - + Save Deck as... Sauvegarder le deck sous… - + Save Deck to Clipboard, Annotated Copier le deck annoté - + + Save Deck to Clipboard, Annotated (No Set Info) + + + + Save Deck to Clipboard Copier le deck - + + Save Deck to Clipboard (No Set Info) + + + + Load Local Deck... Charger un deck local… - + Load Remote Deck... Charger un deck distant… - + Set Ready to Start Marquer prêt à démarrer - + Toggle Sideboard Lock Verrouiller / déverrouiller la réserve - - + + Add Green Counter Ajouter un marqueur vert - - + + Remove Green Counter Retirer un marqueur vert - - + + Set Green Counters... Nombre de marqueurs verts… - + Add Yellow Counter Ajouter un marqueur jaune - + Remove Yellow Counter Retirer un marqueur jaune - + Set Yellow Counters... Nombre de marqueurs jaunes… - - + + Add Red Counter Ajouter un marqueur rouge - - + + Remove Red Counter Retirer un marqueur rouge - - + + Set Red Counters... Nombre de marqueurs rouges… - + Add Life Counter Gagner 1 point de vie - + Remove Life Counter Perdre 1 point de vie - + Set Life Counters... Nombre de points de vie… - + Add White Counter Ajouter un marqueur blanc - + Remove White Counter Retirer un marqueur blanc - + Set White Counters... Nombre de marqueurs blancs… - + Add Blue Counter Ajouter un marqueur bleu - + Remove Blue Counter Retirer un marqueur bleu - + Set Blue Counters... Nombre de marqueurs bleus… - + Add Black Counter Ajouter un marqueur noir - + Remove Black Counter Retirer un marqueur noir - + Set Black Counters... Nombre de marqueurs noirs… - + Add Colorless Counter Ajouter un marqueur incolore - + Remove Colorless Counter Retirer un marqueur incolore - + Set Colorless Counters... Nombre de marqueurs incolores… - + Add Other Counter Ajouter un autre marqueur - + Remove Other Counter Supprimer un autre marqueur - + Set Other Counters... Mettre d'autres compteurs... - + Add Power (+1/+0) Augmenter la force (+1/+0) - + Remove Power (-1/-0) Diminuer la force (-1/-0) - + Move Toughness to Power (+1/-1) Endurance vers force (+1/-1) - + Add Toughness (+0/+1) Augmenter l'endurance (+0/+1) - + Remove Toughness (-0/-1) Diminuer l'endurance (-0/-1) - + Move Power to Toughness (-1/+1) Force vers endurance (-1/+1) - + Add Power and Toughness (+1/+1) Augmenter la force et l'endurance (+1/+1) - + Remove Power and Toughness (-1/-1) Diminuer la force et l'endurance (-1/-1) - + Set Power and Toughness... Changer la force et l'endurance… - + Reset Power and Toughness Réinitialiser la force et l'endurance - + Untap Dégager - + Upkeep Entretien - + Draw Piocher - + First Main Phase Première phase principale - + Start Combat Début de combat - + Attack Attaquer - + Block Bloquer - + Damage Blessures - + End Combat Fin de combat - + Second Main Phase Seconde phase principale - + End Fin - + Next Phase Phase suivante - + Next Phase Action Étape suivante avec action - + Next Turn Tour suivant - + + Hide Card in Reveal Window + + + + Tap / Untap Card Engage / dégager la carte - + Untap All Tout dégager - + Toggle Untap Activer / désactiver le dégagement normal - + Turn Card Over Retourner la carte - + Peek Card Regarder la carte - + Play Card Jouer la carte - + Attach Card... Attacher la carte… - + Unattach Card Détacher la carte - + Clone Card Cloner la carte - + Create Token... Créer un jeton… - + Create All Related Tokens Créer tous les jetons associés - + Create Another Token Créer un autre jeton - + Set Annotation... Annoter… - - + + Select All Cards in Zone + + + + + Select All Cards in Row + + + + + Select All Cards in Column + + + + + Bottom of Library Dessous de la bibliothèque - - - - + + + + Exile Exil - - - - + + + + Graveyard Cimetière - - + + Hand Main - - + + Top of Library Dessus de la bibliothèque - - - + + + Battlefield, Face Down Champ de bataille, face cachée - + Battlefield Champ de bataille - + Library Bibliothèque - + Sideboard Réserve - + Top Cards of Library Cartes du dessus de la bibliothèque - + + Bottom Cards of Library + + + + Close Recent View Fermer la vue récente - - + + Stack Pile - - + + Graveyard (Multiple) Cimetière (plusieurs) - - + + Exile (Multiple) Exil (plusieurs) - + Stack Until Found - + Draw Bottom Card Piocher la carte du dessous - + Draw Multiple Cards from Bottom... Piocher plusieurs cartes du dessous... - + Draw Arrow... Tracer une flèche… - + Remove Local Arrows Retirer les flèches locales - + Leave Game Quitter la partie - + Concede Concéder - + Roll Dice... Lancer un dé… - + Shuffle Library Mélanger la bibliothèque - + + Shuffle Top Cards of Library + + + + + Shuffle Bottom Cards of Library + + + + Mulligan Mulligan - + Draw a Card Piocher une carte - + Draw Multiple Cards... Piocher plusieurs cartes… - + Undo Draw Annuler la dernière pioche - + Always Reveal Top Card Toujours révéler la carte du dessus - + Always Look At Top Card Toujours révéler la carte du dessus - + Rotate View Clockwise Pivoter la vue dans le sens horaire - + Rotate View Counterclockwise Pivoter la vue dans le sens anti-horaire - + Unfocus Text Box Lâcher le focus de la boîte de texte - + Focus Chat Focus sur le chat - + Clear Chat Effacer le chat - + Refresh Rafraichir - - Play/Pause + + Skip Forward - + + Skip Backward + + + + + Skip Forward by a lot + + + + + Skip Backward by a lot + + + + + Play/Pause + Jouer/Pause + + + Toggle Fast Forward + + + Visual Deck Storage + + + + + Deck Storage + + + + + Server + + + + + Account + + + + + Administration + + + + + Logs + + \ No newline at end of file diff --git a/cockatrice/translations/cockatrice_it.ts b/cockatrice/translations/cockatrice_it.ts index 1a61c95ca..928c46439 100644 --- a/cockatrice/translations/cockatrice_it.ts +++ b/cockatrice/translations/cockatrice_it.ts @@ -2,103 +2,257 @@ AbstractCounter - + &Set counter... - Imposta &contatore... + Imposta segnalino AbstractCounterDialog - + Set counter - Imposta contatore + Imposta segnalino - + New value for counter '%1': - Nuovo valore per il contatore '%1': + Nuovo valore per il segnalino '%1': + + + + AbstractDlgDeckTextEdit + + + &Refresh + &Aggiorna + + + + Parse Set Name and Number (if available) + Acquisisci nome set e numero (se disponibili) + + + + AbstractTabDeckEditor + + + Open in new tab + Apri in una nuova scheda + + + + Are you sure? + Sei sicuro? + + + + The decklist has been modified. +Do you want to save the changes? + La lista del mazzo è stata modificata. +Vuoi salvare i cambiamenti? + + + + + + + + + + Error + Errore + + + + Could not open deck at %1 + Impossibile aprire il mazzo a %1 + + + + Could not save remote deck + Impossibile salvare mazzo remoto + + + + + The deck could not be saved. +Please check that the directory is writable and try again. + Il mazzo non può essere salvato. +Controlla se la cartella è valida e prova ancora. + + + + Save deck + Salva mazzo + + + + The deck could not be saved. + Il mazzo non può essere salvato. + + + + There are no cards in your deck to be exported + Non ci sono carte da esportare nel tuo mazzo + + + + No deck was selected to be exported. + Nessun mazzo da esportare è stato selezionato. + + + + AdminNotesDialog + + + Update Notes + Aggiorna note + + + + Admin Notes for %1 + Note amministratori su %1 + + + + AllZonesCardAmountWidget + + + Mainboard + Mazzo + + + + Sideboard + Panchina AppearanceSettingsPage - + Error Errore - + Could not create themes directory at '%1'. Impossibile creare la cartella dei temi in '%1'. - + Theme settings Impostazioni temi - + Current theme: Tema attuale: - + Open themes folder Apri cartella temi - + + Menu settings + Menù impostazioni. + + + + Show keyboard shortcuts in right-click menus + Mostra scorciatoie da tastiera nel menù del tasto destro del mouse. + + + Card rendering Visualizzazione delle carte - + Display card names on cards having a picture Visualizza nome delle carte sopra le immagini - + + Auto-Rotate cards with sideways layout + Ruota automaticamente carte con disposizione orizzontale. + + + + Override all card art with personal set preference (Pre-ProviderID change behavior) [Requires Client restart] + Sovrascrivi tutte le arti delle carte con set di preferenza personale (Comportamento di modifica dell'ID pre-provider) [Richiede il riavvio del Client] + + + + Bump sets that the deck contains cards from to the top in the printing selector + Imposta le carte che il mazzo contiene dall'alto verso il basso nel selettore di stampa + + + Scale cards on mouse over Ingrandisci la carta sotto il mouse - - Minimum overlap percentage of cards on the stack and in vertical hand + + Use rounded card corners - + + Minimum overlap percentage of cards on the stack and in vertical hand + Percentuale minima di sovrapposizione delle carte in pila e in mano verticale + + + + Maximum initial height for card view window: + Altezza iniziale massima per la finestra di visualizzazione della scheda: + + + + + rows + file + + + + Maximum expanded height for card view window: + Altezza massima per la finestra di visualizzazione della carta: + + + Hand layout Layout della mano - + Display hand horizontally (wastes space) Disponi la mano orizzontalmente (spreca spazio) - + Enable left justification Allinea a sinistra - + Table grid layout Layout della griglia tabellare - + Invert vertical coordinate Inverti le coordinate verticali - + Minimum player count for multi-column layout: Numero di giocatori minimo per layout multicolonna: - + Maximum font size for information displayed on cards: Dimensione massima carattere per le informazioni mostrate sulle carte: @@ -106,112 +260,112 @@ BanDialog - + ban &user name Banna &user name - + ban &IP address Banna indirizzo &IP - + ban client I&D banna client I&D - + Ban type Tipo di ban - + &permanent ban Ban &permanente - + &temporary ban Ban &temporaneo - + &Days: &Giorni: - + &Hours: &Ore: - + &Minutes: &Minuti: - + Duration of the ban Durata del ban - + Please enter the reason for the ban. This is only saved for moderators and cannot be seen by the banned person. Per favore inserisci la ragione del ban. Questa è visibile solo ai moderatori e non alla persona bannata. - + Please enter the reason for the ban that will be visible to the banned person. Per favore inserisci la ragione del ban che sarà visibile alla persona bannata. - + Redact all messages from this user in all rooms Elimina tutti i messaggi di questo utente in tutte le stanze - + &OK &OK - + &Cancel &Annulla - + Ban user from server Banna utente dal server - - - - + + + + Error Errore - + You have to select a name-based, IP-based, clientId based, or some combination of the three to place a ban. Seleziona un nome, un IP, un client ID o una combinazione di questi per impostare un ban. - + You must have a value in the name ban when selecting the name ban checkbox. Devi inserire un valore nel campo nome quando il ban per nome è selezionato. - + You must have a value in the ip ban when selecting the ip ban checkbox. Devi inserire un valore nel campo ip quando il ban per ip è selezionato. - + You must have a value in the clientid ban when selecting the clientid ban checkbox. Devi inserire un valore nel campo clientid quando il ban per clientid è selezionato. @@ -219,22 +373,22 @@ Questa è visibile solo ai moderatori e non alla persona bannata. BetaReleaseChannel - - Beta Releases - Versioni beta + + Beta + Beta - + No reply received from the release update server. Nessuna risposta ricevuta dal server degli aggiornamenti. - + Invalid reply received from the release update server. Risposta non valida ricevuta dal server degli aggiornamenti. - + No reply received from the file update server. Nessuna risposta ricevuta dal server degli aggiornamenti. @@ -242,32 +396,32 @@ Questa è visibile solo ai moderatori e non alla persona bannata. CardDatabaseModel - + Name Nome - + Sets Set - + Mana cost Costo - + Card type Tipo - + P/T F/C - + Color(s) Colore @@ -278,25 +432,25 @@ Questa è visibile solo ai moderatori e non alla persona bannata. AND Logical conjunction operator used in card filter - AND + E OR Logical disjunction operator used in card filter - OR + O AND NOT Negated logical conjunction operator used in card filter - AND NOT + E NON OR NOT Negated logical disjunction operator used in card filter - OR NOT + O NON @@ -360,37 +514,65 @@ Questa è visibile solo ai moderatori e non alla persona bannata. - CardFrame + CardInfoFrameWidget - + Image Immagine - + Description - Testo + Descrizione - + Both Entrambi + + + View transformation + Mostra trasformazione + - CardInfoText + CardInfoPictureWidget - + + View related cards + Mostra carte correlate + + + + Add card to deck + Aggiungi carta al &mazzo + + + + Mainboard + Mazzo + + + + Sideboard + Sideboard + + + + CardInfoTextWidget + + Name: Nome: - + Related cards: - Carte collegate: + Carte correlate: - + Unknown card: Carta sconosciuta: @@ -398,287 +580,555 @@ Questa è visibile solo ai moderatori e non alla persona bannata. CardItem - + &Move to &Metti in - + &Power / toughness &Forza / Costituzione + + CardSizeWidget + + + Card Size + Dimensioni della carta + + CardZone - + their hand nominative la sua mano - + %1's hand nominative Mano di %1 - + their library look at zone il suo grimorio - + %1's library look at zone Grimorio di %1 - + of their library top cards of zone, del suo grimorio - + of %1's library top cards of zone del grimorio di %1 - + their library reveal zone il suo grimorio - + %1's library reveal zone Grimorio di %1 - + their library shuffle il suo grimorio - + %1's library shuffle Grimorio di %1 - + their library nominative il suo grimorio - + %1's library nominative Grimorio di %1 - + their graveyard nominative il suo cimitero - + %1's graveyard nominative Cimitero di %1 - + their exile nominative la sua zona di esilio - + %1's exile nominative Esilio di %1 - + their sideboard look at zone la sua sideboard - + %1's sideboard look at zone Sideboard di %1 - + their sideboard nominative la sua sideboard - + %1's sideboard nominative Sideboard di %1 - DeckEditorSettingsPage + DeckEditorCardInfoDockWidget - - - Update Spoilers - Aggiorna spoiler + + Card Info + Info carta + + + + DeckEditorDatabaseDisplayWidget + + + Search by card name (or search expressions) + Cerca per nome della carta (o espressioni di ricerca) - - + + Add to Deck + Aggiungi al mazzo + + + + Add to Sideboard + Aggiungi alla sideboard + + + + Select Printing + Seleziona Stampa + + + + Show on EDHREC (Commander) + Mostra su EDHREC (Commander) + + + + Show on EDHREC (Card) + Mostra su EDHREC (Carta) + + + + Show Related cards + Mostra carte correlate + + + + Add card to &maindeck + Aggiungi carta al &mazzo + + + + Add card to &sideboard + Aggiungi carta alla &sideboard + + + + DeckEditorDeckDockWidget + + + Banner Card + Carta copertina + + + + Select Printing + Seleziona stampa + + + + Deck + Mazzo + + + + Deck &name: + &Nome mazzo: + + + + &Comments: + &Commenti: + + + + Hash: + Hash: + + + + &Increment number + &Aumenta il numero + + + + &Decrement number + &Diminuisci il numero + + + + &Remove row + &Rimuovi carta + + + + Swap card to/from sideboard + Sposta carta in maindeck/sideboard + + + + DeckEditorFilterDockWidget + + + Filters + Filtri + + + + &Clear all filters + &Elimina tutti i filtri + + + + Delete selected + Elimina filtro + + + + DeckEditorMenu + + + &Deck Editor + &Editor dei mazzi + + + + &New deck + &Nuovo mazzo + + + + &Load deck... + &Carica mazzo... + + + + Load recent deck... + Carica mazzo recente... + + + + Clear + Svuota + + + + &Save deck + &Salva mazzo + + + + Save deck &as... + Salva mazzo &con nome... + + + + Load deck from cl&ipboard... + Carica mazzo dagli app&unti... + + + + Edit deck in clipboard + Modifica mazzo negli appunti + + + + + Annotated + Con annotazioni + + + + + Not Annotated + Senza annotazioni + + + + Save deck to clipboard + Salva il mazzo negli appunti + + + + Annotated (No set info) + Con annotazioni (senza info set) + + + + Not Annotated (No set info) + Senza annotazioni (senza info set) + + + + &Print deck... + Stam&pa mazzo... + + + + &Send deck to online service + &Invia mazzo al servizio online + + + + Create decklist (decklist.org) + Crea una decklist (decklist.org) + + + + Create decklist (decklist.xyz) + Crea una decklist (decklist.xyz) + + + + Analyze deck (deckstats.net) + Analizza mazzo (deckstats.net) + + + + Analyze deck (tappedout.net) + Analizza mazzo (tappedout.net) + + + + &Close + &Chiudi + + + + DeckEditorPrintingSelectorDockWidget + + + Printing Selector + Selettore di stampa + + + + DeckEditorSettingsPage + + + + Update Spoilers + Aggiorna Spoiler + + + + Success Fatto - + Download URLs have been reset. Gli indirizzi di download sono stati resettati. - + Downloaded card pictures have been reset. Le immagini delle carte scaricate sono state eliminate. - + Error Errore - + One or more downloaded card pictures could not be cleared. Non è stato possibile eliminare alcune delle immagini delle carte scaricate. - + Add URL Aggiungi indirizzo - - + + URL: Indirizzo: - - + + Edit URL Modifica indirizzo: - + + Network Cache Size: + Dimensione cache di rete: + + + + Redirect Cache TTL: + Reindirizza TTL della cache: + + + + How long cached redirects for urls are valid for. + Per quanto tempo sono validi i reindirizzamenti memorizzati nella cache per gli URL. + + + + Picture Cache Size: + Dimensione cache immagini: + + + Add New URL Aggiungi indirizzo URL - + Remove URL Elimina indirizzo URL - + + Day(s) + Giorno/i + + + Updating... Aggiornando... - + Choose path Scegli il percorso - + URL Download Priority Ordine di priorità degli indirizzi - + Spoilers Spoiler - + Download Spoilers Automatically Scarica spoiler automaticamente - + Spoiler Location: Indirizzo spoiler: - + Last Change Ultimo cambiamento - + Spoilers download automatically on launch Scarica spoiler automaticamente all'avvio - + Press the button to manually update without relaunching Premi il pulsante per aggiornare manualmente senza riavviare - + Do not close settings until manual update is complete Non chiudere le impostazioni fino a che l'aggiornamento manuale sia completato - + Download card pictures on the fly Download immagini delle carte - + How to add a custom URL Come aggiungere indirizzi personalizzati - + Delete Downloaded Images Elimina immagini scaricate - + Reset Download URLs Resetta indirizzi di download - - Downloaded images directory size: - Dimensione cartella immagini scaricate: - - - + On-disk cache for downloaded pictures Cache su disco immagini scaricate - - Picture cache size: - Dimensione cache immagini: - - - + In-memory cache for pictures not currently on screen Cache in memoria per immagini non attualmente su schermo @@ -686,16 +1136,218 @@ Questa è visibile solo ai moderatori e non alla persona bannata. DeckListModel - + + Count + Conteggio + + + + Set + Set + + + Number Quantità - + + Provider ID + ID Provider + + + Card Carta + + DeckLoader + + + Common deck formats (%1) + Formati di mazzo comuni (%1) + + + + All files (*.*) + Tutti i file (*.*) + + + + DeckPreviewColorIdentityFilterWidget + + + Mode: Exact Match + Modalità: Corrispondenza esatta + + + + Mode: Includes + Modalità: Include + + + + DeckPreviewDeckTagsDisplayWidget + + + Edit tags ... + Modifica etichette... + + + + DeckPreviewTagDialog + + + Deck Tags Manager + Gestore etichette del mazzo + + + + Manage your deck tags. Check or uncheck tags as needed, or add new ones: + Gestisci le etichette del mazzo. Assegna o rimuovi etichette, oppure creane di nuove. + + + + Add a new tag (e.g., Aggro️) + Aggiungi una nuova etichetta (Esempio: Aggro️) + + + + Add Tag + Aggiungi etichetta + + + + Filter tags... + Filtra etichette... + + + + OK + Conferma + + + + Cancel + Annulla + + + + Invalid Input + Input non valido + + + + Tag name cannot be empty! + Il nome dell'etichetta non può essere vuoto! + + + + Duplicate Tag + Duplica Etichetta + + + + This tag already exists. + Questa etichetta esiste già. + + + + DeckPreviewWidget + + + Banner Card + Carta copertina + + + + Open in deck editor + Apri nell'editor dei mazzi + + + + Edit Tags + Modifica etichette + + + + Rename Deck + Rinomina mazzo + + + + Save Deck to Clipboard + Salva il mazzo negli appunti + + + + Annotated + Con annotazioni + + + + Annotated (No set info) + Con annotazioni (senza info set) + + + + Not Annotated + Senza annotazioni + + + + Not Annotated (No set info) + Senza annotazioni (senza info set) + + + + Rename File + Rinomina file + + + + Delete File + Elimina file + + + + Set Banner Card + Imposta carta copertina + + + + + New name: + Nuovo nome: + + + + + Error + Errore + + + + Rename failed + Rinomina non riuscita + + + + Delete file + Elimina file + + + + Are you sure you want to delete the selected file? + Vuoi davvero eliminare il file selezionato? + + + + Delete failed + Eliminazione non riuscita + + DeckStatsInterface @@ -713,181 +1365,219 @@ Questa è visibile solo ai moderatori e non alla persona bannata. DeckViewContainer - + Load deck... Carica mazzo... - + Load remote deck... Carica mazzo remoto... - + + Unload deck + Unload deck... + + + + Ready to start Pronto ad iniziare - + + Force start + Forza avvio + + + Sideboard unlocked Sideboard sbloccata - + Sideboard locked Sideboard bloccata - - Load deck - Carica mazzo - - - + Error Errore - + The selected file could not be loaded. I file selezionati non posso essere caricati. + + DialogConvertDeckToCodFormat + + + Deck Format Conversion + Conversione formato mazzo + + + + You tried to add a tag to a .txt format deck. + Tags can only be added to .cod format decks. + Do you want to convert the deck to the .cod format? + Stai cercando di aggiungere un'etichetta ad un mazzo in formato .txt. +Le etichette possono essere aggiunte solo a mazzi in formato .cod. +Vuoi convertire il mazzo al formato .cod? + + + + Remember and automatically apply choice in the future + Ricorda e applica automaticamente la scelta in futuro + + DlgConnect - + Downloading... Scaricamento... - + Known Hosts Host Conosciuti - + + Delete the currently selected saved server + Elimina il server salvato attualmente selezionato + + + Refresh the server list with known public servers Aggiorna la lista dei server con server pubblici - + New Host Nuovo indirizzo - + Name: Nome: - + &Host: &Host: - + &Port: &Porta: - + Player &name: &Nome giocatore: - + P&assword: P&assword: - + &Save password &Salva password - + A&uto connect Connetti a&utomaticamente - + Automatically connect to the most recent login when Cockatrice opens Connetti automaticamente all'ultimo server utilizzato all'avvio di Cockatrice - + If you have any trouble connecting or registering then contact the server staff for help! Se hai problemi a connetterti o registrarti contatta lo staff del server per aiuto! - - + + Webpage Sito web - + Reset Password Resetta Password - + + Forgot password? + Hai dimenticato la password? + + + &Connect &Connetti - + Server Server - + Login Login - + Server Contact Contatto server - + Connect to Server Connetti al server - + Server URL URL Server - + Communication Port Numero porta - + Unique Server Name Nome univoco del server - + Connection Warning Avviso di connessione - + You need to name your new connection profile. È necessario impostare un nome per il nuovo profilo di connessione. - + Connect Warning Problema di connessione - + The player name can't be empty. Il nome del giocatore non può essere vuoto. @@ -970,27 +1660,37 @@ Questa è visibile solo ai moderatori e non alla persona bannata. Spettatori - + + Starting life total: + Totale vita iniziale: + + + + Game setup options + Opzioni di configurazione del gioco + + + &Clear Pulisci - + Create game Crea partita - + Game information Informazioni partita - + Error Errore - + Server error. Errore del server. @@ -998,92 +1698,92 @@ Questa è visibile solo ai moderatori e non alla persona bannata. DlgCreateToken - + &Name: &Nome: - + Token Pedina - + C&olor: C&olore: - + white bianco - + blue blu - + black nero - + red rosso - + green verde - + multicolor multicolore - + colorless incolore - + &P/T: &F/C: - + &Annotation: &Note: - + &Destroy token when it leaves the table &Distruggi la pedina quando lascia il tavolo - + Token data Dati pedina - + Show &all tokens Mostra &tutte le pedine - + Show tokens from this &deck Mostra pedine di questo &mazzo - + Choose token from list Scegli pedina dalla lista - + Create token Crea pedina @@ -1129,6 +1829,24 @@ Per rimuovere il tuo avatar attuale, conferma senza scegliere una nuova immagine Immagine selezionata non valida. + + DlgEditDeckInClipboard + + + Edit deck in clipboard + Modifica mazzo negli appunti + + + + Error + Errore + + + + Invalid deck list. + Lista del mazzo non valida. + + DlgEditPassword @@ -1171,93 +1889,93 @@ Per rimuovere il tuo avatar attuale, conferma senza scegliere una nuova immagine DlgEditTokens - + &Name: &Nome: - + C&olor: C&olore: - + white bianco - + blue blu - + black nero - + red rosso - + green verde - + multicolor multicolore - + colorless incolore - + &P/T: &F/C: - + &Annotation: &Note: - + Token data Dati pedina - - + + Add token Aggiungi pedina - + Remove token Rimuovi pedina - + Edit custom tokens Modifica pedine personalizzate - + Please enter the name of the token: Inserisci il nome della pedina: - + Error Errore - + The chosen name conflicts with an existing card or token. Make sure to enable the 'Token' set in the "Manage sets" dialog to display them correctly. Il nome scelto è in conflitto con una carta o pedina esistente. @@ -1295,132 +2013,132 @@ Assicurati di abilitare il set "Pedine" nella finestra "Organizza DlgFilterGames - + no limit nessun limite - + 5 minutes 5 minuti - + 10 minutes 10 minuti - + 30 minutes 30 minuti - + 1 hour 1 ora - + 2 hours 2 ore - - Show '&buddies only' games - Mostra partite 'solo per amici' + + Hide 'buddies only' games + Nascondi partite 'solo per amici' - - Show &full games - Visualizza &tutte le partite + + Hide full games + Nascondi partite al completo - - Show games &that have started - Visualizza &tutte le partite iniziate + + Hide games that have started + Nascondi partite iniziate - - Show &password protected games - Mostra partite protette da &password + + Hide password protected games + Nascondi partite protette da password - - Hide '&ignored user' games - Nascondi '&ignored user' partite + + Hide 'ignored user' games + Nascondi partite degli utenti ignorati - + &Newer than: &Più nuovo di: - + Game &description: &Descrizione partita: - + &Creator name: Nome &creatore: - + General Generale - + &Game types Tipi di &partita - + at &least: Minimo: - + at &most: Massimo: - + Maximum player count Numero di giocatori - + Restrictions Restrizioni - + Show games only if &spectators can watch Mostra le partite solo se &gli spettatori possono guardare - + Show spectator password p&rotected games Mostra partite protette da password - + Show only if spectators can ch&at Mostra le partite solo se gli spettatori possono guardare - + Show only if spectators can see &hands Mostra le partite solo se gli spettatori possono guardare le mani - + Spectators Spettatori - + Filter games Filtro partite @@ -1603,27 +2321,28 @@ Assicurati di abilitare il set "Pedine" nella finestra "Organizza Le password non coincidono. + + DlgLoadDeck + + + Load Deck + Carica mazzo + + DlgLoadDeckFromClipboard - - &Refresh - &Aggiorna - - - + Load deck from clipboard Carica mazzo dagli appunti - - + Error Errore - - + Invalid deck list. Lista del mazzo non valida. @@ -1631,11 +2350,49 @@ Assicurati di abilitare il set "Pedine" nella finestra "Organizza DlgLoadRemoteDeck - + Load deck Carica mazzo + + DlgMoveTopCardsUntil + + + Card name (or search expressions): + Nome della carta (o ricerca espressioni): + + + + Number of hits: + Numero di risultati: + + + + Auto play hits + Riproduzione automatica dei successi + + + + Put top cards on stack until... + Metti le carte in cima alla pila fino a... + + + + No cards matching the search expression exists in the card database. Proceed anyways? + Non esiste alcuna carta corrispondente agli estremi di ricerca nel database. Procedere comunque? + + + + Cockatrice + Cockatrice + + + + Invalid filter + Filtro non valido + + DlgRegister @@ -1734,28 +2491,28 @@ La tua email verrà utilizzata per verificare il tuo account. Number of sides: - + Numero di lati: Number of dice: - + Numero di dadi: Roll Dice - + Lancia dado DlgSettings - + Unknown Error loading card database Errore sconosciuto durante il caricamento del database delle carte - + Your card database is invalid. Cockatrice may not function correctly with an invalid database @@ -1772,7 +2529,7 @@ Ti consigliamo di avviare oracle per aggiornare il tuo database delle carte. Vuoi modificare le impostazioni della posizione del database della carte? - + Your card database version is too old. This can cause problems loading card information or images @@ -1789,7 +2546,7 @@ Ti consigliamo di avviare oracle per aggiornare il tuo database delle carte. Vuoi modificare le impostazioni della posizione del database della carte? - + Your card database did not finish loading Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues with your cards.xml attached @@ -1801,7 +2558,7 @@ Per favore crea un ticket di assistenza su https://github.com/Cockatrice/Cockatr Desideri modificare l'impostazione della posizione del database? - + File Error loading your card database. Would you like to change your database location setting? @@ -1810,7 +2567,7 @@ Would you like to change your database location setting? Vuoi modificare le impostazioni della posizione del database della carte? - + Your card database was loaded but contains no cards. Would you like to change your database location setting? @@ -1819,7 +2576,7 @@ Would you like to change your database location setting? Vuoi modificare le impostazioni della posizione del database della carte? - + Unknown card database load status Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues @@ -1832,59 +2589,59 @@ https://github.com/Cockatrice/Cockatrice/issues Desideri modificare l'impostazione della posizione del database? - - - + + + Error Errore - + The path to your deck directory is invalid. Would you like to go back and set the correct path? Il percorso della cartella del mazzo non è valido. Vuoi tornare in dietro e impostare il percorso corretto? - + The path to your card pictures directory is invalid. Would you like to go back and set the correct path? Il percorso della cartella delle immagini delle carte è invilido. Vuoi tornare indietro e impostare il percorso corretto? - + Settings Impostazioni - + General Generale - + Appearance Aspetto - + User Interface Interfaccia - + Card Sources Immagini carte - + Chat Chat - + Sound Suoni - + Shortcuts Combinazioni di tasti @@ -1910,40 +2667,40 @@ Desideri modificare l'impostazione della posizione del database? DlgUpdate - + Current release channel Canale della versione corrente - + Reinstall Reinstalla - + Cancel Download Annulla download - + Open Download Page Apri pagina di download - + Check for Client Updates Verifica la presenza di aggiornamenti del client - - - - + + + + Error Errore - + Cockatrice was not built with SSL support, therefore you cannot download updates automatically! Please visit the download page to update manually. Cockatrice non è stato creato con il supporto SSL, quindi gli aggiornamenti non possono essere scaricati automaticamente! @@ -1951,126 +2708,129 @@ Please visit the download page to update manually. Per favore, visitate la pagina di download per aggiornare manualmente. - - Downloading update... - Scarico l'aggiornamento... + + Downloading update: %1 + Downloading update... + Download dell'aggiornamento: %1 - + Checking for updates... Ricerca aggiornamenti... - + Finished checking for updates Controllo aggiornamenti terminato - + No Update Available Nessun aggiornamento disponibile - + Cockatrice is up to date! Cockatrice è aggiornato! - + You are already running the latest version available in the chosen release channel. Hai già la versione più recente nel canale della versione scelto. - + Current version Versione corrente - + Selected release channel Canale della versione selezionato - - + + Update Available Aggiornamento disponibile - - + + A new version of Cockatrice is available! Una nuova versione di Cockatrice è disponibile! - - + + New version Nuova versione - - + + Released Rilasciato - - + + Changelog Cambiamenti della versione - + Do you want to update now? Vuoi aggiornare adesso? - - Unfortunately there are no download packages available for your operating system. -You may have to build from source yourself. - Sfortunatamente non ci sono pacchetti scaricabili disponibili per il tuo sistema operativo. -Potresti dover creare dal codice sorgente da solo. + + Unfortunately, the automatic updater failed to find a compatible download. +You may have to manually download the new version. + Unfortunately there are no download packages available for your operating system. +You may have to build from source yourself. + - - Please check the download page manually and visit the wiki for instructions on compiling. - Per favore visita la pagina di download manualmente e visita la wiki per istruzioni su come compilare. + + Please check the <a href="%1">releases page</a> on our Github and download the build for your system. + Please check the download page manually and visit the wiki for instructions on compiling. + - - - + + + Update Error Errore durante l'aggiornamento - + An error occurred while checking for updates: Errore durante il controllo degli aggiornamenti: - + An error occurred while downloading an update: Errore durante il download di un aggiornamento: - + Installing... Installazione... - + Cockatrice is unable to open the installer. Cockatrice non riesce ad aprire il file di installazione. - + Try to update manually by closing Cockatrice and running the installer. Prova ad aggiornare manualmente chiudendo Cockatrice ed eseguento il file di installazione. - + Download location Percorso download @@ -2088,6 +2848,14 @@ Potresti dover creare dal codice sorgente da solo. Registro di debug + + EdhrecCommanderResponseCommanderDetailsDisplayWidget + + + Salt: + Sale: + + FilterBuilder @@ -2099,105 +2867,120 @@ Potresti dover creare dal codice sorgente da solo. GameSelector - - - - - - - - - + + + + + + + + + Error Errore - + Please join the appropriate room first. Si prega di entrare prima in una stanza adeguata. - + Wrong password. Password errata. - + Spectators are not allowed in this game. Spettatori non ammessi in questa partita. - + The game is already full. La partita è piena. - + The game does not exist any more. Questa partita non esiste più. - + This game is only open to registered users. Questa partita è solo per utenti registrati. - + This game is only open to its creator's buddies. Questa stanza è aperta solo agli amici del suo creatore. - + You are being ignored by the creator of this game. Sei stato ingnorato dal creatore di questa partita. - + + Join Game + + + + + Spectate Game + + + + + Game Information + Informazioni partita + + + Join game Entra nella partita - + Password: Password: - + Please join the respective room first. Si prega di entrare nella rispettiva stanza prima. - + &Filter games &Filtra partite - + C&lear filter E&limina filtri - + C&reate Cr&ea - + &Join &Entra - + J&oin as spectator Entra c&ome spettatore - + Games shown: %1 / %2 Partite mostrate: %1 / %2 - + Games Partite @@ -2205,100 +2988,100 @@ Potresti dover creare dal codice sorgente da solo. GamesModel - + >1 day >1 giorno - + %1%2 hr short age in hours %1%2 ora%1%2 ore%1%2 ora(e) - + new nuovo - + %1%2 min short age in minutes %1%2 min%1%2 min%1%2 min - + password password - + buddies only solo amici - + reg. users only solo utenti registrati - - + + can chat può chattare - + see hands vede mani - + can see hands può vedere mani - + not allowed non ammessi - + Room Stanza - + Age Età - + Description Descrizione - + Creator Creatore - + Type Tipo - + Restrictions Restrizioni - + Players Giocatori - + Spectators Spettatori @@ -2306,92 +3089,102 @@ Potresti dover creare dal codice sorgente da solo. GeneralSettingsPage - + Reset all paths Reimposta tutti gli indirizzi di download - + All paths have been reset Gli indirizzi di download sono stati resettati - - + + Choose path Seleziona il percorso - + Personal settings Impostazioni personali - + Language: Lingua: - + Paths (editing disabled in portable mode) Destinazioni (non si può personalizzare in modalità portatile) - + Paths Percorsi - + + How to help with translations + Come aiutare con le traduzioni + + + Decks directory: Cartella mazzi: - + Replays directory: Cartella replay: - + Pictures directory: Cartella immagini: - + Card database: Database carte: - + Custom database directory: Cartella database personalizzata. - + Token database: Database pedine: - + Update channel Canale di aggiornamento: - + + Check for client updates on startup + Controlla gli aggiornamenti del client all'avvio + + + Notify if a feature supported by the server is missing in my client Avvisami se una funzionalità supportata dal server manca nel mio programma - + Automatically run Oracle when running a new version of Cockatrice Avvia automaticamente Oracle se Cockatrice è stato aggiornato - + Show tips on startup Mostra suggerimenti all'avvio @@ -2399,61 +3192,61 @@ Potresti dover creare dal codice sorgente da solo. MainWindow - - + + The server has reached its maximum user capacity, please check back later. Il server ha raggiunto la sua massima capacità di utenti, riprova più tardi. - + There are too many concurrent connections from your address. Ci sono troppe connessioni contemporanee dal tuo indirizzo. - + Banned by moderator Bannato dal moderatore - + Expected end time: %1 Fine prevista: %1 - + This ban lasts indefinitely. Questo ban dura a tempo indeterminato. - + Scheduled server shutdown. Spegnimento del server in programma. - - + + Invalid username. Nome utente non valido. - + You have been logged out due to logging in at another location. Sei stato disconnesso: ti sei connesso da un'altra postazione. - + Connection closed Connessione chiusa - + The server has terminated your connection. Reason: %1 Hai perso la connessione con il server. Ragione: %1 - + The server is going to be restarted in %n minute(s). All running games will be lost. Reason for shutdown: %1 @@ -2466,558 +3259,568 @@ Tutti le partite saranno perse. Ragione dello spegnimento: %1 - + Scheduled server shutdown Spegnimento del server in programma - - + + Success Successo - + Registration accepted. Will now login. Registrazione accettata. Login in corso. - + Account activation accepted. Will now login. Attivazione account accettata. Login in corso. - + Number of players Numero di giocatori - + Please enter the number of players. Inserisci il numero di giocatori: - - + + Player %1 Giocatore %1 - + Load replay Carica replay - + About Cockatrice Info su Cockatrice - + Version Versione - + Cockatrice Webpage Sito di Cockatrice - + Project Manager: Manager del progetto: - + Past Project Managers: Manager precedenti: - + Developers: Sviluppatori: - + Our Developers I nostri sviluppatori - + Help Develop! Aiutaci nello sviluppo! - + Translators: Traduttori: - + Our Translators I nostri traduttori - + Help Translate! Aiutaci nella traduzione! - + Support: Supporto: - + Report an Issue Segnala un problema - + Troubleshooting Risoluzione dei problemi - + F.A.Q. F.A.Q. - - - - - - - - - - - - - - - + + + + + + + + + + + + + - - - + + + + + Error Errore - + Server timeout Timeout del server - + Failed Login Login fallito - + Your client seems to be missing features this server requires for connection. La tua versione del programma non supporta funzionalità richieste dal server, aggiorna il programma e riprova. - + To update your client, go to 'Help -> Check for Client Updates'. Per aggiornare la tua versione di Cockatrice, vai su 'Aiuto -> Controlla aggiornamenti client' - + Incorrect username or password. Please check your authentication information and try again. Nome utente o password non validi. Ricontrolla i tuoi dati di accesso e riprova. - + There is already an active session using this user name. Please close that session first and re-login. Sei già loggato in un'altra sessione con questo username. Chiudi prima quella sessione e riprova a loggare. - - + + You are banned until %1. Sei bannato fino a %1. - - + + You are banned indefinitely. Sei stato bannato per tempo indeterminato. - + This server requires user registration. Do you want to register now? Questo server richiede la registrazione degli utenti. Vuoi registrarti adesso? - + This server requires client IDs. Your client is either failing to generate an ID or you are running a modified client. Please close and reopen your client to try again. Il server richiede un client ID. Il tuo programma non ha generato un client ID. Riprova dopo aver chiuso e riaperto il programma. - + An internal error has occurred, please close and reopen Cockatrice before trying again. If the error persists, ensure you are running the latest version of the software and if needed contact the software developers. Si è verificato un errore, riprova dopo aver chiuso e riaperto il programma. Se l'errore persiste, aggiorna il programma all'ultima versione e se necessario chiedi assistenza agli sviluppatori. - + Account activation Attivazione account - + Your account has not been activated yet. You need to provide the activation token received in the activation email. Il tuo account non è ancora stato attivato. Per attivarlo inserisci il codice di attivazione ricevuto tramite email, - + Server Full Server pieno - + Unknown login error: %1 Errore di login sconosciuto: %1 - - + + This usually means that your client version is out of date, and the server sent a reply your client doesn't understand. Solitamente questo significa che stai usando una vecchia versione del programma, e il server ha inviato un messaggio che il tuo programma non è in grado di comprendere. - + Your username must respect these rules: Il nome utente deve seguire queste regole: - + is %1 - %2 characters long essere lungo %1 - %2 caratteri - + can %1 contain lowercase characters %1 può contenere caratteri minuscoli - - - - + + + + NOT NON - + can %1 contain uppercase characters %1 può contenere caratteri maiuscoli - + can %1 contain numeric characters %1 può contenere numeri - + can contain the following punctuation: %1 può contenere la punteggiatura: %1 - + first character can %1 be a punctuation mark il primo carattere %1 può essere punteggiatura - + no unacceptable language as specified by these server rules: note that the following lines will not be translated non usare un linguaggio inaccettabile, come specificato nelle regole del server: - + can not contain any of the following words: %1 non può contenere le seguenti parole: %1 - + can not match any of the following expressions: %1 non può corrispondere alle seguenti espressioni: %1 - + You may only use A-Z, a-z, 0-9, _, ., and - in your username. Puoi usare solo i caratteri A-Z, a-z, 0-9, _, ., e - nel tuo nome utente. - - - - - - + + + + + + Registration denied Registrazione negata - + Registration is currently disabled on this server La registrazione è disabilitata su questo server - + There is already an existing account with the same user name. Esiste già un account con lo stesso nome utente. - + It's mandatory to specify a valid email address when registering. E' obbligatorio specificare un indirizzo email valido per la registrazione. - + It appears you are attempting to register a new account on this server yet you already have an account registered with the email provided. This server restricts the number of accounts a user can register per address. Please contact the server operator for further assistance or to obtain your credential information. Sembra che tu stia tentando di registrare un nuovo account su questo server ma hai già un account registrato sull'indirizzo email fornito. Questo server restringe il numero di account un utente più avere per indirizzo. Per favore contatta l'amministratore del server per ulteriore assistenza o per ottenere le tue credenziali di accesso. - + Password too short. Password troppo corta. - + Registration failed for a technical problem on the server. La registrazione è fallita a causa di un problema tecnico sul server. - + Unknown registration error: %1 Errore di registrazione sconosciuto: %1 - + Account activation failed Attivazione account fallita - + Socket error: %1 Errore nella connessione: %1 - + You are trying to connect to an obsolete server. Please downgrade your Cockatrice version or connect to a suitable server. Local version is %1, remote version is %2. Stai cercando di connetterti a un server obsoleto. Declassa la versione di Cockatrice per farlo funzionare o connetti ad un altro server. La tua versione è la %1, la versione remota è la %2. - + Your Cockatrice client is obsolete. Please update your Cockatrice version. Local version is %1, remote version is %2. La tua versione di Cockatrice è obsoleta. Aggiorna la tua versione di Cockatrice. La tua versione è la %1, la versione online è la %2. - + Connecting to %1... Connessione a %1... - + Registering to %1 as %2... Registrazione su %1 come %2... - + Disconnected Disconnesso - + Connected, logging in at %1 Connesso, login in corso su %1 - - - + + + Requesting forgotten password to %1 as %2... Richiesta recupero password su %1 come %2... - + &Connect... &Connetti... - + &Disconnect &Disconnetti - + Start &local game... Inizia &partita in locale... - + &Watch replay... &Guarda replay... - - &Deck editor - &Editor dei mazzi - - - + &Full screen &Schermo intero - + &Register to server... &Registrati sul server... - + &Restore password... Recupera Password... - + &Settings... &Impostazioni... - + &Exit &Esci - + A&ctions A&zioni - + &Cockatrice &Cockatrice - + C&ard Database Database delle c&arte - + &Manage sets... &Organizza set... - + Edit custom &tokens... Modifica pedine personalizzate - + Open custom image folder Apri cartella immagini personalizzate - + Open custom sets folder Apri cartella set personalizzati - + Add custom sets/cards Aggiungi set di carte personalizzato - + + Reload card database + Ricarica database carte + + + + Tabs + Schede + + + &Help &Aiuto - + &About Cockatrice &Info su Cockatrice - + &Tip of the Day Suggerimento del giorno - + Check for Client Updates Controlla aggiornamenti client - + Check for Card Updates... Cerca Aggiornamenti Carte... - + View &Debug Log Vedi Log %Debug - + + Open Settings Folder + Apri cartella impostazioni + + + Show/Hide Mostra/Nascondi - + New Version Nuova versione - + Congratulations on updating to Cockatrice %1! Oracle will now launch to update your card database. Congratulazioni per aver aggiornato Cockatrice alla versione %1! Oracle verrà avviato per aggiornare anche il database delle carte. - + Cockatrice installed Cockatrice installata - + Congratulations on installing Cockatrice %1! Oracle will now launch to install the initial card database. Congratulazioni per aver installato Cockatrice %1! Oracle verrà avviato per installare il database delle carte. - + Card database Database carte - + Cockatrice is unable to load the card database. Do you want to update your card database now? If unsure or first time user, choose "Yes" @@ -3026,29 +3829,29 @@ Vuoi provare ad aggiornare il database adesso? Se è la prima volta che usi Cockatrice o non sei sicuro, scegli "Sì" - - + + Yes - - + + No No - + Open settings Apri impostazioni - + New sets found Nuovi set trovati - + %n new set(s) found in the card database Set code(s): %1 Do you want to enable it/them? @@ -3061,17 +3864,17 @@ Codici dei set: %1 Desideri attivarli? - + View sets Vedi set - + Welcome Benvenuto - + Hi! It seems like you're running this version of Cockatrice for the first time. All the sets in the card database have been enabled. Read more about changing the set order or disabling specific sets and consequent effects in the "Manage Sets" dialog. @@ -3080,58 +3883,59 @@ Tutti i set nell'archivio delle carte sono stati abilitati. Scopri metodi alternativi per visualizzare i set o disabilitare set ed effetti nella finestra "Organizza set". - - + + Information Informazione - + A card database update is already running. L'aggiornamento delle carte è già in corso. - + Unable to run the card database updater: Impossibile avviare l'aggiornamento delle carte: - - failed to start. - avvio fallito. + + Failed to start. The file might be missing, or permissions might be incorrect. + - - crashed. - crashato. + + The process crashed some time after starting successfully. + - - timed out. - time out. + + Timed out. The process took too long to respond. The last waitFor...() function timed out. + - - write error. - errore di scrittura. + + An error occurred when attempting to write to the process. For example, the process may not be running, or it may have closed its input channel. + - - read error. - errore di lettura. + + An error occurred when attempting to read from the process. For example, the process may not be running. + - - unknown error. - errore sconosciuto. + + Unknown error occurred. + - - The card database updater exited with an error: %1 - L'aggiornamento delle carte ha causato un errore: %1 + + The card database updater exited with an error: +%1 + - + This server supports additional features that your client doesn't have. This is most likely not a problem, but this message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -3142,55 +3946,55 @@ Solitamente non si dovrebbe incorrere in problemi, ma questo messaggio può sign Per aggiornare il tuo client, vai su Aiuto -> Controlla aggiornamenti client - - - - - + + + + + Load sets/cards Carica set di carte - + Selected file cannot be found. Il file selezionato non può essere caricato. - + You can only import XML databases at this time. E' possibile importare solamente database XML. - + The new sets/cards have been added successfully. Cockatrice will now reload the card database. Il nuovo set di carte è stato aggiunto correttamente. Il database delle carte verrà ricaricato. - + Sets/cards failed to import. Il nuovo set di carte non è stato importato a causa di un errore. - - - + + + Reset Password Reimposta Password - + Your password has been reset successfully, you can now log in using the new credentials. La tua password è stata reimpostata correttamente, ora puoi accedere utilizzando le nuove credenziali. - + Failed to reset user account password, please contact the server operator to reset your password. La reimpostazione della password è fallita, contatta l'amministratore del server per reimpostare la password. - + Activation request received, please check your email for an activation token. Richiesta di attivazione ricevuta, controlla il tuo indirizzo email per il codice di attivazione. @@ -3464,181 +4268,193 @@ Il database delle carte verrà ricaricato. - %1 is looking at the top %3 card(s) %2. + %1 is looking at the %4 %3 card(s) %2. + %1 is looking at the top %3 card(s) %2. top card for singular, top %3 cards for plural - %1 sta guardando la prima carta %2.%1 sta guardando le prime %3 carte %2.%1 sta guardando le prime %3 carte %2. + %1 sta guardando una carta %4 %2.%1 sta guardando %3 carte %4 %2.%1 sta guardando %3 carte %4 %2. - + + bottom + in fondo + + + + top + in cima + + + %1 turns %2 face-down. %1 gira %2 a faccia in giù. - + %1 turns %2 face-up. %1 gira %2 a faccia in su. - + The game has been closed. La partita è stata chiusa. - + The game has started. La partita è iniziata. - + %1 has joined the game. %1 è entrato nella partita. - + %1 is now watching the game. %1 sta osservando la partita. - + You have been kicked out of the game. Sei stato cacciato dalla partita. - + %1 has left the game (%2). %1 ha abbandonato la partita (%2). - + %1 is not watching the game any more (%2). %1 non sta più guardando la partita (%2). - + %1 is not ready to start the game any more. %1 non è più pronto a iniziare la partita. - + %1 shuffles their deck and draws a new hand of %2 card(s). %1 mischia il proprio mazzo e pesca una nuova mano di %2 carta.%1 mischia il proprio mazzo e pesca una nuova mano di %2 carte.%1 mischia il proprio mazzo e pesca una nuova mano di %2 carte. - + %1 shuffles their deck and draws a new hand. %1 mischia il proprio mazzo e pesca una nuova mano. - + You are watching a replay of game #%1. Stai guardando il replay della partita #%1. - + %1 is ready to start the game. %1 è pronto a iniziare la partita. - + cards an unknown amount of cards carte - + %1 card(s) a card for singular, %1 cards for plural una carta%1 carte%1 carte - + %1 lends %2 to %3. - + %1 presta %2 a %3. - + %1 reveals %2 to %3. %1 rivela %2 a %3. - + %1 reveals %2. %1 rivela %2. - + %1 randomly reveals %2%3 to %4. %1 rivela a caso %2%3 a %4. - + %1 randomly reveals %2%3. %1 rivela a caso %2%3. - + %1 peeks at face down card #%2. %1 ha sbirciato la carta a faccia in giù #%2. - + %1 peeks at face down card #%2: %3. %1 ha sbirciato la carta a faccia in giù #%2: %3. - + %1 reveals %2%3 to %4. %1 rivela %2%3 a %4. - + %1 reveals %2%3. %1 rivela %2%3. - + %1 reversed turn order, now it's %2. %1 ha rovesciato l'ordine dei turni, ora è %2. - + reversed invertito - + normal normale - + Heads Testa - + Tails Croce - + %1 flipped a coin. It landed as %2. %1 ha lanciato una moneta. Il risultato è %2. - + %1 rolls a %2 with a %3-sided die. %1 lancia un dado a %3 facce e ottiene %2. - + %1 flips %2 coins. There are %3 heads and %4 tails. - + %1 lancia %2 monete. Sono uscite %3 teste e %4 croci. +  - + %1 rolls a %2-sided dice %3 times: %4. - + %1 lancia un dado a %2 facce %3 volte: %4. @@ -3756,17 +4572,17 @@ Il database delle carte verrà ricaricato. %1 mescola le carte %3 - %4 da %2. - + %1 unattaches %2. %1 toglie %2. - + %1 undoes their last draw. %1 annulla la sua ultima pescata. - + %1 undoes their last draw (%2). %1 annulla la sua ultima pescata (%2). @@ -3774,110 +4590,110 @@ Il database delle carte verrà ricaricato. MessagesSettingsPage - + Word1 Word2 Word3 Parola1 Parola2 Parola3 - + Add New Message Aggiungi Nuovo Messaggio - + Edit Message Modifica Messaggio - + Remove Message Rimuovi Messaggio - + Add message Aggiungi messaggio - - + + Message: Messaggio: - + Edit message Modifica messaggio - + Chat settings Impostazioni chat - + Custom alert words Lista parole evidenziate - + Enable chat mentions Abilita menzioni in chat - + Enable mention completer Abilita completamento menzioni - + In-game message macros Messaggi rapidi in partita - + How to use in-game message macros Come usare i messaggi macro in gioco - + Ignore chat room messages sent by unregistered users Ignora i messaggi in chat inviati dagli utenti non registrati - + Ignore private messages sent by unregistered users Ignora i messaggi privati inviati dagli utenti non registrati - - + + Invert text color Inverti colore testo - + Enable desktop notifications for private messages Abilita notifiche desktop per i messaggi privati - + Enable desktop notification for mentions Abilita notifiche sul desktop per le menzioni - + Enable room message history on join Abilita messaggi recenti all'ingresso - - + + (Color is hexadecimal) (Colore in esadecimale) - + Separate words with a space, alphanumeric characters only Separare le parole con uno spazio; solo caratteri alfanumerici @@ -3935,6 +4751,24 @@ Il database delle carte verrà ricaricato. Identità di colore + + OverlapControlWidget + + + Cards to overlap: + Carte da sovrapporre: + + + + Overlap percentage: + Percentuale di sovrapposizione: + + + + Overlap direction: + Direzione di sovrapposizione: + + Phase @@ -4001,57 +4835,57 @@ Il database delle carte verrà ricaricato. PhasesToolbar - + Untap step Sottofase di STAP - + Upkeep step Sottofase di mantenimento - + Draw step Sottofase di acquisizione - + First main phase Prima fase principale - + Beginning of combat step Sottofase di inizio combattimento - + Declare attackers step Sottofase di dichiarazione degli attaccanti - + Declare blockers step Sottofase di dichiarazione dei bloccanti - + Combat damage step Sottofase di danno da combattimento - + End of combat step Sottofase di fine combattimento - + Second main phase Seconda fase principale - + End of turn step Sottofase finale @@ -4059,7 +4893,7 @@ Il database delle carte verrà ricaricato. PictureLoader - + en code for scryfall's language property, not available for all languages it @@ -4068,647 +4902,781 @@ Il database delle carte verrà ricaricato. Player - + Reveal top cards of library Rivela le prime carte del grimorio - - - - - - - - - + + + + + + + + + + + + Number of cards: (max. %1) Numero di carte: (max %1) - + &View graveyard &Guarda il cimitero - + &View exile &Guarda la zona di esilio - + Player "%1" Giocatore "%1" - - - - + + + + &Graveyard &Cimitero - - - - + + + + &Exile &Esilio - + &Move hand to... &Sposta mano in... - - - + + + &Top of library Cima al grimorio - - - + + + &Bottom of library Fondo al grimorio - + &Move graveyard to... &Muovi cimitero in... - - - - + + + + &Hand &Mano - + &Move exile to... &Muovi esilio in... - + &View library &Guarda il grimorio - + &View hand &Vedi mano - + View &top cards of library... Guarda &le prime carte del grimorio... - + Reveal &library to... Rive&la grimorio a... - + &Always reveal top card Rivela &sempre la prima carta - + &View sideboard &Guarda la sideboard - + &Draw card &Pesca una carta - + D&raw cards... P&esca carte... - + &Undo last draw &Annulla l'ultima pescata - + Take &mulligan Mu&lliga - - &Shuffle - &Mescola - - - + Play top card &face down Gioca la prima carta a faccia in giù - + Move top card to grave&yard Metti la prima carta nel c&imitero - + Move top card to e&xile E&silia la prima carta - + Move top cards to &graveyard... Metti le prime carte nel &cimitero... - + Move top cards to &exile... &Esilia le prime carte... - + Put top card on &bottom Metti la prima carta in &fondo - - Lend library to... - + + View bottom cards of library... + Guarda le carte in fondo al grimorio... - + + Lend library to... + Presta la libreria a... + + + + Shuffle + Mescola + + + Put top cards on stack &until... Take top cards &until... + Metti le carte in cima alla pila fino a... + + + + Shuffle top cards... - + + Shuffle bottom cards... + + + + &Reveal hand to... &Rivela mano a... - + Reveal r&andom card to... Rivela carta c&asuale a... - + Reveal random card to... Rivela carta casuale a... - + &Sideboard &Sideboard - + &Library &Grimorio - + &Counters &Contatori - + &Untap all permanents &STAPpa tutti i permanenti - + R&oll die... L&ancia un dado... - + &Create token... &Crea una pedina... - + C&reate another token C&rea un'altra pedina - + Cr&eate predefined token Cr&ea pedina predefinita - - + + &All players &Tutti i giocatori - + S&ay P&arla - + + &Select All + Seleziona tutto + + + + S&elect Row + Seleziona fila + + + + S&elect Column + Seleziona colonna + + + &Play &Gioca - + &Hide &Nascondi - + Play &Face Down Gioca a &faccia in giù - + &Tap / Untap Turn sideways or back again &TAPpa/STAPpa - + Toggle &normal untapping Blocca/Sblocca &STAP normale - + T&urn Over Turn face up/face down Capovolgi - + &Peek at card face &Sbircia la faccia della carta - + &Clone &Copia - + Attac&h to card... Asse&gna alla carta... - + Unattac&h Tog&li - + &Draw arrow... &Disegna una freccia... - + &Increase power &Aumenta forza - + &Decrease power &Diminuisci forza - + I&ncrease toughness A&umenta costituzione - + D&ecrease toughness D&iminuisci costituzione - + In&crease power and toughness Au&menta forza e costituzione - + Dec&rease power and toughness Dim&inuisci forza e costituzione - + Increase power and decrease toughness Incrementa forza e decrementa costituzione - + Decrease power and increase toughness Decrementa forza e incrementa costituzione - + Set &power and toughness... Imposta &forza e costituzione... - + Reset p&ower and toughness Resetta &forza e costituzione - + &Set annotation... &Imposta note... - + Red Rosso - + Yellow Giallo - + Green Verde - + &Add counter (%1) &Aggiungi segnalino (%1) - + &Remove counter (%1) &Rimuovi segnalino (%1) - + &Set counters (%1)... Imposta &segnalini (%1)... - + &Top of library in random order - + &In cima al mazzo in ordine casuale - + X cards from the top of library... Posizione X dalla cima del grimorio... - + &Bottom of library in random order In fondo al grimorio in ordine casuale - + View top cards of library Guarda le prima carte del grimorio - + + View bottom cards of library + Guarda le carte in fondo al grimorio + + + + Shuffle top cards of library + Mescola le carte in cima al grimorio + + + + Shuffle bottom cards of library + Mescola le carte in fondo al grimorio + + + Which position should this card be placed: In che posizione dovrebbe essere messa questa carta: - + (max. %1) (max. %1) - + Draw hand Pesca mano - + 0 and lower are in comparison to current hand size 0 e inferiore sono relativi al numero attuale di carte in mano - + Draw cards Pesca carte - - + + Number: Numero: - + Move top cards to grave Metti le prime carte nel cimitero - + Reveal &top cards to... Rivela le &prime carte a... - + &Top of library... &In cima al grimorio... - + &Bottom of library... &In fondo alla libreria... - + &Always look at top card &Vedi sempre la prima carta - + &Open deck in deck editor &Apri il mazzo nell'editor dei mazzi - + &Play top card &Gioca la prima carta - + &Draw bottom card &Pesca l'ultima carta dal fondo - + D&raw bottom cards... P&esca carte dal fondo... - + &Play bottom card &Gioca l'ultima carta dal fondo - + Play bottom card &face down Gioca l'ultima carta dal fondo a &faccia in giù - + Move bottom card to grave&yard Metti l'ultima carta nel cimitero - + Move bottom card to e&xile Metti l'ultima carta in esilio - + Move bottom cards to &graveyard... Metti le ultime carte nel cimitero... - + Move bottom cards to &exile... Metti le ultime carte in esilio... - + Put bottom card on &top Metti l'ultima carta in cima - + Selec&ted cards Carte selezionate - + Move top cards to exile Esilia le prime carte - + Move bottom cards to grave Metti le ultime care nel cimitero - + Move bottom cards to exile Metti le ultime care in esilio - + Draw bottom cards Pesca le ultime carte - - + + C&reate another %1 token C&rea un'altra pedina %1 - + Create tokens Crea pedine - + Place card X cards from top of library Metti la carta in posizione X dalla cima del grimorio - + Change power/toughness Cambia forza/costituzione - + Change stats to: Cambia valori a: - + Set annotation Imposta nota - + Please enter the new annotation: Inserisci le nuove note: - + Set counters Imposta i segnalini - + Re&veal to... Ri&vela a... - + View related cards Vedi carte collegate - + Token: Pedina: - + All tokens Tutte le pedine + + PrintingSelector + + + Display Navigation Buttons + + + + + <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. + + + + + PrintingSelectorCardOverlayWidget + + + Preference + Preferenza + + + + Pin Printing + Fissa stampa in cima alla lista + + + + Unpin Printing + Rimuovi stampa dalla cima della lista + + + + Show Related cards + Mostra carte correlate + + + + PrintingSelectorCardSearchWidget + + + Search by set name or set code + Cerca per codice o nome del set + + + + PrintingSelectorCardSelectionWidget + + + Previous Card in Deck + Carta precedente nel mazzo + + + + Next Card in Deck + Carta successiva nel mazzo + + + + PrintingSelectorCardSortingWidget + + + Alphabetical + Alfabetico + + + + Preference + Preferenza + + + + Release Date + Data di rilascio + + + + + Descending + Discendente + + + + Ascending + Ascendente + + QMenuBar - + Services Servizi - + Hide %1 Nascondi %1 - + Hide Others Nascondi altre - + Show All Mostra tutte - + Preferences... Preferenze... - + Quit %1 Esci da %1 - + About %1 Informazioni su %1 @@ -4716,38 +5684,32 @@ Il database delle carte verrà ricaricato. QObject - + Cockatrice card database (*.xml) Database delle carte di Cockatrice (*.xml) - - + All files (*.*) Tutti i file (*.*) - + Cockatrice replays (*.cor) Replay di Cockatrice (*.cor) - - Common deck formats (*.cod *.dec *.dek *.txt *.mwDeck) - Formati di mazzo comuni (*.cod *.dec *.dek *.txt *.mwDeck) - - - + Maindeck Maindeck - + Sideboard Sideboard - + Tokens Token @@ -4755,92 +5717,92 @@ Il database delle carte verrà ricaricato. QPlatformTheme - + OK Conferma - + Save Salva - + Save All Salva tutto - + Open Apri - + &Yes &Sì - + Yes to &All Sì a &tutto - + &No &No - + N&o to All N&o a tutto - + Abort Interrompi - + Retry Riprova - + Ignore Ignora - + Close Chiudi - + Cancel Annulla - + Discard Abbandona - + Help Aiuto - + Apply Applica - + Reset Resetta - + Restore Defaults Ripristina predefiniti @@ -4848,17 +5810,17 @@ Il database delle carte verrà ricaricato. RemoteDeckList_TreeModel - + Name Nome - + ID ID - + Upload time Tempo di upload @@ -4866,32 +5828,32 @@ Il database delle carte verrà ricaricato. RemoteReplayList_TreeModel - + ID ID - + Name Nome - + Players Giocatori - + Keep Tieni - + Time started Inizio tempo - + Duration (sec) Durata (sec) @@ -4937,32 +5899,32 @@ Il database delle carte verrà ricaricato. SequenceEdit - + Choose an action from the table Scegli un'azione dalla tabella - + Hit the key/combination of keys you want to set for this action Premi il tasto/combinazione di tasti che vuoi impostare per questa azione - - Shortcut already in use - Combinazione di tasti già in uso - - - + Invalid key Tasto non valido - + + Shortcut already in use by: + Scorciatoia già utilizzata da: + + + Clear Pulisci - + Restore default Ripristina predefinito @@ -4998,73 +5960,81 @@ Il database delle carte verrà ricaricato. ShortcutSettingsPage - - + + Restore all default shortcuts Ripristina tutte le scorciatoie predefinite - + Do you really want to restore all default shortcuts? Sei scuro di voler ripristinare tutte le scorciatoie predefinite? - + Clear all default shortcuts Rimuovi tutte le scorciatoie predefinite - + Do you really want to clear all shortcuts? Sei sicuro di voler rimuovere tutte le scorciatoie? - - Action - Azione - - - - Shortcut - Scorciatoia - - - + Section: Sezione: - + Action: Azione: - + Shortcut: Scorciatoia: - + How to set custom shortcuts Come impostare scorciatoie predefinite - + Clear all shortcuts Rimuovi tutte le shortcuts + + + Search by shortcut name + Cerca per nome scorciatoia + + + + ShortcutTreeView + + + Action + Azione + + + + Shortcut + Scorciatoia + ShortcutsSettings - + Your configuration file contained invalid shortcuts. Please check your shortcut settings! Il file di configurazione conteneva combinazioni di tasti non valide. Controlla le impostazioni! - + The following shortcuts have been set to default: Le seguenti combinazioni di tasti sono state reimpostate al valore predefinito: @@ -5074,17 +6044,17 @@ Controlla le impostazioni! ShutdownDialog - + &Reason for shutdown: &Ragione dello spegnimento: - + &Time until shutdown (minutes): &Tempo allo spegnimento (minuti): - + Shut down server Spegni il server @@ -5092,27 +6062,27 @@ Controlla le impostazioni! SoundSettingsPage - + Enable &sounds Abilita &suoni - + Current sounds theme: Tema sonoro attuale: - + Test system sound engine Prova il funzionamento dei suoni - + Sound settings Impostazioni suoni - + Master volume Volume @@ -5120,48 +6090,48 @@ Controlla le impostazioni! SpoilerBackgroundUpdater - + Spoilers season has ended La stagione degli spoiler è finita - + Deleting spoiler.xml. Please run Oracle Cancellando spoiler.xml. Per favore avviare Oracle - - + + Spoilers download failed Download degli spoiler fallito - + No internet connection Connessione internet assente - + Error Errore - + Spoilers already up to date Gli spoiler sono aggiornati - + No new spoilers added Nessun nuovo spoiler aggiunto - + Spoilers have been updated! Gli spoiler sono stati aggiornati! - + Last change: Ultimo cambiamento: @@ -5169,637 +6139,593 @@ Controlla le impostazioni! StableReleaseChannel - - Stable Releases - Versioni stabili + + Default + Predefinito - + No reply received from the release update server. Nessuna risposta ricevuta dal server degli aggiornamenti di versione. - + Invalid reply received from the release update server. Risposta non valida ricevuta dal server degli aggiornamenti di versione. - + No reply received from the tag update server. Nessuna risposta ricevuta dal server dei tag di aggiornamento. - + Invalid reply received from the tag update server. Risposta non valida ricevuta dal server dei tag di aggiornamento. + + TabAccount + + + Add to Buddy List + Aggiungi a Lista Amici + + + + Add to Ignore List + Aggiungi a Lista Ignorati + + + + Account + Account + + TabAdmin - + Administration Amministrazione - + Update server &message Aggiorna &messaggio del server - + &Shut down server &Spegni il server - + &Reload configuration &Ricarica configurazione - + Server administration functions Funzioni da amministratore del server - + + Server moderator functions + Funzioni da moderatore del server + + + + Replay ID + Riproduci ID + + + + Grant Replay Access + Concedi accesso alla riproduzione + + + + Username to Activate + Nome utente da attivare + + + + Force Activate User + Forza attivazione utente + + + &Unlock functions &Sblocca funzioni - + &Lock functions &Blocca funzioni + + + + Success + Successo + + + + Replay access granted + Accesso replay concesso + + + + + + + + Error + Errore + + + + Unable to grant replay access. Replay ID invalid + Impossibile concedere accesso alla riproduzione. ID riproduzione non valido + + + + Unable to grant replay access. Internal error + Impossibile concedere accesso alla riproduzione. Errore interno + + + + User successfully activated + Utente attivato con successo + + + + Unable to activate user. Username invalid + Impossibile attivare utente. Nome utente non valido + + + + Unable to activate user. User already active + Impossibile attivare utente. L'utente è già attivo + + + + Unable to activate user. Internal error + Impossibile attivare utente. Errore interno + TabDeckEditor - - Search by card name (or search expressions) - Search by card name - Cerca per nome della carta (o per contenuto) - - - - Add to Deck - Aggiungi al mazzo - - - - Add to Sideboard - Aggiungi alla sideboard - - - - Show Related cards - Mostra carte collegate - - - - &Clear all filters - Elimina tutti i filtri - - - - Delete selected - Elimina filtro - - - - Deck &name: - &Nome mazzo: - - - - &Comments: - &Commenti: - - - - Hash: - Hash: - - - - &New deck - &Nuovo mazzo - - - - &Load deck... - &Carica mazzo... - - - - &Save deck - &Salva mazzo - - - - Save deck &as... - Salva mazzo &con nome... - - - - Load deck from cl&ipboard... - Carica mazzo dagli app&unti... - - - - Save deck to clipboard - Salva il mazzo negli appunti - - - - Annotated - Con annotazioni - - - - Not Annotated - Senza annotazioni - - - - &Print deck... - &Stampa mazzo... - - - - &Send deck to online service - &Invia mazzo al servizio online - - - - Create decklist (decklist.org) - Crea una decklist (decklist.org) - - - - Analyze deck (deckstats.net) - Analizza mazzo (deckstats.net) - - - - Analyze deck (tappedout.net) - Analizza mazzo (tappedout.net) - - - - &Close - &Chiudi - - - - Add card to &maindeck - Aggiungi carta al &maindeck - - - - Add card to &sideboard - Aggiungi carta alla &sideboard - - - - &Remove row - &Rimuovi carta - - - - &Increment number - &Aumenta il numero - - - - &Decrement number - &Diminuisci il numero - - - - &Deck Editor - Editor dei mazzi - - - - + Card Info Info carta - - + Deck Mazzo - - + Filters Filtri - + &View &Visualizza - - - + + Printing + Stampa + + + + + + Visible Mostra - - - + + + + Floating Separata - + Reset layout Resetta disposizione - + Deck: %1 Mazzo: %1 - - - Are you sure? - Sei sicuro? - - - - The decklist has been modified. -Do you want to save the changes? - La lista del mazzo è stata modificata. -Vuoi salvare i cambiamenti? - - - - Load deck - Carica mazzo - - - - - - - - - Error - Errore - - - - The deck could not be saved. - Il mazzo non può essere salvato. - - - - Could not save remote deck - Impossibile salvare mazzo remoto - - - - - The deck could not be saved. -Please check that the directory is writable and try again. - Il mazzo non può essere salvato. -Controlla se la cartella è valida e prova ancora. - - - - Save deck - Salva mazzo - - - - There are no cards in your deck to be exported - Non ci sono carte da esportare nel tuo mazzo - - - - No deck was selected to be saved. - Nessun mazzo da salvare è stato selezionato. - TabDeckStorage - - Deck storage - Archivio mazzi - - - + Local file system File locali - + Server deck storage Mazzi su server - - + + Open in deck editor Apri nell'editor dei mazzi - + + Rename deck or folder + Rinomina mazzo o cartella + + + Upload deck Upload mazzo - + Download deck Scarica mazzo - - + + + + New folder Nuova cartella - - + + Delete Elimina - - + + Open decks folder + Apri cartella mazzi + + + + Rename local folder + Rinomina cartella locale + + + + Rename local file + Rinomina file locale + + + + New name: + Nuovo nome: + + + + + + Error Errore - + + Rename failed + Rinomina non riuscita + + + + Invalid deck file File mazzo non valido - + Enter deck name Inserisci il nome del mazzo - + This decklist does not have a name. Please enter a name: Questo mazzo non ha un nome. Per favore inserisci un nome: - + Unnamed deck Mazzo senza nome - + Failed to upload deck to server Impossibile caricare il mazzo sul server - + Delete local file Elimina file locali - - - - Are you sure you want to delete "%1"? - Sei sicuro di voler eliminare %1? + + Are you sure you want to delete the selected files? + Vuoi davvero eliminare i file selezionati? - + + Delete remote decks + Elimina i deck remoti + + + + Are you sure you want to delete the selected decks? + Vuoi davvero eliminare i mazzi selezionati? + + + + Name of new folder: Nome della nuova cartella: - - Delete remote folder - Cancella cartella remota + + Deck Storage + Archivio mazzi + + + + TabDeckStorageVisual + + + Visual Deck Storage + Galleria mazzi - - Delete remote deck - Elimina mazzo remoto + + Error + Errore + + + + Could not open deck at %1 + + + + + TabEdhRec + + + EDHREC: + EDHREC: TabGame - - - + + + Replay Replay - - + + Game Partita - - + + Player List Giocatori - - + + Card Info Info carta - - + + Messages Messaggi - - + + Replay Timeline Replay - + &Phases &Fasi - + &Game &Partita - + Next &phase Prossima &fase - + Next phase with &action Prossima sottofase + &azione - + Next &turn Prossimo &turno - + Reverse turn order Inverti l'ordine dei turni - + &Remove all local arrows &Rimuovi tutte le frecce - + Rotate View Cl&ockwise Ruota vista in senso &orario - + Rotate View Co&unterclockwise R&uota vista in senso antiorario - + Game &information &Informazioni partita - + + Un&concede + Rientra in gioco + + + &Concede &Concedi - + &Leave game &Lascia partita - + C&lose replay C&hiudi replay - + &Focus Chat Focus sulla Chat - + &Say: &Parla: - + &View &Visualizza - - - - + + + + Visible Mostra - - - - + + + + Floating Separata - + Reset layout Resetta disposizione - + Concede Concedi - + Are you sure you want to concede this game? Vuoi veramente concedere la partita? - + Unconcede Annulla concedi - + You have already conceded. Do you want to return to this game? Hai già concesso. Vuoi ritornare in questa partita? - + Leave game Lascia la partita - + Are you sure you want to leave this game? Sei sicuro di voler lasciare la partita? - + You are flooding the game. Please wait a couple of seconds. Stai spammando la partita. Attendi un paio di secondi. - + A player has joined game #%1 Un giocatore si è unito alla partita #%1 - + %1 has joined the game %1 si è unito alla partita - + kicked by game host or moderator espulso da proprietario del gioco o moderatore - + player left the game il giocatore ha lasciato la partita - + player disconnected from server il giocatore si è disconnesso dal server - + reason unknown ragione sconosciuta - + You have been kicked out of the game. Sei stato kickato fuori dalla partita. @@ -5812,153 +6738,153 @@ Please enter a name: Registri - - - + + + Time;SenderName;SenderIP;Message;TargetID;TargetName Ora;NomeMittente;IPMittente;Messaggio;IDDestinatario;NomeDestinatario - + Room Logs Registri stanze - + Game Logs Registri partite - + Chat Logs Registri chat - - + + Error Errore - + You must select at least one filter. Seleziona almeno un filtro. - + You have to select a valid number of days to locate. Seleziona un numero di giorni valido. - + Username: Nome utente: - + IP Address: Indirizzo IP: - + Game Name: Nome partita: - + GameID: ID Partita: - + Message: Messaggio: - + Main Room Stanza principale - + Game Room Stanza partita - + Private Chat Chat privata - + Past X Days: Ultimi X giorni: - + Today Oggi - + Last Hour Ultima ora - + Maximum Results: Massimo numero di risultati: - + At least one filter is required. The more information you put in, the more specific your results will be. E' richiesto perlomeno un filtro. Più informazioni inserisci, più specifici saranno i risultati. - + Get User Logs Ottieni log utente - + Clear Filters Elimina filtri - + Filters Filtri - + Log Locations Posizioni registro - + Date Range Periodo - + Maximum Results Massimo numero di risultati - - + + Message History Storico messaggi - + Failed to collect message history information. Impossibile recuperare la storia dei ban. - + There are no messages for the selected filters. Nessun messaggio corrisponde ai filtri selezionati. @@ -5981,22 +6907,22 @@ Più informazioni inserisci, più specifici saranno i risultati. %1 - Chat privata - + This user is ignoring you, they cannot see your messages in main chat and you cannot join their games. Questo utente ti sta ignorando, non può vedere i tuoi messaggi nella chat principale e tu non puoi entrare nelle sue partite. - + Private message from Messaggio privato da - + %1 has left the server. %1 è uscito dal server. - + %1 has joined the server. %1 è entrato nel server. @@ -6004,117 +6930,153 @@ Più informazioni inserisci, più specifici saranno i risultati. TabReplays - - Game replays - Replay partite - - - + Local file system File locali - + Server replay storage Replay sul server - - + + Watch replay Guarda replay - - + + Rename + Rinomina + + + + + New folder + Nuova cartella + + + + Delete Elimina - + + Open replays folder + Apri cartella replay + + + Download replay Scarica replay - + Toggle expiration lock Metti/togli blocco scadenza - + + Rename local folder + Rinomina cartella locale + + + + Rename local file + Rinomina file locale + + + + New name: + Nuovo nome: + + + + Error + Errore + + + + Rename failed + Rinomina non riuscita + + + + Name of new folder: + Nome della nuova cartella: + + + Delete local file Elimina il file locale - - Are you sure you want to delete "%1"? - Sei sicuro di voler eliminare "%1"? + + Are you sure you want to delete the selected files? + Vuoi davvero eliminare i file selezionati? - - Downloading Replays - Scaricamento replay + + Are you sure you want to delete the selected replays? + Vuoi davvero eliminare i replay selezionati? - - Folder download is not yet supported. Please download replays individually. - Il download di cartelle non è ancora supportato. Per faovre scarica i replay uno alla volta. - - - + Delete remote replay Elimina replay remoto - - Are you sure you want to delete the replay of game %1? - Sei sicuro di voler eliminare il replay della partita %1? + + Game Replays + Replay partite TabRoom - + &Say: &Parla: - + Chat Chat - + &Room &Stanza - + &Leave room &Lascia stanza - + &Clear chat Pulisci &chat - + Chat Settings... Impostazioni chat... - + mentioned you. ti ha menzionato. - + Click to view Clicca per visualizzare - + You are flooding the chat. Please wait a couple of seconds. Stai spammando la chat. Attendi un paio di secondi. @@ -6127,30 +7089,30 @@ Più informazioni inserisci, più specifici saranno i risultati. Server - - - - + + + + Error Errore - + Failed to join the server room: it doesn't exist on the server. Impossibile unirsi alla stanza: non esiste sul server. - + The server thinks you are in the server room but your client is unable to display it. Try restarting your client. Il server pensa che tu sia nella stanza ma il tuo client non è in grado di mostrarlo. Prova a riavviare il client. - + You do not have the required permission to join this server room. Non hai i requisiti necessari per unirti a questa stanza. - + Failed to join the server room due to an unknown error: %1. Impossibile unirsi alla stanza a causa di un errore sconosciuto: %1. @@ -6158,32 +7120,72 @@ Più informazioni inserisci, più specifici saranno i risultati. TabSupervisor - + + Deck Editor + Editor dei mazzi + + + + &Visual Deck Storage + &Galleria mazzi + + + + Server + Server + + + + Account + Account + + + + Deck Storage + Archivio mazzi + + + + Game Replays + Replay partite + + + + Administration + Amministrazione + + + + Logs + Registri + + + Are you sure? Sei sicuro? - + There are still open games. Are you sure you want to quit? Ci sono ancora delle partite aperte. Sei sicuro di voler uscire? - + Click to view Clicca per visualizzare - + Your buddy %1 has signed on! Il tuo amico %1 si è collegato - + Unknown Event Evento sconosciuto. - + The server has sent you a message that your client does not understand. This message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -6194,63 +7196,45 @@ Questo messaggio può significare che è disponibile una nuove versione di Cocka Per aggiornare il tuo client, vai su Aiuto -> Controlla aggiornamenti client - + Idle Timeout Timeout inattività - + You are about to be logged out due to inactivity. Stai per essere disconnesso per inattività. - + Promotion Promozione - + You have been promoted. Please log out and back in for changes to take effect. Sei stato promosso. Esci e rientra per dare effetto alle modifiche. - + Warned Avviso - + You have received a warning due to %1. Please refrain from engaging in this activity or further actions may be taken against you. If you have any questions, please private message a moderator. Hai ricevuto un avviso a causa di %1. Se pregato di evitare di continuare questa attività o potrebbero venire presi ulteriori provvedimenti nel tuoi confronti. Per qualsiasi domanda, manda un messaggio ad un moderatore. - + You have received the following message from the server. (custom messages like these could be untranslated) Hai ricevuto il seguente messaggio dal server. (il messaggio personalizzato potrebbe non essere tradotto) - - TabUserLists - - - Account - Account - - - - Add to Buddy List - Aggiungi a Lista Amici - - - - Add to Ignore List - Aggiungi a Lista Ignorati - - TappedOutInterface @@ -6336,173 +7320,184 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u UserContextMenu - + User &details &Dettagli utente - + Private &chat &Chat privata - + Show this user's &games Visualizza le &partite dell'utente - + Add to &buddy list Aggiungi alla lista &amici - + Remove from &buddy list Rimuovi dalla lista &amici - + Add to &ignore list Aggiungi alla lista &ignorati - + Remove from &ignore list Rimuovi dalla lista &ignorati - + Kick from &game Caccia dalla &partita - + Warn user Avvisa utente - + View user's war&n history Mostra storia avvisi ute&nte - + Ban from &server Banna dal &server - + View user's &ban history Mostra storia &ban utente - + &Promote user to moderator &Promuovi utente a moderatore - + Dem&ote user from moderator Degrada il m&oderatore ad utente - + Promote user to &judge Promuovi utente a &giudice - + Demote user from judge Degrada utente da giudice - + + View admin notes + Visualizza le note degli amministratori + + + %1's games Partite di %1 - - - + + + Ban History Storico ban - + Ban Time;Moderator;Ban Length;Ban Reason;Visible Reason Ora Ban;Moderatore;Durata Ban;Ragione Ban;Ragione Visibile - + User has never been banned. L'utente non è mai stato bannato. - + Failed to collect ban information. Impossibile recuperare le informazioni sui ban. - - - + + + Warning History Storico avvisi - + Warning Time;Moderator;User Name;Reason Ora Avviso;Moderatore;Nome Utente;Ragione - + User has never been warned. L'utente non ha mai ricevuto avvisi. - + Failed to collect warning information. Impossibile recuperare le informazioni sugli avvisi. - - + + Failed to get admin notes. + Impossibile ottenere le note degli amministratori + + + + Success Successo - + Successfully promoted user. Utente promosso. - + Successfully demoted user. Utente degradato. - - + + + Failed Fallito - + Failed to promote user. Promozione fallita. - + Failed to demote user. Degradazione fallita. - + Copy hash to clipboard Copia hash nella clipboard - + Remove this user's messages Rimuovi i messaggi di questo utente @@ -6510,173 +7505,173 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u UserInfoBox - + Location: Posizione: - + Account Age: Età account: - + Edit Modifica - + Change password Cambia password - + Change avatar Cambia avatar - + Administrator Amministratore - + Moderator Moderatore - + Registered user Utente registrato - - + + Unregistered user Utente non registrato - + Judge Arbitro - + Unknown Sconosciuta - + The entered password does not match your account. La password inserita non corrisponde al tuo accound. - - - + + + Information Informazione - + User information updated. Informazioni utente aggiornate. - - - - - - - - - - + + + + + + + + + + Error Errore - + User Information Informazioni Utente - + Real Name: Nome Reale: - + User Level: Livello dell'Utente: - + %n Year(s), amount of years (only shown if more than 0) 1 Anno.%n Anni.%n Anno(i). - + %10%n Day(s) %20 amount of years (if more than 0), amount of days, date in local short format %10%n Giorno %20%10%n Giorni %20%10%n Giorno(i) %20 - + Enter Password Inserisci Password - + Password verification is required in order to change your email address La verifica della password è necessaria per cambiare il tuo indirizzo email - - - + + + An error occurred while trying to update your user information. E' stato riscontrato un errore durante l'aggiornamento delle tue informazioni utente. - + This server does not permit you to update your user informations. Questo server non ti permette di aggiornare le tue informazioni utente. - + Password changed. Password modificata. - + This server does not permit you to change your password. Questo server non ti permette di modificare la password. - + The new password is too short. La nuova password è troppo corta. - + The old password is incorrect. La vecchia password è sbagliata. - + Avatar updated. Avatar modificato. - + This server does not permit you to update your avatar. Questo server non ti permette di aggiornare il tuo avatar. - + An error occured while trying to updater your avatar. Si è verificato un errore durante l'aggiornamento del tuo avatar. @@ -6684,124 +7679,251 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u UserInterfaceSettingsPage - + General interface settings Impostazioni generali di interfaccia - + &Double-click cards to play them (instead of single-click) &Doppio click sulle carte per giocarle (disabilitato un solo click) - + + &Clicking plays all selected cards (instead of just the clicked card) + Gioca tutte le carte selezionate + + + &Play all nonlands onto the stack (not the battlefield) by default Gioca tutte le carte non terra nella &pila (invece che sul campo di battaglia) - + + Close card view window when last card is removed + + + + Annotate card text on tokens Annota il testo della carta sulle pedine - + Use tear-off menus, allowing right click menus to persist on screen Usa menù-a-strappo, permettendo ai menù del tasto destro del mouse di rimanere sullo schermo - + Notifications settings Impostazioni notifiche - + Enable notifications in taskbar Abilita notifiche nella barra delle applicazioni - + Notify in the taskbar for game events while you are spectating Abilita notifiche anche per i giochi in cui si è solo uno spettatore - + Notify in the taskbar when users in your buddy list connect Notifca nella barra delle applicazioni quando degli utenti nella tua lista amici si connetto - + Animation settings Impostazioni delle animazioni - + &Tap/untap animation Animazioni &TAPpa/STAPpa + + + Deck editor/storage settings + Impostazioni editor/archivio mazzi + + + + Open deck in new tab by default + Apri il mazzo in una nuova scheda automaticamente + + + + Prompt before converting .txt decks to .cod format + Chiedi prima di convertire mazzi .txt al formato .cod + + + + Always convert if not prompted + + + + + Use visual deck storage in game lobby + + + + + Replay settings + Impostazioni di riproduzione + + + + Buffer time for backwards skip via shortcut: + Tempo di attesa per saltare indietro dopo aver premuto la scorciatoia + - UserList + UserListWidget - + Users connected to server: %1 Utenti connessi al server: %1 - + Users in this room: %1 Utenti in questa stanza: %1 - + Buddies online: %1 / %2 Amici online: %1 / %2 - + Ignored users online: %1 / %2 Utenti ignorati online: %1 / %2 + + VisualDeckStorageFolderDisplayWidget + + + Deck Storage + Archivio mazzi + + + + VisualDeckStorageSearchWidget + + + Search by filename + Cerca per nome del file + + + + VisualDeckStorageSortWidget + + + Sort Alphabetically (Deck Name) + Ordina alfabeticamente (Nome del mazzo) + + + + Sort Alphabetically (Filename) + Ordina alfabeticamente (Nome del file) + + + + Sort by Last Modified + Ordina per ultima modifica + + + + Sort by Last Loaded + Ordina per ultimo caricato + + + + VisualDeckStorageWidget + + + Loading database ... + Caricamento database... + + + + Show Folders + Mostra cartelle + + + + Show Tag Filter + Mostra filtro etichette + + + + Show Tags On Deck Previews + Mostra etichette su anteprime mazzi + + + + Show Banner Card Selection Option + Mostra opzione per selezione copertina + + + + Include Folder Names in Search + + + + + Draw unused Color Identities + + + + + Unused Color Identities Opacity + + + WarningDialog - + Which warning would you like to send? Che avviso vorresti inviare? - + Redact all messages from this user in all rooms Ottieni tutti i messaggi di questo utente in tutte le stanze - + &OK &OK - + &Cancel &Annulla - + Warn user for misconduct Avvisa l'utente per cattivo comportamento - - + + Error Errore - + User name to send a warning to can not be blank, please specify a user to warn. Il nome utente a cui inviare l'avviso non può essere vuoto. - + Warning to use can not be blank, please select a valid warning to send. L'avviso da inviare non può essere vuoto. @@ -6809,128 +7931,133 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u WndSets - + Move selected set to the top Muovi set selezionato in cima - + Move selected set up Muovi set selezionato su - + Move selected set down Muovi set selezionato giù - + Move selected set to the bottom Muovi set selezionato in fondo - + Search by set name, code, or type Cerca per nome, codice o tipo del set - + Default order Ordine predefinito - + Restore original art priority order Ripristina ordine originale priorità immagini - + Enable all sets Abilita tutti i set - + Disable all sets Disabilita tutti i set - + Enable selected set(s) Abilita i set selezionati - + Disable selected set(s) Disabilita i set selezionati - + Deck Editor Editor dei mazzi - + Use CTRL+A to select all sets in the view. Usa CTRL+A per selezionare le espansioni nella visuale. - + Only cards in enabled sets will appear in the card list of the deck editor. Solo carte in espansioni abilitate appariranno nella lista carte dell'editor di mazzo. - + Image priority is decided in the following order: La priorità di immagine è decisa nell'ordine seguente: - + first the CUSTOM Folder (%1), then the Enabled Sets in this dialog (Top to Bottom) %1 is a link to the wiki innanzitutto la cartella CUSTOM (%1), poi le Espansioni Abilitate in questo dialogo (da Cima a Fondo) - + Card Art Immagine carta - + How to use custom card art Come usare immagini personalizzate per le carte - + Hints Suggerimenti - + Note Note - + Sorting by column allows you to find a set while not changing set priority. L'ordinamento per colonne ti permette di trovare un set senza cambiare il loro ordine di priorità. - + To enable ordering again, click the column header until this message disappears. Per riabilitare l'ordinamento, clicca sull'intestazione della colonna finché questo messaggio non scompare. - + Use the current sorting as the set priority instead Usa l'ordinamento corrente per impostare la priorità dei set - + Sorts the set priority using the same column Imposta priorità dei set usando l'ordinamento per colonna - + + Include online-only (Arena) cards [requires restart] + Includi carte solo online (Arena) [richiede riavvio] + + + Manage sets Organizza set @@ -6938,22 +8065,67 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u ZoneViewWidget - - sort by name - Ordina per nome + + Ungrouped + Non raggruppato - - sort by type + + Group by Type + Raggruppa per tipo + + + + Group by Mana Value + Raggruppa per valore di mana + + + + Group by Color + Raggruppa per colore + + + + Unsorted + Non ordinato + + + + Sort by Name + Ordina per nome  + + + + Sort by Type Ordina per tipo - + + Sort by Mana Cost + Ordina per costo di mana + + + + Sort by Colors + Ordina per colori + + + + Sort by P/T + Ordina per F/C + + + + Sort by Set + Ordina per Set + + + shuffle when closing Mescola quando chiudi - + pile view Raggruppa per tipo @@ -6961,7 +8133,7 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u i18n - + English Italiano (Italian) @@ -6969,12 +8141,12 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u main - + Connect on startup Connetti all'avvio - + Debug to file Debug su file @@ -6982,786 +8154,908 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u shortcutsTab - + Main Window Finestra principale - - + + Deck Editor Editor dei mazzi - + Game Lobby Lobby del gioco - + Card Counters Segnalini Carta - + Player Counters Segnalini Giocatore - + Power and Toughness Forza e costituzione - + Game Phases Fasi di gioco - + Playing Area Area di gioco - + Move Selected Card Muovi la carta selezionata - + View Guarda - + Move Top Card Muovi la prima carta - + Move Bottom Card Muovi l'ultima carta - + Gameplay Partita - + Drawing Pescare - + Chat Room Chat Room - + Game Window Finestra di Gioco - + Load Deck from Clipboard Carica mazzo dalla clipboard - + + Replays - + Replay - + + Tabs + Schede + + + Check for Card Updates... Controlla per aggiornamenti carte... - + Connect... Connetti... - + Disconnect Disconnetti - + Exit Esci - + Full screen Schermo intero - + Register... Registrati... - + Settings... Impostazioni... - + Start a Local Game... Inizia una partita in locale... - + Watch Replay... Guarda Replay... - - Analyze Deck - Analizza mazzo + + Analyze Deck (deckstats.net) + Analyze Deck + Analizza mazzo (deckstats.net) - + + Analyze Deck (tappedout.net) + Analizza mazzo (tappedout.net) + + + Clear All Filters Elimina tutti i filtri - + Clear Selected Filter Elimina il filtro selezionato - + Close Chiudi - + Remove Card Rimuovi carta - + Manage Sets... Gestisci Espansioni... - + Edit Custom Tokens... Modifica pedine personalizzate - - Export Deck - Esporta mazzo + + Export Deck (decklist.org) + Esporta mazzo (decklist.org) - + + Export Deck (decklist.xyz) + Esporta mazzo (decklist.xyz) + + + Add Card Aggiungi Carta - + Load Deck... Carica mazzo... - + Load Deck from Clipboard... Carica Mazzo da Appunti... - + + Edit Deck in Clipboard, Annotated + Modifica il mazzo negli appunti con annotazioni + + + + Edit Deck in Clipboard + Modifica mazzo negli appunti + + + New Deck Nuovo mazzo - + Open Custom Pictures Folder Apri Cartella Immagini Personalizzate - + Print Deck... Stampa mazzo... - + Delete Card Elimina carta - - + + Reset Layout Reimposta Layout - + Save Deck Salva mazzo - + Save Deck as... Salva mazzo con nome - + Save Deck to Clipboard, Annotated Salva Mazzo in Appunti, Annotato - + + Save Deck to Clipboard, Annotated (No Set Info) + Salva il mazzo negli appunti con annotazioni (senza info set) + + + Save Deck to Clipboard Salva Mazzo in Appunti - + + Save Deck to Clipboard (No Set Info) + Salva il mazzo negli appunti (senza info set) + + + Load Local Deck... Carica Mazzo Locale... - + Load Remote Deck... Carica Mazzo da Remoto... - + Set Ready to Start Imposta Pronto a Iniziare - + Toggle Sideboard Lock Attiva Blocco della Sideboard - - + + Add Green Counter Aggiungi Segnalino Verde - - + + Remove Green Counter Rimuovi Segnalino Verde - - + + Set Green Counters... Imposta Segnalini Verdi... - + Add Yellow Counter Aggiungi Segnalino Giallo - + Remove Yellow Counter Rimuovi Segnalino Giallo - + Set Yellow Counters... Imposta Segnalini Gialli - - + + Add Red Counter Aggiungi Segnalino Rosso - - + + Remove Red Counter Rimuovi Segnalino Rosso - - + + Set Red Counters... Imposta Segnalini Rossi... - + Add Life Counter Aggiungi Segnalino Vita - + Remove Life Counter Rimuovi Segnalino Vita - + Set Life Counters... Imposta Segnalini Vita... - + Add White Counter Aggiungi Segnalino Bianco - + Remove White Counter Rimuovi Segnalino Bianco - + Set White Counters... Imposta Segnalini Bianchi - + Add Blue Counter Aggiungi Contatore Blu - + Remove Blue Counter Rimuovi Segnalino Blu - + Set Blue Counters... Imposta Segnalini Blu... - + Add Black Counter Aggiungi Segnalino Nero - + Remove Black Counter Rimuovi Segnalino Nero - + Set Black Counters... Imposta Segnalino Nero... - + Add Colorless Counter Aggiungi Segnalino Incolore - + Remove Colorless Counter Rimuovi Segnalino Incolore - + Set Colorless Counters... Imposta Segnalini Incolore... - + Add Other Counter Aggiungi Altro Segnalino - + Remove Other Counter Rimuovi Altro Segnalino - + Set Other Counters... Imposta Altri Segnalini... - + Add Power (+1/+0) Aggiungi Forza (+1/+0) - + Remove Power (-1/-0) Rimuovi Forza (-1/-0) - + Move Toughness to Power (+1/-1) Sposta Costituzione a Forza (+1/-1) - + Add Toughness (+0/+1) Aggiungi Costituzione (+0/+1) - + Remove Toughness (-0/-1) Rimuovi Costituzione (-0/-1) - + Move Power to Toughness (-1/+1) Sposta Forza a Costituzione (-1/+1) - + Add Power and Toughness (+1/+1) Aggiungi Forza e Costituzione (+1/+1) - + Remove Power and Toughness (-1/-1) Rimuovi Forza e Costituzione (-1/-1) - + Set Power and Toughness... Imposta Forza e Costituzione... - + Reset Power and Toughness Resetta Forza e Costituzione - + Untap STAP - + Upkeep Mantenimento - + Draw Acquisizione - + First Main Phase Prima Fase Principale - + Start Combat Inizio Combattimento - + Attack Dichiarazione attaccanti - + Block Dichiarazione bloccanti - + Damage Danno da combattimento - + End Combat Fine Combattimento - + Second Main Phase Seconda Fase Principale - + End Finale - + Next Phase Fase Successiva - + Next Phase Action Nuova Fase - + Next Turn Turno Successivo - + + Hide Card in Reveal Window + Nascondi la carta nella finestra di visualizzazione + + + Tap / Untap Card TAPpa / STAPpa carta - + Untap All Stappa Tutto - + Toggle Untap Abilita STAPpa - + Turn Card Over Gira Carta - + Peek Card Osserva Carta - + Play Card Gioca Carta - + Attach Card... Attacca Carta... - + Unattach Card Stacca Carta - + Clone Card Clona Carta - + Create Token... Crea Pedina... - + Create All Related Tokens Crea Tutte le Pedine Correlate - + Create Another Token Crea un'Altra Pedina - + Set Annotation... Imposta annotazione... - - + + Select All Cards in Zone + Seleziona tutte le carte nella zona + + + + Select All Cards in Row + Seleziona tutte le carte nella riga + + + + Select All Cards in Column + Seleziona tutte le carte nella colonna + + + + Bottom of Library Fondo del mazzo - - - - + + + + Exile Esilio - - - - + + + + Graveyard Cimitero - - + + Hand Mano - - + + Top of Library Vedi Cima della Libreria - - - + + + Battlefield, Face Down Campo di battaglia, faccia in giù - + Battlefield Campo di battaglia - + Library Grimorio - + Sideboard Sideboard - + Top Cards of Library Vedi Prime Carte della Libreria - + + Bottom Cards of Library + Carte in fondo al grimorio + + + Close Recent View Chiudi Viste di Recente - - + + Stack Pila - - + + Graveyard (Multiple) Cimitero (multiplo) - - + + Exile (Multiple) Esilia (multiplo) - + Stack Until Found Impila fino a trovare - + Draw Bottom Card Pesca la carta in fondo al mazzo - + Draw Multiple Cards from Bottom... Pesca più carte dal fondo del mazzo... - + Draw Arrow... Disegna freccia... - + Remove Local Arrows Rimuovi frecce locali - + Leave Game Abbandona Partita - + Concede Concedi - + Roll Dice... Tira un Dado... - + Shuffle Library Mischia il Mazzo - - Mulligan - Mulliga + + Shuffle Top Cards of Library + Mescola carte in cima al grimorio - + + Shuffle Bottom Cards of Library + Mescola carte in fondo al grimorio + + + + Mulligan + Mulligan + + + Draw a Card Pesca una Carta - + Draw Multiple Cards... Pesca più carte... - + Undo Draw Annulla Disegno - + Always Reveal Top Card Rivela sempre la carta in cima - + Always Look At Top Card Guarda sempre la carta in cima - + Rotate View Clockwise Ruota la vista in senso orario - + Rotate View Counterclockwise Ruota la vista in senso antiorario - + Unfocus Text Box Non focalizzare il Blocco di Testo - + Focus Chat - Focus sulla Chat + Focalizza Chat - + Clear Chat Cancella chat - + Refresh Aggiorna - - Play/Pause - + + Skip Forward + Salta avanti - + + Skip Backward + Salta indietro + + + + Skip Forward by a lot + Salta avanti di molto + + + + Skip Backward by a lot + Salta indietro di molto + + + + Play/Pause + Riproduci/Pausa + + + Toggle Fast Forward - + Attiva/disattiva avanzamento rapido + + + + Visual Deck Storage + Galleria mazzi + + + + Deck Storage + Archivio mazzi + + + + Server + Server + + + + Account + Account + + + + Administration + Amministrazione + + + + Logs + Registri \ No newline at end of file diff --git a/cockatrice/translations/cockatrice_ja.ts b/cockatrice/translations/cockatrice_ja.ts index ac0d3054c..0beb8fe29 100644 --- a/cockatrice/translations/cockatrice_ja.ts +++ b/cockatrice/translations/cockatrice_ja.ts @@ -2,7 +2,7 @@ AbstractCounter - + &Set counter... カウンターの数を決める... @@ -10,95 +10,247 @@ AbstractCounterDialog - + Set counter カウンターの設定 - + New value for counter '%1': '%1'カウンターの新しい値: + + AbstractDlgDeckTextEdit + + + &Refresh + + + + + Parse Set Name and Number (if available) + + + + + AbstractTabDeckEditor + + + Open in new tab + + + + + Are you sure? + + + + + The decklist has been modified. +Do you want to save the changes? + + + + + + + + + + + Error + + + + + Could not open deck at %1 + + + + + Could not save remote deck + + + + + + The deck could not be saved. +Please check that the directory is writable and try again. + + + + + Save deck + + + + + The deck could not be saved. + + + + + There are no cards in your deck to be exported + + + + + No deck was selected to be exported. + + + + + AdminNotesDialog + + + Update Notes + リリースノート + + + + Admin Notes for %1 + + + + + AllZonesCardAmountWidget + + + Mainboard + メインボード + + + + Sideboard + サイドボード + + AppearanceSettingsPage - + Error エラー - + Could not create themes directory at '%1'. '%1' にテーマディレクトリを作成できませんでした。 - + Theme settings テーマ設定 - + Current theme: 現在のテーマ - + Open themes folder テーマフォルダを開く - + + Menu settings + メニュー設定 + + + + Show keyboard shortcuts in right-click menus + 右クリックメニューにキーボードショートカットを表示する + + + Card rendering カードの描画 - + Display card names on cards having a picture 画像のあるカードにカード名を表示 - + + Auto-Rotate cards with sideways layout + 横向きレイアウトのカードを自動回転 + + + + Override all card art with personal set preference (Pre-ProviderID change behavior) [Requires Client restart] + すべてのカード アートを個人設定の環境設定で上書きする (Pre-ProviderID 変更動作) [クライアントの再起動が必要] + + + + Bump sets that the deck contains cards from to the top in the printing selector + デッキに含まれるカードを印刷セレクターの一番上に上げる + + + Scale cards on mouse over マウスオーバーでカードを拡大 - - Minimum overlap percentage of cards on the stack and in vertical hand + + Use rounded card corners - + + Minimum overlap percentage of cards on the stack and in vertical hand + スタックと手札垂直配置時の重なり率の最小値 + + + + Maximum initial height for card view window: + カードビューウィンドウの初期高さの最大値 + + + + + rows + + + + + Maximum expanded height for card view window: + + + + Hand layout 手札のレイアウト - + Display hand horizontally (wastes space) 手札を横に並べる - + Enable left justification 左揃えを有効 - + Table grid layout テーブルのレイアウト - + Invert vertical coordinate カード配置の垂直反転 - + Minimum player count for multi-column layout: プレイヤーを複数列レイアウトにする最少人数: - + Maximum font size for information displayed on cards: カードに表示する情報の最大のフォントサイズ: @@ -106,112 +258,112 @@ BanDialog - + ban &user name ユーザー名でBAN - + ban &IP address IPアドレスでBAN - + ban client I&D クライアントIDでBAN - + Ban type BANタイプ - + &permanent ban 永久BAN - + &temporary ban 一時BAN - + &Days: - + &Hours: 時間 - + &Minutes: - + Duration of the ban BAN期間 - + Please enter the reason for the ban. This is only saved for moderators and cannot be seen by the banned person. BANの理由を入力してください。 これはモデレーターによって保存されBANされた人には見えません。 - + Please enter the reason for the ban that will be visible to the banned person. BANの理由を入力してください。これはBANされる人に通知されます。 - + Redact all messages from this user in all rooms すべてのルームでこのユーザーからのすべてのメッセージを編集します - + &OK OK - + &Cancel キャンセル - + Ban user from server サーバーからBANされたユーザーです - - - - + + + + Error エラー - + You have to select a name-based, IP-based, clientId based, or some combination of the three to place a ban. BANの理由を選んで下さい:不適切な名前、不適切なIPアドレス、クライアントID - + You must have a value in the name ban when selecting the name ban checkbox. 名前BANチェックボックスを選択するときは、名前BANの値を持っている必要があります。 - + You must have a value in the ip ban when selecting the ip ban checkbox. IP BANチェックボックスを選択するときは、IP BANの値を持っている必要があります。 - + You must have a value in the clientid ban when selecting the clientid ban checkbox. クライアントID BANチェックボックスを選択するときは、クライアントID BANに値を持っている必要があります。 @@ -219,22 +371,22 @@ This is only saved for moderators and cannot be seen by the banned person. BetaReleaseChannel - - Beta Releases - ベータリリース + + Beta + Beta(開発中バージョン) - + No reply received from the release update server. リリース更新サーバーから応答がありませんでした。 - + Invalid reply received from the release update server. リリース更新サーバーから無効な応答が受信されました。 - + No reply received from the file update server. ファイル更新サーバーからの応答がありませんでした。 @@ -242,32 +394,32 @@ This is only saved for moderators and cannot be seen by the banned person. CardDatabaseModel - + Name カード名 - + Sets カードセット - + Mana cost マナ・コスト - + Card type カード・タイプ - + P/T P/T - + Color(s) @@ -360,37 +512,65 @@ This is only saved for moderators and cannot be seen by the banned person. - CardFrame + CardInfoFrameWidget - + Image カード画像 - + Description カード文章 - + Both 画像+文章 + + + View transformation + 別面を見る + - CardInfoText + CardInfoPictureWidget - + + View related cards + 関連カードを見る + + + + Add card to deck + デッキにカードを追加 + + + + Mainboard + メインボード + + + + Sideboard + サイドボード + + + + CardInfoTextWidget + + Name: カード名: - + Related cards: 関連カード: - + Unknown card: 謎のカード: @@ -398,287 +578,555 @@ This is only saved for moderators and cannot be seen by the banned person. CardItem - + &Move to 領域の移動 - + &Power / toughness パワー / タフネス + + CardSizeWidget + + + Card Size + カードの大きさ + + CardZone - + their hand nominative 手札 - + %1's hand nominative %1の手札 - + their library look at zone ライブラリー - + %1's library look at zone %1のライブラリー - + of their library top cards of zone, ライブラリーの - + of %1's library top cards of zone %1のライブラリーの - + their library reveal zone ライブラリー - + %1's library reveal zone %1のライブラリー - + their library shuffle ライブラリー - + %1's library shuffle %1のライブラリー - + their library nominative ライブラリー - + %1's library nominative %1のライブラリー - + their graveyard nominative 墓地 - + %1's graveyard nominative %1の墓地 - + their exile nominative 追放領域 - + %1's exile nominative %1の追放領域 - + their sideboard look at zone サイドボード - + %1's sideboard look at zone %1のサイドボード - + their sideboard nominative サイドボード - + %1's sideboard nominative %1のサイドボード + + DeckEditorCardInfoDockWidget + + + Card Info + + + + + DeckEditorDatabaseDisplayWidget + + + Search by card name (or search expressions) + + + + + Add to Deck + + + + + Add to Sideboard + + + + + Select Printing + + + + + Show on EDHREC (Commander) + + + + + Show on EDHREC (Card) + + + + + Show Related cards + + + + + Add card to &maindeck + + + + + Add card to &sideboard + + + + + DeckEditorDeckDockWidget + + + Banner Card + + + + + Select Printing + + + + + Deck + + + + + Deck &name: + + + + + &Comments: + + + + + Hash: + + + + + &Increment number + + + + + &Decrement number + + + + + &Remove row + + + + + Swap card to/from sideboard + + + + + DeckEditorFilterDockWidget + + + Filters + + + + + &Clear all filters + + + + + Delete selected + + + + + DeckEditorMenu + + + &Deck Editor + + + + + &New deck + + + + + &Load deck... + + + + + Load recent deck... + + + + + Clear + + + + + &Save deck + + + + + Save deck &as... + + + + + Load deck from cl&ipboard... + + + + + Edit deck in clipboard + + + + + + Annotated + + + + + + Not Annotated + + + + + Save deck to clipboard + + + + + Annotated (No set info) + + + + + Not Annotated (No set info) + + + + + &Print deck... + + + + + &Send deck to online service + + + + + Create decklist (decklist.org) + + + + + Create decklist (decklist.xyz) + + + + + Analyze deck (deckstats.net) + + + + + Analyze deck (tappedout.net) + + + + + &Close + + + + + DeckEditorPrintingSelectorDockWidget + + + Printing Selector + + + DeckEditorSettingsPage - - + + Update Spoilers スポイラーを更新 - - + + Success 完了 - + Download URLs have been reset. ダウンロードURLをリセットしました。 - + Downloaded card pictures have been reset. ダウンロードしたカード画像を削除しました。 - + Error エラー - + One or more downloaded card pictures could not be cleared. 一部のカード画像が削除できませんでした。 - + Add URL URLを追加 - - + + URL: URL: - - + + Edit URL URLを編集 - + + Network Cache Size: + ネットワークキャッシュサイズ: + + + + Redirect Cache TTL: + リダイレクトキャッシュの有効期限: + + + + How long cached redirects for urls are valid for. + キャッシュされたURLの転送の有効期間。 + + + + Picture Cache Size: + 画像キャッシュサイズ: + + + Add New URL 新しいURLを追加 - + Remove URL URLを削除 - + + Day(s) + + + + Updating... 更新中... - + Choose path パスを選ぶ - + URL Download Priority ダウンロードURL優先設定 - + Spoilers スポイラー - + Download Spoilers Automatically スポイラーを自動的にダウンロード - + Spoiler Location: スポイラーの保存場所: - + Last Change 最新に更新 - + Spoilers download automatically on launch スポイラーは起動時に自動的にダウンロードされます。 - + Press the button to manually update without relaunching ボタンを押すと再起動せずに手動で更新します。 - + Do not close settings until manual update is complete 手動更新が完了するまでこのウィンドウを閉じないでください。 - + Download card pictures on the fly カード画像を自動的にダウンロード(英語) - + How to add a custom URL URLの追加方法について(英語) - + Delete Downloaded Images ダウンロードした画像を削除 - + Reset Download URLs ダウンロードURLをデフォルトに戻す - - Downloaded images directory size: - ダウンロード画像ディレクトリのサイズ: - - - + On-disk cache for downloaded pictures ダウンロードした画像のディスク上キャッシュ - - Picture cache size: - 画像キャッシュサイズ: - - - + In-memory cache for pictures not currently on screen 現在画面に表示されていない画像のメモリ内キャッシュ @@ -686,16 +1134,218 @@ This is only saved for moderators and cannot be seen by the banned person. DeckListModel - + + Count + 枚数 + + + + Set + セット + + + Number 枚数 - + + Provider ID + プロバイダーID + + + Card カード名 + + DeckLoader + + + Common deck formats (%1) + + + + + All files (*.*) + + + + + DeckPreviewColorIdentityFilterWidget + + + Mode: Exact Match + 検索モード: 完全一致 + + + + Mode: Includes + 検索モード: 一部に含む + + + + DeckPreviewDeckTagsDisplayWidget + + + Edit tags ... + タグを編集... + + + + DeckPreviewTagDialog + + + Deck Tags Manager + デッキタグマネージャー + + + + Manage your deck tags. Check or uncheck tags as needed, or add new ones: + デッキタグの管理ができます。新たなタグを追加することもできます: + + + + Add a new tag (e.g., Aggro️) + 新しいタグを追加する(例: イニシアチブ) + + + + Add Tag + タグを追加 + + + + Filter tags... + タグをフィルタ... + + + + OK + OK + + + + Cancel + キャンセル + + + + Invalid Input + 無効な入力 + + + + Tag name cannot be empty! + タグ名を入力してください! + + + + Duplicate Tag + タグの重複 + + + + This tag already exists. + このタグはすでに存在します。 + + + + DeckPreviewWidget + + + Banner Card + + + + + Open in deck editor + + + + + Edit Tags + + + + + Rename Deck + + + + + Save Deck to Clipboard + + + + + Annotated + + + + + Annotated (No set info) + + + + + Not Annotated + + + + + Not Annotated (No set info) + + + + + Rename File + + + + + Delete File + + + + + Set Banner Card + + + + + + New name: + + + + + + Error + + + + + Rename failed + + + + + Delete file + + + + + Are you sure you want to delete the selected file? + + + + + Delete failed + + + DeckStatsInterface @@ -713,181 +1363,219 @@ This is only saved for moderators and cannot be seen by the banned person. DeckViewContainer - + Load deck... デッキを開く... - + Load remote deck... サーバーのデッキを開く... - + + Unload deck + Unload deck... + デッキを撤去 + + + Ready to start 準備完了!( ・`д・´) - + + Force start + 強制開始 + + + Sideboard unlocked サイドボード使用可能 - + Sideboard locked サイドボードロック中 - - Load deck - デッキを開く - - - + Error エラー - + The selected file could not be loaded. 選択したファイルが開けませんでした。 + + DialogConvertDeckToCodFormat + + + Deck Format Conversion + デッキフォーマット変換 + + + + You tried to add a tag to a .txt format deck. + Tags can only be added to .cod format decks. + Do you want to convert the deck to the .cod format? + txt形式のデッキにタグを追加しようとしています。 +タグはcod形式のデッキにのみ追加できます。 +デッキをcod形式に変換しますか? + + + + Remember and automatically apply choice in the future + 選択を記憶し、今後も自動的に適用する + + DlgConnect - + Downloading... ダウンロード中... - + Known Hosts ホストを選択 - + + Delete the currently selected saved server + 選択中のサーバーを削除 + + + Refresh the server list with known public servers デフォルトの公開サーバーリストに更新します - + New Host 新しいホスト - + Name: サーバー名: - + &Host: ホストアドレス: - + &Port: ポート: - + Player &name: プレイヤーネーム: - + P&assword: パスワード: - + &Save password パスワードを保存 - + A&uto connect 起動時に自動的に接続 - + Automatically connect to the most recent login when Cockatrice opens Cockatriceを開いた時に自動的に最後のログインに接続 - + If you have any trouble connecting or registering then contact the server staff for help! 接続や登録に問題がある場合は、サーバー管理者にお問い合わせください。 - - + + Webpage ウェブページ - + Reset Password パスワードをリセット - + + Forgot password? + パスワード忘れた + + + &Connect 接続 - + Server サーバー - + Login ログイン - + Server Contact サーバーの連絡先 - + Connect to Server サーバーに接続 - + Server URL サーバーURL - + Communication Port 通信ポート - + Unique Server Name ユニークサーバー名 - + Connection Warning 接続の警告 - + You need to name your new connection profile. 新しい接続プロファイルに名前を付ける必要があります。 - + Connect Warning 接続の警告 - + The player name can't be empty. プレイヤー名を入力して下さい。 @@ -970,27 +1658,37 @@ This is only saved for moderators and cannot be seen by the banned person.観戦者 - + + Starting life total: + 開始時のライフ: + + + + Game setup options + ゲーム準備オプション + + + &Clear 設定をクリア - + Create game ゲームを作成 - + Game information ゲーム情報 - + Error エラー - + Server error. サーバーエラー。 @@ -998,92 +1696,92 @@ This is only saved for moderators and cannot be seen by the banned person. DlgCreateToken - + &Name: トークン名: - + Token トークン - + C&olor: 色: - + white - + blue - + black - + red - + green - + multicolor 多色 - + colorless 無色 - + &P/T: P/T: - + &Annotation: 注釈: - + &Destroy token when it leaves the table 戦場を離れる際に破棄 - + Token data トークン設定 - + Show &all tokens すべてのトークンを見る - + Show tokens from this &deck このデッキのトークンを見る - + Choose token from list 一覧からトークンを作成 - + Create token トークンを作成 @@ -1129,6 +1827,24 @@ To remove your current avatar, confirm without choosing a new image. この画像は使用できません。 + + DlgEditDeckInClipboard + + + Edit deck in clipboard + + + + + Error + + + + + Invalid deck list. + + + DlgEditPassword @@ -1171,93 +1887,93 @@ To remove your current avatar, confirm without choosing a new image. DlgEditTokens - + &Name: トークン名: - + C&olor: 色: - + white - + blue - + black - + red - + green - + multicolor 多色 - + colorless 無色 - + &P/T: P/T: - + &Annotation: 注釈: - + Token data トークンの設定 - - + + Add token トークンを追加 - + Remove token トークンを削除 - + Edit custom tokens カスタムトークンを編集 - + Please enter the name of the token: トークン名を入力: - + Error エラー - + The chosen name conflicts with an existing card or token. Make sure to enable the 'Token' set in the "Manage sets" dialog to display them correctly. 選択された名前は、既存のカードやトークンと競合しています。 @@ -1295,132 +2011,132 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgFilterGames - + no limit 指定なし - + 5 minutes 5分 - + 10 minutes 10分 - + 30 minutes 30分 - + 1 hour 1時間 - + 2 hours 2時間 - - Show '&buddies only' games - 「フレンドのみ」のゲームを表示 + + Hide 'buddies only' games + 「フレンドのみ」のゲームを非表示 - - Show &full games - ゲーム全体を表示 (Alt &F) + + Hide full games + 満員のゲームを非表示 - - Show games &that have started - 開始しているゲームを表示 (Alt T) + + Hide games that have started + 進行中のゲームを非表示 - - Show &password protected games - パスワードが必要なゲームを表示 + + Hide password protected games + パスワードが必要なゲームを非表示 - - Hide '&ignored user' games + + Hide 'ignored user' games 無視ユーザーのゲームを非表示 - + &Newer than: 部屋の経過時間: - + Game &description: 説明でフィルター: - + &Creator name: 作成者でフィルター: - + General 全般 - + &Game types ゲームタイプ - + at &least: 最少: - + at &most: 最大: - + Maximum player count プレイヤー人数: - + Restrictions 制限 - + Show games only if &spectators can watch - + 観戦できるゲームのみを表示 + + + + Show spectator password p&rotected games + 観戦にパスワードが必要なゲームも表示 - Show spectator password p&rotected games - + Show only if spectators can ch&at + 観戦者がチャットできるゲームのみを表示 - Show only if spectators can ch&at - - - - Show only if spectators can see &hands - + 手札が閲覧できるゲームのみを表示 - + Spectators - + 観戦 - + Filter games ゲームフィルタ @@ -1430,17 +2146,17 @@ Make sure to enable the 'Token' set in the "Manage sets" dia Reset Password Challenge Warning - + パスワードリセットの警告 A problem has occurred. Please try to request a new password again. - + 問題が発生しました。新しいパスワードのリクエストを再試行してください。 Enter the information of the server and the account you'd like to request a new password for. - + 新しいパスワードをリクエストしたいサーバーの情報とアカウントを入力してください。 @@ -1465,12 +2181,12 @@ Make sure to enable the 'Token' set in the "Manage sets" dia Reset Password Challenge - + パスワードリセットチャレンジ Reset Password Challenge Error - + パスワードリセットエラー @@ -1483,7 +2199,7 @@ Make sure to enable the 'Token' set in the "Manage sets" dia Enter the information of the server you'd like to request a new password for. - + 新しいパスワードをリクエストするサーバーの情報を入力してください。 @@ -1503,12 +2219,12 @@ Make sure to enable the 'Token' set in the "Manage sets" dia Reset Password Request - + パスワードリセット要求 Reset Password Error - + パスワードリセットエラー @@ -1521,17 +2237,17 @@ Make sure to enable the 'Token' set in the "Manage sets" dia Reset Password Warning - + パスワードリセットの警告 A problem has occurred. Please try to request a new password again. - + 問題が発生しました。新しいパスワードのリクエストを再試行してください。 Enter the received token and the new password in order to set your new password. - + 新しいパスワードを設定するには、受け取ったトークンと新しいパスワードを入力します。 @@ -1562,7 +2278,7 @@ Make sure to enable the 'Token' set in the "Manage sets" dia Reset Password - + パスワードリセット @@ -1575,7 +2291,7 @@ Make sure to enable the 'Token' set in the "Manage sets" dia Reset Password Error - + パスワードリセットエラー @@ -1590,12 +2306,12 @@ Make sure to enable the 'Token' set in the "Manage sets" dia Error - + エラー Your password is too short. - + パスワードが短すぎます。 @@ -1603,27 +2319,28 @@ Make sure to enable the 'Token' set in the "Manage sets" dia パスワードが一致しませんでした。 + + DlgLoadDeck + + + Load Deck + デッキを開く + + DlgLoadDeckFromClipboard - - &Refresh - 更新 - - - + Load deck from clipboard クリップボードからデッキを開く - - + Error エラー - - + Invalid deck list. 無効なデッキリストです。 @@ -1631,18 +2348,57 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgLoadRemoteDeck - + Load deck デッキを開く + + DlgMoveTopCardsUntil + + + Card name (or search expressions): + カード名または検索条件: + + + + Number of hits: + 枚数 + + + + Auto play hits + 自動的にプレイ + + + + Put top cards on stack until... + 見つかるまで上からスタックに置く... + + + + No cards matching the search expression exists in the card database. Proceed anyways? + 条件に一致するカードはデータベースに存在しません。続行しますか? + + + + Cockatrice + 悪魔からの助言 + + + + Invalid filter + 不正な条件 + + DlgRegister Enter your information and the information of the server you'd like to register to. Your email will be used to verify your account. - + あなたの情報と登録したいサーバーの情報を入力してください。 +あなたのメールアドレスはアカウントの確認に使用されます。 @@ -1710,7 +2466,7 @@ Your email will be used to verify your account. Your password is too short. - + パスワードが短すぎます。 @@ -1733,28 +2489,28 @@ Your email will be used to verify your account. Number of sides: - + 面の数: Number of dice: - + ダイスの数 Roll Dice - + 賽は投げられそう DlgSettings - + Unknown Error loading card database カードデータベース読み込みの不明なエラー。 - + Your card database is invalid. Cockatrice may not function correctly with an invalid database @@ -1771,7 +2527,7 @@ Oracle Importerでデータベースを更新する必要があります。 データベースの場所の設定を変更してみてください。 - + Your card database version is too old. This can cause problems loading card information or images @@ -1788,16 +2544,20 @@ Oracle Importerでデータベースを更新する必要があります。 データベースの場所の設定を変更してみてください。 - + Your card database did not finish loading Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues with your cards.xml attached Would you like to change your database location setting? - + カード データベースの読み込みが完了しませんでした。 + +お手数ですが https://github.com/Cockatrice/Cockatrice/issues でcards.xmlを添付してチケットを作成していただきますよう、ご協力お願いいたします。 + +データベースの場所の設定を変更しますか? - + File Error loading your card database. Would you like to change your database location setting? @@ -1806,7 +2566,7 @@ Would you like to change your database location setting? データベースの場所の設定を変更してみてください。 - + Your card database was loaded but contains no cards. Would you like to change your database location setting? @@ -1815,68 +2575,72 @@ Would you like to change your database location setting? データベースの場所の設定を変更してみてください。 - + Unknown card database load status Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues Would you like to change your database location setting? - + 不明なカードデータベースのロードステータス。 + +お手数ですが https://github.com/Cockatrice/Cockatrice/issues でチケットを作成していただきますよう、ご協力お願いいたします。 + +データベースの場所設定を変更しますか? - - - + + + Error エラー - + The path to your deck directory is invalid. Would you like to go back and set the correct path? あなたのデッキディレクトリへのパスは無効です。前に戻って正しいパスを設定してください。 - + The path to your card pictures directory is invalid. Would you like to go back and set the correct path? あなたのカード画像ディレクトリへのパスは無効です。前に戻って正しいパスを設定してください。 - + Settings 設定 - + General 全般 - + Appearance 外観 - + User Interface UI - + Card Sources カードダウンロード - + Chat チャット - + Sound サウンド - + Shortcuts ショートカット @@ -1902,166 +2666,169 @@ Would you like to change your database location setting? DlgUpdate - + Current release channel 現在のリリースチャンネル: - + Reinstall 再インストール - + Cancel Download ダウンロードをキャンセル - + Open Download Page ダウンロードページを開く - + Check for Client Updates - + クライアントの更新チェック - - - - + + + + Error エラー - + Cockatrice was not built with SSL support, therefore you cannot download updates automatically! Please visit the download page to update manually. CockatriceはSSLをサポートしていなかったため、更新を自動ダウンロード出来ません。 ダウンロードページから手動で更新してください。 - - Downloading update... - 更新をダウンロード中... + + Downloading update: %1 + Downloading update... + - + Checking for updates... 更新のチェック中... - + Finished checking for updates 更新のチェックが完了しました - + No Update Available 更新がありません - + Cockatrice is up to date! Cockatriceは最新版です。やったね! - + You are already running the latest version available in the chosen release channel. 選択したリリースチャンネルで最新のバージョンを使用しています。 - + Current version 現在のバージョン - + Selected release channel 選択したリリースチャンネル - - + + Update Available 更新できます!!! - - + + A new version of Cockatrice is available! 新しいバージョンのCockatriceが利用できます! - - + + New version 新しいバージョン - - + + Released 公開日時 - - + + Changelog 変更履歴 - + Do you want to update now? 今すぐ更新しますか? - - Unfortunately there are no download packages available for your operating system. -You may have to build from source yourself. - 残念ながら、お使いのオペレーティングシステム用のダウンロードパッケージはありません。 -自分でソースからビルドする必要があるかもしれません。 + + Unfortunately, the automatic updater failed to find a compatible download. +You may have to manually download the new version. + Unfortunately there are no download packages available for your operating system. +You may have to build from source yourself. + - - Please check the download page manually and visit the wiki for instructions on compiling. - ダウンロードページを手動で確認し、wikiにアクセスしてコンパイルの手順を確認してください。 + + Please check the <a href="%1">releases page</a> on our Github and download the build for your system. + Please check the download page manually and visit the wiki for instructions on compiling. + - - - + + + Update Error 更新エラー - + An error occurred while checking for updates: 更新のチェック中にエラーが発生しました: - + An error occurred while downloading an update: 更新のダウンロード中にエラーが発生しました: - + Installing... インストール中... - + Cockatrice is unable to open the installer. インストーラーが開けませんでした。 - + Try to update manually by closing Cockatrice and running the installer. Cockatriceを終了した後、インストーラーから手動で更新してください。 - + Download location ダウンロード場所 @@ -2079,6 +2846,14 @@ You may have to build from source yourself. デバッグログ + + EdhrecCommanderResponseCommanderDetailsDisplayWidget + + + Salt: + + + FilterBuilder @@ -2090,105 +2865,120 @@ You may have to build from source yourself. GameSelector - - - - - - - - - + + + + + + + + + Error エラー - + Please join the appropriate room first. 適切な部屋に参加してください。 - + Wrong password. パスワードが間違っています。 - + Spectators are not allowed in this game. このゲームは観戦出来ません。 - + The game is already full. このゲームはすでに満員です。 - + The game does not exist any more. このゲームはすでに存在しません。 - + This game is only open to registered users. このゲームは登録済みプレイヤーのみ参加できます。 - + This game is only open to its creator's buddies. このゲームは作成者のフレンドのみ参加できます。 - + You are being ignored by the creator of this game. あなたはゲームの作成者によって参加拒否されています。 - + + Join Game + 参加 + + + + Spectate Game + 観戦 + + + + Game Information + ゲーム情報 + + + Join game 参加 - + Password: パスワード: - + Please join the respective room first. 最初にルームに参加してください。 - + &Filter games ゲームフィルタ - + C&lear filter フィルタ解除 - + C&reate ゲームを作成 - + &Join 参加 - + J&oin as spectator 観戦 - + Games shown: %1 / %2 表示されているゲーム: %1 / %2 - + Games ゲーム @@ -2196,100 +2986,100 @@ You may have to build from source yourself. GamesModel - + >1 day - + >1 日 - + %1%2 hr short age in hours - + %1%2 時 - + new - + できたて - + %1%2 min short age in minutes - + %1%2 分 - + password パスワード - + buddies only フレンドのみ - + reg. users only 登録ユーザーのみ - - + + can chat チャット可 - + see hands 手札閲覧可 - + can see hands 手札閲覧可 - + not allowed 観戦不可 - + Room ルーム - + Age 作成時 - + Description 説明 - + Creator 作成者 - + Type タイプ - + Restrictions 制限 - + Players 人数 - + Spectators 観戦者 @@ -2297,92 +3087,102 @@ You may have to build from source yourself. GeneralSettingsPage - + Reset all paths - + すべてのパスをリセット - + All paths have been reset - + すべてのパスをリセットしました - - + + Choose path パスを選択 - + Personal settings 個人設定 - + Language: 言語: - + Paths (editing disabled in portable mode) パス(ポータブルモードでの編集は無効) - + Paths パス - + + How to help with translations + + + + Decks directory: デッキフォルダ: - + Replays directory: リプレイフォルダ: - + Pictures directory: カード画像フォルダ: - + Card database: カードデータベース: - + Custom database directory: - + カスタムデータベースフォルダ: - + Token database: トークンデータベース: - + Update channel 更新チャンネル - + + Check for client updates on startup + 起動時にクライアントの更新をチェック + + + Notify if a feature supported by the server is missing in my client サーバーでサポートされている機能がクライアントに存在しない場合に通知する - + Automatically run Oracle when running a new version of Cockatrice 新バージョンのCockatriceを起動したら自動的にOracleも起動する - + Show tips on startup 起動時にヒントを表示 @@ -2390,61 +3190,61 @@ You may have to build from source yourself. MainWindow - - + + The server has reached its maximum user capacity, please check back later. サーバーが満員です。 - + There are too many concurrent connections from your address. あなたのアドレスからの同時接続が多すぎます。 - + Banned by moderator モデレーターによるBAN - + Expected end time: %1 予想終了時間: %1 - + This ban lasts indefinitely. 永久BANされています。 - + Scheduled server shutdown. サーバーシャットダウン予定時間。 - - + + Invalid username. ユーザー名が無効です。 - + You have been logged out due to logging in at another location. 別の場所でログインしているため、ログアウトしました。 - + Connection closed 通信切断 - + The server has terminated your connection. Reason: %1 サーバーはあなたの接続を切断しました。 理由: %1 - + The server is going to be restarted in %n minute(s). All running games will be lost. Reason for shutdown: %1 @@ -2453,559 +3253,569 @@ Reason for shutdown: %1 シャットダウンの理由: %1 - + Scheduled server shutdown サーバーシャットダウン予定時刻 - - + + Success 成功 - + Registration accepted. Will now login. 登録が完了しました。 ログインします。 - + Account activation accepted. Will now login. アカウント有効化が完了しました。 ログインします。 - + Number of players プレイヤー人数 - + Please enter the number of players. プレイヤーの人数を入れてください. - - + + Player %1 プレイヤー %1 - + Load replay リプレイを開く - + About Cockatrice Cockatriceについて - + Version バージョン - + Cockatrice Webpage Cockatriceウェブページ - + Project Manager: プロジェクトマネージャ: - + Past Project Managers: 元プロジェクトマネージャ: - + Developers: デベロッパ: - + Our Developers リンク先参照 - + Help Develop! 開発者募集中! - + Translators: 翻訳者: - + Our Translators 翻訳者一覧 - + Help Translate! 翻訳者募集中! - + Support: サポート: - + Report an Issue 問題を報告 - + Troubleshooting トラブルシューティング - + F.A.Q. F.A.Q. - - - - - - - - - - - - - - - + + + + + + + + + + + + + - - - + + + + + Error エラー - + Server timeout サーバータイムアウト - + Failed Login ログイン失敗 - + Your client seems to be missing features this server requires for connection. あなたのクライアントは、サーバーが必要とする機能をサポートしていません。クライアントを更新して再度お試しください。 - + To update your client, go to 'Help -> Check for Client Updates'. クライアントをアップデートするには、メニューの「ヘルプ」から「クライアントの更新チェック」をクリックします。 - + Incorrect username or password. Please check your authentication information and try again. ユーザー名かパスワードが正しくありません。 - + There is already an active session using this user name. Please close that session first and re-login. 既にこのユーザー名を使用しているアクティブなセッションが存在します。 アクティブなセッションを閉じて再ログインしてください。 - - + + You are banned until %1. あなたは%1までBANされています。 - - + + You are banned indefinitely. あなたは無期限BANされています。 - + This server requires user registration. Do you want to register now? このサーバーはユーザー登録が必要です。 今すぐ登録しますか? - + This server requires client IDs. Your client is either failing to generate an ID or you are running a modified client. Please close and reopen your client to try again. このサーバはクライアントのIDを必要とします。あなたのクライアントはIDの生成に失敗しているか、変更されたクライアントを実行しています。 クライアントを再起動してもう一度お試し下さい。 - + An internal error has occurred, please close and reopen Cockatrice before trying again. If the error persists, ensure you are running the latest version of the software and if needed contact the software developers. 内部エラーが発生しました。Cockatriceを再起動して再度お試しください。 エラーが解決しない場合は、ソフトウェアの最新バージョンを実行していることを確認し、必要に応じてソフトウェア開発者にお問い合わせください。 - + Account activation アカウント有効化 - + Your account has not been activated yet. You need to provide the activation token received in the activation email. このアカウントはまだ有効化されていません。 登録されたアドレスに送信されているアクティベーショントークンを入力して下さい。 - + Server Full サーバーが満員です。 - + Unknown login error: %1 不明なログインエラー: %1 - - + + This usually means that your client version is out of date, and the server sent a reply your client doesn't understand. これは通常、あなたのクライアントのバージョンが古くなっていて、サーバーがあなたのクライアントを認識できないと返答してきていることを意味しています。 - + Your username must respect these rules: ユーザー名は、これらの規則を遵守する必要があります: - + is %1 - %2 characters long 文字数は%1~%2文字まで - + can %1 contain lowercase characters 小文字%1可 - - - - + + + + NOT - + can %1 contain uppercase characters 大文字%1可 - + can %1 contain numeric characters 数字%1可 - + can contain the following punctuation: %1 次の記号が使用可:%1 - + first character can %1 be a punctuation mark 最初の文字を記号にすることは%1可能 - + no unacceptable language as specified by these server rules: note that the following lines will not be translated - + サーバールールで定められている不適切な言葉は使用できません: - + can not contain any of the following words: %1 次の単語を含めることは不可能:%1 - + can not match any of the following expressions: %1 次の文字列を含めることは不可能:%1 - + You may only use A-Z, a-z, 0-9, _, ., and - in your username. ユーザー名には『半角英数字』、および半角の「_」「.」「-」のみが使えます。 - - - - - - + + + + + + Registration denied 登録が拒否されました。 - + Registration is currently disabled on this server 現在このサーバーでは登録が無効になっています。 - + There is already an existing account with the same user name. 既に同じユーザー名のアカウントが登録されています。 - + It's mandatory to specify a valid email address when registering. 登録時に有効な電子メールアドレスを指定する必要があります。 - + It appears you are attempting to register a new account on this server yet you already have an account registered with the email provided. This server restricts the number of accounts a user can register per address. Please contact the server operator for further assistance or to obtain your credential information. このサーバーに新しいアカウントを登録しようとしていますが、提供されたメールアドレスに登録されているアカウントが既に存在しています。このサーバーは、ユーザーがアドレスごとに登録できるアカウントの数を制限しています。詳細なサポートや資格情報の入手については、サーバーのオペレーターにお問い合わせください。 - + Password too short. パスワードが短すぎます。 - + Registration failed for a technical problem on the server. ユーザー登録に失敗しました(サーバーの技術的問題)。 - + Unknown registration error: %1 不明な登録エラー: %1 - + Account activation failed アカウントの有効化に失敗しました。 - + Socket error: %1 ソケットエラー: %1 - + You are trying to connect to an obsolete server. Please downgrade your Cockatrice version or connect to a suitable server. Local version is %1, remote version is %2. あなたは古いバージョンのサーバーに接続しようとしています。Cockatriceのバージョンをダウングレードするか適切なサーバーに接続してください。 ローカルVer %1,サーバーVer %2 - + Your Cockatrice client is obsolete. Please update your Cockatrice version. Local version is %1, remote version is %2. あなたのCockatriceのバージョンがサーバーのバージョンより古いです。Cockatriceをアップデートしてください。 ローカルVer %1,サーバーVer %2 - + Connecting to %1... %1へ接続しています... - + Registering to %1 as %2... %1に%2として登録中... - + Disconnected 切断されました - + Connected, logging in at %1 接続完了、%1にログイン中 - - - + + + Requesting forgotten password to %1 as %2... - + 忘れたパスワードを %1 に %2 として要求しています... - + &Connect... サーバーに接続... - + &Disconnect サーバーから切断 - + Start &local game... ローカルゲームを開始... - + &Watch replay... リプレイを見る... - - &Deck editor - デッキエディター - - - + &Full screen フルスクリーン - + &Register to server... サーバーに登録する - + &Restore password... - + パスワードリセット... - + &Settings... 設定... - + &Exit 終了 - + A&ctions アクション - + &Cockatrice Cockatrice - + C&ard Database カードデータベース - + &Manage sets... セットを管理... - + Edit custom &tokens... カスタムトークンを編集... - + Open custom image folder カスタム画像フォルダを開く - + Open custom sets folder カスタムセットフォルダを開く - + Add custom sets/cards カスタムセット/カードを追加 - + + Reload card database + カードデータベースを再読み込み + + + + Tabs + タブ + + + &Help ヘルプ - + &About Cockatrice Cockatriceについて - + &Tip of the Day 今日のヒント - + Check for Client Updates クライアントの更新チェック - + Check for Card Updates... - + カードデータベース更新 - + View &Debug Log - + デバッグログを見る - + + Open Settings Folder + 設定フォルダを開く + + + Show/Hide - + 表示/非表示 - + New Version 新しいバージョン - + Congratulations on updating to Cockatrice %1! Oracle will now launch to update your card database. Cockatrice %1へのアップデートおめでとうございます! Oracleが起動し、カードデータベースが更新されます。 - + Cockatrice installed Cockatriceがインストールされました - + Congratulations on installing Cockatrice %1! Oracle will now launch to install the initial card database. Cockatrice %1のインストールおめでとうございます! Oracleを起動して、初期カードデータベースをインストールします。 - + Card database カードデータベース - + Cockatrice is unable to load the card database. Do you want to update your card database now? If unsure or first time user, choose "Yes" @@ -3014,29 +3824,29 @@ If unsure or first time user, choose "Yes" よくわからない場合、初回起動時などは「はい」を押して下さい。 - - + + Yes はい - - + + No いいえ - + Open settings 設定を開く - + New sets found 新しいセットが見つかりました - + %n new set(s) found in the card database Set code(s): %1 Do you want to enable it/them? @@ -3045,17 +3855,17 @@ Do you want to enable it/them? 有効にしますか? - + View sets セットを見る - + Welcome ようこそ - + Hi! It seems like you're running this version of Cockatrice for the first time. All the sets in the card database have been enabled. Read more about changing the set order or disabling specific sets and consequent effects in the "Manage Sets" dialog. @@ -3064,58 +3874,59 @@ Read more about changing the set order or disabling specific sets and consequent 「セットの設定」ウィンドウ内の特定のセットを無効にしたり、順序を変更をする方法の詳細をお読みください。 - - + + Information 情報 - + A card database update is already running. データベース更新は既に起動中です。 - + Unable to run the card database updater: データベース更新が実行できませんでした: - - failed to start. - 開始失敗。 + + Failed to start. The file might be missing, or permissions might be incorrect. + - - crashed. - クラッシュしました。 + + The process crashed some time after starting successfully. + - - timed out. - タイムアウトしました。 + + Timed out. The process took too long to respond. The last waitFor...() function timed out. + - - write error. - 書き込みに失敗しました。 + + An error occurred when attempting to write to the process. For example, the process may not be running, or it may have closed its input channel. + - - read error. - 読み込みに失敗しました。 + + An error occurred when attempting to read from the process. For example, the process may not be running. + - - unknown error. - 不明なエラー。 + + Unknown error occurred. + - - The card database updater exited with an error: %1 - データベース更新に失敗しました: %1 + + The card database updater exited with an error: +%1 + - + This server supports additional features that your client doesn't have. This is most likely not a problem, but this message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -3125,55 +3936,55 @@ To update your client, go to Help -> Check for Updates. クライアントを更新するには、ヘルプ→クライアントの更新のチェックをクリックしてください。 - - - - - + + + + + Load sets/cards セット/カードを読み込む - + Selected file cannot be found. 選択したファイルが見つかりませんでした。 - + You can only import XML databases at this time. ここではXMLデータベースのみをインポートできます。 - + The new sets/cards have been added successfully. Cockatrice will now reload the card database. 新しいセット/カードが正常に追加されました。 データベースが再読込されます。 - + Sets/cards failed to import. セット/カードのインポートに失敗 - - - + + + Reset Password - + パスワードリセット - + Your password has been reset successfully, you can now log in using the new credentials. パスワードをリセットしました。 - + Failed to reset user account password, please contact the server operator to reset your password. パスワードをリセットできませんでした。サーバー管理者にリセットしてもらってください。 - + Activation request received, please check your email for an activation token. アクティベーションリクエストを受信しました。メールでアクティベーショントークンを確認してください。 @@ -3273,12 +4084,12 @@ Cockatrice will now reload the card database. %1 can now look at top card %2 at any time. - + %1はいつでも%2一番上のカードを見ることができる。 %1 no longer can look at top card %2 at any time. - + %1はもう%2一番上のカードを見ることができない。 @@ -3293,7 +4104,7 @@ Cockatrice will now reload the card database. %1 has unconceded the game. - + %1が投了を撤回した。 @@ -3378,7 +4189,7 @@ Cockatrice will now reload the card database. %1 puts %2 into play%3 face down. - + %1 は %2 を%3裏向きでプレイした。 @@ -3408,7 +4219,7 @@ Cockatrice will now reload the card database. %1 puts %2%3 onto the bottom of their library. - + %1 は %2 を%3ライブラリーの一番下に置いた。 @@ -3433,7 +4244,7 @@ Cockatrice will now reload the card database. %1 tries to draw from an empty library - + %1 は……カードがないライブラリーからカードを引こうとした! @@ -3447,181 +4258,192 @@ Cockatrice will now reload the card database. - %1 is looking at the top %3 card(s) %2. + %1 is looking at the %4 %3 card(s) %2. + %1 is looking at the top %3 card(s) %2. top card for singular, top %3 cards for plural - %1 は%2上から%3枚のカードを見ている。 + %1 は%2%4%3枚のカードを見ている。 - + + bottom + 下から + + + + top + 上から + + + %1 turns %2 face-down. %1 は %2 を裏向きにした。 - + %1 turns %2 face-up. %1 は %2 を表向きにした。 - + The game has been closed. ゲームがクローズされました。 - + The game has started. ◇◇ゲーム開始!◇◇ - + %1 has joined the game. %1 がゲームに参加した。 - + %1 is now watching the game. %1 が観戦に参加した。 - + You have been kicked out of the game. ゲームからキックされました。 - + %1 has left the game (%2). %1 はゲームから離脱した(%2)。 - + %1 is not watching the game any more (%2). %1 が観戦から離脱した(%2)。 - + %1 is not ready to start the game any more. %1 は準備完了を解除した。 - + %1 shuffles their deck and draws a new hand of %2 card(s). %1はライブラリーを切り直し、新たにカードを%2枚引いた。 - + %1 shuffles their deck and draws a new hand. %1はライブラリーを切り直し、新たに手札を引いた。 - + You are watching a replay of game #%1. ゲーム#%1のリプレイを再生しています。 - + %1 is ready to start the game. %1 はゲーム開始の準備が完了した!! - + cards an unknown amount of cards - + カード - + %1 card(s) a card for singular, %1 cards for plural - + %1枚のカード - + %1 lends %2 to %3. - + %1 は %3 に%2を貸した。 - + %1 reveals %2 to %3. %1 は %3 に%2を見せた。 - + %1 reveals %2. %1 は%2を公開した。 - + %1 randomly reveals %2%3 to %4. %1 は%3無作為に %2 を選び、%4に見せた。 - + %1 randomly reveals %2%3. %1 は%3無作為に %2 を選び、公開した。 - + %1 peeks at face down card #%2. %1 は裏向きのカード#%2の表面を確認した。 - + %1 peeks at face down card #%2: %3. %1 は裏向きのカード#%2の表面を確認した ( %3 ) 。 - + %1 reveals %2%3 to %4. %1 は%3 %2 を %4 に見せた。 - + %1 reveals %2%3. %1 は%3 %2 を公開した。 - + %1 reversed turn order, now it's %2. %1はターン順を逆にした(%2)。 - + reversed 逆転 - + normal 順転 - + Heads - + Tails - + %1 flipped a coin. It landed as %2. %1 はコインを投げた。結果は……【%2】だ! - + %1 rolls a %2 with a %3-sided die. %1 は%3面ダイスをふり、【%2】を出した。 - + %1 flips %2 coins. There are %3 heads and %4 tails. - + %1 は%2枚のコインを投げた。表が【%3】枚、裏が【%4】枚出た。 - + %1 rolls a %2-sided dice %3 times: %4. - + %1 は%2面体サイコロを%3個振り、それぞれ【%4】を出した。 @@ -3736,20 +4558,20 @@ Cockatrice will now reload the card database. %1 shuffles cards %3 - %4 of %2. - + %1 は%2の %3 - %4 枚を無作為の順番で置いた。 - + %1 unattaches %2. %1 は %2 をはずした。 - + %1 undoes their last draw. %1 は最後に引いたカードを戻した。 - + %1 undoes their last draw (%2). %1 は最後に引いたカード ( %2 ) を戻した 。 @@ -3757,110 +4579,110 @@ Cockatrice will now reload the card database. MessagesSettingsPage - + Word1 Word2 Word3 ここに入力してください - + Add New Message - + 定型文を追加 - + Edit Message - + 定型文を編集 - + Remove Message - + 定型文を削除 - + Add message 定型文を追加 - - + + Message: 定型文: - + Edit message 定型文を編集 - + Chat settings チャット設定 - + Custom alert words 反応するキーワード - + Enable chat mentions メンション機能を有効 - + Enable mention completer メンション補完を有効 - + In-game message macros 定型文 - + How to use in-game message macros - + ゲーム内メッセージマクロの使い方 - + Ignore chat room messages sent by unregistered users 未登録ユーザーのルームメッセージを無視 - + Ignore private messages sent by unregistered users 未登録ユーザーの個人チャットを無視 - - + + Invert text color 反転テキストの色 - + Enable desktop notifications for private messages 個人チャットのデスクトップ通知を有効 - + Enable desktop notification for mentions メンションのデスクトップ通知を有効 - + Enable room message history on join 参加時にルームのメッセージ履歴を表示 - - + + (Color is hexadecimal) (色は16進数) - + Separate words with a space, alphanumeric characters only 英数字のみ使用可能。単語を半角スペースで区切って下さい。 @@ -3875,7 +4697,7 @@ Cockatrice will now reload the card database. Mana Value - + マナ総量 @@ -3918,6 +4740,24 @@ Cockatrice will now reload the card database. 固有色 + + OverlapControlWidget + + + Cards to overlap: + + + + + Overlap percentage: + + + + + Overlap direction: + + + Phase @@ -3984,57 +4824,57 @@ Cockatrice will now reload the card database. PhasesToolbar - + Untap step アンタップ・ステップ - + Upkeep step アップキープ・ステップ - + Draw step ドロー・ステップ - + First main phase 戦闘前メイン・フェイズ - + Beginning of combat step 戦闘開始ステップ - + Declare attackers step 攻撃クリーチャー指定ステップ - + Declare blockers step ブロック・クリーチャー指定ステップ - + Combat damage step 戦闘ダメージ・ステップ - + End of combat step 戦闘終了ステップ - + Second main phase 戦闘後メイン・フェイズ - + End of turn step 最終フェイズ @@ -4042,7 +4882,7 @@ Cockatrice will now reload the card database. PictureLoader - + en code for scryfall's language property, not available for all languages ja @@ -4051,647 +4891,781 @@ Cockatrice will now reload the card database. Player - + Reveal top cards of library ライブラリーの一番上のカードを公開する - - - - - - - - - + + + + + + + + + + + + Number of cards: (max. %1) カード枚数:(最大%1) - + &View graveyard 墓地を見る - + &View exile 追放領域を見る - + Player "%1" プレイヤー: %1 - - - - + + + + &Graveyard 墓地 - - - - + + + + &Exile 追放領域 - + &Move hand to... 手札を移動させる... - - - + + + &Top of library ライブラリーの一番上 - - - + + + &Bottom of library ライブラリーの一番下 - + &Move graveyard to... 墓地を移動させる... - - - - + + + + &Hand 手札 - + &Move exile to... 追放領域を移動させる... - + &View library ライブラリーを見る - + &View hand 手札を見る - + View &top cards of library... 上からX枚見る - + Reveal &library to... ライブラリーを公開する... - + &Always reveal top card 一番上のカードを公開したままプレイ - + &View sideboard サイドボードを見る - + &Draw card カードを引く - + D&raw cards... カードをX枚引く - + &Undo last draw 最後に引いたカードを戻す - + Take &mulligan マリガンを行う - - &Shuffle - ライブラリーを切り直す - - - + Play top card &face down 一番上のカードを裏向きにプレイ - + Move top card to grave&yard 一番上のカードを墓地に置く - + Move top card to e&xile 一番上のカードを追放する - + Move top cards to &graveyard... 上からX枚墓地へ置く - + Move top cards to &exile... 上からX枚追放する - + Put top card on &bottom 一番上のカードを一番下に置く - + + View bottom cards of library... + 下からX枚見る... + + + Lend library to... + ライブラリーを貸す... + + + + Shuffle - + Put top cards on stack &until... Take top cards &until... + 見つかるまで上からスタックに置く... + + + + Shuffle top cards... - + + Shuffle bottom cards... + + + + &Reveal hand to... 手札を見せる... - + Reveal r&andom card to... 無作為に1枚選んで公開する - + Reveal random card to... 無作為に1枚選んで公開する - + &Sideboard サイドボード - + &Library ライブラリー - + &Counters カウンター - + &Untap all permanents 全てアンタップする - + R&oll die... X面ダイスをふる - + &Create token... トークンを作成する... - + C&reate another token 同じトークンを作成する - + Cr&eate predefined token 定義済みのトークンを作成する - - + + &All players すべてのプレイヤー - + S&ay 定型文 - + + &Select All + すべて選択 + + + + S&elect Row + 行を選択 + + + + S&elect Column + 列を選択 + + + &Play プレイ - + &Hide 隠す - + Play &Face Down 裏向きにプレイ - + &Tap / Untap Turn sideways or back again タップ / アンタップ - + Toggle &normal untapping 通常のアンタップの切り替え - + T&urn Over Turn face up/face down 裏返す - + &Peek at card face 表面を見る - + &Clone 複製する - + Attac&h to card... カードにつける... - + Unattac&h はずす - + &Draw arrow... 対象を指定 - + &Increase power パワーを上げる - + &Decrease power パワーを下げる - + I&ncrease toughness タフネスを上げる - + D&ecrease toughness タフネスを下げる - + In&crease power and toughness P/Tを上げる - + Dec&rease power and toughness P/Tを下げる - + Increase power and decrease toughness パワーを上げてタフネスを下げる - + Decrease power and increase toughness パワーを下げてタフネスを上げる - + Set &power and toughness... P/Tを設定する... - + Reset p&ower and toughness P/Tをリセットする - + &Set annotation... 注釈をつける... - + Red - + Yellow - + Green - + &Add counter (%1) %1色のカウンターを置く - + &Remove counter (%1) %1色のカウンターを取り除く - + &Set counters (%1)... %1色のカウンターの数を設定 - + &Top of library in random order - + 一番上に無作為の順番で - + X cards from the top of library... ライブラリーの一番上からX枚... - + &Bottom of library in random order ライブラリーの一番下に無作為の順番で - + View top cards of library 上からX枚見る - + + View bottom cards of library + 下からX枚見る... + + + + Shuffle top cards of library + + + + + Shuffle bottom cards of library + + + + Which position should this card be placed: - + (max. %1) - + (最大%1) - + Draw hand 手札を引く - + 0 and lower are in comparison to current hand size 0以下の数を指定すると現在の枚数から減らします - + Draw cards カードを引く - - + + Number: 数: - + Move top cards to grave 上からX枚墓地へ置く - + Reveal &top cards to... - + ライブラリーの一番上のカードを見せる... - + &Top of library... - + ライブラリーの一番上を... - + &Bottom of library... - + ライブラリーの一番下を... - + &Always look at top card - + 一番上のカードを常に見る - + &Open deck in deck editor - - - - - &Play top card - - - - - &Draw bottom card - - - - - D&raw bottom cards... - - - - - &Play bottom card - - - - - Play bottom card &face down - - - - - Move bottom card to grave&yard - - - - - Move bottom card to e&xile - - - - - Move bottom cards to &graveyard... - + デッキエディターで開く + &Play top card + 一番上のカードをプレイ + + + + &Draw bottom card + 一番下のカードを手札に + + + + D&raw bottom cards... + 一番下のカードをX枚手札に... + + + + &Play bottom card + 一番下のカードをプレイ + + + + Play bottom card &face down + 一番下のカードを裏向きにプレイ + + + + Move bottom card to grave&yard + 一番下のカードを墓地に置く + + + + Move bottom card to e&xile + 一番下のカードを追放する + + + + Move bottom cards to &graveyard... + 一番下のカードをX枚墓地に置く... + + + Move bottom cards to &exile... - + 一番下のカードをX枚追放する... - + Put bottom card on &top - + 一番下のカードを一番上に置く - + Selec&ted cards - + 選択中のカード - + Move top cards to exile 上からX枚追放する - + Move bottom cards to grave - + 一番下のカードを墓地に置く - + Move bottom cards to exile - + 一番下のカードを追放する - + Draw bottom cards - + 一番下のカードを手札に - - + + C&reate another %1 token 別の %1 トークンを出す - + Create tokens トークンを作成 - + Place card X cards from top of library 上からX枚目に置く - + Change power/toughness P/Tを変更 - + Change stats to: - + P/Tを変更: - + Set annotation 注釈をつける - + Please enter the new annotation: 新しい注釈: - + Set counters カウンターの設定 - + Re&veal to... - + プレイヤーに見せる... - + View related cards 関連カードを見る - + Token: トークン: - + All tokens すべてのトークン + + PrintingSelector + + + Display Navigation Buttons + + + + + <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. + + + + + PrintingSelectorCardOverlayWidget + + + Preference + 優先設定 + + + + Pin Printing + ピン留めする + + + + Unpin Printing + ピン留めを外す + + + + Show Related cards + 関連カードを見る + + + + PrintingSelectorCardSearchWidget + + + Search by set name or set code + セット名、セット略号で検索 + + + + PrintingSelectorCardSelectionWidget + + + Previous Card in Deck + デッキの前のカード + + + + Next Card in Deck + デッキの次のカード + + + + PrintingSelectorCardSortingWidget + + + Alphabetical + 名前順 + + + + Preference + + + + + Release Date + 提供日時順 + + + + + Descending + 降順 + + + + Ascending + 昇順 + + QMenuBar - + Services サービス - + Hide %1 %1を隠す - + Hide Others 他を隠す - + Show All すべて表示 - + Preferences... 設定... - + Quit %1 %1から出る - + About %1 %1について @@ -4699,149 +5673,143 @@ Cockatrice will now reload the card database. QObject - + Cockatrice card database (*.xml) Cockatriceカードデータベース(*.xml) - - + All files (*.*) 全てのファイル (*.*) - + Cockatrice replays (*.cor) Cockatriceリプレイファイル (*.cor) - - Common deck formats (*.cod *.dec *.dek *.txt *.mwDeck) - 共通デッキ形式 (*.cod *.dec *.txt *.mwDeck) - - - + Maindeck - + メインデッキ - + Sideboard - + サイドボード - + Tokens - + トークン QPlatformTheme - + OK - + OK - + Save - + 保存 - + Save All - + 全て保存 - + Open - + 開く - + &Yes - + はい - + Yes to &All - + すべてはい - + &No - + いいえ - + N&o to All - + すべていいえ - + Abort - + 中止 - + Retry - + 再試行 - + Ignore - + 無視 - + Close - + 閉じる - + Cancel キャンセル - + Discard カードを捨てる - + Help ヘルプ - + Apply - + 適用 - + Reset - + リセット - + Restore Defaults - + デフォルトを復元 RemoteDeckList_TreeModel - + Name デッキ名 - + ID ID - + Upload time アップロード日 @@ -4849,32 +5817,32 @@ Cockatrice will now reload the card database. RemoteReplayList_TreeModel - + ID ID - + Name 説明 - + Players 参加プレイヤー - + Keep ロック - + Time started 開始日時 - + Duration (sec) 期間 (秒) @@ -4920,34 +5888,34 @@ Cockatrice will now reload the card database. SequenceEdit - + Choose an action from the table - + 表の中からアクションを選択 - + Hit the key/combination of keys you want to set for this action - + このアクションに設定するキー(の組み合わせ)を押してください - - Shortcut already in use - - - - + Invalid key - + 無効なキー - + + Shortcut already in use by: + ショートカットは既に使われています: + + + Clear - + クリア - + Restore default - + デフォルトを復元 @@ -4981,73 +5949,81 @@ Cockatrice will now reload the card database. ShortcutSettingsPage - - + + Restore all default shortcuts すべてのショートカットを元に戻す - + Do you really want to restore all default shortcuts? 本当にすべてのショートカットキーを元に戻しますか? - + Clear all default shortcuts すべてのショートカットをクリア - + Do you really want to clear all shortcuts? 本当にすべてのショートカットキーをクリアしますか? - - Action - アクション - - - - Shortcut - ショートカットキー - - - + Section: セクション: - + Action: アクション: - + Shortcut: ショートカット: - + How to set custom shortcuts ショートカットキーの設定方法について(英語) - + Clear all shortcuts - + すべてのショートカットをクリア + + + + Search by shortcut name + ショートカット名で検索 + + + + ShortcutTreeView + + + Action + アクション + + + + Shortcut + ショートカットキー ShortcutsSettings - + Your configuration file contained invalid shortcuts. Please check your shortcut settings! あなたの設定ファイルには無効なショートカットキーが含まれています。 ショートカット設定を見直してください! - + The following shortcuts have been set to default: 次のショートカットがデフォルトに設定されています: @@ -5057,17 +6033,17 @@ Please check your shortcut settings! ShutdownDialog - + &Reason for shutdown: シャットダウンの理由: - + &Time until shutdown (minutes): シャットダウンまでの時間(分): - + Shut down server サーバーシャットダウン @@ -5075,27 +6051,27 @@ Please check your shortcut settings! SoundSettingsPage - + Enable &sounds サウンドを有効 - + Current sounds theme: 現在のサウンドテーマ: - + Test system sound engine サウンドテスト - + Sound settings サウンド設定 - + Master volume 音量 @@ -5103,48 +6079,48 @@ Please check your shortcut settings! SpoilerBackgroundUpdater - + Spoilers season has ended スポイラーシーズンが終了しました - + Deleting spoiler.xml. Please run Oracle spoiler.xmlを削除します。Oracleを起動してください。 - - + + Spoilers download failed スポイラーのダウンロードに失敗しました。 - + No internet connection インターネット接続がありません(´・ω・`) - + Error エラー - + Spoilers already up to date スポイラーリストは最新です。 - + No new spoilers added 新たなスポイラーはありません。 - + Spoilers have been updated! スポイラーが更新されました!! - + Last change: 最後の変更: @@ -5152,638 +6128,594 @@ Please check your shortcut settings! StableReleaseChannel - - Stable Releases - 安定版リリース + + Default + デフォルト - + No reply received from the release update server. リリース更新サーバーから応答がありませんでした。 - + Invalid reply received from the release update server. リリース更新サーバーから無効な応答が受信されました。 - + No reply received from the tag update server. タグ更新サーバーからの応答がありませんでした。 - + Invalid reply received from the tag update server. タグ更新サーバーから無効な応答が受信されました。 + + TabAccount + + + Add to Buddy List + フレンドリストに追加する名前を入力 + + + + Add to Ignore List + 無視リストに追加する名前を入力 + + + + Account + アカウント + + TabAdmin - + Administration 管理 - + Update server &message サーバー更新&メッセージ - + &Shut down server サーバーシャットダウン - + &Reload configuration 設定を再読み込み - + Server administration functions サーバー管理機能 - + + Server moderator functions + サーバーモデレーター機能 + + + + Replay ID + リプレイID + + + + Grant Replay Access + リプレイへのアクセス権を付与 + + + + Username to Activate + 有効にするユーザー名 + + + + Force Activate User + 強制的にユーザーを有効にする + + + &Unlock functions 機能をアンロックする - + &Lock functions 機能をロックする + + + + Success + 成功 + + + + Replay access granted + リプレイへのアクセスを付与 + + + + + + + + Error + エラー + + + + Unable to grant replay access. Replay ID invalid + リプレイへのアクセス権を付与できません。リプレイIDが無効です + + + + Unable to grant replay access. Internal error + リプレイアクセス権を付与できません。内部エラー + + + + User successfully activated + ユーザーの有効化に成功 + + + + Unable to activate user. Username invalid + ユーザーを有効化できません。ユーザー名が無効です + + + + Unable to activate user. User already active + ユーザーを有効化できません。すでに有効です + + + + Unable to activate user. Internal error + ユーザーを有効化できません。内部エラー + TabDeckEditor - - Search by card name (or search expressions) - Search by card name - - - - - Add to Deck - デッキに追加 - - - - Add to Sideboard - サイドボードに追加 - - - - Show Related cards - 関連カードを見る - - - - &Clear all filters - 全フィルタ解除 - - - - Delete selected - 選択を削除 - - - - Deck &name: - デッキ名: - - - - &Comments: - コメント: - - - - Hash: - ハッシュ: - - - - &New deck - 新しいデッキ - - - - &Load deck... - デッキを開く... - - - - &Save deck - デッキを保存 - - - - Save deck &as... - 名前を付けて保存... - - - - Load deck from cl&ipboard... - クリップボードからデッキを開く - - - - Save deck to clipboard - クリップボードにデッキをコピー - - - - Annotated - 注釈付き - - - - Not Annotated - 注釈無し - - - - &Print deck... - デッキを印刷... - - - - &Send deck to online service - オンラインサービスにデッキを送信... - - - - Create decklist (decklist.org) - デッキリストを作成する (decklist.org) - - - - Analyze deck (deckstats.net) - デッキを分析 (deckstats.net) - - - - Analyze deck (tappedout.net) - デッキを分析 (tappedout.net) - - - - &Close - 閉じる - - - - Add card to &maindeck - カードをメインデッキに追加 - - - - Add card to &sideboard - カードをサイドボードに追加 - - - - &Remove row - カードを取り除く - - - - &Increment number - 枚数を増やす - - - - &Decrement number - 枚数を減らす - - - - &Deck Editor - デッキエディター - - - - + Card Info カード情報 - - + Deck デッキ - - + Filters フィルタ - + &View レイアウト - - - + + Printing + + + + + + + Visible 表示する - - - + + + + Floating 別ウィンドウにする - + Reset layout レイアウトをリセット - + Deck: %1 デッキ: %1 - - - Are you sure? - 確認 - - - - The decklist has been modified. -Do you want to save the changes? - デッキリストが変更されています。 -変更を保存しますか? - - - - Load deck - デッキを開く - - - - - - - - - Error - エラー - - - - The deck could not be saved. - デッキが保存できませんでした。 - - - - Could not save remote deck - - - - - - The deck could not be saved. -Please check that the directory is writable and try again. - デッキは保存できませんでした。 -ディレクトリが書き込み可能であることを確認してから、もう一度やり直してください。 - - - - Save deck - デッキを保存 - - - - There are no cards in your deck to be exported - エクスポート出来るカードがデッキにありません。 - - - - No deck was selected to be saved. - 保存するデッキが選ばれていません。 - TabDeckStorage - - Deck storage - デッキストレージ - - - + Local file system ローカルのデッキ - + Server deck storage サーバーに保存されているデッキ - - + + Open in deck editor デッキエディターで開く - + + Rename deck or folder + + + + Upload deck デッキをアップロード - + Download deck デッキをダウンロード - - + + + + New folder 新しいフォルダーを作成する - - + + Delete 削除 - - + + Open decks folder + + + + + Rename local folder + + + + + Rename local file + + + + + New name: + + + + + + + Error + エラー + + + + Rename failed - + + Invalid deck file - + 無効なデッキファイル - + Enter deck name デッキの名前を入力 - + This decklist does not have a name. Please enter a name: このデッキリストには名前がありません。 名前を入力してください: - + Unnamed deck 無名のデッキ - + Failed to upload deck to server - + Delete local file ローカルファイルの削除 - - - - Are you sure you want to delete "%1"? - 本当に "%1" を削除してもよろしいですか? + + Are you sure you want to delete the selected files? + - + + Delete remote decks + + + + + Are you sure you want to delete the selected decks? + 選択したデッキを削除してもよろしいですか? + + + + Name of new folder: 新しいフォルダの名前: - - Delete remote folder - サーバーフォルダーの削除 + + Deck Storage + デッキストレージ + + + + TabDeckStorageVisual + + + Visual Deck Storage + ビジュアルデッキストレージ - - Delete remote deck - サーバーファイルの削除 + + Error + + + + + Could not open deck at %1 + + + + + TabEdhRec + + + EDHREC: + EDHREC: TabGame - - - + + + Replay リプレイ - - + + Game ゲーム - - + + Player List プレイヤーリスト - - + + Card Info カード情報 - - + + Messages メッセージ - - + + Replay Timeline リプレイタイムライン - + &Phases フェイズ - + &Game ゲーム - + Next &phase 次のフェイズ - + Next phase with &action ターン起因処理をしながら次のフェイズ - + Next &turn 次のターン - + Reverse turn order ゲームのターン順を逆転する - + &Remove all local arrows 全ての対象指定を消す - + Rotate View Cl&ockwise 席を時計回りに回転 - + Rotate View Co&unterclockwise 席を反時計回りに回転 - + Game &information ゲーム情報 - + + Un&concede + 投了を撤回 + + + &Concede 投了する - + &Leave game ゲームから離脱する - + C&lose replay リプレイを閉じる - + &Focus Chat - + &Say: 発言欄: - + &View レイアウト - - - - + + + + Visible 表示する - - - - + + + + Floating 別ウィンドウにする - + Reset layout レイアウトをリセット - + Concede 投了する - + Are you sure you want to concede this game? 本当にこのゲームを投了しますか? - + Unconcede 投了を取り消す - + You have already conceded. Do you want to return to this game? あなたはすでに投了しています! 本当にゲームに復帰しますか? - + Leave game ゲームから離脱する - + Are you sure you want to leave this game? 本当にこのゲームから離脱しますか? - + You are flooding the game. Please wait a couple of seconds. あなたはゲームから弾かれました。少々お待ちください. - + A player has joined game #%1 - + プレイヤーがゲーム#%1に参加した。 - + %1 has joined the game - + %1 がゲームに参加した - + kicked by game host or moderator キック - + player left the game 離脱 - + player disconnected from server 切断 - + reason unknown 原因不明 - + You have been kicked out of the game. ゲームからキックされました。 @@ -5796,153 +6728,153 @@ Please enter a name: ログ - - - + + + Time;SenderName;SenderIP;Message;TargetID;TargetName 時間;送信者名;送信者IP;メッセージ;対象ID;対象名 - + Room Logs ルームログ - + Game Logs ゲームログ - + Chat Logs チャットログ - - + + Error エラー - + You must select at least one filter. フィルターを選んで下さい。 - + You have to select a valid number of days to locate. - + 検索するには、有効な日数を選択する必要があります。 - + Username: ユーザー名: - + IP Address: IPアドレス: - + Game Name: ゲーム名: - + GameID: ゲームID: - + Message: メッセージ: - + Main Room メインルーム - + Game Room ゲームルーム - + Private Chat プライベートチャット - + Past X Days: X日経過: - + Today 今日 - + Last Hour - + Maximum Results: - + At least one filter is required. The more information you put in, the more specific your results will be. 少なくとも一つのフィルタが必要です。 情報を入力すればするほど詳細な検索結果になります。 - + Get User Logs ユーザーログを取得 - + Clear Filters フィルタ解除 - + Filters フィルタ - + Log Locations ログの場所 - + Date Range - + Maximum Results - - + + Message History メッセージ履歴 - + Failed to collect message history information. メッセージ履歴情報の収集に失敗しました。 - + There are no messages for the selected filters. 選択したフィルタのためのメッセージはありません。 @@ -5965,22 +6897,22 @@ The more information you put in, the more specific your results will be.%1 - 個人チャット - + This user is ignoring you, they cannot see your messages in main chat and you cannot join their games. このユーザーはあなたを無視しています。メインチャットのメッセージは表示されず、ゲームに参加することもできません。 - + Private message from 個人チャット: - + %1 has left the server. %1はサーバーから退出しました。 - + %1 has joined the server. %1がサーバーに参加しました。 @@ -5988,117 +6920,153 @@ The more information you put in, the more specific your results will be. TabReplays - - Game replays - リプレイ - - - + Local file system ローカルのリプレイ - + Server replay storage サーバーに保存されたリプレイ - - + + Watch replay リプレイを再生 - - + + Rename + 名前変更 + + + + + New folder + 新しいフォルダ + + + + Delete 削除 - + + Open replays folder + リプレイフォルダを開く + + + Download replay リプレイをダウンロード - + Toggle expiration lock 有効期限ロックの切り替え - + + Rename local folder + ローカルフォルダの名前を変更 + + + + Rename local file + ローカルファイルの名前を変更 + + + + New name: + 新しい名前: + + + + Error + エラー + + + + Rename failed + 変更失敗 + + + + Name of new folder: + 新しいフォルダの名前: + + + Delete local file ローカルファイルの削除 - - Are you sure you want to delete "%1"? - 本当に "%1" を削除してもよろしいですか? + + Are you sure you want to delete the selected files? + 選択したファイルを削除してもよろしいですか? - - Downloading Replays - リプレイをダウンロード中 + + Are you sure you want to delete the selected replays? + 選択したリプレイを削除してもよろしいですか? - - Folder download is not yet supported. Please download replays individually. - - - - + Delete remote replay サーバーファイルの削除 - - Are you sure you want to delete the replay of game %1? - 本当にゲーム:%1のリプレイを削除してもよろしいですか? + + Game Replays + リプレイ TabRoom - + &Say: 発言欄: - + Chat チャット - + &Room ルーム - + &Leave room ルームから出る - + &Clear chat チャットをクリア - + Chat Settings... チャット設定... - + mentioned you. あなたにメンションした。 - + Click to view クリックで見る - + You are flooding the chat. Please wait a couple of seconds. あなたはチャットルームから弾かれました。少々お待ちください。 @@ -6111,63 +7079,103 @@ The more information you put in, the more specific your results will be.サーバー - - - - + + + + Error エラー - + Failed to join the server room: it doesn't exist on the server. - + サーバールームに参加できませんでした: サーバーに存在しません。 - + The server thinks you are in the server room but your client is unable to display it. Try restarting your client. - + サーバーからサーバールームに入室中の応答がありましたが、クライアントが表示できませんでした。クライアントを再起動してみてください。 - + You do not have the required permission to join this server room. - + あなたはこのサーバールームに参加するための権限がありません。 - + Failed to join the server room due to an unknown error: %1. - + 原因不明のエラーによってサーバールームに参加できませんでした: %1。 TabSupervisor - + + Deck Editor + デッキエディター + + + + &Visual Deck Storage + ビジュアルデッキストレージ + + + + Server + サーバー + + + + Account + アカウント + + + + Deck Storage + デッキストレージ + + + + Game Replays + リプレイ + + + + Administration + 管理 + + + + Logs + ログ + + + Are you sure? よろしいですか? - + There are still open games. Are you sure you want to quit? ゲームがまだ開いています。本当に退出しますか? - + Click to view クリックで見る - + Your buddy %1 has signed on! %1がオンライン! - + Unknown Event 不明なイベント - + The server has sent you a message that your client does not understand. This message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -6178,63 +7186,45 @@ To update your client, go to Help -> Check for Updates. クライアントを更新するには、ヘルプ→更新のチェックをクリックしてください。 - + Idle Timeout 放置タイムアウト - + You are about to be logged out due to inactivity. 非アクティブのためログアウトしています。 - + Promotion 昇格 - + You have been promoted. Please log out and back in for changes to take effect. あなたは昇格されました。変更を有効にするためにログインしなおして下さい。 - + Warned 警告 - + You have received a warning due to %1. Please refrain from engaging in this activity or further actions may be taken against you. If you have any questions, please private message a moderator. 以下の理由によって警告を受けました: %1。 これらのような行動はご遠慮頂ますようお願い致します。質問がある場合は、モデレーターにプライベートメッセージを送信してください。 - + You have received the following message from the server. (custom messages like these could be untranslated) サーバーから次のメッセージを受け取りました。 (これらのカスタムメッセージは翻訳されない可能性があります) - - TabUserLists - - - Account - アカウント - - - - Add to Buddy List - フレンドリストに追加 - - - - Add to Ignore List - 無視リストに追加 - - TappedOutInterface @@ -6320,347 +7310,358 @@ Please refrain from engaging in this activity or further actions may be taken ag UserContextMenu - + User &details ユーザー詳細 - + Private &chat プライベートチャット - + Show this user's &games このユーザーのゲームを表示 - + Add to &buddy list フレンドリストに追加 - + Remove from &buddy list フレンドリストから削除 - + Add to &ignore list 無視リストに追加 - + Remove from &ignore list 無視リストから削除 - + Kick from &game ゲームからキックする - + Warn user ユーザーに警告する - + View user's war&n history ユーザーの警告履歴を見る - + Ban from &server サーバーからBANする - + View user's &ban history ユーザーのBAN履歴を見る - + &Promote user to moderator ユーザーを昇格する - + Dem&ote user from moderator ユーザーを降格する - + Promote user to &judge - + ユーザーをジャッジに昇格する - + Demote user from judge - + ユーザーをジャッジから降格する - + + View admin notes + 管理者ノートを見る + + + %1's games %1のゲーム - - - + + + Ban History BAN履歴 - + Ban Time;Moderator;Ban Length;Ban Reason;Visible Reason BAN回数;モデレーター;BAN期間;BAN理由;可視理由 - + User has never been banned. ユーザーはBANされていません。 - + Failed to collect ban information. - + BAN情報の収集に失敗しました。 - - - + + + Warning History 警告履歴 - + Warning Time;Moderator;User Name;Reason 警告回数;モデレーター;ユーザー名;理由 - + User has never been warned. ユーザーは警告されていません。 - + Failed to collect warning information. - + 警告情報の収集に失敗しました。 - - + + Failed to get admin notes. + 管理者ノートの取得に失敗しました。 + + + + Success 成功 - + Successfully promoted user. ユーザーを昇格しました。 - + Successfully demoted user. ユーザーを降格しました。 - - + + + Failed 失敗 - + Failed to promote user. 昇格に失敗しました。 - + Failed to demote user. 降格に失敗しました。 - + Copy hash to clipboard クリップボードにハッシュをコピー - + Remove this user's messages - + このユーザーのメッセージを削除 UserInfoBox - + Location: 現在地: - + Account Age: アカウント年齢: - + Edit 編集 - + Change password パスワード変更 - + Change avatar アバター変更 - + Administrator 管理者 - + Moderator モデレーター - + Registered user 登録ユーザー - - + + Unregistered user 未登録ユーザー - + Judge - + ジャッジ - + Unknown 不明 - + The entered password does not match your account. - + 入力されたパスワードはアカウントと一致しません。 - - - + + + Information 情報 - + User information updated. ユーザー情報が更新されました。 - - - - - - - - - - + + + + + + + + + + Error エラー - + User Information - + ユーザー情報 - + Real Name: - + 本名: - + User Level: - + ユーザー種別: - + %n Year(s), amount of years (only shown if more than 0) - + %n年 - + %10%n Day(s) %20 amount of years (if more than 0), amount of days, date in local short format - + %10%n日%20 - + Enter Password - + パスワードを入力 - + Password verification is required in order to change your email address - + メールアドレスを変更するには、パスワード認証が必要です - - - + + + An error occurred while trying to update your user information. - + ユーザー情報の更新中にエラーが発生しました。 - + This server does not permit you to update your user informations. このサーバーはユーザーの情報を更新することができません。 - + Password changed. パスワードが変更されました。 - + This server does not permit you to change your password. このサーバーはパスワードを変更することができません。 - + The new password is too short. 新しいパスワードが短すぎます。 - + The old password is incorrect. 前のパスワードが一致しませんでした。 - + Avatar updated. アバターを更新しました。 - + This server does not permit you to update your avatar. このサーバーはアバターを更新することができません。 - + An error occured while trying to updater your avatar. アバターの更新中にエラーが発生しました。 @@ -6668,124 +7669,251 @@ Please refrain from engaging in this activity or further actions may be taken ag UserInterfaceSettingsPage - + General interface settings インターフェース全般設定 - + &Double-click cards to play them (instead of single-click) ダブルクリックでカードをプレイする (シングルクリックの代わり) - + + &Clicking plays all selected cards (instead of just the clicked card) + クリックしたカードだけでなく、選択したすべてのカードをプレイする。 + + + &Play all nonlands onto the stack (not the battlefield) by default 土地でないカードをプレイする時、すぐに戦場に出さずにスタックに置く - + + Close card view window when last card is removed + 最後のカードが削除されたときにカード表示ウィンドウを閉じる + + + Annotate card text on tokens トークンのカードテキストを注釈にもつける - + Use tear-off menus, allowing right click menus to persist on screen ティアオフメニューを使用して、右クリックメニューを画面に表示したままにする - + Notifications settings - + 通知設定 - + Enable notifications in taskbar タスクバーの通知を有効 - + Notify in the taskbar for game events while you are spectating タスクバーに観戦中のゲームイベントを通知する - + Notify in the taskbar when users in your buddy list connect フレンドリストのユーザーが接続したときにタスクバーで通知する - + Animation settings アニメーション設定 - + &Tap/untap animation タップ/アンタップアニメーション - - - UserList - - Users connected to server: %1 - ユーザーが%1サーバーに接続 + + Deck editor/storage settings + デッキエディタ/ストレージ設定 - + + Open deck in new tab by default + デフォルトでデッキを新しいタブで開く + + + + Prompt before converting .txt decks to .cod format + .txtデッキを.cod形式に変換する前にプロンプトを表示する + + + + Always convert if not prompted + プロンプトが表示されない場合は常に変換します + + + + Use visual deck storage in game lobby + ゲームロビーでビジュアルデッキストレージを使用 + + + + Replay settings + リプレイ設定 + + + + Buffer time for backwards skip via shortcut: + + + + + UserListWidget + + + Users connected to server: %1 + ユーザーがサーバーに接続: %1 + + + Users in this room: %1 ルームのユーザー数: %1 - + Buddies online: %1 / %2 フレンドオンライン: %1 / %2 - + Ignored users online: %1 / %2 無視ユーザーオンライン: %1 / %2 + + VisualDeckStorageFolderDisplayWidget + + + Deck Storage + デッキストレージ + + + + VisualDeckStorageSearchWidget + + + Search by filename + ファイル名で検索する + + + + VisualDeckStorageSortWidget + + + Sort Alphabetically (Deck Name) + デッキ名順 + + + + Sort Alphabetically (Filename) + ファイル名順 + + + + Sort by Last Modified + 更新日順 + + + + Sort by Last Loaded + 最近使った順 + + + + VisualDeckStorageWidget + + + Loading database ... + データベースをロード中... + + + + Show Folders + + + + + Show Tag Filter + + + + + Show Tags On Deck Previews + + + + + Show Banner Card Selection Option + + + + + Include Folder Names in Search + + + + + Draw unused Color Identities + + + + + Unused Color Identities Opacity + + + WarningDialog - + Which warning would you like to send? どの警告を送信しますか? - + Redact all messages from this user in all rooms - + すべてのルームでこのユーザーからのすべてのメッセージを編集します - + &OK OK - + &Cancel キャンセル - + Warn user for misconduct 不正行為をユーザーに警告 - - + + Error エラー - + User name to send a warning to can not be blank, please specify a user to warn. ユーザー名は空欄に出来ません。有効なユーザー名を選択して下さい。 - + Warning to use can not be blank, please select a valid warning to send. 警告は空欄に出来ません。有効な警告を選択して下さい。 @@ -6793,128 +7921,133 @@ Please refrain from engaging in this activity or further actions may be taken ag WndSets - + Move selected set to the top 選択したセットを一番上へ - + Move selected set up 選択中のセットを上へ - + Move selected set down 選択中のセットを下へ - + Move selected set to the bottom 選択したセットを一番下へ - + Search by set name, code, or type セット名、略称、タイプで検索 - + Default order 初期順に戻す - + Restore original art priority order 元のアートの優先順位を復元する - + Enable all sets すべてのセットを有効 - + Disable all sets すべてのセットを無効 - + Enable selected set(s) 選択したセットを有効 - + Disable selected set(s) 選択したセットを無効 - + Deck Editor デッキエディター - - - Use CTRL+A to select all sets in the view. - - - - - Only cards in enabled sets will appear in the card list of the deck editor. - - - - - Image priority is decided in the following order: - - + Use CTRL+A to select all sets in the view. + Ctrl + A キーですべてのセットを選択します。 + + + + Only cards in enabled sets will appear in the card list of the deck editor. + 有効にしたセットのカードのみがデッキエディタのカードリストに表示されます。 + + + + Image priority is decided in the following order: + 画像の優先順位は、次の順序で決定されます: + + + first the CUSTOM Folder (%1), then the Enabled Sets in this dialog (Top to Bottom) %1 is a link to the wiki - + 最優先はCUSTOMフォルダ(%1)、以降はこのダイアログで有効化されたセットの並び順 - + Card Art カードアート - + How to use custom card art - + カスタムカードアートの使い方 - + Hints ヒント - + Note - + 注意 - + Sorting by column allows you to find a set while not changing set priority. 列でソートすると、セットの優先度設定を変えずにセットを探すことができます。 - + To enable ordering again, click the column header until this message disappears. 優先度設定を行うには、このメッセージが消えるまで同じ列見出しをクリックしてください。 - + Use the current sorting as the set priority instead 現在のソートを優先度設定として使用する - + Sorts the set priority using the same column 同じ列を使用して優先度をソートする - + + Include online-only (Arena) cards [requires restart] + + + + Manage sets セットを管理 @@ -6922,22 +8055,67 @@ Please refrain from engaging in this activity or further actions may be taken ag ZoneViewWidget - - sort by name - 名前順で並び替え + + Ungrouped + グループなし - - sort by type - カード・タイプ順で並び替え + + Group by Type + タイプ別グループ - + + Group by Mana Value + マナ・コスト別グループ + + + + Group by Color + 色別グループ + + + + Unsorted + 順不同 + + + + Sort by Name + 名前順 + + + + Sort by Type + タイプ順 + + + + Sort by Mana Cost + マナ・コスト順 + + + + Sort by Colors + 色順 + + + + Sort by P/T + P/T順 + + + + Sort by Set + セット順 + + + shuffle when closing 閉じる時に切り直す - + pile view タイプ別に重ねて表示 @@ -6945,7 +8123,7 @@ Please refrain from engaging in this activity or further actions may be taken ag i18n - + English 日本語 (Japanese) @@ -6953,12 +8131,12 @@ Please refrain from engaging in this activity or further actions may be taken ag main - + Connect on startup - + 起動時に接続 - + Debug to file @@ -6966,786 +8144,908 @@ Please refrain from engaging in this activity or further actions may be taken ag shortcutsTab - + Main Window メインウィンドウ - - + + Deck Editor デッキエディター - + Game Lobby ゲームロビー - + Card Counters - + Player Counters - + Power and Toughness パワーとタフネス - + Game Phases フェイズ - + Playing Area プレイエリア - + Move Selected Card - + 選択中のカードを移動 - + View 場を見る - - - Move Top Card - - - Move Bottom Card - + Move Top Card + 一番上のカードを移動 + Move Bottom Card + 一番下のカードを移動 + + + Gameplay ゲームプレイ - + Drawing ドロー - + Chat Room - + Game Window - + Load Deck from Clipboard - + + Replays - + + Tabs + + + + Check for Card Updates... - + Connect... - + Disconnect 切断 - + Exit 終了 - + Full screen フルスクリーン - + Register... - + Settings... - + Start a Local Game... - + Watch Replay... - - Analyze Deck + + Analyze Deck (deckstats.net) + Analyze Deck - + + Analyze Deck (tappedout.net) + + + + Clear All Filters - + Clear Selected Filter - + Close 閉じる - + Remove Card - + Manage Sets... - + Edit Custom Tokens... - - Export Deck - - - - - Add Card - - - - - Load Deck... - - - - - Load Deck from Clipboard... + + Export Deck (decklist.org) - New Deck + Export Deck (decklist.xyz) - - Open Custom Pictures Folder + + Add Card - - Print Deck... + + Load Deck... + Load Deck from Clipboard... + + + + + Edit Deck in Clipboard, Annotated + + + + + Edit Deck in Clipboard + + + + + New Deck + + + + + Open Custom Pictures Folder + + + + + Print Deck... + + + + Delete Card - - + + Reset Layout - + Save Deck - + Save Deck as... - + Save Deck to Clipboard, Annotated - - Save Deck to Clipboard - - - - - Load Local Deck... - - - - - Load Remote Deck... - - - - - Set Ready to Start - - - - - Toggle Sideboard Lock - - - - - - Add Green Counter - - - - - - Remove Green Counter - - - - - - Set Green Counters... + + Save Deck to Clipboard, Annotated (No Set Info) + Save Deck to Clipboard + + + + + Save Deck to Clipboard (No Set Info) + + + + + Load Local Deck... + + + + + Load Remote Deck... + + + + + Set Ready to Start + + + + + Toggle Sideboard Lock + + + + + + Add Green Counter + + + + + + Remove Green Counter + + + + + + Set Green Counters... + + + + Add Yellow Counter - + Remove Yellow Counter - + Set Yellow Counters... - - + + Add Red Counter - - + + Remove Red Counter - - + + Set Red Counters... - + Add Life Counter - + Remove Life Counter - + Set Life Counters... - + Add White Counter - + Remove White Counter - + Set White Counters... - + Add Blue Counter - + Remove Blue Counter - + Set Blue Counters... - + Add Black Counter - + Remove Black Counter - + Set Black Counters... - + Add Colorless Counter - + Remove Colorless Counter - + Set Colorless Counters... - + Add Other Counter - + Remove Other Counter - + Set Other Counters... - + Add Power (+1/+0) - + Remove Power (-1/-0) - + Move Toughness to Power (+1/-1) - + Add Toughness (+0/+1) - + Remove Toughness (-0/-1) - + Move Power to Toughness (-1/+1) - + Add Power and Toughness (+1/+1) - + Remove Power and Toughness (-1/-1) - + Set Power and Toughness... - + Reset Power and Toughness - + Untap アンタップ - + Upkeep アップキープ - + Draw ドロー - + First Main Phase 戦闘前メイン・フェイズ - + Start Combat - + Attack 攻撃 - + Block ブロック - + Damage ダメージ - + End Combat - + Second Main Phase 戦闘後メイン・フェイズ - + End エンド - + Next Phase 次のフェイズ - + Next Phase Action 処理しながら次のフェイズ - + Next Turn - + + Hide Card in Reveal Window + + + + Tap / Untap Card タップ / アンタップ - + Untap All - + Toggle Untap - + Turn Card Over - + Peek Card - + Play Card - + Attach Card... - + Unattach Card - + Clone Card - + Create Token... - + Create All Related Tokens - + Create Another Token - + Set Annotation... - - + + Select All Cards in Zone + + + + + Select All Cards in Row + + + + + Select All Cards in Column + + + + + Bottom of Library - - - - + + + + Exile 追放領域 - - - - + + + + Graveyard 墓地 - - + + Hand 手札 - - + + Top of Library - - - + + + Battlefield, Face Down - + Battlefield - + Library 自分のライブラリー - + Sideboard サイドボード - + Top Cards of Library - + + Bottom Cards of Library + + + + Close Recent View - - + + Stack - - + + Graveyard (Multiple) - - + + Exile (Multiple) - + Stack Until Found - + Draw Bottom Card - + Draw Multiple Cards from Bottom... - + Draw Arrow... - + Remove Local Arrows - + Leave Game - + Concede 投了 - + Roll Dice... - + Shuffle Library - + + Shuffle Top Cards of Library + + + + + Shuffle Bottom Cards of Library + + + + Mulligan マリガン - + Draw a Card - + Draw Multiple Cards... - + Undo Draw - + Always Reveal Top Card - + Always Look At Top Card - + Rotate View Clockwise - + Rotate View Counterclockwise - + Unfocus Text Box - + Focus Chat - + Clear Chat - + Refresh - + + Skip Forward + + + + + Skip Backward + + + + + Skip Forward by a lot + + + + + Skip Backward by a lot + + + + Play/Pause - + Toggle Fast Forward + + + Visual Deck Storage + + + + + Deck Storage + + + + + Server + + + + + Account + + + + + Administration + + + + + Logs + + \ No newline at end of file diff --git a/cockatrice/translations/cockatrice_ko.ts b/cockatrice/translations/cockatrice_ko.ts index 1170558f4..94d30d6d4 100644 --- a/cockatrice/translations/cockatrice_ko.ts +++ b/cockatrice/translations/cockatrice_ko.ts @@ -2,7 +2,7 @@ AbstractCounter - + &Set counter... 카운터 설정... @@ -10,95 +10,247 @@ AbstractCounterDialog - + Set counter - + New value for counter '%1': + + AbstractDlgDeckTextEdit + + + &Refresh + + + + + Parse Set Name and Number (if available) + + + + + AbstractTabDeckEditor + + + Open in new tab + + + + + Are you sure? + + + + + The decklist has been modified. +Do you want to save the changes? + + + + + + + + + + + Error + + + + + Could not open deck at %1 + + + + + Could not save remote deck + + + + + + The deck could not be saved. +Please check that the directory is writable and try again. + + + + + Save deck + + + + + The deck could not be saved. + + + + + There are no cards in your deck to be exported + + + + + No deck was selected to be exported. + + + + + AdminNotesDialog + + + Update Notes + + + + + Admin Notes for %1 + + + + + AllZonesCardAmountWidget + + + Mainboard + + + + + Sideboard + + + AppearanceSettingsPage - + Error - + Could not create themes directory at '%1'. - + Theme settings 테마 설정 - + Current theme: 현재 테마: - + Open themes folder - + + Menu settings + + + + + Show keyboard shortcuts in right-click menus + + + + Card rendering 카드 렌더링 - + Display card names on cards having a picture 이미지가 존재하는 카드에도 카드 이름 표시 - + + Auto-Rotate cards with sideways layout + + + + + Override all card art with personal set preference (Pre-ProviderID change behavior) [Requires Client restart] + + + + + Bump sets that the deck contains cards from to the top in the printing selector + + + + Scale cards on mouse over 마우스 오버 시 카드 확대 - + + Use rounded card corners + + + + Minimum overlap percentage of cards on the stack and in vertical hand - + + Maximum initial height for card view window: + + + + + + rows + + + + + Maximum expanded height for card view window: + + + + Hand layout 손 레이아웃 - + Display hand horizontally (wastes space) 손의 카드를 가로로 정렬 - + Enable left justification 손의 카드를 좌측으로 정렬 - + Table grid layout 테이블 격자 레이아웃 - + Invert vertical coordinate 전장의 상하배치를 전환 (대지를 앞에 배치) - + Minimum player count for multi-column layout: 다열 레이아웃를 위한 최소 플레이어 인원 (4명 이상 권장) - + Maximum font size for information displayed on cards: 카드 정보에 표시할 폰트의 최대 크기: @@ -106,112 +258,112 @@ BanDialog - + ban &user name 사용자 계정 추방 - + ban &IP address IP 주소 추방 - + ban client I&D 클라이언트 ID 추방 - + Ban type 추방 유형 - + &permanent ban 영구 추방 - + &temporary ban 기간제 추방 - + &Days: 일: - + &Hours: 시간: - + &Minutes: 분: - + Duration of the ban 추방 기간 - + Please enter the reason for the ban. This is only saved for moderators and cannot be seen by the banned person. 추방 사유를 적어주세요. 해당 사유는 관리자들만 볼 수 있고 추방 당한 사용자는 볼 수 없습니다. - + Please enter the reason for the ban that will be visible to the banned person. 추방된 사용자가 보게 될 추방 사유를 적어주세요. - + Redact all messages from this user in all rooms - + &OK 확인 - + &Cancel 취소 - + Ban user from server 사용자를 서버에서 추방 - - - - + + + + Error 오류 - + You have to select a name-based, IP-based, clientId based, or some combination of the three to place a ban. 추방의 유형을 선택해 주세요. - + You must have a value in the name ban when selecting the name ban checkbox. 사용자명 추방을 하기 위해선 사용자명을 입력하셔야 합니다. - + You must have a value in the ip ban when selecting the ip ban checkbox. 아이피 추방을 하기 위해선 아이피 주소를 입력하셔야 합니다. - + You must have a value in the clientid ban when selecting the clientid ban checkbox. 클라이언트 ID 추방을 하기 위해선 클라이언트 ID를 입력하셔야 합니다. @@ -219,22 +371,22 @@ This is only saved for moderators and cannot be seen by the banned person. BetaReleaseChannel - - Beta Releases + + Beta - + No reply received from the release update server. - + Invalid reply received from the release update server. - + No reply received from the file update server. @@ -242,32 +394,32 @@ This is only saved for moderators and cannot be seen by the banned person. CardDatabaseModel - + Name 카드 이름 - + Sets 확장판 - + Mana cost 마나 비용 - + Card type 카드 유형 - + P/T 공/방 - + Color(s) 색(들) @@ -360,37 +512,65 @@ This is only saved for moderators and cannot be seen by the banned person. - CardFrame + CardInfoFrameWidget - + Image - 이미지 + - + Description - 텍스트 + - + Both - 모두 보기 + + + + + View transformation + - CardInfoText + CardInfoPictureWidget - - Name: - 카드 이름: + + View related cards + - + + Add card to deck + + + + + Mainboard + + + + + Sideboard + + + + + CardInfoTextWidget + + + Name: + + + + Related cards: - + Unknown card: @@ -398,287 +578,555 @@ This is only saved for moderators and cannot be seen by the banned person. CardItem - + &Move to 다른 영역으로 이동: - + &Power / toughness 공격력 / 방어력 설정 + + CardSizeWidget + + + Card Size + + + CardZone - + their hand nominative 자신의 손 - + %1's hand nominative %1의 손 - + their library look at zone 자신의 서고 - + %1's library look at zone %1의 서고 - + of their library top cards of zone, 자신의 서고에서 - + of %1's library top cards of zone %1의 서고에서 - + their library reveal zone 자신의 서고 - + %1's library reveal zone %1의 서고 - + their library shuffle 자신의 서고 - + %1's library shuffle %1의 서고 - + their library nominative 자신의 서고 - + %1's library nominative %1의 서고 - + their graveyard nominative 자신의 무덤 - + %1's graveyard nominative %1의 무덤 - + their exile nominative 자신의 추방 영역 - + %1's exile nominative %1의 추방 영역 - + their sideboard look at zone 자신의 사이드보드 - + %1's sideboard look at zone %1의 사이드보드 - + their sideboard nominative 자신의 사이드보드 - + %1's sideboard nominative %1의 사이드보드 + + DeckEditorCardInfoDockWidget + + + Card Info + + + + + DeckEditorDatabaseDisplayWidget + + + Search by card name (or search expressions) + + + + + Add to Deck + + + + + Add to Sideboard + + + + + Select Printing + + + + + Show on EDHREC (Commander) + + + + + Show on EDHREC (Card) + + + + + Show Related cards + + + + + Add card to &maindeck + + + + + Add card to &sideboard + + + + + DeckEditorDeckDockWidget + + + Banner Card + + + + + Select Printing + + + + + Deck + + + + + Deck &name: + + + + + &Comments: + + + + + Hash: + + + + + &Increment number + + + + + &Decrement number + + + + + &Remove row + + + + + Swap card to/from sideboard + + + + + DeckEditorFilterDockWidget + + + Filters + + + + + &Clear all filters + + + + + Delete selected + + + + + DeckEditorMenu + + + &Deck Editor + + + + + &New deck + + + + + &Load deck... + + + + + Load recent deck... + + + + + Clear + + + + + &Save deck + + + + + Save deck &as... + + + + + Load deck from cl&ipboard... + + + + + Edit deck in clipboard + + + + + + Annotated + + + + + + Not Annotated + + + + + Save deck to clipboard + + + + + Annotated (No set info) + + + + + Not Annotated (No set info) + + + + + &Print deck... + + + + + &Send deck to online service + + + + + Create decklist (decklist.org) + + + + + Create decklist (decklist.xyz) + + + + + Analyze deck (deckstats.net) + + + + + Analyze deck (tappedout.net) + + + + + &Close + + + + + DeckEditorPrintingSelectorDockWidget + + + Printing Selector + + + DeckEditorSettingsPage - - + + Update Spoilers - - + + Success - + Download URLs have been reset. - + Downloaded card pictures have been reset. - + Error - + One or more downloaded card pictures could not be cleared. - + Add URL - - + + URL: - - + + Edit URL - + + Network Cache Size: + + + + + Redirect Cache TTL: + + + + + How long cached redirects for urls are valid for. + + + + + Picture Cache Size: + + + + Add New URL - + Remove URL - + + Day(s) + + + + Updating... - + Choose path - + URL Download Priority - + Spoilers - + Download Spoilers Automatically - + Spoiler Location: - + Last Change - + Spoilers download automatically on launch - + Press the button to manually update without relaunching - + Do not close settings until manual update is complete - + Download card pictures on the fly - + How to add a custom URL - + Delete Downloaded Images - + Reset Download URLs - - Downloaded images directory size: - - - - + On-disk cache for downloaded pictures - - Picture cache size: - - - - + In-memory cache for pictures not currently on screen @@ -686,16 +1134,218 @@ This is only saved for moderators and cannot be seen by the banned person. DeckListModel - + + Count + + + + + Set + + + + Number 카드 장수 - + + Provider ID + + + + Card 카드 이름 + + DeckLoader + + + Common deck formats (%1) + + + + + All files (*.*) + + + + + DeckPreviewColorIdentityFilterWidget + + + Mode: Exact Match + + + + + Mode: Includes + + + + + DeckPreviewDeckTagsDisplayWidget + + + Edit tags ... + + + + + DeckPreviewTagDialog + + + Deck Tags Manager + + + + + Manage your deck tags. Check or uncheck tags as needed, or add new ones: + + + + + Add a new tag (e.g., Aggro️) + + + + + Add Tag + + + + + Filter tags... + + + + + OK + + + + + Cancel + + + + + Invalid Input + + + + + Tag name cannot be empty! + + + + + Duplicate Tag + + + + + This tag already exists. + + + + + DeckPreviewWidget + + + Banner Card + + + + + Open in deck editor + + + + + Edit Tags + + + + + Rename Deck + + + + + Save Deck to Clipboard + + + + + Annotated + + + + + Annotated (No set info) + + + + + Not Annotated + + + + + Not Annotated (No set info) + + + + + Rename File + + + + + Delete File + + + + + Set Banner Card + + + + + + New name: + + + + + + Error + + + + + Rename failed + + + + + Delete file + + + + + Are you sure you want to delete the selected file? + + + + + Delete failed + + + DeckStatsInterface @@ -713,181 +1363,217 @@ This is only saved for moderators and cannot be seen by the banned person. DeckViewContainer - + Load deck... 덱 불러오기 - + Load remote deck... 원격 덱 불러오기 - + + Unload deck + Unload deck... + + + + Ready to start - + + Force start + + + + Sideboard unlocked - + Sideboard locked - - Load deck - 덱 불러오기 - - - + Error 오류 - + The selected file could not be loaded. 지정하신 파일을 불러올 수 없었습니다. + + DialogConvertDeckToCodFormat + + + Deck Format Conversion + + + + + You tried to add a tag to a .txt format deck. + Tags can only be added to .cod format decks. + Do you want to convert the deck to the .cod format? + + + + + Remember and automatically apply choice in the future + + + DlgConnect - + Downloading... - + Known Hosts 기록한 호스트 - + + Delete the currently selected saved server + + + + Refresh the server list with known public servers - + New Host 새로운 서버 - + Name: 호스트명: - + &Host: 호스트 IP: - + &Port: 포트: - + Player &name: 사용자명: - + P&assword: 비밀번호: - + &Save password 비밀번호 저장 - + A&uto connect 자동연결 - + Automatically connect to the most recent login when Cockatrice opens 코카트리스를 실행할 때 최근에 로그인한 서버로 연결합니다. - + If you have any trouble connecting or registering then contact the server staff for help! - - + + Webpage - + Reset Password - + + Forgot password? + + + + &Connect - + Server 서버 - + Login 로그인 - + Server Contact - + Connect to Server - + Server URL - + Communication Port - + Unique Server Name - + Connection Warning - + You need to name your new connection profile. - + Connect Warning - + The player name can't be empty. 사용자명은 비어있을 수 없습니다. @@ -970,27 +1656,37 @@ This is only saved for moderators and cannot be seen by the banned person.관전자 - + + Starting life total: + + + + + Game setup options + + + + &Clear 설정 초기화 - + Create game 게임 만들기 - + Game information 게임 정보 - + Error 오류 - + Server error. 서버 오류. @@ -998,92 +1694,92 @@ This is only saved for moderators and cannot be seen by the banned person. DlgCreateToken - + &Name: 토큰 이름: - + Token 토큰 - + C&olor: 토큰 색: - + white 백색 - + blue 청색 - + black 흑색 - + red 적색 - + green 녹색 - + multicolor 다색 - + colorless 무색 - + &P/T: 공/방 - + &Annotation: 주석: - + &Destroy token when it leaves the table 토큰이 전장을 떠나면 게임에서 제거됨 - + Token data 토큰 정보 - + Show &all tokens 모든 토큰 보기 - + Show tokens from this &deck 이 덱에서 사용하는 토큰 보기 - + Choose token from list 목록에서 토큰 선택 - + Create token 토큰 생성 @@ -1129,6 +1825,24 @@ To remove your current avatar, confirm without choosing a new image. 잘못된 이미지를 선택하셨습니다. + + DlgEditDeckInClipboard + + + Edit deck in clipboard + + + + + Error + + + + + Invalid deck list. + + + DlgEditPassword @@ -1171,93 +1885,93 @@ To remove your current avatar, confirm without choosing a new image. DlgEditTokens - + &Name: 토큰 이름: - + C&olor: 토큰 색: - + white 백색 - + blue 청색 - + black 흑색 - + red 적색 - + green 녹색 - + multicolor 다색 - + colorless 무색 - + &P/T: 공/방 - + &Annotation: 주석: - + Token data 토큰 정보 - - + + Add token 토큰 추가 - + Remove token 토큰 삭제 - + Edit custom tokens - + Please enter the name of the token: 토큰의 이름을 입력해 주세요. - + Error 오류 - + The chosen name conflicts with an existing card or token. Make sure to enable the 'Token' set in the "Manage sets" dialog to display them correctly. @@ -1294,132 +2008,132 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgFilterGames - + no limit - + 5 minutes - + 10 minutes - + 30 minutes - + 1 hour - + 2 hours - - Show '&buddies only' games + + Hide 'buddies only' games - - Show &full games + + Hide full games - - Show games &that have started + + Hide games that have started - - Show &password protected games - 비밀번호가 걸려있는 게임도 표시 - - - - Hide '&ignored user' games + + Hide password protected games - + + Hide 'ignored user' games + + + + &Newer than: - + Game &description: 게임 이름: - + &Creator name: 방장 이름: - + General - + &Game types 게임 종류 - + at &least: 최소 참가 인원: - + at &most: 최대 참가 인원: - + Maximum player count 최대 플레이어 인원 - + Restrictions - + Show games only if &spectators can watch - + Show spectator password p&rotected games - + Show only if spectators can ch&at - + Show only if spectators can see &hands - + Spectators - + Filter games 게임 필터 @@ -1602,27 +2316,28 @@ Make sure to enable the 'Token' set in the "Manage sets" dia + + DlgLoadDeck + + + Load Deck + + + DlgLoadDeckFromClipboard - - &Refresh - 새로고침 - - - + Load deck from clipboard 클립보드에서 덱 가져오기 - - + Error 오류 - - + Invalid deck list. 클립보드의 덱 리스트를 읽어올 수 없습니다. @@ -1630,11 +2345,49 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgLoadRemoteDeck - + Load deck 덱 불러오기 + + DlgMoveTopCardsUntil + + + Card name (or search expressions): + + + + + Number of hits: + + + + + Auto play hits + + + + + Put top cards on stack until... + + + + + No cards matching the search expression exists in the card database. Proceed anyways? + + + + + Cockatrice + + + + + Invalid filter + + + DlgRegister @@ -1748,12 +2501,12 @@ Your email will be used to verify your account. DlgSettings - + Unknown Error loading card database 데이터베이스를 불러올 때 미상의 오류가 발생하였습니다. - + Your card database is invalid. Cockatrice may not function correctly with an invalid database @@ -1770,7 +2523,7 @@ Would you like to change your database location setting? 데이터베이스 경로를 다시 설정하시겠습니까? - + Your card database version is too old. This can cause problems loading card information or images @@ -1787,7 +2540,7 @@ Would you like to change your database location setting? 데이터베이스 경로를 다시 설정하시겠습니까? - + Your card database did not finish loading Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues with your cards.xml attached @@ -1796,7 +2549,7 @@ Would you like to change your database location setting? - + File Error loading your card database. Would you like to change your database location setting? @@ -1805,7 +2558,7 @@ Would you like to change your database location setting? 데이터베이스 경로를 다시 설정하시겠습니까? - + Your card database was loaded but contains no cards. Would you like to change your database location setting? @@ -1814,7 +2567,7 @@ Would you like to change your database location setting? 데이터베이스 경로를 다시 설정하시겠습니까? - + Unknown card database load status Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues @@ -1823,61 +2576,61 @@ Would you like to change your database location setting? - - - + + + Error 오류 - + The path to your deck directory is invalid. Would you like to go back and set the correct path? 덱 파일을 보관하는 디렉토리의 경로가 잘못되었습니다. 경로를 다시 설정하시겠습니까? - + The path to your card pictures directory is invalid. Would you like to go back and set the correct path? 카드 이미지 파일을 보관하는 디렉토리의 경로가 잘못되었습니다. 경로를 다시 설정하시겠습니까? - + Settings 환경설정 - + General 일반 - + Appearance 외형 - + User Interface 인터페이스 - + Card Sources - + Chat 대화 - + Sound 소리 - + Shortcuts 단축키 @@ -1903,164 +2656,168 @@ Would you like to change your database location setting? DlgUpdate - + Current release channel - + Reinstall 재설치 - + Cancel Download 다운로드 취소 - + Open Download Page 다운로드 페이지 열기 - + Check for Client Updates - - - - + + + + Error 오류 - + Cockatrice was not built with SSL support, therefore you cannot download updates automatically! Please visit the download page to update manually. - - Downloading update... - 업데이트 다운로드중... + + Downloading update: %1 + Downloading update... + - + Checking for updates... 업데이트 확인중... - + Finished checking for updates - + No Update Available - + Cockatrice is up to date! Cockatrice는 최신 버젼입니다! - + You are already running the latest version available in the chosen release channel. - + Current version 현재 버젼 - + Selected release channel - - + + Update Available - - + + A new version of Cockatrice is available! - - + + New version 새로운 버젼 - - + + Released - - + + Changelog 변경점 - + Do you want to update now? 업데이트 하시겠습니까? - - Unfortunately there are no download packages available for your operating system. -You may have to build from source yourself. + + Unfortunately, the automatic updater failed to find a compatible download. +You may have to manually download the new version. + Unfortunately there are no download packages available for your operating system. +You may have to build from source yourself. - - Please check the download page manually and visit the wiki for instructions on compiling. + + Please check the <a href="%1">releases page</a> on our Github and download the build for your system. + Please check the download page manually and visit the wiki for instructions on compiling. - - - + + + Update Error 업데이트 오류 - + An error occurred while checking for updates: - + An error occurred while downloading an update: - + Installing... 설치중... - + Cockatrice is unable to open the installer. - + Try to update manually by closing Cockatrice and running the installer. - + Download location 다운로드 위치 @@ -2078,6 +2835,14 @@ You may have to build from source yourself. 디버그 로그 + + EdhrecCommanderResponseCommanderDetailsDisplayWidget + + + Salt: + + + FilterBuilder @@ -2089,105 +2854,120 @@ You may have to build from source yourself. GameSelector - - - - - - - - - + + + + + + + + + Error 오류 - + Please join the appropriate room first. 해당 게임이 열려있는 채널에 먼저 들어가시기 바랍니다. - + Wrong password. 잘못된 비밀번호를 입력하셨습니다. - + Spectators are not allowed in this game. 관전이 허용되지 않은 게임입니다. - + The game is already full. 게임 인원이 전부 찼습니다. - + The game does not exist any more. 게임이 더 이상 존재하지 않습니다. - + This game is only open to registered users. 서버에 가입한 사용자만 참가 할 수 있습니다. - + This game is only open to its creator's buddies. 방장의 친구 목록에 등록된 사용자만 참가 할 수 있습니다. - + You are being ignored by the creator of this game. 방장이 당신을 차단하였습니다. - + + Join Game + + + + + Spectate Game + + + + + Game Information + + + + Join game 게임 참가 - + Password: 비밀번호: - + Please join the respective room first. 해당 게임이 열려있는 채널에 먼저 들어가시기 바랍니다. - + &Filter games 게임 필터 - + C&lear filter 필터 해제 - + C&reate 게임 생성 - + &Join 게임 참가 - + J&oin as spectator 관전자로 참가하기 - + Games shown: %1 / %2 - + Games 게임 목록 @@ -2195,100 +2975,100 @@ You may have to build from source yourself. GamesModel - + >1 day - + %1%2 hr short age in hours - + new - + %1%2 min short age in minutes - + password 비밀번호 필요 - + buddies only 친구만 - + reg. users only 가입한 사용자 전용 - - + + can chat 관전자 대화 가능 - + see hands 손 공개 - + can see hands 관전자에게 손 공개 - + not allowed 허용 안됨 - + Room 게임 - + Age - + Description 게임 이름 - + Creator 방장 - + Type - + Restrictions 입장 제한 - + Players 플레이어 - + Spectators 관전자 @@ -2296,92 +3076,102 @@ You may have to build from source yourself. GeneralSettingsPage - + Reset all paths - + All paths have been reset - - + + Choose path 경로 선택 - + Personal settings 개인 설정 - + Language: 언어: - + Paths (editing disabled in portable mode) - + Paths 디렉토리 경로 - + + How to help with translations + + + + Decks directory: 덱 파일 경로: - + Replays directory: 리플레이 파일 경로: - + Pictures directory: 카드 이미지 경로: - + Card database: 카드 데이터베이스 경로: - + Custom database directory: - + Token database: 토큰 데이터베이스 경로: - + Update channel - + + Check for client updates on startup + + + + Notify if a feature supported by the server is missing in my client 서버에서 서포트하는 기능이 클라이언트에 존재하지 않을 경우 알림 - + Automatically run Oracle when running a new version of Cockatrice - + Show tips on startup @@ -2389,61 +3179,61 @@ You may have to build from source yourself. MainWindow - - + + The server has reached its maximum user capacity, please check back later. 서버가 꽉 찼습니다. 나중에 다시 접속하여 주십시오. - + There are too many concurrent connections from your address. 해당 IP주소에서 동시에 연결된 회선이 너무 많습니다. - + Banned by moderator 관리자에 의해 서버에서 추방 당하였습니다. - + Expected end time: %1 예상 추방 종료 시간: %1 - + This ban lasts indefinitely. 영구적 추방입니다. - + Scheduled server shutdown. 정기 점검 중입니다. - - + + Invalid username. 잘못된 사용자명입니다. - + You have been logged out due to logging in at another location. 다른 장소에서 로그인하여 접속이 해제되었습니다. - + Connection closed 서버와의 연결이 끊어졌습니다. - + The server has terminated your connection. Reason: %1 서버에서 연결을 끊었습니다. 사유: %1 - + The server is going to be restarted in %n minute(s). All running games will be lost. Reason for shutdown: %1 @@ -2452,362 +3242,362 @@ Reason for shutdown: %1 서버 재시작 사유: %1 - + Scheduled server shutdown 정기 점검 - - + + Success 성공 - + Registration accepted. Will now login. 가입이 승인되었습니다. 로그인합니다. - + Account activation accepted. Will now login. 계정이 활성화 되었습니다. 로그인합니다. - + Number of players 플레이어 인원 - + Please enter the number of players. 최대 플레이어 인원을 입력해 주세요. - - + + Player %1 플레이어 %1 - + Load replay 리플레이 불러오기 - + About Cockatrice 코카트리스에 관하여 - + Version - + Cockatrice Webpage 코카트리스 홈페이지 - + Project Manager: 현 프로젝트 매니저: - + Past Project Managers: 전 프로젝트 매니저: - + Developers: 개발진: - + Our Developers 기여자 목록 - + Help Develop! 개발을 도와주세요! - + Translators: 번역진: - + Our Translators - + Help Translate! 번역을 도와주세요! - + Support: 기술 지원: - + Report an Issue 문제점 보고 - + Troubleshooting 문제 해결 - + F.A.Q. 자주 묻는 질문 - - - - - - - - - - - - - - - + + + + + + + + + + + + + - - - + + + + + Error 오류 - + Server timeout 서버 응답시간 초과 - + Failed Login 로그인 실패 - + Your client seems to be missing features this server requires for connection. - + To update your client, go to 'Help -> Check for Client Updates'. - + Incorrect username or password. Please check your authentication information and try again. 잘못된 사용자명이나 비밀번호입니다. 확인 후 다시 시도해 주세요. - + There is already an active session using this user name. Please close that session first and re-login. 해당 사용자명으로 연결된 다른 세션이 있습니다. 해당 세션을 종료 한 후에 다시 시도해 주세요. - - + + You are banned until %1. %1까지 추방 당하였습니다. - - + + You are banned indefinitely. 당신은 무기한 추방 당하였습니다. - + This server requires user registration. Do you want to register now? 본 서버는 가입이 필요합니다. 지금 가입하시겠습니까? - + This server requires client IDs. Your client is either failing to generate an ID or you are running a modified client. Please close and reopen your client to try again. - + An internal error has occurred, please close and reopen Cockatrice before trying again. If the error persists, ensure you are running the latest version of the software and if needed contact the software developers. - + Account activation 계정 활성화 - + Your account has not been activated yet. You need to provide the activation token received in the activation email. - + Server Full - + Unknown login error: %1 알 수 없는 로그인 오류: %1 - - + + This usually means that your client version is out of date, and the server sent a reply your client doesn't understand. 대부분 클라이언트가 오래되어서 서버와 클라이언트가 말이 제대로 통하지 않을때 발생하는 문제입니다. - + Your username must respect these rules: 사용자명은 다음 조건을 만족해야 합니다: - + is %1 - %2 characters long %1에서 %2자 사이 - + can %1 contain lowercase characters 소문자 사용 %1가능 - - - - + + + + NOT - + can %1 contain uppercase characters 대문자 사용 %1가능 - + can %1 contain numeric characters 숫자 사용 %1가능 - + can contain the following punctuation: %1 다음 문장 부호를 사용 가능 : - + first character can %1 be a punctuation mark 첫 글자를 문장 부호로 입력 %1가능 - + no unacceptable language as specified by these server rules: note that the following lines will not be translated - + can not contain any of the following words: %1 - + can not match any of the following expressions: %1 - + You may only use A-Z, a-z, 0-9, _, ., and - in your username. 사용자명에는 영어 대소문자, 숫자, _(밑줄), .(마침표)나 -(대쉬)만 사용하실 수 있습니다. - - - - - - + + + + + + Registration denied 가입 실패 - + Registration is currently disabled on this server 본 서버는 현재 가입을 받지 않습니다. - + There is already an existing account with the same user name. 이미 존재하는 사용자명 입니다. - + It's mandatory to specify a valid email address when registering. 사용 가능한 이메일 주소를 작성하셔야 합니다. - + It appears you are attempting to register a new account on this server yet you already have an account registered with the email provided. This server restricts the number of accounts a user can register per address. Please contact the server operator for further assistance or to obtain your credential information. - + Password too short. 비밀번호가 너무 짧습니다. - + Registration failed for a technical problem on the server. 서버의 기술적 문제로 가입에 실패하였습니다. - + Unknown registration error: %1 가입 중 알 수 없는 오류 발생: %1 - + Account activation failed 계정 활성화 실패 - + Socket error: %1 소켓 오류: %1 - + You are trying to connect to an obsolete server. Please downgrade your Cockatrice version or connect to a suitable server. Local version is %1, remote version is %2. 서버 버전이 클라이언트보다 오래되었습니다. @@ -2815,7 +3605,7 @@ Local version is %1, remote version is %2. 클라이언트 버전 %1, 서버 버전 %2. - + Your Cockatrice client is obsolete. Please update your Cockatrice version. Local version is %1, remote version is %2. 코카트리스 클라이언트 버전이 오래되었습니다. @@ -2823,291 +3613,301 @@ Local version is %1, remote version is %2. 클라이언트 버전 %1, 서버 버전 %2. - + Connecting to %1... %1로 연결 시도 중... - + Registering to %1 as %2... 서버 %1에 %2(으)로 가입 중... - + Disconnected 연결 안됨 - + Connected, logging in at %1 연결됨, %1로 로그인 시도 중 - - - + + + Requesting forgotten password to %1 as %2... - + &Connect... 서버로 연결 - + &Disconnect 서버와의 연결 해제 - + Start &local game... 오프라인 게임 시작 - + &Watch replay... 리플레이 재생 - - &Deck editor - 덱 편집기 - - - + &Full screen 전체 화면 - + &Register to server... 서버에 가입 - + &Restore password... - + &Settings... 환경설정 - + &Exit 끝내기 - + A&ctions 액션 - + &Cockatrice 코카트리스 - + C&ard Database - + &Manage sets... - + Edit custom &tokens... - + Open custom image folder 커스텀 이미지 폴더 열기 - + Open custom sets folder 커스텀 셋 폴더 열기 - + Add custom sets/cards 커스텀 셋/카드 추가 - + + Reload card database + + + + + Tabs + + + + &Help 도움말 - + &About Cockatrice 코카트리스에 관하여 - + &Tip of the Day - + Check for Client Updates 클라이언트 업데이트 확인 - + Check for Card Updates... - + View &Debug Log - + + Open Settings Folder + + + + Show/Hide - + New Version - + Congratulations on updating to Cockatrice %1! Oracle will now launch to update your card database. - + Cockatrice installed - + Congratulations on installing Cockatrice %1! Oracle will now launch to install the initial card database. - + Card database 카드 데이터베이스 - + Cockatrice is unable to load the card database. Do you want to update your card database now? If unsure or first time user, choose "Yes" - - + + Yes - - + + No 아니오 - + Open settings 설정 열기 - + New sets found - + %n new set(s) found in the card database Set code(s): %1 Do you want to enable it/them? - + View sets 셋 보기 - + Welcome - + Hi! It seems like you're running this version of Cockatrice for the first time. All the sets in the card database have been enabled. Read more about changing the set order or disabling specific sets and consequent effects in the "Manage Sets" dialog. - - + + Information 알림 - + A card database update is already running. 이미 카드 데이터베이스 업데이트가 진행 중입니다. - + Unable to run the card database updater: 카드 데이터베이스 업데이트를 진행할 수 없습니다: - - failed to start. - 업데이트 시작 실패 + + Failed to start. The file might be missing, or permissions might be incorrect. + - - crashed. - 강제 종료됨. + + The process crashed some time after starting successfully. + - - timed out. - 시간 초과. + + Timed out. The process took too long to respond. The last waitFor...() function timed out. + - - write error. - 파일 쓰기 오류. + + An error occurred when attempting to write to the process. For example, the process may not be running, or it may have closed its input channel. + - - read error. - 파일 읽기 오류. + + An error occurred when attempting to read from the process. For example, the process may not be running. + - - unknown error. - 알 수 없는 오류. + + Unknown error occurred. + - - The card database updater exited with an error: %1 - 카드 데이터베이스 업데이트 중 다음 오류가 발생하였습니다: -%1 + + The card database updater exited with an error: +%1 + - + This server supports additional features that your client doesn't have. This is most likely not a problem, but this message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -3115,54 +3915,54 @@ To update your client, go to Help -> Check for Updates. - - - - - + + + + + Load sets/cards - + Selected file cannot be found. - + You can only import XML databases at this time. - + The new sets/cards have been added successfully. Cockatrice will now reload the card database. - + Sets/cards failed to import. - - - + + + Reset Password - + Your password has been reset successfully, you can now log in using the new credentials. - + Failed to reset user account password, please contact the server operator to reset your password. - + Activation request received, please check your email for an activation token. @@ -3436,179 +4236,190 @@ Cockatrice will now reload the card database. - %1 is looking at the top %3 card(s) %2. + %1 is looking at the %4 %3 card(s) %2. + %1 is looking at the top %3 card(s) %2. top card for singular, top %3 cards for plural - + + bottom + + + + + top + + + + %1 turns %2 face-down. - + %1 turns %2 face-up. - + The game has been closed. 게임이 종료되었습니다. - + The game has started. 게임이 시작되었습니다. - + %1 has joined the game. %1님이 게임에 입장했습니다. - + %1 is now watching the game. %1이(가) 관전을 시작하였습니다. - + You have been kicked out of the game. 게임에서 강제 퇴장 당하였습니다. - + %1 has left the game (%2). - + %1 is not watching the game any more (%2). - + %1 is not ready to start the game any more. - + %1 shuffles their deck and draws a new hand of %2 card(s). - + %1 shuffles their deck and draws a new hand. - + You are watching a replay of game #%1. - + %1 is ready to start the game. %1님이 게임 시작 준비를 끝냈습니다. - + cards an unknown amount of cards - + %1 card(s) a card for singular, %1 cards for plural - + %1 lends %2 to %3. - + %1 reveals %2 to %3. - + %1 reveals %2. %1님이 %2을(를) 공개. - + %1 randomly reveals %2%3 to %4. - + %1 randomly reveals %2%3. - + %1 peeks at face down card #%2. - + %1 peeks at face down card #%2: %3. - + %1 reveals %2%3 to %4. - + %1 reveals %2%3. - + %1 reversed turn order, now it's %2. - + reversed - + normal - + Heads 앞면 - + Tails 뒷면 - + %1 flipped a coin. It landed as %2. - + %1 rolls a %2 with a %3-sided die. - + %1 flips %2 coins. There are %3 heads and %4 tails. - + %1 rolls a %2-sided dice %3 times: %4. @@ -3728,17 +4539,17 @@ Cockatrice will now reload the card database. - + %1 unattaches %2. %1님이 %2을(를) 뗴어냈습니다. - + %1 undoes their last draw. - + %1 undoes their last draw (%2). @@ -3746,110 +4557,110 @@ Cockatrice will now reload the card database. MessagesSettingsPage - + Word1 Word2 Word3 - + Add New Message - + Edit Message - + Remove Message - + Add message 메세지 추가 - - + + Message: 메세지: - + Edit message - + Chat settings 대화 설정 - + Custom alert words 키워드 알림 - + Enable chat mentions 대화 중 본인의 사용자명 멘션 시 해당 문장을 강조 - + Enable mention completer 멘션 자동완성 기능 - + In-game message macros 게임 내 대화 매크로 - + How to use in-game message macros - + Ignore chat room messages sent by unregistered users 서버에 가입하지 않은 사용자의 채널 대화 차단 - + Ignore private messages sent by unregistered users 서버에 가입하지 않은 사용자가 보낸 1:1 대화 차단 - - + + Invert text color 문장 색 반전 - + Enable desktop notifications for private messages 1:1 대화를 받을 시 데스크탑 알림 설정 - + Enable desktop notification for mentions 자신의 사용자명 멘션 시 데스크탑 알림 설정 - + Enable room message history on join 채널 입장 시 이전 대화 기록 표시 - - + + (Color is hexadecimal) (16진수 색상 코드) - + Separate words with a space, alphanumeric characters only 각 단어마다 스페이스바로 띄어써주세요. 문자 및 숫자만 가능합니다. @@ -3907,6 +4718,24 @@ Cockatrice will now reload the card database. + + OverlapControlWidget + + + Cards to overlap: + + + + + Overlap percentage: + + + + + Overlap direction: + + + Phase @@ -3973,57 +4802,57 @@ Cockatrice will now reload the card database. PhasesToolbar - + Untap step 언탭단 - + Upkeep step 유지단 - + Draw step 뽑기단 - + First main phase 첫번째 본단계 - + Beginning of combat step 전투 시작단 - + Declare attackers step 공격자 선언단 - + Declare blockers step 방어자 선언단 - + Combat damage step 전투 피해단 - + End of combat step 전투 종료단 - + Second main phase 두번째 본단계 - + End of turn step 종료단 @@ -4031,7 +4860,7 @@ Cockatrice will now reload the card database. PictureLoader - + en code for scryfall's language property, not available for all languages ko @@ -4040,647 +4869,781 @@ Cockatrice will now reload the card database. Player - + Reveal top cards of library 서고 맨 위 카드 공개 - - - - - - - - - + + + + + + + + + + + + Number of cards: (max. %1) 카드 장수 (최대 %1장): - + &View graveyard 무덤 보기 - + &View exile 추방 영역 보기 - + Player "%1" 플레이어 "%1" - - - - + + + + &Graveyard 무덤 - - - - + + + + &Exile 추방 영역 - + &Move hand to... 손의 카드 전부 이동: - - - + + + &Top of library 서고 맨 위 - - - + + + &Bottom of library 서고 맨 아래 - + &Move graveyard to... 무덤의 카드 전부 이동: - - - - + + + + &Hand - + &Move exile to... 추방 영역의 카드 전부 이동: - + &View library 서고 보기 - + &View hand - + View &top cards of library... 서고 맨 위 X장 보기 - + Reveal &library to... 서고 공개: - + &Always reveal top card 서고 맨 위 카드 항상 공개 - + &View sideboard 사이드보드 보기 - + &Draw card 카드 뽑기 - + D&raw cards... 카드 X장 뽑기 - + &Undo last draw 마지막으로 뽑은 카드 서고에 돌려놓기 - + Take &mulligan 멀리건 - - &Shuffle - 서고 섞기 - - - + Play top card &face down 서고 맨 위 카드를 뒷면으로 발동 - + Move top card to grave&yard - + Move top card to e&xile - + Move top cards to &graveyard... 맨 위 X장의 카드를 무덤으로 이동 - + Move top cards to &exile... 서고 맨 위 X장의 카드를 추방 - + Put top card on &bottom 서고 맨 위 카드를 맨 아래로 내리기 - + + View bottom cards of library... + + + + Lend library to... - + + Shuffle + + + + Put top cards on stack &until... Take top cards &until... - + + Shuffle top cards... + + + + + Shuffle bottom cards... + + + + &Reveal hand to... 손의 카드 전부 공개: - + Reveal r&andom card to... 손에서 무작위 카드 공개: - + Reveal random card to... - + &Sideboard 사이드보드 - + &Library 서고 - + &Counters 카운터 - + &Untap all permanents 모든 지속물 언탭 - + R&oll die... 주사위 굴리기 - + &Create token... 토큰 생성 - + C&reate another token 같은 종류의 토큰 추가 생성 - + Cr&eate predefined token 미리 지정된 토큰 생성 - - + + &All players 모든 플레이어 - + S&ay 말하기 - + + &Select All + + + + + S&elect Row + + + + + S&elect Column + + + + &Play 발동 - + &Hide 숨기기 - + Play &Face Down 뒷면으로 발동 - + &Tap / Untap Turn sideways or back again - + Toggle &normal untapping 언탭단에서의 언탭 여부 변경 - + T&urn Over Turn face up/face down - + &Peek at card face 카드 앞면 보기 - + &Clone 카드 복제 - + Attac&h to card... 다른 카드에 부착 - + Unattac&h 카드에서 떼어냄 - + &Draw arrow... 화살표 그리기 - + &Increase power 공격력 증가 - + &Decrease power 공격력 감소 - + I&ncrease toughness 방어력 증가 - + D&ecrease toughness 방어력 감소 - + In&crease power and toughness 공격력 및 방어력 증가 - + Dec&rease power and toughness 공격력 및 방어력 감소 - + Increase power and decrease toughness - + Decrease power and increase toughness - + Set &power and toughness... 공격력 및 방어력 설정 - + Reset p&ower and toughness - + &Set annotation... 카드 주석 설정 - + Red 적색 - + Yellow 황색 - + Green 녹색 - + &Add counter (%1) &카운터 추가 (%1) - + &Remove counter (%1) &카운터 제거 (%1) - + &Set counters (%1)... &카운터 설정 (%1)... - + &Top of library in random order - + X cards from the top of library... - + &Bottom of library in random order - + View top cards of library 서고 맨 위 X장 보기 - + + View bottom cards of library + + + + + Shuffle top cards of library + + + + + Shuffle bottom cards of library + + + + Which position should this card be placed: - + (max. %1) - + Draw hand - + 0 and lower are in comparison to current hand size - + Draw cards 카드 X장 뽑기 - - + + Number: 원하는 숫자: - + Move top cards to grave 맨 위 카드 X장을 무덤으로 이동 - + Reveal &top cards to... - + &Top of library... - + &Bottom of library... - + &Always look at top card - + &Open deck in deck editor - + &Play top card - + &Draw bottom card - + D&raw bottom cards... - + &Play bottom card - + Play bottom card &face down - + Move bottom card to grave&yard - + Move bottom card to e&xile - + Move bottom cards to &graveyard... - + Move bottom cards to &exile... - + Put bottom card on &top - + Selec&ted cards - + Move top cards to exile 맨 위 카드 X장을 추방 - + Move bottom cards to grave - + Move bottom cards to exile - + Draw bottom cards - - + + C&reate another %1 token - + Create tokens 토큰 생성 - + Place card X cards from top of library - + Change power/toughness - + Change stats to: - + Set annotation 주석 설정 - + Please enter the new annotation: 새로운 주석을 입력해 주세요: - + Set counters 카운터 설정 - + Re&veal to... - + View related cards - + Token: 토큰: - + All tokens 모든 토큰 + + PrintingSelector + + + Display Navigation Buttons + + + + + <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. + + + + + PrintingSelectorCardOverlayWidget + + + Preference + + + + + Pin Printing + + + + + Unpin Printing + + + + + Show Related cards + + + + + PrintingSelectorCardSearchWidget + + + Search by set name or set code + + + + + PrintingSelectorCardSelectionWidget + + + Previous Card in Deck + + + + + Next Card in Deck + + + + + PrintingSelectorCardSortingWidget + + + Alphabetical + + + + + Preference + + + + + Release Date + + + + + + Descending + + + + + Ascending + + + QMenuBar - + Services 서비스 - + Hide %1 %1 가리기 - + Hide Others 기타 가리기 - + Show All 모두 보기 - + Preferences... 환경설정... - + Quit %1 %1 종료 - + About %1 %1에 관하여 @@ -4688,38 +5651,32 @@ Cockatrice will now reload the card database. QObject - + Cockatrice card database (*.xml) 코카트리스 카드 데이터베이스 파일 (*.xml) - - + All files (*.*) 모든 파일 (*.*) - + Cockatrice replays (*.cor) 코카트리스 리플레이 파일 (*.cor) - - Common deck formats (*.cod *.dec *.dek *.txt *.mwDeck) - - - - + Maindeck - + Sideboard - + Tokens @@ -4727,92 +5684,92 @@ Cockatrice will now reload the card database. QPlatformTheme - + OK - + Save - + Save All - + Open - + &Yes - + Yes to &All - + &No - + N&o to All - + Abort - + Retry - + Ignore - + Close - + Cancel - + Discard - + Help - + Apply - + Reset - + Restore Defaults @@ -4820,17 +5777,17 @@ Cockatrice will now reload the card database. RemoteDeckList_TreeModel - + Name 덱 이름 - + ID ID - + Upload time 업로드 시간 @@ -4838,32 +5795,32 @@ Cockatrice will now reload the card database. RemoteReplayList_TreeModel - + ID ID - + Name 게임 이름 - + Players 플레이어 - + Keep 보존 - + Time started 게임 시작 시간 - + Duration (sec) 게임 길이 @@ -4909,32 +5866,32 @@ Cockatrice will now reload the card database. SequenceEdit - + Choose an action from the table - + Hit the key/combination of keys you want to set for this action - - Shortcut already in use - - - - + Invalid key - + + Shortcut already in use by: + + + + Clear - + Restore default @@ -4970,72 +5927,80 @@ Cockatrice will now reload the card database. ShortcutSettingsPage - - + + Restore all default shortcuts - + Do you really want to restore all default shortcuts? - + Clear all default shortcuts - + Do you really want to clear all shortcuts? - - Action - - - - - Shortcut - - - - + Section: - + Action: - + Shortcut: - + How to set custom shortcuts - + Clear all shortcuts + + + Search by shortcut name + + + + + ShortcutTreeView + + + Action + + + + + Shortcut + + ShortcutsSettings - + Your configuration file contained invalid shortcuts. Please check your shortcut settings! - + The following shortcuts have been set to default: @@ -5044,17 +6009,17 @@ Please check your shortcut settings! ShutdownDialog - + &Reason for shutdown: 서버 종료 사유: - + &Time until shutdown (minutes): 서버 종료까지 남은 시간(분): - + Shut down server 서버 종료 @@ -5062,27 +6027,27 @@ Please check your shortcut settings! SoundSettingsPage - + Enable &sounds 음향 효과 켜기 - + Current sounds theme: 현재 소리 테마: - + Test system sound engine 시스템 사운드 엔진 테스트 - + Sound settings 음향 설정 - + Master volume 주 음량 @@ -5090,48 +6055,48 @@ Please check your shortcut settings! SpoilerBackgroundUpdater - + Spoilers season has ended - + Deleting spoiler.xml. Please run Oracle - - + + Spoilers download failed - + No internet connection - + Error - + Spoilers already up to date - + No new spoilers added - + Spoilers have been updated! - + Last change: @@ -5139,638 +6104,594 @@ Please check your shortcut settings! StableReleaseChannel - - Stable Releases + + Default - + No reply received from the release update server. - + Invalid reply received from the release update server. - + No reply received from the tag update server. - + Invalid reply received from the tag update server. + + TabAccount + + + Add to Buddy List + + + + + Add to Ignore List + + + + + Account + + + TabAdmin - + Administration 관리자 메뉴 - + Update server &message 서버 공지사항 작성 - + &Shut down server 서버 종료 - + &Reload configuration 설정 새로고침 - + Server administration functions 서버 관리 기능 - + + Server moderator functions + + + + + Replay ID + + + + + Grant Replay Access + + + + + Username to Activate + + + + + Force Activate User + + + + &Unlock functions 기능 잠금 해제 - + &Lock functions 기능 잠금 + + + + Success + + + + + Replay access granted + + + + + + + + + Error + + + + + Unable to grant replay access. Replay ID invalid + + + + + Unable to grant replay access. Internal error + + + + + User successfully activated + + + + + Unable to activate user. Username invalid + + + + + Unable to activate user. User already active + + + + + Unable to activate user. Internal error + + TabDeckEditor - - Search by card name (or search expressions) - Search by card name - - - - - Add to Deck - - - - - Add to Sideboard - - - - - Show Related cards - - - - - &Clear all filters - 모든 필터 초기화 - - - - Delete selected - 선택한 필터 삭제 - - - - Deck &name: - 덱 이름: - - - - &Comments: - 설명: - - - - Hash: - 해시값: - - - - &New deck - 새로운 덱 작성 - - - - &Load deck... - 덱 불러오기 - - - - &Save deck - 덱 저장 - - - - Save deck &as... - 다른 이름으로 저장 - - - - Load deck from cl&ipboard... - 클립보드에서 덱 가져오기 - - - - Save deck to clipboard - - - - - Annotated - - - - - Not Annotated - - - - - &Print deck... - 덱 인쇄 - - - - &Send deck to online service - - - - - Create decklist (decklist.org) - - - - - Analyze deck (deckstats.net) - - - - - Analyze deck (tappedout.net) - - - - - &Close - 덱 닫기 - - - - Add card to &maindeck - 메인덱에 카드 추가 - - - - Add card to &sideboard - 사이드보드에 카드 추가 - - - - &Remove row - 선택한 카드 전부 제거 - - - - &Increment number - 선택한 카드 한장 추가 - - - - &Decrement number - 선택한 카드 한장 제거 - - - - &Deck Editor - 덱 편집기 - - - - + Card Info 카드 정보 - - + Deck - - + Filters 카드 필터 - + &View &보기 - - - + + Printing + + + + + + + Visible 표시 - - - + + + + Floating 띄우기 - + Reset layout 화면 레이아웃 초기화 - + Deck: %1 덱 편집기: %1 - - - Are you sure? - 확실하십니까? - - - - The decklist has been modified. -Do you want to save the changes? - 덱 리스트가 변경되었습니다. -변경사항을 저장하시겠습니까? - - - - Load deck - 덱 불러오기 - - - - - - - - - Error - 오류 - - - - The deck could not be saved. - 덱을 저장하는데 실패하였습니다. - - - - Could not save remote deck - - - - - - The deck could not be saved. -Please check that the directory is writable and try again. - 덱을 저장하는데 실패하였습니다. -덱 파일 디렉토리에 쓰기가 가능한지 확인하시고 다시 시도해 주세요. - - - - Save deck - 덱 저장 - - - - There are no cards in your deck to be exported - - - - - No deck was selected to be saved. - - TabDeckStorage - - Deck storage - 덱 보관함 - - - + Local file system 로컬 파일 시스템 - + Server deck storage 서버 덱 보관함 - - + + Open in deck editor 덱 편집기로 열기 - + + Rename deck or folder + + + + Upload deck 덱 업로드 - + Download deck 덱 다운로드 - - + + + + New folder 새 폴더 - - + + Delete 삭제 - - + + Open decks folder + + + + + Rename local folder + + + + + Rename local file + + + + + New name: + + + + + + + Error - + + Rename failed + + + + + Invalid deck file - + Enter deck name 덱 이름 작성 - + This decklist does not have a name. Please enter a name: 이 덱리스트는 이름이 없습니다. 이름을 입력해 주세요: - + Unnamed deck 이름 없는 덱 - + Failed to upload deck to server - + Delete local file 로컬 파일 삭제 - - - - Are you sure you want to delete "%1"? - 정말로 "%1"을(를) 삭제하시겠습니까? + + Are you sure you want to delete the selected files? + - + + Delete remote decks + + + + + Are you sure you want to delete the selected decks? + + + + + Name of new folder: 새 폴더의 이름: - - Delete remote folder - 서버 보관함의 폴더 삭제 + + Deck Storage + + + + + TabDeckStorageVisual + + + Visual Deck Storage + - - Delete remote deck - 서버에 저장된 덱 삭제 + + Error + + + + + Could not open deck at %1 + + + + + TabEdhRec + + + EDHREC: + TabGame - - - + + + Replay 리플레이 - - + + Game 게임 - - + + Player List 플레이어 목록 - - + + Card Info 카드 정보 - - + + Messages 메세지 - - + + Replay Timeline 리플레이 타임라인 - + &Phases 단계 - + &Game 게임 - + Next &phase 다음 단계로 진행 - + Next phase with &action - + Next &turn 턴 넘기기 - + Reverse turn order - + &Remove all local arrows 내가 그린 화살표 제거 - + Rotate View Cl&ockwise 플레이어 위치 시계방향으로 조정 - + Rotate View Co&unterclockwise 플레이어 위치 반시계방향으로 조정 - + Game &information 게임 정보 - + + Un&concede + + + + &Concede 항복 - + &Leave game 게임 나가기 - + C&lose replay 리플레이 닫기 - + &Focus Chat - + &Say: 말하기: - + &View &보기 - - - - + + + + Visible 표시 - - - - + + + + Floating 띄우기 - + Reset layout 레이아웃 초기화 - + Concede 항복 - + Are you sure you want to concede this game? 정말 게임에서 항복하시겠습니까? - + Unconcede - + You have already conceded. Do you want to return to this game? - + Leave game 게임 나가기 - + Are you sure you want to leave this game? 정말 게임에서 나가시겠습니까? - + You are flooding the game. Please wait a couple of seconds. 너무 빨리 말하고 있습니다. 조금만 기다려 주십시오. - + A player has joined game #%1 - + %1 has joined the game - + kicked by game host or moderator 게임 호스트 또는 조정자에 의해 퇴장당했습니다 - + player left the game 플레이어가 게임을 나갔습니다 - + player disconnected from server 플레이어가 서버와의 연결을 끊었습니다. - + reason unknown - + You have been kicked out of the game. 게임에서 강제 퇴장 당하였습니다. @@ -5783,153 +6704,153 @@ Please enter a name: 기록 - - - + + + Time;SenderName;SenderIP;Message;TargetID;TargetName - + Room Logs - + Game Logs 게임 기록 - + Chat Logs 대화 기록 - - + + Error 오류 - + You must select at least one filter. 최소한 한 개 이상의 필터를 선택해주세요. - + You have to select a valid number of days to locate. - + Username: 사용자명: - + IP Address: IP 주소: - + Game Name: 게임 이름: - + GameID: 게임 ID: - + Message: 메세지: - + Main Room 대화 채널 - + Game Room 게임 방 - + Private Chat 1:1 대화 - + Past X Days: ~일 전: - + Today 오늘 - + Last Hour 1시간 이내 - + Maximum Results: - + At least one filter is required. The more information you put in, the more specific your results will be. 최소한 한개 이상의 필터를 지정해주세요. 자세히 지정하시면 좀 더 세분화된 결과를 보실 수 있습니다. - + Get User Logs - + Clear Filters - + Filters 필터 - + Log Locations - + Date Range - + Maximum Results - - + + Message History - + Failed to collect message history information. - + There are no messages for the selected filters. @@ -5952,22 +6873,22 @@ The more information you put in, the more specific your results will be.%1 - 1:1 대화 - + This user is ignoring you, they cannot see your messages in main chat and you cannot join their games. - + Private message from - + %1 has left the server. %1이(가) 서버와의 연결을 끊었습니다. - + %1 has joined the server. %1이(가) 서버에 연결하였습니다. @@ -5975,117 +6896,153 @@ The more information you put in, the more specific your results will be. TabReplays - - Game replays - 게임 리플레이 - - - + Local file system 로컬 파일 시스템 - + Server replay storage 서버 리플레이 보관함 - - + + Watch replay 리플레이 보기 - - + + Rename + + + + + + New folder + + + + + Delete 삭제 - + + Open replays folder + + + + Download replay 리플레이 다운로드 - + Toggle expiration lock 자동 삭제 여부 토글 - + + Rename local folder + + + + + Rename local file + + + + + New name: + + + + + Error + + + + + Rename failed + + + + + Name of new folder: + + + + Delete local file 로컬 파일 삭제 - - Are you sure you want to delete "%1"? - 정말로 "%1"을(를) 삭제하시겠습니까? - - - - Downloading Replays + + Are you sure you want to delete the selected files? - - Folder download is not yet supported. Please download replays individually. + + Are you sure you want to delete the selected replays? - + Delete remote replay 서버에 저장된 리플레이 삭제 - - Are you sure you want to delete the replay of game %1? - 정말 게임 %1의 리플레이를 삭제하시겠습니까? + + Game Replays + TabRoom - + &Say: 말하기: - + Chat 대화 - + &Room 채널 - + &Leave room 채널에서 나가기 - + &Clear chat 대화 내용 삭제 - + Chat Settings... 대화 설정... - + mentioned you. 이(가) 당신에게 멘션을 보냈습니다. - + Click to view 여기를 눌러 확인 - + You are flooding the chat. Please wait a couple of seconds. 너무 빨리 말하고 있습니다. 조금만 기다려 주세요. @@ -6098,30 +7055,30 @@ The more information you put in, the more specific your results will be.서버 - - - - + + + + Error 오류 - + Failed to join the server room: it doesn't exist on the server. - + The server thinks you are in the server room but your client is unable to display it. Try restarting your client. - + You do not have the required permission to join this server room. - + Failed to join the server room due to an unknown error: %1. @@ -6129,33 +7086,73 @@ The more information you put in, the more specific your results will be. TabSupervisor - + + Deck Editor + + + + + &Visual Deck Storage + + + + + Server + + + + + Account + + + + + Deck Storage + + + + + Game Replays + + + + + Administration + + + + + Logs + + + + Are you sure? 확실하십니까? - + There are still open games. Are you sure you want to quit? 참가 중인 게임이 있습니다. 정말로 코카트리스를 종료하시겠습니까? - + Click to view - + Your buddy %1 has signed on! - + Unknown Event - + The server has sent you a message that your client does not understand. This message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -6163,32 +7160,32 @@ To update your client, go to Help -> Check for Updates. - + Idle Timeout - + You are about to be logged out due to inactivity. - + Promotion 승급 - + You have been promoted. Please log out and back in for changes to take effect. - + Warned 경고 - + You have received a warning due to %1. Please refrain from engaging in this activity or further actions may be taken against you. If you have any questions, please private message a moderator. 운영진으로부터 %1의 사유로 경고를 받았습니다. @@ -6196,30 +7193,12 @@ Please refrain from engaging in this activity or further actions may be taken ag 문의사항은 관리자에게 1:1대화로 물어보시기 바랍니다. - + You have received the following message from the server. (custom messages like these could be untranslated) - - TabUserLists - - - Account - 사용자 정보 - - - - Add to Buddy List - 친구 목록에 추가 - - - - Add to Ignore List - 차단 목록에 추가 - - TappedOutInterface @@ -6303,173 +7282,184 @@ Please refrain from engaging in this activity or further actions may be taken ag UserContextMenu - + User &details 사용자 정보 - + Private &chat 1:1 대화 - + Show this user's &games 이 사용자의 게임 보기 - + Add to &buddy list 친구 목록에 추가 - + Remove from &buddy list 친구 목록에서 삭제 - + Add to &ignore list 차단 목록에 추가 - + Remove from &ignore list 차단 목록에서 제거 - + Kick from &game 게임에서 강제 퇴장 - + Warn user - + View user's war&n history - + Ban from &server 서버에서 추방 - + View user's &ban history - + &Promote user to moderator 사용자를 관리자로 승급 - + Dem&ote user from moderator 사용자를 관리자로부터 강등 - + Promote user to &judge - + Demote user from judge - + + View admin notes + + + + %1's games %1의 게임 - - - + + + Ban History - + Ban Time;Moderator;Ban Length;Ban Reason;Visible Reason - + User has never been banned. - + Failed to collect ban information. - - - + + + Warning History - + Warning Time;Moderator;User Name;Reason - + User has never been warned. 경고를 받은 적이 없는 사용자입니다. - + Failed to collect warning information. - - + + Failed to get admin notes. + + + + + Success 성공 - + Successfully promoted user. 해당 사용자 승급에 성공하였습니다. - + Successfully demoted user. 해당 사용자 강등에 성공하였습니다. - - + + + Failed 실패 - + Failed to promote user. 해당 사용자 승급에 실패하였습니다. - + Failed to demote user. 해당 사용자 강등에 실패하였습니다. - + Copy hash to clipboard - + Remove this user's messages @@ -6477,173 +7467,173 @@ Please refrain from engaging in this activity or further actions may be taken ag UserInfoBox - + Location: 거주지: - + Account Age: 계정 나이: - + Edit 프로필 수정 - + Change password 비밀번호 변경 - + Change avatar 아바타 변경 - + Administrator 운영자 - + Moderator 관리자 - + Registered user 서버에 가입한 사용자 - - + + Unregistered user 서버에 가입하지 않은 사용자 - + Judge - + Unknown 미상 - + The entered password does not match your account. - - - + + + Information 사용자 정보 - + User information updated. 사용자 정보가 갱신되었습니다. - - - - - - - - - - + + + + + + + + + + Error 오류 - + User Information - + Real Name: - + User Level: - + %n Year(s), amount of years (only shown if more than 0) - + %10%n Day(s) %20 amount of years (if more than 0), amount of days, date in local short format - + Enter Password - + Password verification is required in order to change your email address - - - + + + An error occurred while trying to update your user information. - + This server does not permit you to update your user informations. 본 서버는 당신의 사용자 정보를 수정 할 수 없게 설정되어 있습니다. - + Password changed. 비밀번호가 변경되었습니다. - + This server does not permit you to change your password. 본 서버는 당신의 비밀번호를 수정 할 수 없게 설정되어 있습니다. - + The new password is too short. 새로운 비밀번호가 너무 짧습니다. - + The old password is incorrect. 예전 비밀번호가 잘못되었습니다. - + Avatar updated. 아바타를 변경하였습니다. - + This server does not permit you to update your avatar. 본 서버는 당신의 아바타를 변경 할 수 없게 설정되어 있습니다. - + An error occured while trying to updater your avatar. 아바타 변경 중 오류가 발생하였습니다. @@ -6651,124 +7641,251 @@ Please refrain from engaging in this activity or further actions may be taken ag UserInterfaceSettingsPage - + General interface settings 일반 인터페이스 설정 - + &Double-click cards to play them (instead of single-click) 카드를 더블 클릭해서 발동 (해제시 한번만 클릭하면 발동 됨) - + + &Clicking plays all selected cards (instead of just the clicked card) + + + + &Play all nonlands onto the stack (not the battlefield) by default 모든 대지가 아닌 카드를 발동 시에 스택으로 이동 (해제시 전장으로 바로 이동) - + + Close card view window when last card is removed + + + + Annotate card text on tokens 토큰에 카드 텍스트 주석 자동 추가 - + Use tear-off menus, allowing right click menus to persist on screen - + Notifications settings - + Enable notifications in taskbar 상태 표시줄 알림 설정 - + Notify in the taskbar for game events while you are spectating 관전중인 게임의 상태 표시줄 알림 설정 - + Notify in the taskbar when users in your buddy list connect - + Animation settings 애니메이션 설정 - + &Tap/untap animation 탭/언탭 애니메이션 + + + Deck editor/storage settings + + + + + Open deck in new tab by default + + + + + Prompt before converting .txt decks to .cod format + + + + + Always convert if not prompted + + + + + Use visual deck storage in game lobby + + + + + Replay settings + + + + + Buffer time for backwards skip via shortcut: + + - UserList + UserListWidget - + Users connected to server: %1 - 서버에 접속한 사용자 수: %1 + - + Users in this room: %1 - 현재 채널에 있는 사용자 목록 : %1 + - + Buddies online: %1 / %2 - 온라인 친구 목록: %1 / %2 + - + Ignored users online: %1 / %2 - 차단한 사용자 목록: %1 / %2 + + + + + VisualDeckStorageFolderDisplayWidget + + + Deck Storage + + + + + VisualDeckStorageSearchWidget + + + Search by filename + + + + + VisualDeckStorageSortWidget + + + Sort Alphabetically (Deck Name) + + + + + Sort Alphabetically (Filename) + + + + + Sort by Last Modified + + + + + Sort by Last Loaded + + + + + VisualDeckStorageWidget + + + Loading database ... + + + + + Show Folders + + + + + Show Tag Filter + + + + + Show Tags On Deck Previews + + + + + Show Banner Card Selection Option + + + + + Include Folder Names in Search + + + + + Draw unused Color Identities + + + + + Unused Color Identities Opacity + WarningDialog - + Which warning would you like to send? 어떤 종류의 경고를 보내시겠습니까? - + Redact all messages from this user in all rooms - + &OK 확인 - + &Cancel 취소 - + Warn user for misconduct 사용자의 부적절한 행동에 경고 - - + + Error 오류 - + User name to send a warning to can not be blank, please specify a user to warn. - + Warning to use can not be blank, please select a valid warning to send. @@ -6776,128 +7893,133 @@ Please refrain from engaging in this activity or further actions may be taken ag WndSets - + Move selected set to the top 선택한 확장판을 맨 위로 올리기 - + Move selected set up 선택한 확장판을 한 단계 올리기 - + Move selected set down 선택한 확장판을 한 단계 내리기 - + Move selected set to the bottom 선택한 확장판을 맨 아래로 내리기 - + Search by set name, code, or type - + Default order - + Restore original art priority order - + Enable all sets 모든 확장판 활성화 - + Disable all sets 모든 확장판 비활성화 - + Enable selected set(s) 선택한 세트 적용 - + Disable selected set(s) 선택한 세트 미적용 - + Deck Editor 덱 편집기 - + Use CTRL+A to select all sets in the view. - + Only cards in enabled sets will appear in the card list of the deck editor. - + Image priority is decided in the following order: - + first the CUSTOM Folder (%1), then the Enabled Sets in this dialog (Top to Bottom) %1 is a link to the wiki - + Card Art 카드 이미지 - + How to use custom card art - + Hints - + Note - + Sorting by column allows you to find a set while not changing set priority. - + To enable ordering again, click the column header until this message disappears. - + Use the current sorting as the set priority instead - + Sorts the set priority using the same column - + + Include online-only (Arena) cards [requires restart] + + + + Manage sets @@ -6905,22 +8027,67 @@ Please refrain from engaging in this activity or further actions may be taken ag ZoneViewWidget - - sort by name - 카드 이름순 정렬 + + Ungrouped + - - sort by type - 카드 유형별 정렬 + + Group by Type + - + + Group by Mana Value + + + + + Group by Color + + + + + Unsorted + + + + + Sort by Name + + + + + Sort by Type + + + + + Sort by Mana Cost + + + + + Sort by Colors + + + + + Sort by P/T + + + + + Sort by Set + + + + shuffle when closing 닫은 후 덱 섞기 - + pile view 카드 유형별 행 정렬 @@ -6928,7 +8095,7 @@ Please refrain from engaging in this activity or further actions may be taken ag i18n - + English 한국어 (Korean) @@ -6936,12 +8103,12 @@ Please refrain from engaging in this activity or further actions may be taken ag main - + Connect on startup - + Debug to file @@ -6949,786 +8116,908 @@ Please refrain from engaging in this activity or further actions may be taken ag shortcutsTab - + Main Window 메인 화면 - - + + Deck Editor 덱 편집기 - + Game Lobby 게임 로비 - + Card Counters - + Player Counters - + Power and Toughness 공격력 및 방어력 - + Game Phases 턴 단계 - + Playing Area 전장 - + Move Selected Card - + View 보기 - + Move Top Card - + Move Bottom Card - + Gameplay 게임플레이 - + Drawing - + Chat Room - + Game Window - + Load Deck from Clipboard - + + Replays - + + Tabs + + + + Check for Card Updates... - + Connect... - + Disconnect 서버와의 연결 해제 - + Exit 끝내기 - + Full screen 전체 화면 - + Register... - + Settings... - + Start a Local Game... - + Watch Replay... - - Analyze Deck + + Analyze Deck (deckstats.net) + Analyze Deck - + + Analyze Deck (tappedout.net) + + + + Clear All Filters - + Clear Selected Filter - + Close 덱 닫기 - + Remove Card - + Manage Sets... - + Edit Custom Tokens... - - Export Deck - - - - - Add Card - - - - - Load Deck... - - - - - Load Deck from Clipboard... + + Export Deck (decklist.org) - New Deck + Export Deck (decklist.xyz) - - Open Custom Pictures Folder + + Add Card - - Print Deck... + + Load Deck... + Load Deck from Clipboard... + + + + + Edit Deck in Clipboard, Annotated + + + + + Edit Deck in Clipboard + + + + + New Deck + + + + + Open Custom Pictures Folder + + + + + Print Deck... + + + + Delete Card - - + + Reset Layout - + Save Deck - + Save Deck as... - + Save Deck to Clipboard, Annotated - - Save Deck to Clipboard - - - - - Load Local Deck... - - - - - Load Remote Deck... - - - - - Set Ready to Start - - - - - Toggle Sideboard Lock - - - - - - Add Green Counter - - - - - - Remove Green Counter - - - - - - Set Green Counters... + + Save Deck to Clipboard, Annotated (No Set Info) + Save Deck to Clipboard + + + + + Save Deck to Clipboard (No Set Info) + + + + + Load Local Deck... + + + + + Load Remote Deck... + + + + + Set Ready to Start + + + + + Toggle Sideboard Lock + + + + + + Add Green Counter + + + + + + Remove Green Counter + + + + + + Set Green Counters... + + + + Add Yellow Counter - + Remove Yellow Counter - + Set Yellow Counters... - - + + Add Red Counter - - + + Remove Red Counter - - + + Set Red Counters... - + Add Life Counter - + Remove Life Counter - + Set Life Counters... - + Add White Counter - + Remove White Counter - + Set White Counters... - + Add Blue Counter - + Remove Blue Counter - + Set Blue Counters... - + Add Black Counter - + Remove Black Counter - + Set Black Counters... - + Add Colorless Counter - + Remove Colorless Counter - + Set Colorless Counters... - + Add Other Counter - + Remove Other Counter - + Set Other Counters... - + Add Power (+1/+0) - + Remove Power (-1/-0) - + Move Toughness to Power (+1/-1) - + Add Toughness (+0/+1) - + Remove Toughness (-0/-1) - + Move Power to Toughness (-1/+1) - + Add Power and Toughness (+1/+1) - + Remove Power and Toughness (-1/-1) - + Set Power and Toughness... - + Reset Power and Toughness - + Untap 언탭단 - + Upkeep 유지단 - + Draw 뽑기단 - + First Main Phase - + Start Combat - + Attack 공격자 선언단 - + Block 방어자 선언단 - + Damage 전투 피해단 - + End Combat - + Second Main Phase - + End 종료단 - + Next Phase - + Next Phase Action - + Next Turn - + + Hide Card in Reveal Window + + + + Tap / Untap Card - + Untap All - + Toggle Untap - + Turn Card Over - + Peek Card - + Play Card - + Attach Card... - + Unattach Card - + Clone Card - + Create Token... - + Create All Related Tokens - + Create Another Token - + Set Annotation... - - + + Select All Cards in Zone + + + + + Select All Cards in Row + + + + + Select All Cards in Column + + + + + Bottom of Library - - - - + + + + Exile 추방 영역 - - - - + + + + Graveyard 무덤 - - + + Hand - - + + Top of Library - - - + + + Battlefield, Face Down - + Battlefield - + Library 서고 - + Sideboard 사이드보드 - + Top Cards of Library - + + Bottom Cards of Library + + + + Close Recent View - - + + Stack - - + + Graveyard (Multiple) - - + + Exile (Multiple) - + Stack Until Found - + Draw Bottom Card - + Draw Multiple Cards from Bottom... - + Draw Arrow... - + Remove Local Arrows - + Leave Game - + Concede 항복 - + Roll Dice... - + Shuffle Library - + + Shuffle Top Cards of Library + + + + + Shuffle Bottom Cards of Library + + + + Mulligan 멀리건 - + Draw a Card - + Draw Multiple Cards... - + Undo Draw - + Always Reveal Top Card - + Always Look At Top Card - + Rotate View Clockwise - + Rotate View Counterclockwise - + Unfocus Text Box - + Focus Chat - + Clear Chat - + Refresh - + + Skip Forward + + + + + Skip Backward + + + + + Skip Forward by a lot + + + + + Skip Backward by a lot + + + + Play/Pause - + Toggle Fast Forward + + + Visual Deck Storage + + + + + Deck Storage + + + + + Server + + + + + Account + + + + + Administration + + + + + Logs + + \ No newline at end of file diff --git a/cockatrice/translations/cockatrice_nl.ts b/cockatrice/translations/cockatrice_nl.ts index ada657826..6a98be428 100644 --- a/cockatrice/translations/cockatrice_nl.ts +++ b/cockatrice/translations/cockatrice_nl.ts @@ -2,7 +2,7 @@ AbstractCounter - + &Set counter... &Teller instellen... @@ -10,95 +10,247 @@ AbstractCounterDialog - + Set counter Teller instellen - + New value for counter '%1': Nieuwe waarde voor teller '%1': + + AbstractDlgDeckTextEdit + + + &Refresh + + + + + Parse Set Name and Number (if available) + + + + + AbstractTabDeckEditor + + + Open in new tab + + + + + Are you sure? + + + + + The decklist has been modified. +Do you want to save the changes? + + + + + + + + + + + Error + + + + + Could not open deck at %1 + + + + + Could not save remote deck + + + + + + The deck could not be saved. +Please check that the directory is writable and try again. + + + + + Save deck + + + + + The deck could not be saved. + + + + + There are no cards in your deck to be exported + + + + + No deck was selected to be exported. + + + + + AdminNotesDialog + + + Update Notes + Update Notities + + + + Admin Notes for %1 + Admin Notities voor %1 + + + + AllZonesCardAmountWidget + + + Mainboard + Mainboard + + + + Sideboard + Sideboard + + AppearanceSettingsPage - + Error Foutmelding - + Could not create themes directory at '%1'. Kon de themes map niet aanmaken op '%1'. - + Theme settings Thema opties - + Current theme: Huidige thema: - + Open themes folder - Open thema folder + Open themafolder - + + Menu settings + Menu instellingen + + + + Show keyboard shortcuts in right-click menus + Laat snelkoppelingen in rechtermuisknop menus zien + + + Card rendering Kaartweergave - + Display card names on cards having a picture - Kaartnamen altijd weergeven + Kaartnamen altijd weergeven op kaarten met een afbeelding - + + Auto-Rotate cards with sideways layout + Draai kaarten samen met zijwaardse layout + + + + Override all card art with personal set preference (Pre-ProviderID change behavior) [Requires Client restart] + Overschrijf alle kaart beelden met eigen set voorkeuren (Gedrag van voor implementatie van ProviderID) [Vereist herstart applicatie] + + + + Bump sets that the deck contains cards from to the top in the printing selector + + + + Scale cards on mouse over Kaarten schalen met de muis - + + Use rounded card corners + + + + Minimum overlap percentage of cards on the stack and in vertical hand Minimum overlap percentage van kaarten op de stack en in verticale handweergave - + + Maximum initial height for card view window: + Grootste begingrootte voor kaartbekijkvenster + + + + + rows + rijen + + + + Maximum expanded height for card view window: + + + + Hand layout Handweergave - + Display hand horizontally (wastes space) Hand horizontaal weergeven (gebruikt extra ruimte) - + Enable left justification Linker justificatie inschakelen - + Table grid layout Tafelindeling - + Invert vertical coordinate Verticale coördinaat omkeren - + Minimum player count for multi-column layout: Minimaal aantal spelers voor kolommenindeling: - + Maximum font size for information displayed on cards: Maximale lettergrootte voor informatie die op kaarten wordt weergegeven: @@ -106,112 +258,112 @@ BanDialog - + ban &user name ban &gebruikersnaam - + ban &IP address ban &IP-adres - + ban client I&D ban gebruiker I&D - + Ban type Ban type - + &permanent ban &definitieve ban - + &temporary ban &tijdelijke ban - + &Days: &Dagen: - + &Hours: &Uur: - + &Minutes: &Minuten: - + Duration of the ban Duur van de ban - + Please enter the reason for the ban. This is only saved for moderators and cannot be seen by the banned person. Voer een reden voor de ban in. Dit wordt opgeslagen voor het moderatorteam en zal niet zichtbaar zijn voor de verbannen persoon. - + Please enter the reason for the ban that will be visible to the banned person. Voer een reden in die zichtbaar zal zijn voor de verbannen persoon. - + Redact all messages from this user in all rooms Verwijder alle berichten van deze gebruiker in alle kamers - + &OK &OK - + &Cancel &Annuleren - + Ban user from server Ban gebruiker van server - - - - + + + + Error Fout - + You have to select a name-based, IP-based, clientId based, or some combination of the three to place a ban. U moet een ban gebaseerd op IP, naam, gebruikers ID of een combinatie van de drie. - + You must have a value in the name ban when selecting the name ban checkbox. U moet een naam meegeven als u een naam gebaseerde ban wilt uitvoeren. - + You must have a value in the ip ban when selecting the ip ban checkbox. U moet een IP meegeven als u een IP gebaseerde ban wilt uitvoeren. - + You must have a value in the clientid ban when selecting the clientid ban checkbox. U moet een gebruikers ID meegeven als u een gebruikers ID gebaseerde ban wilt uitvoeren. @@ -219,22 +371,22 @@ Dit wordt opgeslagen voor het moderatorteam en zal niet zichtbaar zijn voor de v BetaReleaseChannel - - Beta Releases - Beta Releases + + Beta + Beta - + No reply received from the release update server. Geen antwoord ontvangen van de release update server. - + Invalid reply received from the release update server. Ongeldig antwoord ontvangen van de release update server. - + No reply received from the file update server. Geen antwoord ontvangen van de bestandsupdateserver. @@ -242,32 +394,32 @@ Dit wordt opgeslagen voor het moderatorteam en zal niet zichtbaar zijn voor de v CardDatabaseModel - + Name Naam - + Sets Sets - + Mana cost Manakosten - + Card type Kaarttype - + P/T P/T - + Color(s) Kleur(en) @@ -360,325 +512,621 @@ Dit wordt opgeslagen voor het moderatorteam en zal niet zichtbaar zijn voor de v - CardFrame + CardInfoFrameWidget - + Image - Afbeelding + Plaatje - + Description - Omschrijving + Beschrijving - + Both Beide + + + View transformation + + - CardInfoText + CardInfoPictureWidget - + + View related cards + Bekijk gerelateerde kaarten + + + + Add card to deck + + + + + Mainboard + + + + + Sideboard + + + + + CardInfoTextWidget + + Name: Naam: - + Related cards: Gerelateerde kaarten: - + Unknown card: - kaart onbekent + Onbekende kaart: CardItem - + &Move to &Verplaatsen naar - + &Power / toughness &Power / toughness + + CardSizeWidget + + + Card Size + Kaartgrootte + + CardZone - + their hand nominative zijn hand - + %1's hand nominative %1s hand - + their library look at zone zijn bibliotheek - + %1's library look at zone %1s library - + of their library top cards of zone, van hun library - + of %1's library top cards of zone van %1s library - + their library reveal zone zijn bibliotheek - + %1's library reveal zone %1s library - + their library shuffle zijn bibliotheek - + %1's library shuffle %1s library - + their library nominative zijn bibliotheek - + %1's library nominative %1s library - + their graveyard nominative aijn begraafplaats - + %1's graveyard nominative %1s graveyard - + their exile nominative hun exile - + %1's exile nominative %1s exile - + their sideboard look at zone hun sideboard - + %1's sideboard look at zone %1s sideboard - + their sideboard nominative hun sideboard - + %1's sideboard nominative %1s sideboard + + DeckEditorCardInfoDockWidget + + + Card Info + + + + + DeckEditorDatabaseDisplayWidget + + + Search by card name (or search expressions) + + + + + Add to Deck + + + + + Add to Sideboard + + + + + Select Printing + + + + + Show on EDHREC (Commander) + + + + + Show on EDHREC (Card) + + + + + Show Related cards + + + + + Add card to &maindeck + + + + + Add card to &sideboard + + + + + DeckEditorDeckDockWidget + + + Banner Card + + + + + Select Printing + + + + + Deck + + + + + Deck &name: + + + + + &Comments: + + + + + Hash: + + + + + &Increment number + + + + + &Decrement number + + + + + &Remove row + + + + + Swap card to/from sideboard + + + + + DeckEditorFilterDockWidget + + + Filters + + + + + &Clear all filters + + + + + Delete selected + + + + + DeckEditorMenu + + + &Deck Editor + + + + + &New deck + + + + + &Load deck... + + + + + Load recent deck... + + + + + Clear + + + + + &Save deck + + + + + Save deck &as... + + + + + Load deck from cl&ipboard... + + + + + Edit deck in clipboard + + + + + + Annotated + + + + + + Not Annotated + + + + + Save deck to clipboard + + + + + Annotated (No set info) + + + + + Not Annotated (No set info) + + + + + &Print deck... + + + + + &Send deck to online service + + + + + Create decklist (decklist.org) + + + + + Create decklist (decklist.xyz) + + + + + Analyze deck (deckstats.net) + + + + + Analyze deck (tappedout.net) + + + + + &Close + + + + + DeckEditorPrintingSelectorDockWidget + + + Printing Selector + + + DeckEditorSettingsPage - - + + Update Spoilers Update Spoilers - - + + Success Succes - + Download URLs have been reset. Download URL's zijn gereset. - + Downloaded card pictures have been reset. Gedownloade kaartafbeeldingen's zijn gereset. - + Error Fout - + One or more downloaded card pictures could not be cleared. Een of meer gedownloade kaartafbeeldingen konden niet worden gewist. - + Add URL URL toevoegen - - + + URL: URL: - - + + Edit URL URL bewerken - + + Network Cache Size: + Netwerk Cache Grootte: + + + + Redirect Cache TTL: + Redirect Cache TTL: + + + + How long cached redirects for urls are valid for. + Hoe lang cached redirects voor urls geldig zijn + + + + Picture Cache Size: + Afbeeldingen Cache Grootte: + + + Add New URL Nieuwe URL toevoegen - + Remove URL URL verwijderen - + + Day(s) + Dag(en) + + + Updating... Bijwerken... - + Choose path Kies locatie - + URL Download Priority URL Download Prioriteit - + Spoilers Spoilers - + Download Spoilers Automatically Spoilers Automatisch Downloaden - + Spoiler Location: Spoiler Locatie: - + Last Change Laatste Wijziging - + Spoilers download automatically on launch Spoilers downloaden automatisch bij opstarten - + Press the button to manually update without relaunching Druk op de knop om handmatig bij te werken zonder opnieuw te starten. - + Do not close settings until manual update is complete Sluit de instellingen niet af voordat de handmatige update is voltooid - + Download card pictures on the fly Kaartafbeeldingen on the fly downloaden - + How to add a custom URL Hoe een aangepaste URL toe te voegen - + Delete Downloaded Images Gedownloade Afbeeldingen Verwijderen - + Reset Download URLs Reset Download URL's - - Downloaded images directory size: - Grootte van map voor gedownloade afbeeldingen: - - - + On-disk cache for downloaded pictures Cache op schijf voor gedownloade afbeeldingen - - Picture cache size: - Afbeeldingen cache grootte: - - - + In-memory cache for pictures not currently on screen Cache in geheugen voor afbeeldingen die momenteel niet op het scherm staan @@ -686,16 +1134,218 @@ Dit wordt opgeslagen voor het moderatorteam en zal niet zichtbaar zijn voor de v DeckListModel - + + Count + Hoeveelheid + + + + Set + Set + + + Number Nummer - + + Provider ID + Provider ID + + + Card Kaart + + DeckLoader + + + Common deck formats (%1) + + + + + All files (*.*) + + + + + DeckPreviewColorIdentityFilterWidget + + + Mode: Exact Match + + + + + Mode: Includes + + + + + DeckPreviewDeckTagsDisplayWidget + + + Edit tags ... + + + + + DeckPreviewTagDialog + + + Deck Tags Manager + + + + + Manage your deck tags. Check or uncheck tags as needed, or add new ones: + + + + + Add a new tag (e.g., Aggro️) + + + + + Add Tag + + + + + Filter tags... + + + + + OK + + + + + Cancel + + + + + Invalid Input + + + + + Tag name cannot be empty! + + + + + Duplicate Tag + + + + + This tag already exists. + + + + + DeckPreviewWidget + + + Banner Card + + + + + Open in deck editor + + + + + Edit Tags + + + + + Rename Deck + + + + + Save Deck to Clipboard + + + + + Annotated + + + + + Annotated (No set info) + + + + + Not Annotated + + + + + Not Annotated (No set info) + + + + + Rename File + + + + + Delete File + + + + + Set Banner Card + + + + + + New name: + + + + + + Error + + + + + Rename failed + + + + + Delete file + + + + + Are you sure you want to delete the selected file? + + + + + Delete failed + + + DeckStatsInterface @@ -713,181 +1363,217 @@ Dit wordt opgeslagen voor het moderatorteam en zal niet zichtbaar zijn voor de v DeckViewContainer - + Load deck... Deck laden... - + Load remote deck... Laad extern deck... - + + Unload deck + Unload deck... + + + + Ready to start Gereed voor start - + + Force start + Geforceerd starten + + + Sideboard unlocked Sideboard ontgrendeld - + Sideboard locked Sideboard vergrendeld - - Load deck - Laad deck - - - + Error Fout - + The selected file could not be loaded. Het geselecteerde bestand kon niet geladen worden. + + DialogConvertDeckToCodFormat + + + Deck Format Conversion + + + + + You tried to add a tag to a .txt format deck. + Tags can only be added to .cod format decks. + Do you want to convert the deck to the .cod format? + + + + + Remember and automatically apply choice in the future + + + DlgConnect - + Downloading... Downloaden... - + Known Hosts Bekende Hosts - + + Delete the currently selected saved server + Verwijder de geselecteerde opgeslagen server + + + Refresh the server list with known public servers Hernieuw de serverlijst met bekende publieke servers - + New Host Nieuwe Eigenaar - + Name: Naam: - + &Host: &Gastheer - + &Port: &Poort - + Player &name: Spelers&naam: - + P&assword: W&achtwoord: - + &Save password Wachtwoord &Opslaan - + A&uto connect A&utomatisch verbinden - + Automatically connect to the most recent login when Cockatrice opens Maak automatisch verbinding met de meest recente login wanneer Cockatrice wordt geopend. - + If you have any trouble connecting or registering then contact the server staff for help! Als u problemen ondervindt bij het aansluiten of registreren, neem dan contact op met het serverteam voor hulp! - - + + Webpage Website - + Reset Password Wachtwoord veranderen - + + Forgot password? + Wachtwoord vergeten? + + + &Connect &Verbinden - + Server Server - + Login Login - + Server Contact Servercontact - + Connect to Server Verbinding maken met server - + Server URL Server URL - + Communication Port Communicatiepoort - + Unique Server Name Unieke Servernaam - + Connection Warning Verbindingswaarschuwing - + You need to name your new connection profile. U moet het nieuwe verbindingsprofiel een naam geven. - + Connect Warning Verbindingswaarschuwing - + The player name can't be empty. De naam van de speler mag niet leeg zijn. @@ -970,27 +1656,37 @@ Dit wordt opgeslagen voor het moderatorteam en zal niet zichtbaar zijn voor de v Toeschouwers - + + Starting life total: + Beginnend levenstotaal: + + + + Game setup options + Spel opzet opties + + + &Clear &Opschonen - + Create game Spel aanmaken - + Game information Spelgegevens - + Error Fout - + Server error. Server Fout @@ -998,92 +1694,92 @@ Dit wordt opgeslagen voor het moderatorteam en zal niet zichtbaar zijn voor de v DlgCreateToken - + &Name: &Naam: - + Token Token - + C&olor: Kle&ur: - + white wit - + blue blauw - + black zwart - + red rood - + green groen - + multicolor veelkleurig - + colorless kleurloos - + &P/T: &P/T: - + &Annotation: &Annotatie: - + &Destroy token when it leaves the table &Vernietig token als het de tafel verlaat - + Token data Token data - + Show &all tokens Laat &alle tokens zien - + Show tokens from this &deck Laat tokens van dit &dek zien - + Choose token from list Kies token uit lijst - + Create token Maak token @@ -1129,6 +1825,24 @@ Om je avatar te verwijderen, accepteer zonder een nieuwe afbeelding te kiezen.Geen geldige afbeelding gekozen + + DlgEditDeckInClipboard + + + Edit deck in clipboard + + + + + Error + + + + + Invalid deck list. + + + DlgEditPassword @@ -1171,93 +1885,93 @@ Om je avatar te verwijderen, accepteer zonder een nieuwe afbeelding te kiezen. DlgEditTokens - + &Name: &Naam: - + C&olor: Kle&ur: - + white wit - + blue blauw - + black zwart - + red rood - + green groen - + multicolor veelkleurig - + colorless kleurloos - + &P/T: &P/T: - + &Annotation: &Annotatie: - + Token data Token data - - + + Add token Voeg token toe - + Remove token Verwijder token - + Edit custom tokens Aangepaste tokens bewerken - + Please enter the name of the token: Gelieve de naam van de token in te voeren: - + Error Fout - + The chosen name conflicts with an existing card or token. Make sure to enable the 'Token' set in the "Manage sets" dialog to display them correctly. De gekozen naam is al in gebruik door een bestaande kaart of token. @@ -1295,132 +2009,132 @@ Zorg ervoor dat u de 'Token' set in het " Beheer sets" dialo DlgFilterGames - + no limit Geen limiet - + 5 minutes 5 minuten - + 10 minutes 10 minuten - + 30 minutes 30 minuten - + 1 hour 1 uur - + 2 hours 2 uur - - Show '&buddies only' games - Toon 'alleen &vrienden' spellen + + Hide 'buddies only' games + - - Show &full games - Laat &volle spelle zien + + Hide full games + - - Show games &that have started - Laat spellen zien die al &begonnen zijn + + Hide games that have started + - - Show &password protected games - Laat &wachtwoord zien van gesloten spellen + + Hide password protected games + - - Hide '&ignored user' games - Verberg spellen door genegeerde gebruikers + + Hide 'ignored user' games + - + &Newer than: &Nieuwer dan: - + Game &description: Spel &omschrijving: - + &Creator name: Naam van &maker: - + General Algemeen - + &Game types &Spel types - + at &least: ten&minste: - + at &most: op zijn &hoogst: - + Maximum player count Maximale aantal spelers - + Restrictions Restricties - + Show games only if &spectators can watch Laat alleen spellen zien als &toeschouwers kunnen kijken - + Show spectator password p&rotected games Laat spellen zien met &wachtwoord voor toeschouwers - + Show only if spectators can ch&at Laat alleen spellen zien als toeschouwers kunnen &chatten - + Show only if spectators can see &hands Laat alleen spellen zien als toeschouwers &handen kunnen zien - + Spectators Toeschouwers - + Filter games Spellen filteren @@ -1603,27 +2317,28 @@ Zorg ervoor dat u de 'Token' set in het " Beheer sets" dialo De wachtwoorden komen niet overeen. + + DlgLoadDeck + + + Load Deck + + + DlgLoadDeckFromClipboard - - &Refresh - &Verversen - - - + Load deck from clipboard Laad dek van klembord - - + Error Fout - - + Invalid deck list. Niet geldige deck lijst. @@ -1631,11 +2346,49 @@ Zorg ervoor dat u de 'Token' set in het " Beheer sets" dialo DlgLoadRemoteDeck - + Load deck Laad dek + + DlgMoveTopCardsUntil + + + Card name (or search expressions): + Kaart naam (of zoekexpressies) + + + + Number of hits: + Hoeveelheid resultaten: + + + + Auto play hits + Speel resultaten automatisch + + + + Put top cards on stack until... + Plaats bovenste kaart op stack totdat... + + + + No cards matching the search expression exists in the card database. Proceed anyways? + + + + + Cockatrice + Cockatrice + + + + Invalid filter + Ongeldige filter + + DlgRegister @@ -1750,12 +2503,12 @@ Je email zal gebruikt worden om je account te controleren. DlgSettings - + Unknown Error loading card database Onbekende Fout in het lezen van de kaart database - + Your card database is invalid. Cockatrice may not function correctly with an invalid database @@ -1772,7 +2525,7 @@ U zou oracle moeten herstarten om uw database te updaten. Zou u de locatie van uw database willen veranderen? - + Your card database version is too old. This can cause problems loading card information or images @@ -1789,7 +2542,7 @@ Dit zou mogelijk opgelost worden door oracle opnieuw te starten en je kaart data Wil je de locatie van je database veranderen? - + Your card database did not finish loading Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues with your cards.xml attached @@ -1802,7 +2555,7 @@ Plaats een klacht op https://github.com/Cockatrice/Cockatrice/issues met uw card Wilt u uw database lokatie aanpassen? - + File Error loading your card database. Would you like to change your database location setting? @@ -1811,7 +2564,7 @@ Would you like to change your database location setting? Wilt u uw database lokatie aanpassen? - + Your card database was loaded but contains no cards. Would you like to change your database location setting? @@ -1820,7 +2573,7 @@ Would you like to change your database location setting? Wilt u uw database lokatie aanpassen? - + Unknown card database load status Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues @@ -1833,59 +2586,59 @@ Plaats een klacht op https://github.com/Cockatrice/Cockatrice/issues asltublieft Wilt u uw database lokatie aanpassen? - - - + + + Error Fout - + The path to your deck directory is invalid. Would you like to go back and set the correct path? Het pad naar uw deck map is niet geldig. Wilt u terug gaan en de correcte pad invullen? - + The path to your card pictures directory is invalid. Would you like to go back and set the correct path? Het pad naar uw kaart afbeelding map is niet geldig. Wil je terug gaan en het juiste pad invullen? - + Settings Instellingen - + General Algemeen - + Appearance Weergave - + User Interface Gebruikersomgeving - + Card Sources Kaart Bronnen - + Chat Chat - + Sound Geluid - + Shortcuts Sneltoetsen @@ -1911,166 +2664,169 @@ Wilt u uw database lokatie aanpassen? DlgUpdate - + Current release channel Huidig releasekanaal - + Reinstall Herinstalleer - + Cancel Download Annuleer Downloaden - + Open Download Page Open de downloadpagina - + Check for Client Updates Controleren op Client-updates - - - - + + + + Error Fout - + Cockatrice was not built with SSL support, therefore you cannot download updates automatically! Please visit the download page to update manually. Cockatrice is niet gebouwd met SSL-ondersteuning, daarom kunt u niet automatisch updates downloaden! Ga naar de downloadpagina om handmatig bij te werken. - - Downloading update... - Update downloaden... + + Downloading update: %1 + Downloading update... + - + Checking for updates... Controleren op updates... - + Finished checking for updates Controle op updates voltooid - + No Update Available Geen Update Beschikbaar - + Cockatrice is up to date! Cockatrice is up to date! - + You are already running the latest version available in the chosen release channel. U draait al de laatste versie die beschikbaar is in het gekozen releasekanaal. - + Current version Huidige versie - + Selected release channel Geselecteerd releasekanaal - - + + Update Available Update Beschikbaar - - + + A new version of Cockatrice is available! Een nieuwe versie van Cockatrice is beschikbaar! - - + + New version Nieuwe versie - - + + Released Released - - + + Changelog Changelog - + Do you want to update now? Wilt u nu updaten? - - Unfortunately there are no download packages available for your operating system. -You may have to build from source yourself. - Helaas zijn er geen download pakketten beschikbaar voor uw besturingssysteem. -Mogelijk moet je zelf van source bouwen. + + Unfortunately, the automatic updater failed to find a compatible download. +You may have to manually download the new version. + Unfortunately there are no download packages available for your operating system. +You may have to build from source yourself. + - - Please check the download page manually and visit the wiki for instructions on compiling. - Controleer de downloadpagina handmatig en bezoek de wiki voor instructies voor het compileren. + + Please check the <a href="%1">releases page</a> on our Github and download the build for your system. + Please check the download page manually and visit the wiki for instructions on compiling. + - - - + + + Update Error Update Fout - + An error occurred while checking for updates: Er is een fout opgetreden bij het controleren op updates: - + An error occurred while downloading an update: Er is een fout opgetreden tijdens het downloaden van een update: - + Installing... Installeren... - + Cockatrice is unable to open the installer. Cockatrice kan het installatieprogramma niet openen. - + Try to update manually by closing Cockatrice and running the installer. Probeer handmatig bij te werken door Cockatrice te sluiten en het installatieprogramma uit te voeren. - + Download location Download locatie @@ -2088,6 +2844,14 @@ Mogelijk moet je zelf van source bouwen. Debug Log + + EdhrecCommanderResponseCommanderDetailsDisplayWidget + + + Salt: + + + FilterBuilder @@ -2099,105 +2863,120 @@ Mogelijk moet je zelf van source bouwen. GameSelector - - - - - - - - - + + + + + + + + + Error Fout - + Please join the appropriate room first. Gelieve eerst naar de juiste kamer te gaan. - + Wrong password. Verkeerd wachtwoord. - + Spectators are not allowed in this game. Toeschouwers zijn niet toegelaten in dit spel. - + The game is already full. Dit spel is reeds vol. - + The game does not exist any more. Het spel bestaat niet meer. - + This game is only open to registered users. Dit spel is alleen toegankelijk voor geregistreerde gebruikers. - + This game is only open to its creator's buddies. Dit spel staat alleen open voor de vrienden van de maker van dit spel. - + You are being ignored by the creator of this game. Je wordt genegeerd door de maker van dit spel. - + + Join Game + + + + + Spectate Game + + + + + Game Information + + + + Join game Aansluiten bij spel - + Password: Wachtwoord: - + Please join the respective room first. Gelieve eerst bij de betreffende kamer aan te sluiten. - + &Filter games &Filter spellen - + C&lear filter W&is filter - + C&reate C&reëer - + &Join &Aansluiten - + J&oin as spectator &Aansluiten als toeschouwer - + Games shown: %1 / %2 Spellen weergegeven: %1 / %2 - + Games Spellen @@ -2205,100 +2984,100 @@ Mogelijk moet je zelf van source bouwen. GamesModel - + >1 day >1 dag - + %1%2 hr short age in hours %1%2 uur%1%2 uur - + new nieuw - + %1%2 min short age in minutes %1%2 min%1%2 min - + password wachtwoord - + buddies only enkel vrienden - + reg. users only enkel geregistreerde gebruikers - - + + can chat kan praten - + see hands zie handen - + can see hands kan handen zien - + not allowed niet toegelaten - + Room Kamer - + Age Ouderdom - + Description Omschrijving - + Creator Maker - + Type Type - + Restrictions Restricties - + Players Spelers - + Spectators Toeschouwers @@ -2306,92 +3085,102 @@ Mogelijk moet je zelf van source bouwen. GeneralSettingsPage - + Reset all paths Reset alle bestandspaden - + All paths have been reset Alle bestandspaden zijn gereset - - + + Choose path Kies pad - + Personal settings Persoonlijke instellingen - + Language: Taal: - + Paths (editing disabled in portable mode) Locaties (bewerken uitgeschakeld in "portable mode") - + Paths Paden - + + How to help with translations + + + + Decks directory: Map voor decks: - + Replays directory: Map voor replays: - + Pictures directory: Map voor afbeeldingen: - + Card database: Kaartdatabase: - + Custom database directory: Custom database map: - + Token database: Token-database - + Update channel Updatekanaal - + + Check for client updates on startup + + + + Notify if a feature supported by the server is missing in my client Waarschuw als een functie die is ondersteund door de server mist in mijn client - + Automatically run Oracle when running a new version of Cockatrice Oracle automatisch uitvoeren bij het opstarten van een nieuwe versie van Cockatrice - + Show tips on startup Toon tips bij het opstarten @@ -2399,61 +3188,61 @@ Mogelijk moet je zelf van source bouwen. MainWindow - - + + The server has reached its maximum user capacity, please check back later. De server heeft zijn maximale gebruikerscapaciteit bereikt, controleer later nogmaals. - + There are too many concurrent connections from your address. Er zijn te veel gelijktijdige verbindingen vanaf uw adres. - + Banned by moderator Verbannen door moderator - + Expected end time: %1 Verwachte eindtijd: %1 - + This ban lasts indefinitely. Deze verbanning duurt voor onbepaalde tijd. - + Scheduled server shutdown. Geplande stillegging van de server. - - + + Invalid username. Ongeldige spelersnaam. - + You have been logged out due to logging in at another location. U bent uitgelogd omdat u op een andere locatie bent ingelogd. - + Connection closed Connectie verbroken - + The server has terminated your connection. Reason: %1 De server heeft uw verbinding beëindigd. Reden: %1 - + The server is going to be restarted in %n minute(s). All running games will be lost. Reason for shutdown: %1 @@ -2464,587 +3253,597 @@ Alle lopende spellen zullen verloren gaan. Reden voor de sluiting: %1 - + Scheduled server shutdown Geplande stillegging server - - + + Success Succesvol - + Registration accepted. Will now login. Registratie geaccepteerd. Logt nu in. - + Account activation accepted. Will now login. Account activering geaccepteerd. Logt nu in. - + Number of players Aantal spelers - + Please enter the number of players. Voer het aantal spelers in. - - + + Player %1 Speler %1 - + Load replay Laad replay - + About Cockatrice Over Cockatrice - + Version Versie - + Cockatrice Webpage Cockatrice Webpagina - + Project Manager: Project Manager: - + Past Project Managers: Vroegere projectmanagers: - + Developers: Ontwikkelaars: - + Our Developers Onze ontwikkelaars - + Help Develop! Help ontwikkelen! - + Translators: Vertalers: - + Our Translators Onze Vertalers - + Help Translate! Help Vertalen! - + Support: Steun: - + Report an Issue Een probleem melden - + Troubleshooting Probleemoplossing - + F.A.Q. F.A.Q. - - - - - - - - - - - - - - - + + + + + + + + + + + + + - - - + + + + + Error Fout - + Server timeout Time-out van de server - + Failed Login Gefaalde Login - + Your client seems to be missing features this server requires for connection. Uw client lijkt functies te missen die deze server nodig heeft voor de verbinding. - + To update your client, go to 'Help -> Check for Client Updates'. Om uw client bij te werken, gaat u naar 'Hulp -> Controleer op Client Updates'. - + Incorrect username or password. Please check your authentication information and try again. Onjuiste gebruikersnaam of wachtwoord. Controleer uw verificatiegegevens en probeer het opnieuw. - + There is already an active session using this user name. Please close that session first and re-login. Er is al een actieve sessie bezig met deze gebruikersnaam. Gelieve die sessie eerst af te sluiten en opnieuw in te loggen. - - + + You are banned until %1. Je bent banned tot %1. - - + + You are banned indefinitely. Je bent voor onbepaalde tijd banned. - + This server requires user registration. Do you want to register now? Deze server vereist een gebruikersregistratie. Wilt u zich nu registreren? - + This server requires client IDs. Your client is either failing to generate an ID or you are running a modified client. Please close and reopen your client to try again. Deze server vereist client ID's. Uw client slaagt er niet in een ID te genereren of u heeft een aangepaste client. Gelieve uw client te sluiten en te heropenen om het opnieuw te proberen. - + An internal error has occurred, please close and reopen Cockatrice before trying again. If the error persists, ensure you are running the latest version of the software and if needed contact the software developers. Er is een interne fout opgetreden, gelieve uw client te sluiten en te heropenen alvorens het opnieuw te proberen. Als de fout zich blijft voordoen, controleer dan of u de laatste versie van de software gebruikt en neem indien nodig contact op met de softwareontwikkelaars. - + Account activation Account activering - + Your account has not been activated yet. You need to provide the activation token received in the activation email. Uw account is nog niet geactiveerd. U moet de activeringscode die u in de activeringsmail hebt ontvangen, invullen. - + Server Full Server Vol - + Unknown login error: %1 Onbekende aanmeldingsfout: %1 - - + + This usually means that your client version is out of date, and the server sent a reply your client doesn't understand. Dit betekent meestal dat uw clientversie verouderd is en dat de server een antwoord heeft gestuurd dat uw client niet begrijpt. - + Your username must respect these rules: Uw gebruikersnaam moet aan deze regels voldoen: - + is %1 - %2 characters long is %1 - %2 karakters lang - + can %1 contain lowercase characters kan %1 kleine letters bevatten. - - - - + + + + NOT NIET - + can %1 contain uppercase characters kan %1 hoofdletters bevatten - + can %1 contain numeric characters kan %1 numerieke tekens bevatten - + can contain the following punctuation: %1 kan de volgende leestekens bevatten: %1 - + first character can %1 be a punctuation mark eerste teken kan %1 een leesteken zijn. - + no unacceptable language as specified by these server rules: note that the following lines will not be translated geen onacceptabel taalgebruik als aangegeven door deze server regels: - + can not contain any of the following words: %1 mag niet een van de volgende woorden bevatten: %1 - + can not match any of the following expressions: %1 mag niet overeenkomen met een van de volgende expressies: %1 - + You may only use A-Z, a-z, 0-9, _, ., and - in your username. U mag alleen A-Z, a-z, 0-9, _, ., en - in uw gebruikersnaam gebruiken. - - - - - - + + + + + + Registration denied Registratie geweigerd - + Registration is currently disabled on this server Registratie is momenteel uitgeschakeld op deze server. - + There is already an existing account with the same user name. Er bestaat al een bestaand account met dezelfde gebruikersnaam. - + It's mandatory to specify a valid email address when registering. Het is verplicht om een geldig e-mailadres op te geven bij de registratie. - + It appears you are attempting to register a new account on this server yet you already have an account registered with the email provided. This server restricts the number of accounts a user can register per address. Please contact the server operator for further assistance or to obtain your credential information. Het lijkt erop dat u probeert een nieuw account te registreren op deze server, maar dat u al een account hebt geregistreerd met de verstrekte e-mail. Deze server beperkt het aantal accounts dat een gebruiker per adres kan registreren. Neem contact op met de serverbeheerder voor verdere hulp of om uw gegevens te verkrijgen. - + Password too short. Wachtwoord te kort. - + Registration failed for a technical problem on the server. De registratie is mislukt wegens een technisch probleem op de server. - + Unknown registration error: %1 Onbekende registratiefout: %1 - + Account activation failed Account activering mislukt - + Socket error: %1 Socket error: %1 - + You are trying to connect to an obsolete server. Please downgrade your Cockatrice version or connect to a suitable server. Local version is %1, remote version is %2. U probeert verbinding te maken met een verouderde server. Gelieve uw Cockatrice versie te downgraden of verbinding te maken met een geschikte server. Lokale versie is %1, externe versie is %2. - + Your Cockatrice client is obsolete. Please update your Cockatrice version. Local version is %1, remote version is %2. Uw Cockatrice klant is verouderd. Gelieve uw Cockatrice versie bij te werken. Lokale versie is %1, externe versie is %2. - + Connecting to %1... Verbinding maken met %1.... - + Registering to %1 as %2... Registreren bij %1 als %2.... - + Disconnected Verbinding verbroken - + Connected, logging in at %1 Verbonden, inloggen bij %1 - - - + + + Requesting forgotten password to %1 as %2... Aanvragen van vergeten wachtwoord tot %1 als %2... - + &Connect... &Verbinden.... - + &Disconnect V&erbinding verbreken - + Start &local game... Start &lokaal spel.... - + &Watch replay... &Bekijk replay... - - &Deck editor - &Deck editor - - - + &Full screen &Volledig scherm - + &Register to server... &Registreer bij server... - + &Restore password... &Reset wachtwoord... - + &Settings... &Instellingen.... - + &Exit &Afsluiten - + A&ctions Act&ies - + &Cockatrice &Cockatrice - + C&ard Database &Kaartdatabase: - + &Manage sets... &Beheer sets... - + Edit custom &tokens... Aangepaste &tokens bewerken - + Open custom image folder Open map voor aangepaste afbeeldingen - + Open custom sets folder Open aangepaste sets map - + Add custom sets/cards Aangepaste sets/kaarten toevoegen - + + Reload card database + + + + + Tabs + + + + &Help &Hulp - + &About Cockatrice &Over Cockatrice - + &Tip of the Day &Tip van de Dag - + Check for Client Updates Controleren op Client-updates - + Check for Card Updates... Kijk of er kaartupdates zijn... - + View &Debug Log Bekijk &debug log - + + Open Settings Folder + + + + Show/Hide Tonen/Verbergen - + New Version Niewe Versie - + Congratulations on updating to Cockatrice %1! Oracle will now launch to update your card database. Gefeliciteerd met de update naar Cockatrice %1! Oracle zal nu starten om uw kaartendatabase bij te werken. - + Cockatrice installed Cockatrice geinstalleerd - + Congratulations on installing Cockatrice %1! Oracle will now launch to install the initial card database. Gefeliciteerd met het installeren van Cockatrice %1! Oracle zal nu starten om uw kaartendatabase bij te werken. - + Card database Kaartdatabase - + Cockatrice is unable to load the card database. Do you want to update your card database now? If unsure or first time user, choose "Yes" Cockatrice kan de kaartendatabase niet laden. - - + + Yes Ja - - + + No Nee - + Open settings Open instellingen - + New sets found Nieuwe sets gevonden - + %n new set(s) found in the card database Set code(s): %1 Do you want to enable it/them? @@ -3055,17 +3854,17 @@ Set codes: %1 Wilt u deze inschakelen? - + View sets Bekijk sets - + Welcome Welkom - + Hi! It seems like you're running this version of Cockatrice for the first time. All the sets in the card database have been enabled. Read more about changing the set order or disabling specific sets and consequent effects in the "Manage Sets" dialog. @@ -3074,58 +3873,59 @@ Alle sets in de kaartendatabase zijn ingeschakeld. Lees meer over het wijzigen van de setvolgorde of het uitschakelen van specifieke sets en de daaruit voortvloeiende effecten in het "Beheer sets" dialoogvenster. - - + + Information Informatie - + A card database update is already running. Een update van de kaartendatabase is al aan de gang. - + Unable to run the card database updater: Kan het updateprogramma van de kaartendatabase niet uitvoeren: - - failed to start. - kan niet opstarten + + Failed to start. The file might be missing, or permissions might be incorrect. + - - crashed. - gecrasht. + + The process crashed some time after starting successfully. + - - timed out. - time-out bereikt. + + Timed out. The process took too long to respond. The last waitFor...() function timed out. + - - write error. - schrijffout. + + An error occurred when attempting to write to the process. For example, the process may not be running, or it may have closed its input channel. + - - read error. - leesfout. + + An error occurred when attempting to read from the process. For example, the process may not be running. + - - unknown error. - onbekende fout. + + Unknown error occurred. + - - The card database updater exited with an error: %1 - Het updateprogramma van de kaartendatabase sloot af met een fout: %1 + + The card database updater exited with an error: +%1 + - + This server supports additional features that your client doesn't have. This is most likely not a problem, but this message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -3136,55 +3936,55 @@ Dit is waarschijnlijk geen probleem, maar dit bericht kan betekenen dat er een n Om uw client bij te werken, ga naar Hulp -> Controleer op updates. - - - - - + + + + + Load sets/cards Laad sets/kaarten - + Selected file cannot be found. Geselecteerd bestand kan niet worden gevonden. - + You can only import XML databases at this time. U kunt op dit moment alleen XML-databases importeren. - + The new sets/cards have been added successfully. Cockatrice will now reload the card database. De nieuwe sets/kaarten zijn succesvol toegevoegd. Cockatrice zal nu de kaartendatabase herladen. - + Sets/cards failed to import. Sets/kaarten zijn niet geïmporteerd. - - - + + + Reset Password Reset Wachtwoord - + Your password has been reset successfully, you can now log in using the new credentials. Uw wachtwoord is succesvol gereset, u kunt nu inloggen met de nieuwe gegevens. - + Failed to reset user account password, please contact the server operator to reset your password. Het opnieuw instellen van het wachtwoord voor uw gebruikersaccount is mislukt, neem contact op met de serverbeheerder om uw wachtwoord opnieuw in te stellen. - + Activation request received, please check your email for an activation token. Activeringsaanvraag ontvangen, kijk in uw e-mail voor een activeringscode. @@ -3458,179 +4258,190 @@ Cockatrice zal nu de kaartendatabase herladen. - %1 is looking at the top %3 card(s) %2. + %1 is looking at the %4 %3 card(s) %2. + %1 is looking at the top %3 card(s) %2. top card for singular, top %3 cards for plural - %1 kijkt naar de bovenste kaart %2.%1 kijkt naar de top %3 kaarten %2. + - + + bottom + + + + + top + + + + %1 turns %2 face-down. %1 draait %2 om met de voorkant naar beneden. - + %1 turns %2 face-up. %1 draait %2 om met de voorkant naar boven. - + The game has been closed. Het spel is afgesloten. - + The game has started. Het spel is begonnen. - + %1 has joined the game. %1 heeft zich bij het spel aangesloten. - + %1 is now watching the game. %1 kijkt nu naar het spel. - + You have been kicked out of the game. Je bent uit het spel geschopt. - + %1 has left the game (%2). %1 heeft het spel verlaten (%2). - + %1 is not watching the game any more (%2). %1 kijkt niet meer naar het spel (%2). - + %1 is not ready to start the game any more. %1 is niet meer klaar om het spel te starten. - + %1 shuffles their deck and draws a new hand of %2 card(s). %1 schudt zijn of haar deck en raapt een nieuwe hand van %2 kaart.%1 schudt zijn of haar deck en raapt een nieuwe hand van %2 kaarten. - + %1 shuffles their deck and draws a new hand. %1 schudt zijn of haar deck en trekt een nieuwe hand. - + You are watching a replay of game #%1. Je kijkt naar een replay van spel #%1. - + %1 is ready to start the game. %1 is klaar om het spel te starten. - + cards an unknown amount of cards kaarten - + %1 card(s) a card for singular, %1 cards for plural één kaart%1 kaarten - + %1 lends %2 to %3. %1 leent %2 aan %3. - + %1 reveals %2 to %3. %1 onthult %2 naar %3. - + %1 reveals %2. %1 onthult %2. - + %1 randomly reveals %2%3 to %4. %1 onthult %2%3 aan %4 gebaseerd op willekeur. - + %1 randomly reveals %2%3. %1 onthult %2%3 gebaseerd op willekeur. - + %1 peeks at face down card #%2. %1 bekijkt verdekte kaart #%2. - + %1 peeks at face down card #%2: %3. %1 bekijkt verdekte kaart #%2: %3. - + %1 reveals %2%3 to %4. %1 onthult %2%3 aan %4. - + %1 reveals %2%3. %1 onthult %2%3. - + %1 reversed turn order, now it's %2. %1 keert de beurtvolgorde om, deze is nu %2. - + reversed omgekeerd - + normal normaal - + Heads Kop - + Tails Munt - + %1 flipped a coin. It landed as %2. %1 gooit een muntje om. Het landde %2. - + %1 rolls a %2 with a %3-sided die. %1 rolt een %2 met een %3-zijdige dobbelsteen. - + %1 flips %2 coins. There are %3 heads and %4 tails. %1 werpt %2 munten. Er zijn %3 koppen en %4 munten. - + %1 rolls a %2-sided dice %3 times: %4. %1 werpt een %2-zijdige dobbelsteen %3 keer: %4. @@ -3750,17 +4561,17 @@ Cockatrice zal nu de kaartendatabase herladen. %1 schudt kaarten %3 - %4 van %2. - + %1 unattaches %2. %1 maakt %2 los. - + %1 undoes their last draw. %1 legt hun laatst getrekte kaart terug. - + %1 undoes their last draw (%2). %1 legt hun laats getrekte kaart terug (%2). @@ -3768,110 +4579,110 @@ Cockatrice zal nu de kaartendatabase herladen. MessagesSettingsPage - + Word1 Word2 Word3 Woord1 Woord2 Woord3 - + Add New Message Bericht toevoegen - + Edit Message Bericht bewerken - + Remove Message Bericht Verwijderen - + Add message Bericht toevoegen - - + + Message: Bericht: - + Edit message Bericht bewerken - + Chat settings Chat instellingen - + Custom alert words Aanpasbare alerteringswoorden - + Enable chat mentions Schakel chat vermeldingen in - + Enable mention completer Vermeldings aanvuller inschakelen - + In-game message macros In-game bericht macro's - + How to use in-game message macros Uitleg gebruik van in-game bericht macro's - + Ignore chat room messages sent by unregistered users Chatroomberichten van niet-geregistreerde gebruikers negeren - + Ignore private messages sent by unregistered users Privéberichten van niet-geregistreerde gebruikers negeren - - + + Invert text color Text kleur inverteren - + Enable desktop notifications for private messages Desktopmeldingen voor privéberichten inschakelen - + Enable desktop notification for mentions Desktopmelding voor vermeldingen inschakelen - + Enable room message history on join Kamerboodschapsgeschiedenis op aansluiting inschakelen - - + + (Color is hexadecimal) (Kleur is hexadecimaal) - + Separate words with a space, alphanumeric characters only Woorden scheiden met een spatie, alleen alfanumerieke tekens @@ -3929,6 +4740,24 @@ Cockatrice zal nu de kaartendatabase herladen. Color Identity + + OverlapControlWidget + + + Cards to overlap: + + + + + Overlap percentage: + + + + + Overlap direction: + + + Phase @@ -3995,57 +4824,57 @@ Cockatrice zal nu de kaartendatabase herladen. PhasesToolbar - + Untap step Untap fase - + Upkeep step Upkeep fase - + Draw step Draw fase - + First main phase Eerste hoofdfase - + Beginning of combat step Begin vechtfase - + Declare attackers step Aanvallers aangeven fase - + Declare blockers step Verdedigers aangeven fase - + Combat damage step Gevechtsschade fase - + End of combat step Einde vechtfase - + Second main phase Tweede hoofdfase - + End of turn step Einde van de beurt fase @@ -4053,7 +4882,7 @@ Cockatrice zal nu de kaartendatabase herladen. PictureLoader - + en code for scryfall's language property, not available for all languages en @@ -4062,647 +4891,781 @@ Cockatrice zal nu de kaartendatabase herladen. Player - + Reveal top cards of library Onthul bovenste kaarten van library - - - - - - - - - + + + + + + + + + + + + Number of cards: (max. %1) Aantal kaarten: (max. %1) - + &View graveyard &Bekijk graveyard - + &View exile &Bekijk exile - + Player "%1" Speler "%1" - - - - + + + + &Graveyard &Graveyard - - - - + + + + &Exile &Exile - + &Move hand to... &Verplaats hand naar.... - - - + + + &Top of library &Bovenkant van library - - - + + + &Bottom of library &Onderkant van library - + &Move graveyard to... &Verplaats graveyard naar... - - - - + + + + &Hand &Hand - + &Move exile to... &Verplaats exile naar... - + &View library &Bekijk library - + &View hand &Bekijk hand - + View &top cards of library... Bekijk de b&ovenste kaarten van de library.... - + Reveal &library to... Onthul &library aan... - + &Always reveal top card Toon &altijd de bovenste kaart - + &View sideboard &Bekijk sideboard - + &Draw card &Trek kaart - + D&raw cards... Tr&ek kaarten.... - + &Undo last draw &Laatste trekking ongedaan maken - + Take &mulligan Neem &mulligan - - &Shuffle - &Schudden - - - + Play top card &face down Speel bovenste kaart &verdekt - + Move top card to grave&yard Verplaats de bovenste kaart naar grave&yard - + Move top card to e&xile Verplaats de bovenste kaart naar exile - + Move top cards to &graveyard... Verplaats de bovenste kaarten naar de &graveyard.... - + Move top cards to &exile... Verplaats bovenste kaarten naar &exile.... - + Put top card on &bottom Leg bovenste kaart aan de &onderkant - + + View bottom cards of library... + + + + Lend library to... Leen library aan... - + + Shuffle + + + + Put top cards on stack &until... Take top cards &until... Plaats bovenste kaart op stack &totdat... - + + Shuffle top cards... + + + + + Shuffle bottom cards... + + + + &Reveal hand to... &Onthul hand aan.... - + Reveal r&andom card to... Onthul &willekeurige kaarten aan.... - + Reveal random card to... Onthul willekeurige kaart aan... - + &Sideboard &Sideboard - + &Library &Library - + &Counters &Counters - + &Untap all permanents &Ontap alles - + R&oll die... &Rol dobbelsteen... - + &Create token... &Creëer token... - + C&reate another token C&reëer nog een token - + Cr&eate predefined token &Voorgedefinieerde token maken - - + + &All players &Alle spelers - + S&ay &Zeg - + + &Select All + + + + + S&elect Row + + + + + S&elect Column + + + + &Play &Speel - + &Hide &Verberg - + Play &Face Down Speel kaart &verdekt - + &Tap / Untap Turn sideways or back again &Tap / Ontap - + Toggle &normal untapping &Normal ontappen schakelen - + T&urn Over Turn face up/face down &Draai om - + &Peek at card face &Gluren naar de voorkant van de kaart - + &Clone &Kloneer - + Attac&h to card... Aan de kaart &bevestigen.... - + Unattac&h &Losmaken - + &Draw arrow... &Teken pijl.... - + &Increase power &Verhoog kracht - + &Decrease power V&erlaag kracht - + I&ncrease toughness Ve&rhoog hardheid - + D&ecrease toughness Ver&laag hardheid - + In&crease power and toughness Ver&hoog kracht en hardheid - + Dec&rease power and toughness Verl&aag kracht en hardheid - + Increase power and decrease toughness Verhoog kracht en verlaag hardheid - + Decrease power and increase toughness Verminder kracht en verhoog hardheid - + Set &power and toughness... &Zet kracht en hardheid - + Reset p&ower and toughness Herstel &kracht en hardheid - + &Set annotation... Aantekeningen &zetten.... - + Red Rood - + Yellow Geel - + Green Groen - + &Add counter (%1) Voeg counter &toe (%1) - + &Remove counter (%1) &Verwijder counter (%1) - + &Set counters (%1)... Counters &instellen (%1)... - + &Top of library in random order &Bovenste kaart van library in willekeurige volgorde - + X cards from the top of library... Kaarten van de bovenkant van de library... - + &Bottom of library in random order &Onderkant van library in willekeurige volgorde - + View top cards of library Bekijk de bovenste kaarten van de library - + + View bottom cards of library + + + + + Shuffle top cards of library + + + + + Shuffle bottom cards of library + + + + Which position should this card be placed: Hoeveelste van boven moet de kaart geplaatst worden: - + (max. %1) (max. %1) - + Draw hand Trek hand - + 0 and lower are in comparison to current hand size 0 en lager zijn in vergelijking met de huidige handgrootte - + Draw cards Trek kaarten - - + + Number: Aantal: - + Move top cards to grave Verplaats bovenste kaarten naar graveyard - + Reveal &top cards to... &Onthul bovenste kaarten aan.... - + &Top of library... &Bovenkant van library - + &Bottom of library... &Onderkant van library - + &Always look at top card Toon &altijd de bovenste kaart - + &Open deck in deck editor O&pen deck in deck editor - + &Play top card &Speel bovenste kaart - + &Draw bottom card &Trek onderste kaart - + D&raw bottom cards... T&rek onderste kaarten... - + &Play bottom card &Speel onderste kaart - + Play bottom card &face down Speel onderste kaart &gesloten - + Move bottom card to grave&yard Verplaats onderste kaart naar grave&yard - + Move bottom card to e&xile Verplaats onderste kaart naar exile - + Move bottom cards to &graveyard... Verplaats onderste kaarten naar &graveyard.... - + Move bottom cards to &exile... Verplaats onderste kaarten naar &exile.... - + Put bottom card on &top Leg onderste kaart &bovenop - + Selec&ted cards &Geselecteerde kaarten - + Move top cards to exile Verplaats bovenste kaarten naar exile - + Move bottom cards to grave Verplaats onderste kaarten naar graveyard - + Move bottom cards to exile Verplaats onderste kaart naar exile - + Draw bottom cards Trek onderste kaarten - - + + C&reate another %1 token &Maak nog een %1 token - + Create tokens Token maken - + Place card X cards from top of library Plaats kaart X kaarten vanaf de bovenkant van de library - + Change power/toughness Verander stats - + Change stats to: Verander stats naar: - + Set annotation Aantekening instellen - + Please enter the new annotation: Voer de nieuwe aantekening in: - + Set counters Zet counters - + Re&veal to... &Onthul aan.... - + View related cards Bekijk gerelateerde kaarten - + Token: Token: - + All tokens Alle tokens + + PrintingSelector + + + Display Navigation Buttons + + + + + <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. + + + + + PrintingSelectorCardOverlayWidget + + + Preference + + + + + Pin Printing + + + + + Unpin Printing + + + + + Show Related cards + + + + + PrintingSelectorCardSearchWidget + + + Search by set name or set code + + + + + PrintingSelectorCardSelectionWidget + + + Previous Card in Deck + + + + + Next Card in Deck + + + + + PrintingSelectorCardSortingWidget + + + Alphabetical + + + + + Preference + + + + + Release Date + + + + + + Descending + + + + + Ascending + + + QMenuBar - + Services Diensten - + Hide %1 Verberg %1 - + Hide Others Verberg Anderen - + Show All Toon Alle - + Preferences... Voorkeuren... - + Quit %1 Stop %1 - + About %1 Over %1 @@ -4710,38 +5673,32 @@ Cockatrice zal nu de kaartendatabase herladen. QObject - + Cockatrice card database (*.xml) Cockatrice kaartendatabase (*.xml) - - + All files (*.*) Alle bestanden (*.*) - + Cockatrice replays (*.cor) Cockatrice replays (*.cor) - - Common deck formats (*.cod *.dec *.dek *.txt *.mwDeck) - Gebruikelijke formaten voor decks (*.cod *.dec *.dek *.txt *.mwDeck) - - - + Maindeck Maindeck - + Sideboard Sideboard - + Tokens Tokens @@ -4749,92 +5706,92 @@ Cockatrice zal nu de kaartendatabase herladen. QPlatformTheme - + OK OK - + Save Opslaan - + Save All Alles Opslaan - + Open Open - + &Yes &Ja - + Yes to &All Ja op A&lle - + &No &Nee - + N&o to All Nee op &Alle - + Abort Afbreken - + Retry Opnieuw proberen - + Ignore Negeren - + Close Sluiten - + Cancel Annuleren - + Discard Afleggen - + Help Hulp - + Apply Toepassen - + Reset Resetten - + Restore Defaults Standaardinstellingen herstellen @@ -4842,17 +5799,17 @@ Cockatrice zal nu de kaartendatabase herladen. RemoteDeckList_TreeModel - + Name Naam - + ID ID - + Upload time Upload tijd @@ -4860,32 +5817,32 @@ Cockatrice zal nu de kaartendatabase herladen. RemoteReplayList_TreeModel - + ID ID - + Name Naam - + Players Spelers - + Keep Bewaar - + Time started Tijd begonnen - + Duration (sec) Duur (sec) @@ -4931,32 +5888,32 @@ Cockatrice zal nu de kaartendatabase herladen. SequenceEdit - + Choose an action from the table Kies een actie uit de tabel - + Hit the key/combination of keys you want to set for this action Druk op de toets/combinatie van toetsen die je voor deze functie wilt gebruiken. - - Shortcut already in use - Sneltoets reeds in gebruik - - - + Invalid key Ongeldige toets - + + Shortcut already in use by: + + + + Clear Wis - + Restore default Herstel standaard @@ -4992,92 +5949,101 @@ Cockatrice zal nu de kaartendatabase herladen. ShortcutSettingsPage - - + + Restore all default shortcuts Alle standaard snelkoppelingen herstellen - + Do you really want to restore all default shortcuts? Wilt u echt alle standaard snelkoppelingen herstellen? - + Clear all default shortcuts Alle standaard snelkoppelingen wissen - + Do you really want to clear all shortcuts? Wil je echt alle snelkoppelingen vrijmaken? - - Action - Functie - - - - Shortcut - Sneltoets - - - + Section: Sectie: - + Action: Functie: - + Shortcut: Sneltoets: - + How to set custom shortcuts Hoe aangepaste snelkoppelingen in te stellen - + Clear all shortcuts Alle sneltoetsen wissen + + + Search by shortcut name + + + + + ShortcutTreeView + + + Action + + + + + Shortcut + + ShortcutsSettings - + Your configuration file contained invalid shortcuts. Please check your shortcut settings! Uw configuratiebestand bevatte ongeldige snelkoppelingen. Controleer uw snelkoppelingsinstellingen! - + The following shortcuts have been set to default: - De volgende snelkoppelingen zijn op de standaardinstelling ingesteld: + De volgende snelkoppelingen zijn op de standaardinstelling ingesteld: + ShutdownDialog - + &Reason for shutdown: &Reden voor de sluiting: - + &Time until shutdown (minutes): &Tijd tot uitschakeling (minuten): - + Shut down server Server uitschakelen @@ -5085,27 +6051,27 @@ Controleer uw snelkoppelingsinstellingen! SoundSettingsPage - + Enable &sounds &Geluiden inschakelen - + Current sounds theme: Huidige geluiden thema: - + Test system sound engine Test geluid engine van systeem - + Sound settings Geluidsinstellingen - + Master volume Hoofdvolume @@ -5113,48 +6079,48 @@ Controleer uw snelkoppelingsinstellingen! SpoilerBackgroundUpdater - + Spoilers season has ended Spoilers seizoen is afgelopen - + Deleting spoiler.xml. Please run Oracle Verwijdert spoiler.xml. Gelieve Oracle te draaien - - + + Spoilers download failed Spoilers downloaden mislukt - + No internet connection Geen internetverbinding - + Error Fout - + Spoilers already up to date Spoilers reeds up-to-date - + No new spoilers added Geen nieuwe spoilers toegevoegd - + Spoilers have been updated! Spoilers zijn bijgewerkt! - + Last change: Laatste Wijziging: @@ -5162,637 +6128,593 @@ Controleer uw snelkoppelingsinstellingen! StableReleaseChannel - - Stable Releases - Stable Releases + + Default + - + No reply received from the release update server. Geen antwoord ontvangen van de release update server. - + Invalid reply received from the release update server. Ongeldig antwoord ontvangen van de release update server. - + No reply received from the tag update server. Geen antwoord ontvangen van de tag update server. - + Invalid reply received from the tag update server. Ongeldig antwoord ontvangen van de tag update server. + + TabAccount + + + Add to Buddy List + + + + + Add to Ignore List + + + + + Account + + + TabAdmin - + Administration Beheer - + Update server &message Update server &bericht - + &Shut down server &Server uitschakelen - + &Reload configuration &Herlaad configuratie - + Server administration functions Functies voor serverbeheer - + + Server moderator functions + + + + + Replay ID + + + + + Grant Replay Access + + + + + Username to Activate + + + + + Force Activate User + + + + &Unlock functions Functies &ontgrendelen - + &Lock functions &Vergrendel functies + + + + Success + + + + + Replay access granted + + + + + + + + + Error + + + + + Unable to grant replay access. Replay ID invalid + + + + + Unable to grant replay access. Internal error + + + + + User successfully activated + + + + + Unable to activate user. Username invalid + + + + + Unable to activate user. User already active + + + + + Unable to activate user. Internal error + + TabDeckEditor - - Search by card name (or search expressions) - Search by card name - Zoek op kaartnaam (of zoekexpressies) - - - - Add to Deck - Toevoegen aan deck - - - - Add to Sideboard - Toevoegen aan sideboard - - - - Show Related cards - Gerelateerde kaarten tonen - - - - &Clear all filters - &Wis alle filters - - - - Delete selected - Selectie verwijderen - - - - Deck &name: - Deck &naam: - - - - &Comments: - &Opmerkingen: - - - - Hash: - Hash: - - - - &New deck - &Nieuw deck - - - - &Load deck... - &Laad deck... - - - - &Save deck - &Bewaar deck - - - - Save deck &as... - Bewaar deck &als... - - - - Load deck from cl&ipboard... - Laad deck van &klembord.... - - - - Save deck to clipboard - Dek opslaan op klembord - - - - Annotated - Geannoteerd - - - - Not Annotated - Niet Geannoteerd - - - - &Print deck... - &Print deck... - - - - &Send deck to online service - &Stuur deck naar online service - - - - Create decklist (decklist.org) - Maak een decklist aan (decklist.org) - - - - Analyze deck (deckstats.net) - Analyseer deck (deckstats.net) - - - - Analyze deck (tappedout.net) - Analyseer deck (tappedout.net) - - - - &Close - &Sluiten - - - - Add card to &maindeck - Kaart aan deck &toevoegen - - - - Add card to &sideboard - Kaart aan &sideboard toevoegen - - - - &Remove row - Verwijder rij - - - - &Increment number - Verhoog getal - - - - &Decrement number - Verlaag getal - - - - &Deck Editor - &Deck Editor - - - - + Card Info Kaart info - - + Deck Deck - - + Filters Filters - + &View &Bekijk - - - + + Printing + + + + + + + Visible Zichtbaar - - - + + + + Floating Zwevend - + Reset layout Layout resetten - + Deck: %1 Dek: %1 - - - Are you sure? - Ben je zeker? - - - - The decklist has been modified. -Do you want to save the changes? - De deklijst is gewijzigd. -Wil je de wijzigingen opslaan? - - - - Load deck - Laad dek - - - - - - - - - Error - Fout - - - - The deck could not be saved. - Het dek kon niet opslagen worden. - - - - Could not save remote deck - Kon remote deck niet opslaan - - - - - The deck could not be saved. -Please check that the directory is writable and try again. - Het dek kon niet opgeslagen worden. -Kijk a.u.b. na of je schrijfrechten op de folder hebt, en probeer opnieuw. - - - - Save deck - Deck opslaan - - - - There are no cards in your deck to be exported - Er zijn geen kaarten in je deck om te exporteren. - - - - No deck was selected to be saved. - Er is geen deck geselecteerd om te worden opgeslagen. - TabDeckStorage - - Deck storage - Dekopslag - - - + Local file system Lokaal bestandssysteem - + Server deck storage Dekopslag op de server - - + + Open in deck editor Open dek in editor - + + Rename deck or folder + + + + Upload deck Upload dek - + Download deck Download dek - - + + + + New folder Nieuwe folder - - + + Delete Verwijder - - + + Open decks folder + + + + + Rename local folder + + + + + Rename local file + + + + + New name: + + + + + + + Error Foutmelding - + + Rename failed + + + + + Invalid deck file Ongeldig deck bestand - + Enter deck name Geeft dek naam in - + This decklist does not have a name. Please enter a name: De deklijst heeft geen naam. Geef een naam in aub. - + Unnamed deck Onbenoemd dek - + Failed to upload deck to server Uploaded van deck naar server mislukt - + Delete local file Verwijder lokaal bestand - - - - Are you sure you want to delete "%1"? - Ben je zeker dat je "%1" wilt verwijderen? + + Are you sure you want to delete the selected files? + - + + Delete remote decks + + + + + Are you sure you want to delete the selected decks? + + + + + Name of new folder: Naam van de folder: - - Delete remote folder - Verwijder folder van server + + Deck Storage + + + + + TabDeckStorageVisual + + + Visual Deck Storage + - - Delete remote deck - Verwijder dek van server + + Error + + + + + Could not open deck at %1 + + + + + TabEdhRec + + + EDHREC: + TabGame - - - + + + Replay Replay - - + + Game Spel - - + + Player List Spelerslijst - - + + Card Info Kaartinfo - - + + Messages Berichten - - + + Replay Timeline Replay Tijdslijn - + &Phases Fases - + &Game Spel - + Next &phase Volgende fase - + Next phase with &action Volgende fase met &handeling - + Next &turn Volgende beurt - + Reverse turn order Keer beurtvolgorde om - + &Remove all local arrows Verwijder alle lokale pijlen - + Rotate View Cl&ockwise Draai de weergave met de klok &mee - + Rotate View Co&unterclockwise Draai de weergave &tegen de klok in - + Game &information Spelgegevens - + + Un&concede + + + + &Concede &Geef op - + &Leave game Verlaat spel - + C&lose replay Sluit opgeslagen spel - + &Focus Chat &Focus Chat - + &Say: Zeg: - + &View &Bekijk - - - - + + + + Visible Zichtbaar - - - - + + + + Floating Zwevend - + Reset layout Reset lay-out - + Concede Opgeven - + Are you sure you want to concede this game? Ben je zeker dat je wilt opgeven voor dit spel? - + Unconcede Opgeven ongedaan maken - + You have already conceded. Do you want to return to this game? Je hebt al opgegeven. Wil je nar dit spel terugkeren? - + Leave game Verlaat spel - + Are you sure you want to leave this game? Ben je zeker dat je dit spel wilt verlaten? - + You are flooding the game. Please wait a couple of seconds. Je bent het spel aan het overstelpen. Wacht enkele seconden aub. - + A player has joined game #%1 Een speler sluitte aan bij spel #%1 - + %1 has joined the game %1 sluitte bij het spel aan - + kicked by game host or moderator gekickt door een game host of moderator - + player left the game speler heeft het spel verlaten - + player disconnected from server verbinding tussen de speler en de server is verbroken - + reason unknown reden onbekend - + You have been kicked out of the game. Je bent uit het spel gegooid. @@ -5805,153 +6727,153 @@ Please enter a name: Logs - - - + + + Time;SenderName;SenderIP;Message;TargetID;TargetName Tijd;Afzendernaam;AfzenderIP; Bericht;DoelID;DoelNaam - + Room Logs Kamerlogboeken - + Game Logs Spellogboeken - + Chat Logs Chat Logs - - + + Error Fout - + You must select at least one filter. U moet ten minste één filter selecteren. - + You have to select a valid number of days to locate. U moet een geldig aantal dagen selecteren om te zoeken. - + Username: Gebruikersnaam: - + IP Address: IP-adres: - + Game Name: Spelnaam: - + GameID: GameID: - + Message: Boodschap: - + Main Room Hoofdkamer - + Game Room Spelkamer - + Private Chat Privéchat - + Past X Days: Afgelopen X dagen: - + Today Vandaag - + Last Hour Afgelopen uur - + Maximum Results: Maximale resultaten: - + At least one filter is required. The more information you put in, the more specific your results will be. Er is minstens één filter nodig. Hoe meer informatie je inbrengt, hoe specifieker je resultaten zullen zijn. - + Get User Logs Gebruikerslogboeken ontvangen - + Clear Filters Wis filters - + Filters Filters - + Log Locations Loglokaties - + Date Range Datumbereik - + Maximum Results Maximale resultaten - - + + Message History Bericht Geschiedenis - + Failed to collect message history information. Het is mislukt om informatie over de berichtgeschiedenis te verzamelen. - + There are no messages for the selected filters. Er zijn geen berichten voor de geselecteerde filters. @@ -5974,22 +6896,22 @@ Hoe meer informatie je inbrengt, hoe specifieker je resultaten zullen zijn.%1 - Privaat gesprek - + This user is ignoring you, they cannot see your messages in main chat and you cannot join their games. Deze gebruiker negeert u, ze kunnen uw berichten niet zien in de hoofdchat en u kunt niet deelnemen aan hun spelletjes. - + Private message from Privé bericht van - + %1 has left the server. %1 heeft zich afgemeld van de server. - + %1 has joined the server. %1 heeft zich aangemeld op de server @@ -5997,117 +6919,153 @@ Hoe meer informatie je inbrengt, hoe specifieker je resultaten zullen zijn. TabReplays - - Game replays - Spellen opnieuw afspelen - - - + Local file system Lokaal bestandssysteem - + Server replay storage Opslag gespeelde spellen op server - - + + Watch replay Bekijk opgeslagen spel - - + + Rename + + + + + + New folder + + + + + Delete Verwijder - + + Open replays folder + + + + Download replay Download opgeslagen spel. - + Toggle expiration lock Schakel expiratieslot om - + + Rename local folder + + + + + Rename local file + + + + + New name: + + + + + Error + + + + + Rename failed + + + + + Name of new folder: + + + + Delete local file Verwijder lokaal opgeslagen spel - - Are you sure you want to delete "%1"? - Ben je zeker dat je "%1" wilt verwijderen? + + Are you sure you want to delete the selected files? + - - Downloading Replays - Downloaden Replays + + Are you sure you want to delete the selected replays? + - - Folder download is not yet supported. Please download replays individually. - Het downloaden van folders is niet ondersteund. Download de individuele replays alstublieft. - - - + Delete remote replay Verwijder op server opgeslagen spel - - Are you sure you want to delete the replay of game %1? - Ben je zeker dat je het opgeslagen spel %1 wilt verwijderen? + + Game Replays + TabRoom - + &Say: Zeg: - + Chat Gesprek - + &Room Kamer - + &Leave room Verlaat kamer - + &Clear chat Maak gespreksgeschiedenis leeg - + Chat Settings... Gespreksinstellingen... - + mentioned you. heeft je genoemd. - + Click to view Click om weer te geven - + You are flooding the chat. Please wait a couple of seconds. Je bent de chat aan het overstelpen. Wacht enkele seconden a.u.b. @@ -6120,30 +7078,30 @@ Hoe meer informatie je inbrengt, hoe specifieker je resultaten zullen zijn.Server - - - - + + + + Error Fout - + Failed to join the server room: it doesn't exist on the server. Aansluiten bij kamer mislukt: deze bestaat niet op de server. - + The server thinks you are in the server room but your client is unable to display it. Try restarting your client. De server denkt dat je in de kamer bent, maar de client is niet in staat om deze te tonen. Probeer de client opnieuw te starten. - + You do not have the required permission to join this server room. U heeft niet de vereiste toestemming om bij deze kamer aan te sluiten. - + Failed to join the server room due to an unknown error: %1. Mislukt om bij de kamer aan te sluiten door een onbekende fout: %1. @@ -6151,32 +7109,72 @@ Hoe meer informatie je inbrengt, hoe specifieker je resultaten zullen zijn. TabSupervisor - + + Deck Editor + + + + + &Visual Deck Storage + + + + + Server + + + + + Account + + + + + Deck Storage + + + + + Game Replays + + + + + Administration + + + + + Logs + + + + Are you sure? Weet je het zeker? - + There are still open games. Are you sure you want to quit? Er staan nog spellen open. Ben je zeker dat je wilt afsluiten? - + Click to view Klik om weer te geven - + Your buddy %1 has signed on! Je maatje %1 heeft in gelogd! - + Unknown Event Onbekend Event - + The server has sent you a message that your client does not understand. This message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -6187,63 +7185,45 @@ Dit bericht kan betekenen dat er een nieuwe versie van Cockatrice beschikbaar is Om uw client bij te werken, ga naar Hulp -> Controleer op updates. - + Idle Timeout Inactiviteit Time-out - + You are about to be logged out due to inactivity. U staat op het punt uitgelogd te worden wegens inactiviteit. - + Promotion Promotie - + You have been promoted. Please log out and back in for changes to take effect. Je bent bevorderd. Gelieve uit te loggen en weer in te loggen om de wijzigingen in werking te laten treden. - + Warned Gewaarschuwd - + You have received a warning due to %1. Please refrain from engaging in this activity or further actions may be taken against you. If you have any questions, please private message a moderator. U heeft een waarschuwing ontvangen wegens %1. Gelieve af te zien van deelname aan deze activiteit of er kunnen verdere acties tegen u worden ondernomen. Als u vragen heeft, kunt u een privé-bericht sturen naar een moderator. - + You have received the following message from the server. (custom messages like these could be untranslated) U heeft het volgende bericht van de server ontvangen. (aangepaste berichten kunnen onvertaald zijn) - - TabUserLists - - - Account - Account - - - - Add to Buddy List - Voeg toe aan vriendenlijst - - - - Add to Ignore List - Voeg toe aan negatielijst - - TappedOutInterface @@ -6329,173 +7309,184 @@ Gelieve af te zien van deelname aan deze activiteit of er kunnen verdere acties UserContextMenu - + User &details Gebruikersdetails - + Private &chat Privaat gesprek - + Show this user's &games Toon spellen van deze gebruiker - + Add to &buddy list Voeg toe aan vriendenlijst - + Remove from &buddy list Verwijder uit vriendenlijst - + Add to &ignore list Voeg toe aan negatielijst - + Remove from &ignore list Verwijder uit negatielijst - + Kick from &game Gooi uit spel - + Warn user Waarschuw gebruiker - + View user's war&n history Bekijk waarschuwings&geschiedenis van gebruiker - + Ban from &server Verban van de &server - + View user's &ban history Bekijk &bangeschiedenis van gebruiker - + &Promote user to moderator &Bevorder gebruiker tot moderator - + Dem&ote user from moderator Demoteer gebruiker van moderator - + Promote user to &judge Bevorder gebruiker tot &judge - + Demote user from judge Demoteer gebruiker van judge - - %1's games - %s's spellen + + View admin notes + - - - + + %1's games + %1's spellen + + + + + Ban History Ban Geschiedenis - + Ban Time;Moderator;Ban Length;Ban Reason;Visible Reason Ban Tijd;Moderator;Ban Lengte; Ban Reden; Zichtbare Reden - + User has never been banned. De gebruiker is nog nooit gebant. - + Failed to collect ban information. Mislukt in het verzamelen van gegevens over ban. - - - + + + Warning History Waarschuwingsgeschiedenis - + Warning Time;Moderator;User Name;Reason Waarschuwingstijd;Moderator;Gebruikersnaam;Reden - + User has never been warned. De gebruiker is nog nooit gewaarschuwd. - + Failed to collect warning information. Mislukt in het verzamelen van gegevens over waarschuwing. - - + + Failed to get admin notes. + + + + + Success Succesvol - + Successfully promoted user. Succesvolle promotie van gebruiker. - + Successfully demoted user. Succesvolle demotie van gebruiker. - - + + + Failed Mislukt - + Failed to promote user. Mislukt in het bevorderen van gebruiker. - + Failed to demote user. De gebruiker is niet gedemoteerd. - + Copy hash to clipboard Copieer hash naar klembord - + Remove this user's messages Verwijder de berichten van deze gebruiker @@ -6503,173 +7494,173 @@ Gelieve af te zien van deelname aan deze activiteit of er kunnen verdere acties UserInfoBox - + Location: Locatie: - + Account Age: Leeftijd lidmaatschap - + Edit Bewerken - + Change password Verander wachtwoord - + Change avatar Verander Icoon - + Administrator Administrator - + Moderator Moderator - + Registered user Geregistreerde gebruiker - - + + Unregistered user Ongeregistreerde gebruiker - + Judge Judge - + Unknown Onbekend - + The entered password does not match your account. Het opgegeven wachtwoord komt niet overeen met jouw account. - - - + + + Information Gegevens - + User information updated. Gebruikersgegevens bijgewerkt. - - - - - - - - - - + + + + + + + + + + Error Fout - + User Information Gebruikersgegevens - + Real Name: Echte Naam: - + User Level: Gebruikersniveau: - + %n Year(s), amount of years (only shown if more than 0) %n jaar, %n jaar, - + %10%n Day(s) %20 amount of years (if more than 0), amount of days, date in local short format %10%n dag %20%10%n Dagen %20 - + Enter Password Wachtwoord Invoeren - + Password verification is required in order to change your email address Wachtwoord controle is vereist om je e-mailadres te veranderen - - - + + + An error occurred while trying to update your user information. Er is een fout opgetreden bij het bijwerken van uw gebruikersgegevens. - + This server does not permit you to update your user informations. Deze server staat jou niet toe om je gebruikersgegevens bij te werken. - + Password changed. Wachtwoord gewijzigd. - + This server does not permit you to change your password. Deze server staat u niet toe uw wachtwoord te wijzigen. - + The new password is too short. Het nieuwe wachtwoord is te kort. - + The old password is incorrect. Het oude wachtwoord is onjuist. - + Avatar updated. Avatar bijgewerkt. - + This server does not permit you to update your avatar. Deze server laat je niet toe om je avatar bij te werken. - + An error occured while trying to updater your avatar. Er is een fout opgetreden bij het updaten van uw avatar. @@ -6677,124 +7668,251 @@ Gelieve af te zien van deelname aan deze activiteit of er kunnen verdere acties UserInterfaceSettingsPage - + General interface settings Algemene weergave-instellingen - + &Double-click cards to play them (instead of single-click) Dubbel-klik op kaarten om ze te spelen (in plaats van enkele klik) - + + &Clicking plays all selected cards (instead of just the clicked card) + + + + &Play all nonlands onto the stack (not the battlefield) by default Speel standaard alle niet-landen op de stapel (niet op het slagveld) - + + Close card view window when last card is removed + + + + Annotate card text on tokens Maak aantekeningen bij de kaarttekst op tokens - + Use tear-off menus, allowing right click menus to persist on screen Gebruik tear-off menus, laat rechtermuisknopmenu's op het scherm staan - + Notifications settings Notificatie instellingen - + Enable notifications in taskbar Activeer meldingen in de taakbalk - + Notify in the taskbar for game events while you are spectating Meld spelgebeurtenissen in de taakbalk terwijl je toekijkt - + Notify in the taskbar when users in your buddy list connect Notificatie in de taakbalk wanneer gebruikers in je buddy lijst verbinding maken - + Animation settings Animatie-instellingen - + &Tap/untap animation &Tap/Untap animatie + + + Deck editor/storage settings + + + + + Open deck in new tab by default + + + + + Prompt before converting .txt decks to .cod format + + + + + Always convert if not prompted + + + + + Use visual deck storage in game lobby + + + + + Replay settings + + + + + Buffer time for backwards skip via shortcut: + + - UserList + UserListWidget - + Users connected to server: %1 - Gebruikers verbonden met de server: %1 + - + Users in this room: %1 - Gebruikers in de kamer: %1 + - + Buddies online: %1 / %2 - Vrienden on-line: %1 / %2 + - + Ignored users online: %1 / %2 - Genegeerde gebruikers on-line: %1 / %2 + + + + + VisualDeckStorageFolderDisplayWidget + + + Deck Storage + + + + + VisualDeckStorageSearchWidget + + + Search by filename + + + + + VisualDeckStorageSortWidget + + + Sort Alphabetically (Deck Name) + + + + + Sort Alphabetically (Filename) + + + + + Sort by Last Modified + + + + + Sort by Last Loaded + + + + + VisualDeckStorageWidget + + + Loading database ... + + + + + Show Folders + + + + + Show Tag Filter + + + + + Show Tags On Deck Previews + + + + + Show Banner Card Selection Option + + + + + Include Folder Names in Search + + + + + Draw unused Color Identities + + + + + Unused Color Identities Opacity + WarningDialog - + Which warning would you like to send? Welke waarschuwing wilt u sturen? - + Redact all messages from this user in all rooms Verwijder alle berichten van deze gebruiker in alle kamers - + &OK &OK - + &Cancel &Cancel - + Warn user for misconduct Waarschuw gebruiker voor wangedrag - - + + Error Fout - + User name to send a warning to can not be blank, please specify a user to warn. Gebruikersnaam om een waarschuwing naar te sturen kan niet leeg zijn, geef een gebruiker op om te waarschuwen. - + Warning to use can not be blank, please select a valid warning to send. Te gebruiken waarschuwing kan niet leeg zijn, selecteer een geldige waarschuwing om te verzenden. @@ -6802,128 +7920,133 @@ Gelieve af te zien van deelname aan deze activiteit of er kunnen verdere acties WndSets - + Move selected set to the top Verplaats de geselecteerde set naar boven - + Move selected set up Verplaats de geselecteerde set naar boven - + Move selected set down Verplaats de geselecteerde set naar beneden - + Move selected set to the bottom Verplaats de geselecteerde set naar beneden - + Search by set name, code, or type Zoeken op setnaam, -code of -type - + Default order Standaard volgorde - + Restore original art priority order Herstel oorspronkelijke volgorde prioriteit afbeeldingen - + Enable all sets Alle sets inschakelen - + Disable all sets Alle sets uitschakelen - + Enable selected set(s) Geselecteerde set(s) inschakelen - + Disable selected set(s) Geselecteerde set(s) uitschakelen - + Deck Editor Deck Editor - + Use CTRL+A to select all sets in the view. Gebruik CTRL+A om alle sets in de lijst te selecteren. - + Only cards in enabled sets will appear in the card list of the deck editor. Alleen kaarten in de ingeschakelde sets zullen verschijnen in de kaartlijst van de deck editor. - + Image priority is decided in the following order: De prioriteit voor afbeeldingen wordt in de volgende volgorde vastgesteld: - + first the CUSTOM Folder (%1), then the Enabled Sets in this dialog (Top to Bottom) %1 is a link to the wiki eerst de CUSTOM Map (%1), daarna de Ingeschakelde Sets in deze dialoog (Boven naar Beneden) - + Card Art Afbeelding - + How to use custom card art Hoe aangepaste kaart afbeeldingen te gebruiken - + Hints Tips - + Note Opmerking - + Sorting by column allows you to find a set while not changing set priority. Sorteren op kolom stelt u in staat om een set te vinden zonder de ingestelde prioriteit te wijzigen. - + To enable ordering again, click the column header until this message disappears. Om de orderen weer mogelijk te maken, klikt u op de kop van de kolom totdat dit bericht verdwijnt. - + Use the current sorting as the set priority instead Gebruik de huidige sortering als de ingestelde prioriteit - + Sorts the set priority using the same column Sorteert de prioriteit van sets met behulp van dezelfde kolom - + + Include online-only (Arena) cards [requires restart] + + + + Manage sets Beheer sets @@ -6931,22 +8054,67 @@ Gelieve af te zien van deelname aan deze activiteit of er kunnen verdere acties ZoneViewWidget - - sort by name - Sorteer op naam + + Ungrouped + - - sort by type - Sorteer op type + + Group by Type + - + + Group by Mana Value + + + + + Group by Color + + + + + Unsorted + + + + + Sort by Name + + + + + Sort by Type + + + + + Sort by Mana Cost + + + + + Sort by Colors + + + + + Sort by P/T + + + + + Sort by Set + + + + shuffle when closing Schudden bij afsluiten - + pile view Stapelweergave @@ -6954,7 +8122,7 @@ Gelieve af te zien van deelname aan deze activiteit of er kunnen verdere acties i18n - + English Nederlands (Dutch) @@ -6962,12 +8130,12 @@ Gelieve af te zien van deelname aan deze activiteit of er kunnen verdere acties main - + Connect on startup Verbinding maken bij het opstarten - + Debug to file Debug naar bestand @@ -6975,785 +8143,907 @@ Gelieve af te zien van deelname aan deze activiteit of er kunnen verdere acties shortcutsTab - + Main Window Hoofdscherm - - + + Deck Editor Deck Editor - + Game Lobby Spel Lobby - + Card Counters Kaart-counters - + Player Counters Tellers - + Power and Toughness Kracht en Hardheid - + Game Phases Spelfasen - + Playing Area Speelruimte - + Move Selected Card Verplaats Geselecteerde Kaart - + View View - + Move Top Card Verplaats Bovenste Kaart - + Move Bottom Card Verplaats Onderste Kaart - + Gameplay Spelverloop - + Drawing Kaarten Trekken - + Chat Room Chatkamer - + Game Window Spel Venster - + Load Deck from Clipboard Laad Deck van Klembord - + + Replays + Replays + + + + Tabs - + Check for Card Updates... Controleren op Kaart Updates... - + Connect... Verbinden... - + Disconnect De verbinding verbreken - + Exit Verlaten - + Full screen Volledig scherm - + Register... Registreer... - + Settings... Instellingen... - + Start a Local Game... Start een Lokaal Spel... - + Watch Replay... Bekijk Replay... - - Analyze Deck - Analyseer Deck + + Analyze Deck (deckstats.net) + Analyze Deck + - + + Analyze Deck (tappedout.net) + + + + Clear All Filters Wis Alle Filters - + Clear Selected Filter Wis Geselecteerde Filter - + Close Sluiten - + Remove Card Verwijder Kaart - + Manage Sets... Beheer Sets... - + Edit Custom Tokens... Aangepaste Tokens Bewerken... - - Export Deck - Exporteer Deck + + Export Deck (decklist.org) + - + + Export Deck (decklist.xyz) + + + + Add Card Voeg Kaart Toe - + Load Deck... Deck Inladen... - + Load Deck from Clipboard... Deck Inladen van Klembord... - + + Edit Deck in Clipboard, Annotated + + + + + Edit Deck in Clipboard + + + + New Deck Nieuw Deck - + Open Custom Pictures Folder Open Custom Afbeeldingen Map - + Print Deck... Print Deck... - + Delete Card Verwijder Kaart - - + + Reset Layout Reset Lay-out - + Save Deck Bewaar Deck - + Save Deck as... Bewaar Deck Als... - + Save Deck to Clipboard, Annotated Bewaar Deck op Klembord, Geannoteerd - + + Save Deck to Clipboard, Annotated (No Set Info) + + + + Save Deck to Clipboard Bewaar Deck op Klembord - + + Save Deck to Clipboard (No Set Info) + + + + Load Local Deck... Lokaal Deck Inladen... - + Load Remote Deck... Remote Deck Inladen... - + Set Ready to Start Klaar om te Beginnen - + Toggle Sideboard Lock Sideboard Vergrendeling Omschakelen - - + + Add Green Counter Groene Counters Omhoog - - + + Remove Green Counter Groene Counters Omlaag - - + + Set Green Counters... Groene Tellers Instellen... - + Add Yellow Counter Gele Counters Omhoog - + Remove Yellow Counter Gele Counters Omlaag - + Set Yellow Counters... Gele Counters Instellen... - - + + Add Red Counter Rode Counters Omhoog - - + + Remove Red Counter Rode Counters Omlaag - - + + Set Red Counters... Rode Counters Instellen... - + Add Life Counter Levensteller Omhoog - + Remove Life Counter Levensteller Omlaag - + Set Life Counters... Levensteller Instellen... - + Add White Counter Witte Teller Omhoog - + Remove White Counter Witte Teller Omlaag - + Set White Counters... Witte Teller Instellen... - + Add Blue Counter Blauwe Teller Omhoog - + Remove Blue Counter Blauwe Teller Omlaag - + Set Blue Counters... Blauwe Teller Instellen... - + Add Black Counter Zwarte Teller Omhoog - + Remove Black Counter Zwarte Teller Omlaag - + Set Black Counters... Zwarte Teller Instellen... - + Add Colorless Counter Kleurloze Teller Omhoog - + Remove Colorless Counter Kleurloze Teller Omlaag - + Set Colorless Counters... Kleurloze Teller Instellen... - + Add Other Counter Overige Teller Omhoog - + Remove Other Counter Overige Teller Omlaag - + Set Other Counters... Overige Teller Instellen... - + Add Power (+1/+0) Kracht Verhogen (+1/+0) - + Remove Power (-1/-0) Kracht Verlagen (-1/-0) - + Move Toughness to Power (+1/-1) Verplaats Hardheid Naar Kracht (+1/-1) - + Add Toughness (+0/+1) Hardheid Verhogen (+0/+1) - + Remove Toughness (-0/-1) Hardheid Verlagen (-0/-1) - + Move Power to Toughness (-1/+1) Verplaats Kracht Naar Hardheid (-1/+1) - + Add Power and Toughness (+1/+1) Kracht en Hardheid Verhogen (+1/+1) - + Remove Power and Toughness (-1/-1) Kracht en Hardheid Verlagen (-1/-1) - + Set Power and Toughness... Kracht en Hardheid Instellen... - + Reset Power and Toughness Kracht en Hardheid Resetten - + Untap Ontap - + Upkeep Upkeep - + Draw Trek - + First Main Phase Eerste Hoofdfase - + Start Combat Start Gevechtfase - + Attack Aanval - + Block Blokkeren - + Damage Schade - + End Combat Einde Gevechtfase - + Second Main Phase Tweede Hoofdfase - + End Einde - + Next Phase Volgende Fase - + Next Phase Action Volgende Fase Handeling - + Next Turn Volgende Beurt - + + Hide Card in Reveal Window + + + + Tap / Untap Card Tap / Ontap Kaart - + Untap All Untap Alles - + Toggle Untap Untap Omschakelen - + Turn Card Over Draai Kaart Om - + Peek Card Bekijk Kaart - + Play Card Speel Kaart - + Attach Card... Kaart Bevestigen Aan... - + Unattach Card Kaart Losmaken - + Clone Card Copieer Kaart - + Create Token... Maak Token... - + Create All Related Tokens Maak Alle Gerelateerde Tokens - + Create Another Token Maak Nog een Token - + Set Annotation... Zet Annotatie... - - + + Select All Cards in Zone + + + + + Select All Cards in Row + + + + + Select All Cards in Column + + + + + Bottom of Library Onderkant van Library - - - - + + + + Exile Exile - - - - + + + + Graveyard Graveyard - - + + Hand Hand - - + + Top of Library Bovenkant van Library - - - + + + Battlefield, Face Down Speelveld, Gesloten - + Battlefield Speelveld - + Library Library - + Sideboard Sideboard - + Top Cards of Library Bovenste Kaarten van Library - + + Bottom Cards of Library + + + + Close Recent View Recent Overzicht Sluiten - - + + Stack Stack - - + + Graveyard (Multiple) Graveyard (Meerdere) - - + + Exile (Multiple) Exile (Meerdere) - + Stack Until Found Stack Totdat Gevonden - + Draw Bottom Card Trek Onderste Kaart - + Draw Multiple Cards from Bottom... Trek Meerdere Kaarten van Onderkant... - + Draw Arrow... Trek Pijl... - + Remove Local Arrows Verwijder Lokale Pijlen - + Leave Game Verlaat Spel - + Concede Opgeven - + Roll Dice... Werp Dobbelstenen... - + Shuffle Library Library Schudden - + + Shuffle Top Cards of Library + + + + + Shuffle Bottom Cards of Library + + + + Mulligan Mulligan - + Draw a Card Trek een Kaart - + Draw Multiple Cards... Trek Meerdere Kaarten... - + Undo Draw Kaart Trekken Ongedaan Maken - + Always Reveal Top Card Altijd Bovenste Kaart Onthullen - + Always Look At Top Card Altijd Bovenste Kaart Bekijken - + Rotate View Clockwise Draai Weergave Met de Klok Mee - + Rotate View Counterclockwise Draai Weergave Tegen de Klok In - + Unfocus Text Box Textvak Verlaten - + Focus Chat Focus Naar Chat - + Clear Chat Wis Chat - + Refresh Verversen - - Play/Pause + + Skip Forward - + + Skip Backward + + + + + Skip Forward by a lot + + + + + Skip Backward by a lot + + + + + Play/Pause + Start/Stop + + + Toggle Fast Forward + Versnelling Schakelen + + + + Visual Deck Storage + + + + + Deck Storage + + + + + Server + + + + + Account + + + + + Administration + + + + + Logs diff --git a/cockatrice/translations/cockatrice_pl.ts b/cockatrice/translations/cockatrice_pl.ts index 786c0783a..4ee1342d1 100644 --- a/cockatrice/translations/cockatrice_pl.ts +++ b/cockatrice/translations/cockatrice_pl.ts @@ -2,7 +2,7 @@ AbstractCounter - + &Set counter... &Ustaw znacznik… @@ -10,95 +10,247 @@ AbstractCounterDialog - + Set counter Ustaw znacznik - + New value for counter '%1': Nowa wartość dla znacznika '%1' - AppearanceSettingsPage + AbstractDlgDeckTextEdit - + + &Refresh + + + + + Parse Set Name and Number (if available) + + + + + AbstractTabDeckEditor + + + Open in new tab + + + + + Are you sure? + + + + + The decklist has been modified. +Do you want to save the changes? + + + + + + + + + + Error - - Could not create themes directory at '%1'. + + Could not open deck at %1 - + + Could not save remote deck + + + + + + The deck could not be saved. +Please check that the directory is writable and try again. + + + + + Save deck + + + + + The deck could not be saved. + + + + + There are no cards in your deck to be exported + + + + + No deck was selected to be exported. + + + + + AdminNotesDialog + + + Update Notes + Aktualności + + + + Admin Notes for %1 + Notatki Administracyjne dla %1 + + + + AllZonesCardAmountWidget + + + Mainboard + Mainboard + + + + Sideboard + Sideboard + + + + AppearanceSettingsPage + + + Error + Błąd + + + + Could not create themes directory at '%1'. + Nie można stworzyć folderu motywów w '%1'. + + + Theme settings Ustawienia motywu - + Current theme: Obecny motyw: - + Open themes folder - + Otwórz folder motywów - + + Menu settings + Ustawienia menu + + + + Show keyboard shortcuts in right-click menus + Pokazuj skróty klawiszowe w menu kontekstowym + + + Card rendering Rendering kart - + Display card names on cards having a picture Wyświetl nazwy kart na kartach z obrazkami - + + Auto-Rotate cards with sideways layout + Automatycznie obracaj karty horyzontalne + + + + Override all card art with personal set preference (Pre-ProviderID change behavior) [Requires Client restart] + Zastąp obrazy kart tymi z preferowanych dodatków (Zachowanie sprzed zmiany ProviderID) [Wymaga restartu kilenta] + + + + Bump sets that the deck contains cards from to the top in the printing selector + Podbijaj dodatki z których karty są w talii. + + + Scale cards on mouse over Skaluj karty po najechaniu kursorem myszy - - Minimum overlap percentage of cards on the stack and in vertical hand + + Use rounded card corners - + + Minimum overlap percentage of cards on the stack and in vertical hand + Minimalny procent zachodzenia kart na siebie na stosie oraz w ręce pionowej + + + + Maximum initial height for card view window: + Maksymalna podstawowa wysokość okna podglądu karty + + + + + rows + rzędy + + + + Maximum expanded height for card view window: + + + + Hand layout Układ ręki - + Display hand horizontally (wastes space) Wyświetl karty na ręce poziomo (marnuje miejsce) - + Enable left justification Wyrównuj karty do lewej strony pola - + Table grid layout Układ stołu - + Invert vertical coordinate Odwróć współrzędne pionowe - + Minimum player count for multi-column layout: Minimalna liczba graczy dla widoku wielokolumnowego - + Maximum font size for information displayed on cards: Maksymalny rozmiar czcionki dla informacji wyświetlanych na kartach @@ -106,112 +258,112 @@ BanDialog - + ban &user name zbanuj nazwę &użytkownika - + ban &IP address zbanuj adres &IP - + ban client I&D ID zablokowanego konta - + Ban type Rodzaj bana - + &permanent ban ban &permanentny - + &temporary ban ban &tymczasowy - + &Days: &Dni: - + &Hours: &Godziny: - + &Minutes: &Minuty: - + Duration of the ban Czas trwania bana - + Please enter the reason for the ban. This is only saved for moderators and cannot be seen by the banned person. Podaj przyczynę bana. Informacja ta zachowywana jest dla moderatorów i nie jest widoczna dla zbanowanej osoby. - + Please enter the reason for the ban that will be visible to the banned person. Podaj przyczynę bana do wglądu dla banowanej osoby. - + Redact all messages from this user in all rooms - + Redaguj wiadomości od tego użytkownika we wszystkich pokojach - + &OK &OK - + &Cancel &Anuluj - + Ban user from server Zbanuj użytkownika z serwera - - - - + + + + Error Błąd - + You have to select a name-based, IP-based, clientId based, or some combination of the three to place a ban. Aby ustawić bana, musisz wybrać nazwę, IP, ID klienta lub dowolną kombinację tej trójki. - + You must have a value in the name ban when selecting the name ban checkbox. Pole imienia musi być wypełnione dla opcji ban imienia. - + You must have a value in the ip ban when selecting the ip ban checkbox. Pole ip musi być wypełnione dla opcji ban ip. - + You must have a value in the clientid ban when selecting the clientid ban checkbox. Pole id klienta musi być wypełnione dla opcji ban id klienta. @@ -219,22 +371,22 @@ Informacja ta zachowywana jest dla moderatorów i nie jest widoczna dla zbanowan BetaReleaseChannel - - Beta Releases - Wersje Beta + + Beta + - + No reply received from the release update server. Brak odpowiedzi z serwera uaktualnień wersji. - + Invalid reply received from the release update server. Nieprawidłowa odpowiedź serwera uaktualnień wersji. - + No reply received from the file update server. Brak odpowiedzi z serwera uaktualnień plików. @@ -242,32 +394,32 @@ Informacja ta zachowywana jest dla moderatorów i nie jest widoczna dla zbanowan CardDatabaseModel - + Name Nazwa - + Sets Dodatki - + Mana cost Koszt many - + Card type Typ karty - + P/T S/W - + Color(s) Kolor(y) @@ -331,7 +483,7 @@ Informacja ta zachowywana jest dla moderatorów i nie jest widoczna dla zbanowan Mana Value - + Wartość Many @@ -360,37 +512,65 @@ Informacja ta zachowywana jest dla moderatorów i nie jest widoczna dla zbanowan - CardFrame + CardInfoFrameWidget - + Image - Karta + Ilustracja - + Description Opis - + Both Karta + opis + + + View transformation + + - CardInfoText + CardInfoPictureWidget - + + View related cards + + + + + Add card to deck + + + + + Mainboard + + + + + Sideboard + + + + + CardInfoTextWidget + + Name: Nazwa: - + Related cards: Powiązane karty: - + Unknown card: Nieznana karta: @@ -398,304 +578,774 @@ Informacja ta zachowywana jest dla moderatorów i nie jest widoczna dla zbanowan CardItem - + &Move to &Przenieść do - + &Power / toughness &Siła / Wytrzymałość + + CardSizeWidget + + + Card Size + Rozmiar Karty + + CardZone - + their hand nominative jego rękę - + %1's hand nominative ręka gracza: %1 - + their library look at zone jego bibliotekę - + %1's library look at zone bibliotekę gracza %1 - + of their library top cards of zone, swoją bibliotekę - + of %1's library top cards of zone z biblioteki gracza %1 - + their library reveal zone jego bibliotekę - + %1's library reveal zone bibliotekę gracza %1. - + their library shuffle jego bibliotekę - + %1's library shuffle bibliotekę gracza %1. - + their library nominative jego bibliotekę - + %1's library nominative bibliotekę gracza %1. - + their graveyard nominative jego cmentarz - + %1's graveyard nominative cmentarz gracza %1. - + their exile nominative jego wygnanie - + %1's exile nominative wygnanie gracza %1. - - - their sideboard - look at zone - jego bibliotekę poboczną - - - - %1's sideboard - look at zone - bibliotekę poboczną gracza %1. - - - - their sideboard - nominative - jego bibliotekę poboczną - + their sideboard + look at zone + jego bibliotekę poboczną + + + + %1's sideboard + look at zone + bibliotekę poboczną gracza %1. + + + + their sideboard + nominative + jego bibliotekę poboczną + + + %1's sideboard nominative bibliotekę poboczną gracza %1. + + DeckEditorCardInfoDockWidget + + + Card Info + + + + + DeckEditorDatabaseDisplayWidget + + + Search by card name (or search expressions) + + + + + Add to Deck + + + + + Add to Sideboard + + + + + Select Printing + + + + + Show on EDHREC (Commander) + + + + + Show on EDHREC (Card) + + + + + Show Related cards + + + + + Add card to &maindeck + + + + + Add card to &sideboard + + + + + DeckEditorDeckDockWidget + + + Banner Card + + + + + Select Printing + + + + + Deck + + + + + Deck &name: + + + + + &Comments: + + + + + Hash: + + + + + &Increment number + + + + + &Decrement number + + + + + &Remove row + + + + + Swap card to/from sideboard + + + + + DeckEditorFilterDockWidget + + + Filters + + + + + &Clear all filters + + + + + Delete selected + + + + + DeckEditorMenu + + + &Deck Editor + + + + + &New deck + + + + + &Load deck... + + + + + Load recent deck... + + + + + Clear + + + + + &Save deck + + + + + Save deck &as... + + + + + Load deck from cl&ipboard... + + + + + Edit deck in clipboard + + + + + + Annotated + + + + + + Not Annotated + + + + + Save deck to clipboard + + + + + Annotated (No set info) + + + + + Not Annotated (No set info) + + + + + &Print deck... + + + + + &Send deck to online service + + + + + Create decklist (decklist.org) + + + + + Create decklist (decklist.xyz) + + + + + Analyze deck (deckstats.net) + + + + + Analyze deck (tappedout.net) + + + + + &Close + + + + + DeckEditorPrintingSelectorDockWidget + + + Printing Selector + + + DeckEditorSettingsPage - - + + Update Spoilers Uaktualnij Spoilery - - + + Success Sukces - + Download URLs have been reset. Pobrane URL zostały zresetowane. - + Downloaded card pictures have been reset. Pobrane obrazy kart zostały zresetowane - + Error Błąd - + One or more downloaded card pictures could not be cleared. Jedno lub więcej pobranych ilustracji kart nie mogło zostać wyczyszczonych. - + Add URL Dodaj URL - - + + URL: URL: - - + + Edit URL Edytuj URL - + + Network Cache Size: + Rozmiar sieciowej pamięci podręcznej: + + + + Redirect Cache TTL: + TTL przekierowania w pamięci podręcznej: + + + + How long cached redirects for urls are valid for. + Jak długo zapisane przekierowania są ważne. + + + + Picture Cache Size: + Rozmiar Pamięci Podręcznej dla Obrazków: + + + Add New URL - + Dodaj Nowy URL - + Remove URL - + Usuń URL - + + Day(s) + Dni + + + Updating... Trwa aktualizacja... - + Choose path Wybierz ścieżkę - + URL Download Priority Priorytet Pobierania URL - + Spoilers Spoilery - + Download Spoilers Automatically Pobierz spoilery automatycznie - + Spoiler Location: Lokalizacja spoilera - + Last Change Ostatnia zmiana - + Spoilers download automatically on launch Spoilery są pobierane automatycznie w czasie premiery. - + Press the button to manually update without relaunching Wciśnij przycisk żeby ręcznie uaktualnić bez restartu. - + Do not close settings until manual update is complete Nie zamykaj ustawień dopóki aktualizacja manualna się nie skończy - + Download card pictures on the fly Pobierz ilustracje kart w locie - + How to add a custom URL Jak dodać niestandardowe URL - + Delete Downloaded Images Usuń Pobrane Ilustracje - + Reset Download URLs Resetuj Pobieranie URLi - - Downloaded images directory size: - - - - + On-disk cache for downloaded pictures - + Pamięć podręczna na dysku dla pobranych obrazków - - Picture cache size: - - - - + In-memory cache for pictures not currently on screen - + Pamięć podręczna w pamięci fizycznej dla obrazków które nie są na ekranie DeckListModel - + + Count + Ilość + + + + Set + Dodatek + + + Number Ilość - + + Provider ID + Provider ID + + + Card Karta + + DeckLoader + + + Common deck formats (%1) + + + + + All files (*.*) + + + + + DeckPreviewColorIdentityFilterWidget + + + Mode: Exact Match + + + + + Mode: Includes + + + + + DeckPreviewDeckTagsDisplayWidget + + + Edit tags ... + + + + + DeckPreviewTagDialog + + + Deck Tags Manager + + + + + Manage your deck tags. Check or uncheck tags as needed, or add new ones: + + + + + Add a new tag (e.g., Aggro️) + + + + + Add Tag + + + + + Filter tags... + + + + + OK + + + + + Cancel + + + + + Invalid Input + + + + + Tag name cannot be empty! + + + + + Duplicate Tag + + + + + This tag already exists. + + + + + DeckPreviewWidget + + + Banner Card + + + + + Open in deck editor + + + + + Edit Tags + + + + + Rename Deck + + + + + Save Deck to Clipboard + + + + + Annotated + + + + + Annotated (No set info) + + + + + Not Annotated + + + + + Not Annotated (No set info) + + + + + Rename File + + + + + Delete File + + + + + Set Banner Card + + + + + + New name: + + + + + + Error + + + + + Rename failed + + + + + Delete file + + + + + Are you sure you want to delete the selected file? + + + + + Delete failed + + + DeckStatsInterface @@ -713,181 +1363,217 @@ Informacja ta zachowywana jest dla moderatorów i nie jest widoczna dla zbanowan DeckViewContainer - + Load deck... Wczytaj talię... - + Load remote deck... Wczytaj zdalną talię... - + + Unload deck + Unload deck... + + + + Ready to start Gotowe do rozpoczęcia - + + Force start + Wymuś rozpoczęcie + + + Sideboard unlocked Biblioteka poboczna odblokowana - + Sideboard locked Biblioteka poboczna zablokowana - - Load deck - Wczytaj talię - - - + Error Błąd - + The selected file could not be loaded. Wybrany plik nie mógł zostać wczytany. + + DialogConvertDeckToCodFormat + + + Deck Format Conversion + + + + + You tried to add a tag to a .txt format deck. + Tags can only be added to .cod format decks. + Do you want to convert the deck to the .cod format? + + + + + Remember and automatically apply choice in the future + + + DlgConnect - + Downloading... Pobieranie... - + Known Hosts Znane Hosty - + + Delete the currently selected saved server + Usuń zaznaczony zapisany serwer + + + Refresh the server list with known public servers Odśwież listę serwerów ze znanymi serwerami publicznymi - + New Host Nowy Host - + Name: Nazwa: - + &Host: &Host: - + &Port: &Port: - + Player &name: &Nazwa gracza: - + P&assword: H&asło: - + &Save password Zapi&sz hasło - + A&uto connect Połącz a&utomatycznie - + Automatically connect to the most recent login when Cockatrice opens Automatycznie połącz ostatnim loginem przy otwarciu Cockatrice - + If you have any trouble connecting or registering then contact the server staff for help! Jeżeli masz problem z połączeniem lub rejestracją, skontaktuj się z obsługą serwera! - - + + Webpage Strona internetowa - + Reset Password - + Zresetuj hasło - + + Forgot password? + Zapomniałeś hasła? + + + &Connect &Połącz - + Server Serwer - + Login Login - + Server Contact Kontakt z Serwerem - + Connect to Server Połącz z Serwerem - + Server URL Serwer URL - + Communication Port Port Komunikacyjny - + Unique Server Name Unikalna Nazwa Serwera - + Connection Warning Ostrzeżenie o połączeniu - + You need to name your new connection profile. Musisz nazwać swój nowy profil połączenia - + Connect Warning Ostrzeżenie połączenia - + The player name can't be empty. Nazwa użytkownika nie może być pusta @@ -962,7 +1648,7 @@ Informacja ta zachowywana jest dla moderatorów i nie jest widoczna dla zbanowan Create game as spectator - + Stwórz grę jako widz @@ -970,27 +1656,37 @@ Informacja ta zachowywana jest dla moderatorów i nie jest widoczna dla zbanowan Widzowie - + + Starting life total: + Początkowa ilość życia: + + + + Game setup options + Opcje tworzenia gry + + + &Clear &Wyczyść - + Create game Stwórz grę - + Game information Informacje o grze - + Error Błąd - + Server error. Błąd serwera. @@ -998,92 +1694,92 @@ Informacja ta zachowywana jest dla moderatorów i nie jest widoczna dla zbanowan DlgCreateToken - + &Name: &Nazwa: - + Token Token - + C&olor: &Kolor: - + white biały - + blue niebieski - + black czarny - + red czerwony - + green zielony - + multicolor wielokolorowy - + colorless bezkolorowy - + &P/T: &S/W: - + &Annotation: &Adnotacja: - + &Destroy token when it leaves the table &Zniszcz tokena kiedy opuści stół - + Token data Dane tokena - + Show &all tokens Pok&azuj wszystkie tokeny - + Show tokens from this &deck Pokazuj tokeny z tej &talii - + Choose token from list Wybierz token z listy: - + Create token Tworzenie tokena @@ -1129,6 +1825,24 @@ By usunąć twojego bieżącego awatara potwierdź bez wybierania nowego obrazka Nieprawidłowy obraz. + + DlgEditDeckInClipboard + + + Edit deck in clipboard + + + + + Error + + + + + Invalid deck list. + + + DlgEditPassword @@ -1160,7 +1874,7 @@ By usunąć twojego bieżącego awatara potwierdź bez wybierania nowego obrazka Your password is too short. - + Twoje hasło jest za krótkie. @@ -1171,93 +1885,93 @@ By usunąć twojego bieżącego awatara potwierdź bez wybierania nowego obrazka DlgEditTokens - + &Name: &Nazwa: - + C&olor: K&olor: - + white biały - + blue niebieski - + black czarny - + red czerwony - + green zielony - + multicolor multikolorowy - + colorless bezkolorowy - + &P/T: &S/W: - + &Annotation: &Adnotacja: - + Token data Dane tokena - - + + Add token Dodaj token - + Remove token Usuń token - + Edit custom tokens Edytuj niestandardowe żetony - + Please enter the name of the token: Wprowadź nazwę tokena: - + Error Błąd - + The chosen name conflicts with an existing card or token. Make sure to enable the 'Token' set in the "Manage sets" dialog to display them correctly. Wybrana nazwa koliduje z istniejącą kartą lub tokenem. @@ -1295,132 +2009,132 @@ Upewnij się że aktywowano 'Token' w oknie "Zarządzaj dodatkami DlgFilterGames - + no limit - + bez limitu + + + + 5 minutes + 5 minut + + + + 10 minutes + 10 minut - 5 minutes - + 30 minutes + 30 minut - 10 minutes - + 1 hour + 1 godzina - 30 minutes - - - - - 1 hour - + 2 hours + 2 godziny - 2 hours + Hide 'buddies only' games - - Show '&buddies only' games - Pokaż gry '&tylko kolesie' - - - - Show &full games + + Hide full games - - Show games &that have started + + Hide games that have started - - Show &password protected games - &Pokaż gry wymagające hasła - - - - Hide '&ignored user' games + + Hide password protected games - + + Hide 'ignored user' games + + + + &Newer than: - + &Nowsze niż: - + Game &description: &Opis gry - + &Creator name: Założy&ciel gry - + General Generalne - + &Game types Typy roz&grywki - + at &least: co &najmniej - + at &most: co naj&wyżej - + Maximum player count Maksymalna liczba graczy - + Restrictions Ograniczenia - + Show games only if &spectators can watch - + Pokazuj gry tylko jeśli &widzowie mogą oglądać + + + + Show spectator password p&rotected games + Pokazuj gry wymagające hasła dla widzów - Show spectator password p&rotected games - + Show only if spectators can ch&at + Pokazuj gry tylko jeśli widzowie mogą rozm&awiać - Show only if spectators can ch&at - - - - Show only if spectators can see &hands - + Pokazuj gry tylko jeśli widzowie mogą oglądać &ręce - + Spectators - + Widzowie - + Filter games Filtruj gry @@ -1430,17 +2144,17 @@ Upewnij się że aktywowano 'Token' w oknie "Zarządzaj dodatkami Reset Password Challenge Warning - + Ostrzeżenie w procesie przypominania hasła A problem has occurred. Please try to request a new password again. - + Wystąpił problem. Poproś o nowe hasło ponownie. Enter the information of the server and the account you'd like to request a new password for. - + Wpisz informacje serwera oraz konta, do którego chciał(a)byś stworzyć nowe hasło. @@ -1465,12 +2179,12 @@ Upewnij się że aktywowano 'Token' w oknie "Zarządzaj dodatkami Reset Password Challenge - + Proces przypominania hasła Reset Password Challenge Error - + Błąd w procesie przypominania hasła @@ -1483,7 +2197,7 @@ Upewnij się że aktywowano 'Token' w oknie "Zarządzaj dodatkami Enter the information of the server you'd like to request a new password for. - + Wpisz informacje serwera na którym chciał(a)byś stworzyć nowe hasło. @@ -1503,12 +2217,12 @@ Upewnij się że aktywowano 'Token' w oknie "Zarządzaj dodatkami Reset Password Request - + Żądanie Resetu Hasła Reset Password Error - + Błąd Resetu Hasła @@ -1521,17 +2235,17 @@ Upewnij się że aktywowano 'Token' w oknie "Zarządzaj dodatkami Reset Password Warning - + Ostrzeżenie Resetu Hasła A problem has occurred. Please try to request a new password again. - + Wystąpił problem. Poproś o nowe hasło ponownie. Enter the received token and the new password in order to set your new password. - + Wpisz otrzymany token oraz nowe hasło aby je ustawić. @@ -1562,7 +2276,7 @@ Upewnij się że aktywowano 'Token' w oknie "Zarządzaj dodatkami Reset Password - + Zresetuj hasło @@ -1575,7 +2289,7 @@ Upewnij się że aktywowano 'Token' w oknie "Zarządzaj dodatkami Reset Password Error - + Błąd Resetu Hasła @@ -1590,12 +2304,12 @@ Upewnij się że aktywowano 'Token' w oknie "Zarządzaj dodatkami Error - + Błąd Your password is too short. - + Twoje hasło jest za krótkie. @@ -1603,27 +2317,28 @@ Upewnij się że aktywowano 'Token' w oknie "Zarządzaj dodatkami Hasła nie są zgodne. + + DlgLoadDeck + + + Load Deck + + + DlgLoadDeckFromClipboard - - &Refresh - &Odśwież - - - + Load deck from clipboard Wczytaj talię ze schowka - - + Error Błąd - - + Invalid deck list. Nieprawidłowa lista talii. @@ -1631,18 +2346,57 @@ Upewnij się że aktywowano 'Token' w oknie "Zarządzaj dodatkami DlgLoadRemoteDeck - + Load deck Wczytaj talię + + DlgMoveTopCardsUntil + + + Card name (or search expressions): + Nazwa karty (lub wyrażenie wyszukiwania): + + + + Number of hits: + Ilość trafień: + + + + Auto play hits + Automatycznie graj trafienia + + + + Put top cards on stack until... + Kładź karty z wierzchu na stosie dopóki... + + + + No cards matching the search expression exists in the card database. Proceed anyways? + W bazie kart nie znaleziono kart które spełniają twoje wyrażenie. Kontynuować mimo to? + + + + Cockatrice + Cockatrice + + + + Invalid filter + Nieprawidłowy filtr + + DlgRegister Enter your information and the information of the server you'd like to register to. Your email will be used to verify your account. - + Wpisz swoje informacje oraz informacje serwera do którego chciałbyś się zarejestrować. +Twój email zostanie użyty aby zweryfikować twoje konto. @@ -1710,7 +2464,7 @@ Your email will be used to verify your account. Your password is too short. - + Twoje hasło jest za krótkie. @@ -1733,28 +2487,28 @@ Your email will be used to verify your account. Number of sides: - + Liczba ścian: Number of dice: - + Liczba kości: Roll Dice - + Rzuć kośćmi DlgSettings - + Unknown Error loading card database W trakcie wczytywania bazy kart wystąpił nieznany błąd - + Your card database is invalid. Cockatrice may not function correctly with an invalid database @@ -1771,7 +2525,7 @@ Może istnieć potrzeba uruchomienia Oracle w celu uaktualnienia bazy kart. Czy chcesz zmienić ustawienia położenia bazy kart? - + Your card database version is too old. This can cause problems loading card information or images @@ -1788,16 +2542,20 @@ Zwykle można temu zaradzić poprzez uruchomienie narzędzia Oracle i uaktualnie Czy chcesz zmienić ustawienia położenia bazy kart? - + Your card database did not finish loading Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues with your cards.xml attached Would you like to change your database location setting? - + Baza kart nie została wczytana do końca. + +Proszę wypełnić zgłoszenie błędu pod adresem https://github.com/Cockatrice/Cockatrice/issues z twoim cards.xml. + +Czy chcesz zmienić ustawienia położenia bazy kart? - + File Error loading your card database. Would you like to change your database location setting? @@ -1806,7 +2564,7 @@ Would you like to change your database location setting? Czy chcesz ustawić nową lokalizację bazy kart? - + Your card database was loaded but contains no cards. Would you like to change your database location setting? @@ -1815,68 +2573,72 @@ Would you like to change your database location setting? Czy chcesz ustawić nową lokalizację bazy kart? - + Unknown card database load status Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues Would you like to change your database location setting? - + Nieznany status ładowania bazy kart + +Proszę wypełnić zgłoszenie błędu pod adresem https://github.com/Cockatrice/Cockatrice/issues + +Czy chcesz zmienić ustawienia położenia bazy kart? - - - + + + Error Błąd - + The path to your deck directory is invalid. Would you like to go back and set the correct path? Ścieżka dostępu do twojego katalogu z taliami jest nieprawidłowa. Czy chcesz wrócić i ustawić poprawną ścieżkę? - + The path to your card pictures directory is invalid. Would you like to go back and set the correct path? Ścieżka dostępu do twojego katalogu z obrazkami jest nieprawidłowa. Czy chcesz wrócić i ustawić poprawną ścieżkę? - + Settings Ustawienia - + General Ogólne - + Appearance Wygląd - + User Interface Interfejs - + Card Sources Źródło Karty - + Chat Czat - + Sound Dźwięk - + Shortcuts Skróty @@ -1902,166 +2664,169 @@ Would you like to change your database location setting? DlgUpdate - + Current release channel Kanał obecnej wersji - + Reinstall Przeinstaluj - + Cancel Download Anuluj Pobieranie - + Open Download Page Otwórz stronę pobierań - + Check for Client Updates - + Sprawdź aktualizacje klienta - - - - + + + + Error Błąd - + Cockatrice was not built with SSL support, therefore you cannot download updates automatically! Please visit the download page to update manually. Cockatrice nie wspiera SSL, dlatego automatyczne pobieranie uaktualnień nie jest możliwe! Odwiedź stronę pobierania żeby uaktualnić ręcznie. - - Downloading update... - Pobieram uaktualnienie... + + Downloading update: %1 + Downloading update... + - + Checking for updates... Sprawdzam aktualizacje... - + Finished checking for updates Skończyłem sprawdzać aktualizacje - + No Update Available Brak nowych aktualizacji - + Cockatrice is up to date! Cockatrice jest aktualny! - + You are already running the latest version available in the chosen release channel. Używasz obecnie najnowszej dostępnęj wersji w wybranym kanale wydania - + Current version Obecna wersja - + Selected release channel Wybierz kanał wydania - - + + Update Available Dostępna aktualizacja - - + + A new version of Cockatrice is available! Nowa wersja Cockatrice jest dostępna! - - + + New version Nowa wersja - - + + Released Wydania - - + + Changelog Zmiany - + Do you want to update now? Chcesz teraz zaktualizować? - - Unfortunately there are no download packages available for your operating system. -You may have to build from source yourself. - Niestety nie ma pakietów dla Twojego systemu operacyjnego. -Być może konieczne jest samodzielne tworzenie ze źródła. + + Unfortunately, the automatic updater failed to find a compatible download. +You may have to manually download the new version. + Unfortunately there are no download packages available for your operating system. +You may have to build from source yourself. + - - Please check the download page manually and visit the wiki for instructions on compiling. - Sprwadź stronę pobierania ręcznie i zajrzyj na wiki po instrukcje kompilacji. + + Please check the <a href="%1">releases page</a> on our Github and download the build for your system. + Please check the download page manually and visit the wiki for instructions on compiling. + - - - + + + Update Error Błąd aktualizacji - + An error occurred while checking for updates: Wystąpił błąd podczas sprawdzania aktualizacji: - + An error occurred while downloading an update: Wystąpił błąd podczas pobierania aktualizacji: - + Installing... Instaluję... - + Cockatrice is unable to open the installer. Cockatrice nie może otworzyć instalatora. - + Try to update manually by closing Cockatrice and running the installer. Spróbuj zaktualizować ręcznie poprzez zamknięcie Cockatrice i włączenie instalatora. - + Download location Lokalizacja pobierania @@ -2079,6 +2844,14 @@ Być może konieczne jest samodzielne tworzenie ze źródła. Log Debugera + + EdhrecCommanderResponseCommanderDetailsDisplayWidget + + + Salt: + + + FilterBuilder @@ -2090,105 +2863,120 @@ Być może konieczne jest samodzielne tworzenie ze źródła. GameSelector - - - - - - - - - + + + + + + + + + Error Błąd - + Please join the appropriate room first. Proszę najpierw dołączyć do właściwego pokoju. - + Wrong password. Nieprawidłowe hasło. - + Spectators are not allowed in this game. Ta rozgrywka nie dopuszcza widzów. - + The game is already full. Ta rozgrywka jest już pełna. - + The game does not exist any more. Ta rozgrywka już nie istnieje. - + This game is only open to registered users. Ta rozgrywka jest dostępna tylko dla zarejestrowanych użytkowników. - + This game is only open to its creator's buddies. Ta rozgrywka jest dostępna tylko dla znajomych jej twórcy. - + You are being ignored by the creator of this game. Twórca tej rozgrywki cię ignoruje. - + + Join Game + + + + + Spectate Game + + + + + Game Information + + + + Join game Dołącz do gry - + Password: Hasło: - + Please join the respective room first. Proszę najpierw dołączyć do odpowiedniego pokoju. - + &Filter games &Filtr rozgrywek - + C&lear filter Usuń fi&ltr - + C&reate Utwó&rz - + &Join &Dołącz - + J&oin as spectator D&ołącz jako widz - + Games shown: %1 / %2 Gra pokazana: %1 / %2 - + Games Gry @@ -2196,100 +2984,100 @@ Być może konieczne jest samodzielne tworzenie ze źródła. GamesModel - + >1 day - + >1 dzień - + %1%2 hr short age in hours - + %1%2 godz%1%2 godz%1%2 godz%1%2 godz - + new - + Nowa - + %1%2 min short age in minutes - + %1%2 min%1%2 min%1%2 min%1%2 min - + password hasło - + buddies only tylko znajomi - + reg. users only tylko zarejestrowani użytkownicy - - + + can chat mogą rozmawiać - + see hands widzą ręce - + can see hands widzą ręce - + not allowed niedozwoleni - + Room Pokój - + Age Wiek - + Description Opis - + Creator Twórca - + Type Typ - + Restrictions Ograniczenia - + Players Gracze - + Spectators Widzowie @@ -2297,92 +3085,102 @@ Być może konieczne jest samodzielne tworzenie ze źródła. GeneralSettingsPage - + Reset all paths - + Resetuj wszystkie ścieżki - + All paths have been reset - + Wszystkie ścieżki zostały zresetowane - - + + Choose path Wybierz ścieżkę - + Personal settings Ustawienia osobiste - + Language: Język: - + Paths (editing disabled in portable mode) Scieżki (edytowanie wyłączone w trybie przenośnym) - + Paths Ścieżki - + + How to help with translations + + + + Decks directory: Katalog z taliami: - + Replays directory: Katalog z powtórkami: - + Pictures directory: Katalog z obrazkami: - + Card database: Baza kart: - + Custom database directory: - + Niestandardowy folder z bazą: - + Token database: Baza tokenów: - + Update channel Aktualizuj kanał - + + Check for client updates on startup + Sprawdzaj aktualizacje klienta przy uruchomieniu + + + Notify if a feature supported by the server is missing in my client Powiadom jeśli właściwości wspierane przez serwer, brakują w kliencie - + Automatically run Oracle when running a new version of Cockatrice Automatycznie uruchom Oracle kiedy uruchomiona jest nowa wersja Cockatrice - + Show tips on startup Pokazuj wskazówki po uruchomieniu @@ -2390,61 +3188,61 @@ Być może konieczne jest samodzielne tworzenie ze źródła. MainWindow - - + + The server has reached its maximum user capacity, please check back later. Ten serwer osiągnął maksymalną liczbę użytkowników, proszę sprawdzić później. - + There are too many concurrent connections from your address. Trwa zbyt wiele równoległych połączeń z twojego adresu. - + Banned by moderator Zbanowany przez moderatora. - + Expected end time: %1 Oczekiwany czas zakończenia: %1 - + This ban lasts indefinitely. Ten ban jest bezterminowy. - + Scheduled server shutdown. Planowe wyłączenie serwera. - - + + Invalid username. Nieprawidłowa nazwa użytkownika. - + You have been logged out due to logging in at another location. Zostałeś wylogowany ze względu inny proces logowania. - + Connection closed Połączenie zakończone - + The server has terminated your connection. Reason: %1 Serwer zakończył twoje połączenie. Przyczyna: %1 - + The server is going to be restarted in %n minute(s). All running games will be lost. Reason for shutdown: %1 @@ -2459,557 +3257,567 @@ Wszystkie trwające gry zostaną utracone. Przyczyna zamknięcia: %1 - + Scheduled server shutdown Planowe wyłączenie serwera - - + + Success Operacja zakończona pomyślnie - + Registration accepted. Will now login. Rejestracja zaakceptowana. Rozpocznij logowanie. - + Account activation accepted. Will now login. Aktywacja konta zakończona pomyślnie. Rozpocznij logowanie. - + Number of players Liczba graczy - + Please enter the number of players. Wprowadź liczbę graczy. - - + + Player %1 Gracz %1 - + Load replay Wczytaj powtórkę - + About Cockatrice O Cockatrice - + Version Wersja - + Cockatrice Webpage Oficjalna strona Cocatrice - + Project Manager: Menadżer projektu: - + Past Project Managers: Poprzedni menadżerowie projektu: - + Developers: Twórcy: - + Our Developers Lista współpracowników - + Help Develop! Dołącz do grona twórców! - + Translators: Tłumacze: - + Our Translators Nasi Tłumacze: - + Help Translate! Pomóż w tłumaczeniu! - + Support: Wsparcie: - + Report an Issue Zgłoś problem - + Troubleshooting Rozwiązywanie problemów - + F.A.Q. Najczęściej zadawane pytania - - - - - - - - - - - - - - - + + + + + + + + + + + + + - - - + + + + + Error Błąd - + Server timeout Upłynął limit czasu odpowiedzi serwera - + Failed Login Nie udało się zalogować - + Your client seems to be missing features this server requires for connection. Twój klient prawdopodobnie nie spełnia wymagań połączenia z tym serwerem. - + To update your client, go to 'Help -> Check for Client Updates'. Aby zaktualizować twojego klienta, pójdź do 'Pomoc -> Sprawdź ustawienia klienta'. - + Incorrect username or password. Please check your authentication information and try again. Nieprawidłowa nazwa użytkownika lub hasło. Sprawdź swoje dane weryfikacyjne i spróbuj ponownie. - + There is already an active session using this user name. Please close that session first and re-login. Istnieje już aktywna sesja dla tej nazwy użytkownika. Zakończ tamtą sesję i zaloguj się ponownie. - - + + You are banned until %1. Twój ban trwa do: %1. - - + + You are banned indefinitely. Zostałeś zbanowany bezterminowo. - + This server requires user registration. Do you want to register now? Ten serwer wymaga rejestracji użytkownika. Chcesz się teraz zarejestrować? - + This server requires client IDs. Your client is either failing to generate an ID or you are running a modified client. Please close and reopen your client to try again. Ten serwer potrzebuje ID klienta. Twój klient nie może wygenerować ID lub używasz zmodyfikowanego klienta. Proszę zamknij i otwórz ponownie swojego klienta aby spróbować ponownie. - + An internal error has occurred, please close and reopen Cockatrice before trying again. If the error persists, ensure you are running the latest version of the software and if needed contact the software developers. Doszło do wewnętrznego błędu, proszę zamknąć i ponownie otworzyć Cockatrice przed ponowną próbą. Jeśli błąd będzie się utrzymywać, upewnij się, że używasz najnowszej wersji programu oraz jeśli potrzebne skontaktuj się z deweloperami programu. - + Account activation Aktywacja konta - + Your account has not been activated yet. You need to provide the activation token received in the activation email. Twoje konto nie zostało jeszcze aktywowane. Musisz podać token aktywujący, otrzymany w wiadomości email. - + Server Full Serwer pełny - + Unknown login error: %1 Nieznany błąd logowania: %1 - - + + This usually means that your client version is out of date, and the server sent a reply your client doesn't understand. To zwykle oznacza że wersja klienta jest nieaktualna i serwer wysłał odpowiedź, której twój klient nie rozpoznaje. - + Your username must respect these rules: Twoja nazwa użytkownika musi być zgodna z poniższymi regułami: - + is %1 - %2 characters long %1 jest o %2 znaków za długi - + can %1 contain lowercase characters może zawierać 1% małych liter - - - - + + + + NOT NIE - + can %1 contain uppercase characters może zawierać %1 dużych liter - + can %1 contain numeric characters może zawierać %1 cyfr - + can contain the following punctuation: %1 Może zawierać następujące interpunkcje: %1 - + first character can %1 be a punctuation mark Pierwszy znak może zawierać %1 - + no unacceptable language as specified by these server rules: note that the following lines will not be translated - + zasady serwera nie pozwalają na takie słownictwo: - + can not contain any of the following words: %1 Nie można używać żadnego z podanych wyrazów: %1 - + can not match any of the following expressions: %1 Nie pasuje to żadnego z podanych wyrażeń: %1 - + You may only use A-Z, a-z, 0-9, _, ., and - in your username. Możesz użyć tylko tych znaków: A-Z, a-z, 0-9, _, ., i - w nazwie użytkownika. - - - - - - + + + + + + Registration denied Rejestracja nieudana - + Registration is currently disabled on this server Rejestracja jest obecnie wyłączona na tym serwerze. - + There is already an existing account with the same user name. Istnieje już konto o tej nazwie. - + It's mandatory to specify a valid email address when registering. Podanie poprawnego adresu email jest wymagane przy rejestracji. - + It appears you are attempting to register a new account on this server yet you already have an account registered with the email provided. This server restricts the number of accounts a user can register per address. Please contact the server operator for further assistance or to obtain your credential information. Zdaje się że próbujesz zarejestrować nowe konto na tym serwerze, posiadając już zarejestrowane konto dla tego adresu. Ten serwer ogranicza liczbę kont, które można zarejestrować dla pojedynczego adresu. Skontaktuj się z operatorem serwera w celu uzyskania pomocy lub otrzymania twoich poufnych danych. - + Password too short. Hasło jest zbyt krótkie. - + Registration failed for a technical problem on the server. Rejestracja nie powiodła się z powodów technicznych na serwerze. - + Unknown registration error: %1 Nieznany błąd rejestracji: %1 - + Account activation failed Aktywacja konta zakończona niepowodzeniem - + Socket error: %1 Błąd gniazda: %1 - + You are trying to connect to an obsolete server. Please downgrade your Cockatrice version or connect to a suitable server. Local version is %1, remote version is %2. Próbujesz połączyć się z nieaktualnym serwerem. Zainstaluj starszą wersję Cockatrice lub wybierz odpowiedni serwer. Lokalna wersja to %1, wersja zdalna to %2. - + Your Cockatrice client is obsolete. Please update your Cockatrice version. Local version is %1, remote version is %2. Twój klient Cockatrice jest nieaktualny. Uaktualnij Cockatrice do nowszej wersji. Lokalna wersja to %1, wersja zdalna to %2. - + Connecting to %1... %1 — łączenie… - + Registering to %1 as %2... Rejestrowanie do %1 jako %2... - + Disconnected Rozłączony - + Connected, logging in at %1 Połączenie ustanowione; logowanie jako %1 - - - + + + Requesting forgotten password to %1 as %2... Proszenie o zapomniane hasło do %1 jako %2... - + &Connect... &Połącz… - + &Disconnect &Rozłącz - + Start &local game... Rozpocznij &lokalną grę… - + &Watch replay... &Obejrzyj powtórkę… - - &Deck editor - &Edytor talii - - - + &Full screen Pełny ekra&n - + &Register to server... &Zarejestruj się... - + &Restore password... - + &Przywróć hasło... - + &Settings... &Ustawienia… - + &Exit &Wyjdź - + A&ctions Ak&cje - + &Cockatrice &Cockatrice - + C&ard Database B&aza kart - + &Manage sets... &Zarządzaj dodatkami... - + Edit custom &tokens... Edytuj niestandardowe %tokens... - + Open custom image folder Otwórz folder ze spersonalizowanymi obrazkami - + Open custom sets folder Otwórz folder ze spersonalizowanymi setami - + Add custom sets/cards Dodaj spersonalizowane sety/karty - + + Reload card database + Przeładuj bazę kart + + + + Tabs + + + + &Help Po&moc - + &About Cockatrice &O Cockatrice - + &Tip of the Day &Wskazówka na Dziś - + Check for Client Updates Sprawdź uaktualnienia klienta - + Check for Card Updates... - + Uaktualnij bazę kart... - + View &Debug Log + Pokaż log &debugowania + + + + Open Settings Folder - + Show/Hide - + Pokaż/Ukryj - + New Version Nowa Wersja - + Congratulations on updating to Cockatrice %1! Oracle will now launch to update your card database. Gratulacje z powodu aktualizacji do Cockatrice %1! Oracle uruchomi się teraz aby zaktualizować bazę danych kart. - + Cockatrice installed Cockatrice zainstalowany - + Congratulations on installing Cockatrice %1! Oracle will now launch to install the initial card database. Gratulacje z powodu instalacji Cockatrice %1! Oracle uruchomi się teraz aby zainstalować początkowe bazy danych kart. - + Card database Baza kart - + Cockatrice is unable to load the card database. Do you want to update your card database now? If unsure or first time user, choose "Yes" @@ -3018,46 +3826,54 @@ Czy chcesz teraz uaktualnić bazę kart? Jeżeli nie masz pewności lub jesteś nowym użytkownikiem, wybierz "Tak" - - + + Yes Tak - - + + No Nie - + Open settings Otwórz ustawienia - + New sets found Znaleziono nowe dodatki - + %n new set(s) found in the card database Set code(s): %1 Do you want to enable it/them? - + Liczba nowych dodatków w bazie kart :%n +Kod dodatku: %1 +Czy chcesz go teraz uaktywnić?Liczba nowych dodatków w bazie kart :%n +Kody dodatków: %1 +Czy chcesz je teraz uaktywnić?Liczba nowych dodatków w bazie kart :%n +Kody dodatków: %1 +Czy chcesz je teraz uaktywnić?Liczba nowych dodatków w bazie kart :%n +Kody dodatków: %1 +Czy chcesz je teraz uaktywnić? - + View sets Pokaż dodatki - + Welcome Witaj - + Hi! It seems like you're running this version of Cockatrice for the first time. All the sets in the card database have been enabled. Read more about changing the set order or disabling specific sets and consequent effects in the "Manage Sets" dialog. @@ -3066,58 +3882,59 @@ Wszystkie dodatki w bazie kart zostały aktywowane. Przeczytaj więcej na temat zmiany kolejności czy wyłączania poszczególnych dodatków i tym podobnych w oknie "Zarządzaj Dodatkami". - - + + Information Informacja - + A card database update is already running. Aktualizacja bazy kart jest w trakcie - + Unable to run the card database updater: Nie można uruchomić aktualizacji bazy kart: - - failed to start. - nie udało się uruchomić + + Failed to start. The file might be missing, or permissions might be incorrect. + - - crashed. - wystąpił błąd + + The process crashed some time after starting successfully. + - - timed out. - przekroczono limit czasu + + Timed out. The process took too long to respond. The last waitFor...() function timed out. + - - write error. - błąd zapisu. + + An error occurred when attempting to write to the process. For example, the process may not be running, or it may have closed its input channel. + - - read error. - błąd odczytu. + + An error occurred when attempting to read from the process. For example, the process may not be running. + - - unknown error. - nieznany błąd. + + Unknown error occurred. + - - The card database updater exited with an error: %1 - Aktualizacja bazy kart została przerwana przez błąd: %1 + + The card database updater exited with an error: +%1 + - + This server supports additional features that your client doesn't have. This is most likely not a problem, but this message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -3128,55 +3945,55 @@ Prawdopodobnie nie jest to problem, ale ta wiadomośc może oznaczać że jest d Aby aktualizować klienta, otwórz okno Pomoc -> Sprawdź Aktualizacje - - - - - + + + + + Load sets/cards Wczytaj dodatki/karty - + Selected file cannot be found. Wybrany plik nie został znaleziony. - + You can only import XML databases at this time. Możesz zaimportować jedynie bazy danych XML w tym momencie. - + The new sets/cards have been added successfully. Cockatrice will now reload the card database. Nowe dodatki/karty dodane pomyślnie. Cockatrice ponownie załaduje teraz bazę danych. - + Sets/cards failed to import. Dodatki/karty nie mogły być zaimportowane. - - - + + + Reset Password - + Zresetuj hasło - + Your password has been reset successfully, you can now log in using the new credentials. Twoje hasło zostały pomyślnie zresetowane, możesz teraz zalogować się używając nowego uwierzytelniania. - + Failed to reset user account password, please contact the server operator to reset your password. Nie udało się zresetować hasła użytkownika, skontaktuj się z operatorem serwera w celu wykonania resetu. - + Activation request received, please check your email for an activation token. Otrzymano żądanie aktywacji, odbierz token aktywujący w swojej skrzynce email. @@ -3276,12 +4093,12 @@ Cockatrice ponownie załaduje teraz bazę danych. %1 can now look at top card %2 at any time. - + %1 może teraz patrzeć na kartę z wierzchu %2 w każdej chwili. %1 no longer can look at top card %2 at any time. - + %1 nie może już patrzeć na kartę z wierzchu %2 w każdej chwili. @@ -3381,7 +4198,7 @@ Cockatrice ponownie załaduje teraz bazę danych. %1 puts %2 into play%3 face down. - + %1 zagrywa kartę %2 na pole bitwy%3 koszulką do góry. @@ -3411,7 +4228,7 @@ Cockatrice ponownie załaduje teraz bazę danych. %1 puts %2%3 onto the bottom of their library. - + %1 przenosi %2 %3 na dno swojej biblioteki. @@ -3436,12 +4253,12 @@ Cockatrice ponownie załaduje teraz bazę danych. %1 tries to draw from an empty library - + %1 próbuje dobrać z pustej biblioteki %1 draws %2 card(s). - + %1 dobiera %2 kartę.%1 dobiera %2 kart.%1 dobiera %2 kart.%1 dobiera %2 kart. @@ -3450,181 +4267,192 @@ Cockatrice ponownie załaduje teraz bazę danych. - %1 is looking at the top %3 card(s) %2. + %1 is looking at the %4 %3 card(s) %2. + %1 is looking at the top %3 card(s) %2. top card for singular, top %3 cards for plural - + + bottom + + + + + top + + + + %1 turns %2 face-down. %1 kładzie %2 grzbietem do góry. - + %1 turns %2 face-up. %1 kładzie %2 grzbietem do dołu. - + The game has been closed. Gra została zakończona. - + The game has started. Gra została rozpoczęta. - + %1 has joined the game. %1 dołączył do gry. - + %1 is now watching the game. %1 obserwuje grę. - + You have been kicked out of the game. Wyrzucono cię z gry. - + %1 has left the game (%2). %1 opuścił grę (%2). - + %1 is not watching the game any more (%2). %1 przestał oglądać grę (%2). - + %1 is not ready to start the game any more. %1 nie jest już gotowy do rozpoczęcia gry. - + %1 shuffles their deck and draws a new hand of %2 card(s). - + %1 tasuje swoją talie i dobiera nową rękę z %2 kartą.%1 tasuje swoją talie i dobiera nową rękę z %2 kartami.%1 tasuje swoją talie i dobiera nową rękę z %2 kartami.%1 tasuje swoją talie i dobiera nową rękę z %2 kartami. - + %1 shuffles their deck and draws a new hand. %1 tasuje swoją talie i dobiera nową rękę. - + You are watching a replay of game #%1. Oglądasz powtórkę gry #%1. - + %1 is ready to start the game. %1 jest gotowy do rozpoczęcia gry. - + cards an unknown amount of cards karty - + %1 card(s) a card for singular, %1 cards for plural - + jedną kartę%1 kart%1 kart%1 kart - + %1 lends %2 to %3. - + %1 pożycza %2 graczowi %3. - + %1 reveals %2 to %3. %1 pokazuję kartę %2 graczowi %3. - + %1 reveals %2. %1 pokazuję kartę %2. - + %1 randomly reveals %2%3 to %4. %1 pokazuje losowo kartę %2 %3 graczowi %4. - + %1 randomly reveals %2%3. %1 pokazuje losowo kartę %2 %3. - + %1 peeks at face down card #%2. %1 spogląda na kartę odwróconą koszulką do góry #%2. - + %1 peeks at face down card #%2: %3. %1 spogląda na kartę odwróconą koszulką do góry #%2: %3. - + %1 reveals %2%3 to %4. %1 pokazuje kartę %2 %3 graczowi %4. - + %1 reveals %2%3. %1 pokazuje kartę %2 %3. - + %1 reversed turn order, now it's %2. %1 odwrócona kolejność tur, teraz %2 - + reversed odwrócone - + normal normalne - + Heads Orzeł - + Tails Reszka - + %1 flipped a coin. It landed as %2. %1 podrzucił monetą. Wylądowała na %2. - + %1 rolls a %2 with a %3-sided die. %1 rzuca %2 kostką %3-ścienną. - + %1 flips %2 coins. There are %3 heads and %4 tails. - + %1 rzuca %2 monetami. Wypadło %3 orłów i %4 reszek. - + %1 rolls a %2-sided dice %3 times: %4. - + %1 rzuca %2-ścienną kostką %3 razy: %4. @@ -3649,17 +4477,17 @@ Cockatrice ponownie załaduje teraz bazę danych. red counter(s) - + czerwony znacznikczerwonych znacznikówczerwonych znacznikówczerwonych znaczników yellow counter(s) - + żółty znacznikżółtych znacznikówżółtych znacznikówżółtych znaczników green counter(s) - + zielony znacznikzielonych znacznikówzielonych znacznikówzielonych znaczników @@ -3742,17 +4570,17 @@ Cockatrice ponownie załaduje teraz bazę danych. %1 tasuje %3 - %4 z %2 kart. - + %1 unattaches %2. %1 odczepia kartę %2. - + %1 undoes their last draw. %1 cofa ostatnią dobraną kartę - + %1 undoes their last draw (%2). %1 cofa ostatnią dobraną kartę (%2) @@ -3760,110 +4588,110 @@ Cockatrice ponownie załaduje teraz bazę danych. MessagesSettingsPage - + Word1 Word2 Word3 Wyraz1 Wyraz2 Wyraz3 - + Add New Message - + Dodaj Nową Wiadomość - + Edit Message - + Edytuj Wiadomość - + Remove Message - + Usuń Wiadomość - + Add message Dodaj wiadomość - - + + Message: Wiadomość: - + Edit message Edytuj wiadomość - + Chat settings Ustawienia czatu - + Custom alert words Niestandardowy tekst alertu. - + Enable chat mentions Włącz wywołania na czacie - + Enable mention completer Włącz autouzupełnianie wywołania - + In-game message macros Makra wiadomości w trakcie gry - + How to use in-game message macros - + Jak korzystać z makr wiadomości w trakcie gry - + Ignore chat room messages sent by unregistered users Ignoruj wiadomości na czacie od niezarejestrowanych użytkowników - + Ignore private messages sent by unregistered users Ignoruj prywatne wiadomości od niezarejestrowanych użytkowników - - + + Invert text color Odwróć kolor tekstu - + Enable desktop notifications for private messages Włącz desktopowe notyfikacje dla prywatnych wiadomości - + Enable desktop notification for mentions Włącz desktopowe notyfikacje dla wywołań - + Enable room message history on join Włącz historię wiadomości w pokoju po dołączeniu - - + + (Color is hexadecimal) (Kolor w kodzie heksadecymalnym) - + Separate words with a space, alphanumeric characters only Rozdziel słowa spacją, tylko alfanumeryczne znaki @@ -3878,7 +4706,7 @@ Cockatrice ponownie załaduje teraz bazę danych. Mana Value - + Wartość Many @@ -3921,6 +4749,24 @@ Cockatrice ponownie załaduje teraz bazę danych. Tożsamość Koloru + + OverlapControlWidget + + + Cards to overlap: + Karty które mają się przykrywać: + + + + Overlap percentage: + Procent przykrycia: + + + + Overlap direction: + Kierunek przykrycia: + + Phase @@ -3987,57 +4833,57 @@ Cockatrice ponownie załaduje teraz bazę danych. PhasesToolbar - + Untap step Krok odtapowania - + Upkeep step Krok utrzymania - + Draw step Krok dobierania - + First main phase Pierwsza faza główna - + Beginning of combat step Krok początku walki - + Declare attackers step Krok deklaracji atakujących - + Declare blockers step Krok deklaracji broniących - + Combat damage step Krok obrażeń bitewnych - + End of combat step Krok zakończenia walki - + Second main phase Druga faza główna - + End of turn step Krok końca tury @@ -4045,7 +4891,7 @@ Cockatrice ponownie załaduje teraz bazę danych. PictureLoader - + en code for scryfall's language property, not available for all languages @@ -4054,647 +4900,781 @@ Cockatrice ponownie załaduje teraz bazę danych. Player - + Reveal top cards of library Odsłoń górną kartę biblioteki - - - - - - - - - + + + + + + + + + + + + Number of cards: (max. %1) Liczba kart: (maksymalnie %1) - + &View graveyard Przegląda&j cmentarz - + &View exile Przeglądaj strefę &wygnania - + Player "%1" Gracz "%1" - - - - + + + + &Graveyard C&mentarz - - - - + + + + &Exile Str&efa wygnania - + &Move hand to... Prze&mieść rękę na… - - - + + + &Top of library Wierzch biblio&teki - - - + + + &Bottom of library Dno &biblioteki - + &Move graveyard to... Prze&mieść cmentarz na… - - - - + + + + &Hand Rę&ka - + &Move exile to... Przen&ieś strefę wygnania na… - + &View library Przegląda&j bibliotekę - + &View hand &Zobacz rękę - + View &top cards of library... Przeglądaj karty z &wierzchu biblioteki… - + Reveal &library to... Pokaż bib&liotekę innym… - + &Always reveal top card Z&awsze graj z odwróconą kartą z wierzchu - + &View sideboard Przegląda&j talię poboczną - + &Draw card &Dobierz kartę - + D&raw cards... Dobie&rz karty… - + &Undo last draw Co&fnij ostatnie dobranie karty - + Take &mulligan Weź &mulligan - - &Shuffle - Przeta&suj - - - + Play top card &face down Zagraj górną kartę &koszulką do góry - + Move top card to grave&yard Przenieś wierzchnią kartę do cmentarza - + Move top card to e&xile Przenieś wierzchnią kartę do wygnanych - + Move top cards to &graveyard... Przemieść kart&y z wierzchu na cmentarz… - + Move top cards to &exile... Przemi&eść karty z wierzchu do strefy wygnania… - + Put top card on &bottom Połóż kartę z wierzc&hu na dnie - - Lend library to... + + View bottom cards of library... - + + Lend library to... + Pożycz bibliotekę innym... + + + + Shuffle + + + + Put top cards on stack &until... Take top cards &until... + Kładź karty z wierzchu na stosie &dopóki... + + + + Shuffle top cards... - + + Shuffle bottom cards... + + + + &Reveal hand to... Pokaż &rękę innym… - + Reveal r&andom card to... Pok&aż losową kartę innym… - + Reveal random card to... Pokaż losową kartę... - + &Sideboard Ta&lia poboczna - + &Library &Biblioteka - + &Counters Li&czniki - + &Untap all permanents Odtap&uj wszystkie permanenty - + R&oll die... Rzuć k&ostką… - + &Create token... Ut&wórz token… - + C&reate another token Utwó&rz kolejny token - + Cr&eate predefined token Utwórz zd&efiniowany token - - + + &All players Wszyscy gr&acze - + S&ay P&owiedz - + + &Select All + Zaznacz wszystko + + + + S&elect Row + W&ybierz rząd + + + + S&elect Column + W&ybierz kolumnę + + + &Play &Zagraj - + &Hide Sc&howaj - + Play &Face Down Zagraj k&oszulką do góry - + &Tap / Untap Turn sideways or back again &Zatapuj / Odtapuj - + Toggle &normal untapping Przełącz &normalne odtapowywanie - + T&urn Over Turn face up/face down Połóż grzbietem do dołu/do góry - + &Peek at card face S&pójrz na awers karty - + &Clone Klo&nuj - + Attac&h to card... Przyłącz do kart&y… - + Unattac&h Odłą&cz - + &Draw arrow... Narysuj wskaźni&k - + &Increase power Zw&iększenie siły - + &Decrease power Re&dukcja siły - + I&ncrease toughness Zwiększe&nie wytrzymałości - + D&ecrease toughness R&edukcja wytrzymałości - + In&crease power and toughness Zwięk&sz siłę i wytrzymałość - + Dec&rease power and toughness Red&ukuj siłę i wytrzymałość - + Increase power and decrease toughness Zwiększ siłę i zmniejsz wytrzymałość - + Decrease power and increase toughness Zmniejsz siłę i zwiększ wytrzymałość - + Set &power and toughness... Ustaw siłę i w&ytrzymałość - + Reset p&ower and toughness Resetuj siłę i wytrzymałość - + &Set annotation... U&staw adnotację… - + Red Czerwony - + Yellow Żółty - + Green Zielony - + &Add counter (%1) Dodaj znacznik (%1) - + &Remove counter (%1) Odejmij znacznik (%1) - + &Set counters (%1)... &Ustaw znaczniki (%1)... - + &Top of library in random order - + &Wierzch biblioteki w losowej kolejności - + X cards from the top of library... X kart z wierzchu biblioteki... - + &Bottom of library in random order Spód biblioteki w losowej kolejności - + View top cards of library Przeglądaj karty z wierzchu biblioteki - + + View bottom cards of library + + + + + Shuffle top cards of library + + + + + Shuffle bottom cards of library + + + + Which position should this card be placed: - + W której pozycji ta karta powinna być zagrana: - + (max. %1) - + (maks. %1) - + Draw hand Dobierz rękę - + 0 and lower are in comparison to current hand size 0 i mniejsze są w porównaniu do aktualnego rozmiaru ręki - + Draw cards Dobierz karty - - + + Number: Liczba: - + Move top cards to grave Przenieś karty z wierzchu na cmentarz - + Reveal &top cards to... - + Pokaż kar&ty z wierzchu innym… - + &Top of library... - + Wierzch biblio&teki... - + &Bottom of library... - + Dno &biblioteki... - + &Always look at top card - + Z&awsze patrz na pierwszą kartę z wierzchu - + &Open deck in deck editor - - - - - &Play top card - - - - - &Draw bottom card - - - - - D&raw bottom cards... - - - - - &Play bottom card - - - - - Play bottom card &face down - - - - - Move bottom card to grave&yard - - - - - Move bottom card to e&xile - - - - - Move bottom cards to &graveyard... - + &Otwórz talię w edytorze talii + &Play top card + Zagraj wierzchnią kartę + + + + &Draw bottom card + &Dobierz kartę z dna + + + + D&raw bottom cards... + Dobie&rz karty z dna... + + + + &Play bottom card + Zagraj kartę z dna + + + + Play bottom card &face down + Zagraj kartę z dna &koszulką do góry + + + + Move bottom card to grave&yard + Przen&ieś kartę z dna na cmentarz + + + + Move bottom card to e&xile + Przenieś kartę z dna do strefy wygnania + + + + Move bottom cards to &graveyard... + Przen&ieś karty z dna na cmentarz... + + + Move bottom cards to &exile... - + Przenieś karty z dna do strefy wygnania - + Put bottom card on &top - + Przenieś kartę z dna na wierzch - + Selec&ted cards - + Wybrane karty - + Move top cards to exile Przenieś karty z wierzchu do strefy wygnania - + Move bottom cards to grave - + Przenieś karty z dna na cmentarz - + Move bottom cards to exile - + Przenieś karty z dna do strefy wygnania - + Draw bottom cards - + Dobierz karty z dna - - + + C&reate another %1 token Stwórz kolejny token %1 - + Create tokens Stwórz tokeny - + Place card X cards from top of library Umieść kartę w kolejności X od wierzchu biblioteki. - + Change power/toughness Zmień siłę/wytrzymałość - + Change stats to: Zmień statystyki na: - + Set annotation Ustaw adnotację - + Please enter the new annotation: Podaj treść nowej adnotacji: - + Set counters Ustaw znaczniki - + Re&veal to... - + Pokaż innym… - + View related cards Zobacz powiązane karty - + Token: Token: - + All tokens Wszystkie tokeny + + PrintingSelector + + + Display Navigation Buttons + + + + + <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. + + + + + PrintingSelectorCardOverlayWidget + + + Preference + + + + + Pin Printing + Przypnij wersję + + + + Unpin Printing + Odepnij wersję + + + + Show Related cards + Pokaż powiązane karty + + + + PrintingSelectorCardSearchWidget + + + Search by set name or set code + Szukaj po nazwie dodatku lub kodzie dodatku + + + + PrintingSelectorCardSelectionWidget + + + Previous Card in Deck + Poprzednia karta w talii + + + + Next Card in Deck + Następna karta w talii + + + + PrintingSelectorCardSortingWidget + + + Alphabetical + Alfabetycznie + + + + Preference + + + + + Release Date + Data wydania + + + + + Descending + Malejąco + + + + Ascending + Rosnąco + + QMenuBar - + Services Usługi - + Hide %1 Ukryj %1 - + Hide Others Ukryj pozostałe - + Show All Pokaż wszystkie - + Preferences... Preferencje… - + Quit %1 Zakończ %1 - + About %1 O programie %1 @@ -4702,149 +5682,143 @@ Cockatrice ponownie załaduje teraz bazę danych. QObject - + Cockatrice card database (*.xml) Baza kart Cockatrice (*.xml) - - + All files (*.*) Wszystkie pliki (*.*) - + Cockatrice replays (*.cor) Powtórki Cockatrice (*.cor) - - Common deck formats (*.cod *.dec *.dek *.txt *.mwDeck) - Powszechne formaty talli (*.cod *.dec *.dek *.txt *.mwDeck) - - - + Maindeck - + Maindeck - + Sideboard - + Sideboard - + Tokens - + Tokeny QPlatformTheme - + OK - + OK - + Save Zapisz - + Save All Zapisz Wszystko - + Open - + Otwórz - + &Yes Tak - + Yes to &All - + Tak - wszystko - + &No - + Nie - + N&o to All - + Nie - wszystko - + Abort - + Przerwij - + Retry - + Powtórz - + Ignore - + Ignoruj - + Close - + Zamknij - + Cancel Anuluj - + Discard Odrzuć - + Help Pomoc - + Apply Aplikuj - + Reset - + Reset - + Restore Defaults - + Przywróć domyślne RemoteDeckList_TreeModel - + Name Nazwa - + ID ID - + Upload time Data dodania @@ -4852,32 +5826,32 @@ Cockatrice ponownie załaduje teraz bazę danych. RemoteReplayList_TreeModel - + ID ID - + Name Nazwa - + Players Gracze - + Keep Zachowaj - + Time started Czas rozpoczęcia - + Duration (sec) Czas trwania (sek) @@ -4923,34 +5897,34 @@ Cockatrice ponownie załaduje teraz bazę danych. SequenceEdit - + Choose an action from the table - + Wybierz akcję z tabeli - + Hit the key/combination of keys you want to set for this action - + Naciśnij klawisz lub kombinację klawiszy którą chcesz ustawić na tą akcję - - Shortcut already in use - - - - + Invalid key - + Nieprawidłowy klawisz - + + Shortcut already in use by: + Skrót aktualnie użyty przez: + + + Clear - + Wyczyść - + Restore default - + Przywróć domyślne @@ -4984,73 +5958,81 @@ Cockatrice ponownie załaduje teraz bazę danych. ShortcutSettingsPage - - + + Restore all default shortcuts - + Przywróć domyślne skróty - + Do you really want to restore all default shortcuts? - + Czy na pewno chcesz przywrócić wszystkie domyślne skróty? - + Clear all default shortcuts - + Wyczyść wszystkie domyślne skróty - + Do you really want to clear all shortcuts? - + Czy na pewno chcesz wyczyścić wszystkie skróty? - - Action - - - - - Shortcut - - - - + Section: - + Sekcja: - + Action: - + Akcja: - + Shortcut: - + Skrót: - + How to set custom shortcuts - + Jak ustawić niestandardowe skróty klawiszowe - + Clear all shortcuts - + Wyczyść wszystkie skróty + + + + Search by shortcut name + Szukaj po nazwie skrótu + + + + ShortcutTreeView + + + Action + Akcja + + + + Shortcut + Skrót ShortcutsSettings - + Your configuration file contained invalid shortcuts. Please check your shortcut settings! Twój plik konfiguracyjny zawierał nieprawidłowe skróty. Sprawdź ustawienia skrótów! - + The following shortcuts have been set to default: Następujące skróty zostały zamienione na domyślne: @@ -5060,17 +6042,17 @@ Sprawdź ustawienia skrótów! ShutdownDialog - + &Reason for shutdown: P&rzyczyna zamknięcia: - + &Time until shutdown (minutes): Czas do zamknięcia (w minu&tach): - + Shut down server Zamknij serwer @@ -5078,27 +6060,27 @@ Sprawdź ustawienia skrótów! SoundSettingsPage - + Enable &sounds Włącz &efekty dźwiękowe - + Current sounds theme: Aktualny zestaw dźwięków: - + Test system sound engine Testuj silnik dźwiękowy systemu - + Sound settings Ustawienia dźwięku - + Master volume Główne ustawienia głośności @@ -5106,48 +6088,48 @@ Sprawdź ustawienia skrótów! SpoilerBackgroundUpdater - + Spoilers season has ended Sezon spoilerów zakończony - + Deleting spoiler.xml. Please run Oracle Usuwanie spoiler.xml. Uruchom Oracle - - + + Spoilers download failed Pobieranie spoilerów nieudane - + No internet connection Brak połączenia z internetem - + Error Błąd - + Spoilers already up to date Spoilery są już aktualne - + No new spoilers added Nie dodano nowych spoilerów - + Spoilers have been updated! Spoilery zostały zaktualizowane. - + Last change: Ostatnia zmiana: @@ -5155,638 +6137,594 @@ Sprawdź ustawienia skrótów! StableReleaseChannel - - Stable Releases - Wersje Stabilne + + Default + - + No reply received from the release update server. Brak odpowiedzi z serwera uaktualnień wersji. - + Invalid reply received from the release update server. Nieprawidłowa odpowiedź serwera uaktualnień wersji. - + No reply received from the tag update server. Brak odpowiedzi z serwera uaktualnień tagów. - + Invalid reply received from the tag update server. Nieprawidłowa odpowiedź serwera uaktualnień tagów. + + TabAccount + + + Add to Buddy List + + + + + Add to Ignore List + + + + + Account + + + TabAdmin - + Administration Administracja - + Update server &message Aktualizuj wiado&mość serwera - + &Shut down server Zamknij &serwer - + &Reload configuration &Załaduj ponownie konfigurację - + Server administration functions Pokaż funkcje administracyjne - + + Server moderator functions + + + + + Replay ID + ID powtórki + + + + Grant Replay Access + Przyznaj Dostęp do Powtórki + + + + Username to Activate + Nazwa użytkownika do aktywowania + + + + Force Activate User + Wymuś aktywowanie użytkownika + + + &Unlock functions Odblok&uj funkcje - + &Lock functions Zab&lokuj funkcje + + + + Success + Operacja zakończona pomyślnie + + + + Replay access granted + Dostęp do powtórki przyznany + + + + + + + + Error + Błąd + + + + Unable to grant replay access. Replay ID invalid + Nie można przyznać dostępu. ID powtórki jest nieprawidłowe + + + + Unable to grant replay access. Internal error + Nie można przyznać dostępu. Błąd wewnętrzny + + + + User successfully activated + Użytkownik pomyślnie aktywowany + + + + Unable to activate user. Username invalid + Nie można aktywować użytkownika. Nazwa użytkownika jest nie prawidłowa + + + + Unable to activate user. User already active + Nie można aktywować użytkownika. Użytkownik jest już aktywny + + + + Unable to activate user. Internal error + Nie można aktywować użytkownika. Błąd wewnętrzny + TabDeckEditor - - Search by card name (or search expressions) - Search by card name - - - - - Add to Deck - Dodaj do talii - - - - Add to Sideboard - Dodaj do talii pobocznej - - - - Show Related cards - Pokaż powiązane karty - - - - &Clear all filters - Wy&czyść wszystkie filtry - - - - Delete selected - Usuń zaznaczone - - - - Deck &name: - &Nazwa talii: - - - - &Comments: - &Komentarze: - - - - Hash: - Hash: - - - - &New deck - &Nowa talia - - - - &Load deck... - &Wczytaj ta&lię… - - - - &Save deck - Zapi&sz talię - - - - Save deck &as... - Zaisz talię j&ako… - - - - Load deck from cl&ipboard... - Wczytaj tal&ię ze schowka… - - - - Save deck to clipboard - Zapisz talię do schowka - - - - Annotated - Z dnotacją - - - - Not Annotated - Bez adnotacji - - - - &Print deck... - &Drukuj talię - - - - &Send deck to online service - Prześlij talię do serwisu online - - - - Create decklist (decklist.org) - Stwórz spis talii (decklist.org) - - - - Analyze deck (deckstats.net) - Analizuj talię (deckstats.net) - - - - Analyze deck (tappedout.net) - Analizuj talię (tappedout.net) - - - - &Close - &Zakończ - - - - Add card to &maindeck - Dodaj kartę do talii &głównej - - - - Add card to &sideboard - Dodaj kartę do talii po&bocznej - - - - &Remove row - Usuń wie&rsz - - - - &Increment number - Zwiększ l&iczbę - - - - &Decrement number - Zmniejsz &liczbę - - - - &Deck Editor - E&dytor talii - - - - + Card Info Informacje o karcie - - + Deck Talia - - + Filters Filtry - + &View &Widok - - - + + Printing + Wersja + + + + + + Visible Widoczne - - - + + + + Floating Pływające - + Reset layout Resetuj układ - + Deck: %1 Talia: %1 - - - Are you sure? - Czy na pewno? - - - - The decklist has been modified. -Do you want to save the changes? - Talia została zmodyfikowana. -Czy chcesz zachować zmiany? - - - - Load deck - Wczytaj talię - - - - - - - - - Error - Błąd - - - - The deck could not be saved. - Talia nie mogła zostać zapisana. - - - - Could not save remote deck - - - - - - The deck could not be saved. -Please check that the directory is writable and try again. - Zapisanie talii nie mogło zostać ukończone. -Sprawdź, czy włączone są uprawnienia do zapisu w katalogu docelowym i spróbuj ponownie. - - - - Save deck - Zapisz talię - - - - There are no cards in your deck to be exported - Brak kart do eksportu w talii - - - - No deck was selected to be saved. - Nie wybrano żadnej talii do zapisu. - TabDeckStorage - - Deck storage - Przechowalnia talii - - - + Local file system Pliki lokalne - + Server deck storage Przechowalnia talii na serwerze - - + + Open in deck editor Otwórz w edytorze talii - + + Rename deck or folder + + + + Upload deck Wyślij talię - + Download deck Pobierz talię - - + + + + New folder Nowy folder - - + + Delete Usuń - - + + Open decks folder + Otwórz folder talii + + + + Rename local folder + + + + + Rename local file + + + + + New name: + + + + + + + Error + Błąd + + + + Rename failed - + + Invalid deck file - + Nieprawidłowy plik talii - + Enter deck name Podaj nazwę talii - + This decklist does not have a name. Please enter a name: Talia nie posiada nazwy. Wprowadź nazwę: - + Unnamed deck Nienazwana talia - + Failed to upload deck to server - + Nie udało się wysłać talii na serwer - + Delete local file Usuń plik lokalny - - - - Are you sure you want to delete "%1"? - Czy na pewno chcesz usunąć "%1"? + + Are you sure you want to delete the selected files? + Czy na pewno chcesz usunąć wybrane pliki? - + + Delete remote decks + Usuń zdalną talię + + + + Are you sure you want to delete the selected decks? + Czy na pewno chcesz usunąć wybrane talie? + + + + Name of new folder: Nazwa nowego folderu: - - Delete remote folder - Usuń zdalny folder + + Deck Storage + + + + + TabDeckStorageVisual + + + Visual Deck Storage + - - Delete remote deck - Usuń przechowywaną talię + + Error + + + + + Could not open deck at %1 + + + + + TabEdhRec + + + EDHREC: + TabGame - - - + + + Replay Powtórka - - + + Game Gra - - + + Player List Lista Graczy - - + + Card Info Informacje o karcie - - + + Messages Wiadomości - - + + Replay Timeline Oś czasu powtórki - + &Phases &Fazy - + &Game &Gra - + Next &phase Następna &faza - + Next phase with &action - + Następna faza z akcją - + Next &turn Następna &tura - + Reverse turn order - + Odwróć kolejność tur - + &Remove all local arrows Usuń wszystkie &wskaźniki - + Rotate View Cl&ockwise Obróć widok &zgodnie z ruchem wskazówek zegara - + Rotate View Co&unterclockwise Obróć widok &przeciwnie do ruchu wskazówek zegara - + Game &information &Informacje o grze - + + Un&concede + Powróć do gry + + + &Concede &Poddaj grę - + &Leave game &Opuść grę - + C&lose replay &Zamknij powtórkę - + &Focus Chat - + Skup czat - + &Say: &Powiedz: - + &View &Widok - - - - + + + + Visible Widoczne - - - - + + + + Floating Pływające - + Reset layout Resetuj układ - + Concede Poddaj grę - + Are you sure you want to concede this game? Czy na pewno chcesz poddać tę grę? - + Unconcede - + Powróć do gry - + You have already conceded. Do you want to return to this game? - + Poddałeś już się. Czy chcesz powrócić do tej gry? - + Leave game Opuść grę - + Are you sure you want to leave this game? Czy na pewno chcesz zakończyć tę rozgrywkę? - + You are flooding the game. Please wait a couple of seconds. Spamujesz. Poczekaj chwilę. - + A player has joined game #%1 - + Gracz dołączył do gry #%1 - + %1 has joined the game - + %1 dołączył(a) do gry. - + kicked by game host or moderator wyrzucony przez gospodarza gry lub moderatora - + player left the game gracz opuścił grę - + player disconnected from server gracz rozłączył się z serwerem - + reason unknown nieznany powód - + You have been kicked out of the game. Wyrzucono cię z gry. @@ -5799,153 +6737,153 @@ Wprowadź nazwę: Logi - - - + + + Time;SenderName;SenderIP;Message;TargetID;TargetName - + Room Logs Logi Pokoju - + Game Logs Logi Gry - + Chat Logs Logi Czatu - - + + Error Błąd - + You must select at least one filter. Musisz wybrać przynajmniej jeden filtr. - + You have to select a valid number of days to locate. Musisz wybrać poprawną liczbę dni żeby zlokalizować. - + Username: Nazwa użytkownika: - + IP Address: Adres IP: - + Game Name: Nazwa rozgrywki: - + GameID: ID Gry: - + Message: Wiadomość: - + Main Room Pokój Główny - + Game Room Pokój - + Private Chat Rozmowa Prywatna - + Past X Days: Ostatnie X Dni: - + Today Dziś - + Last Hour Ostatnia godzina - + Maximum Results: Maksymalny Wynik: - + At least one filter is required. The more information you put in, the more specific your results will be. Wymagany jest co najmniej jeden filtr. Im więcej podanych informacji, tym bardziej sprecyzowany wynik. - + Get User Logs Pobierz Logi Użytkownika - + Clear Filters Wyczyść Filtry - + Filters Filtry - + Log Locations Loguj Połóżenia - + Date Range Okres - + Maximum Results Maksymalny Wynik - - + + Message History Historia wiadomości - + Failed to collect message history information. Nie udało się zebrać informacji o historii wiadomości. - + There are no messages for the selected filters. Brak wiadomości dla wybranych filtrów. @@ -5968,22 +6906,22 @@ Im więcej podanych informacji, tym bardziej sprecyzowany wynik. %1 - Prywatny chat - + This user is ignoring you, they cannot see your messages in main chat and you cannot join their games. Ten użytkownik Cię ignoruje, nie może zobaczyć twoich wiadomości w głównym czacie oraz nie możesz dołączyć do jego gier. - + Private message from Prywatna wiadomość od - + %1 has left the server. Gracz %1 opuścił serwer. - + %1 has joined the server. Gracz %1 pojawił się na serwerze. @@ -5991,117 +6929,153 @@ Im więcej podanych informacji, tym bardziej sprecyzowany wynik. TabReplays - - Game replays - Powtórki - - - + Local file system Lokalny system plików - + Server replay storage Powtórki na serwerze - - + + Watch replay Obejrzyj powtórkę - - + + Rename + Zmień nazwę + + + + + New folder + Nowy folder + + + + Delete Usuń - + + Open replays folder + Otwórz folder powtórek + + + Download replay Pobierz powtórkę - + Toggle expiration lock Wł./Wył. ochronę przed automatycznym usunięciem - + + Rename local folder + Zmień nazwę lokalnego folderu + + + + Rename local file + Zmień nazwę lokalnego pliku + + + + New name: + Nowa nazwa: + + + + Error + Błąd + + + + Rename failed + Zmiana nazwy nie powiodła się + + + + Name of new folder: + Nazwa nowego folderu: + + + Delete local file Usuń plik lokalny - - Are you sure you want to delete "%1"? - Czy na pewno chcesz usunąć "%1"? + + Are you sure you want to delete the selected files? + Czy na pewno chcesz usunąć wybrane pliki? - - Downloading Replays - Pobieranie powtórek + + Are you sure you want to delete the selected replays? + Czy na pewno chcesz usunąć wybrane powtórki? - - Folder download is not yet supported. Please download replays individually. - - - - + Delete remote replay Usuń przechowywaną powtórkę - - Are you sure you want to delete the replay of game %1? - Czy na pewno chcesz usunąć powtórkę gry %1? + + Game Replays + TabRoom - + &Say: &Powiedz: - + Chat Czat - + &Room P&okój - + &Leave room Op&uść pokój - + &Clear chat Wyczyść &czat - + Chat Settings... Ustawienia czatu… - + mentioned you. wspomniał o tobie. - + Click to view Kliknij aby podglądnąć - + You are flooding the chat. Please wait a couple of seconds. Spamujesz. Poczekaj chwilę. @@ -6114,63 +7088,103 @@ Im więcej podanych informacji, tym bardziej sprecyzowany wynik. Serwer - - - - + + + + Error Błąd - + Failed to join the server room: it doesn't exist on the server. - + Błąd podczas próby dołączenia do pokoju: nie istnieje na serwerze. - + The server thinks you are in the server room but your client is unable to display it. Try restarting your client. - + Serwer myśli że jesteś w pokoju ale twój klient nie może tego wyświetlić. Spróbuj zrestartować twojego klienta. - + You do not have the required permission to join this server room. - + Nie masz uprawnień do dołączenia do tego pokoju. - + Failed to join the server room due to an unknown error: %1. - + Błąd podczas próby dołączenia do pokoju z powodu nieznanego błędu: %1 TabSupervisor - + + Deck Editor + + + + + &Visual Deck Storage + + + + + Server + + + + + Account + + + + + Deck Storage + + + + + Game Replays + + + + + Administration + + + + + Logs + + + + Are you sure? Czy na pewno? - + There are still open games. Are you sure you want to quit? Ciągle masz otwarte gry. Czy na pewno chcesz wyjść? - + Click to view - + Kliknij aby podglądnąć - + Your buddy %1 has signed on! - + Twój znajomy %1 zalogował się! - + Unknown Event Nieznane zdarzenie - + The server has sent you a message that your client does not understand. This message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -6181,63 +7195,45 @@ Ta wiadomośc może oznaczać że jest dostępna nowa wersja Cockatrice, lub że Aby aktualizować klienta, otwórz okno Pomoc -> Sprawdź Aktualizacje - + Idle Timeout Przekroczono czas bezczynności - + You are about to be logged out due to inactivity. Zostaniesz wylogowany ze względu na nieaktywność. - + Promotion Awans - + You have been promoted. Please log out and back in for changes to take effect. - + Zostałeś awansowany. Proszę wyloguj się i zaloguj ponownie aby zmiany zostały wprowadzone. - + Warned Ostrzeżony - + You have received a warning due to %1. Please refrain from engaging in this activity or further actions may be taken against you. If you have any questions, please private message a moderator. Otrzymałeś ostrzeżenie z powodu %1. Unikaj tego zachowania, w przeciwnym mogą zostać pozwięte dalsze czynności. Jeżeli masz pytania, wyślij prywatną wiadomość do moderatora. - + You have received the following message from the server. (custom messages like these could be untranslated) Otrzymałeś następującą wiadomość od serwera. (tego typu wiadomości mogą być nieprzetłumaczone) - - TabUserLists - - - Account - Konto - - - - Add to Buddy List - Dodaj do listy znajomych - - - - Add to Ignore List - Dodaj do listy ignorowanych - - TappedOutInterface @@ -6272,42 +7268,42 @@ Unikaj tego zachowania, w przeciwnym mogą zostać pozwięte dalsze czynności. Life - + Życie White - + Biały Blue - + Niebieski Black - + Czarny Red - + Czerwony Green - + Zielony Colorless - + Bezkolorowy Other - + Inne @@ -6321,347 +7317,358 @@ Unikaj tego zachowania, w przeciwnym mogą zostać pozwięte dalsze czynności. UserContextMenu - + User &details &Dane użytkownika - + Private &chat Prywatny &chat - + Show this user's &games Wyświetl &gry użytkownika - + Add to &buddy list &Dodaj do listy znajomych - + Remove from &buddy list &Usuń z listy znajomych - + Add to &ignore list Dodaj do listy &ignorowanych - + Remove from &ignore list Usuń z listy &ignorowanych - + Kick from &game Wyrzuć z &gry - + Warn user Daj ostrzeżenie użytkownikowi - + View user's war&n history Zobacz historie ostrzeżeń użytkownika - + Ban from &server Zbanuj z &serwera - + View user's &ban history Zobacz historie blokad użytkownika - + &Promote user to moderator &Daj użytkownikowi uprawnienia moderatora - + Dem&ote user from moderator &Zabierz użytkownikowi uprawnienia moderatora - + Promote user to &judge - + &Daj użytkownikowi uprawnienia sędziego - + Demote user from judge - + &Zabierz użytkownikowi uprawnienia sędziego - + + View admin notes + Sprawdź notatki administratorskie + + + %1's games Gry gracza %1 - - - + + + Ban History Historia blokad - + Ban Time;Moderator;Ban Length;Ban Reason;Visible Reason Blokada Czas;Moderator;Czas Blokady;Powód Blokady;Widoczny Powód - + User has never been banned. Użytkownik nigdy nie został zablokowany - + Failed to collect ban information. - + Błąd podczas zbierania informacji o blokadach. - - - + + + Warning History Historia ostrzeżeń - + Warning Time;Moderator;User Name;Reason Ostrzeżenie Czas;Moderator;Nazwa Użytkownika;Powód - + User has never been warned. Użytkownik nigdy nie dostał ostrzeżenia. - + Failed to collect warning information. - + Błąd podczas zbierania informacji o ostrzeżeniach. - - + + Failed to get admin notes. + Błąd podczas zbierania notatek administratorskich. + + + + Success Operacja zakończona pomyślnie - + Successfully promoted user. Pomyślny awans gracza. - + Successfully demoted user. Pomyślna degradacja gracza. - - + + + Failed Wystąpił błąd - + Failed to promote user. Błąd podczas awansu gracza. - + Failed to demote user. Błąd podczas degradacji gracza. - + Copy hash to clipboard - + Zapisz hash do schowka - + Remove this user's messages - + Usuń wiadomości tego użytkownika UserInfoBox - + Location: Lokacja: - + Account Age: Wiek konta: - + Edit Edytuj - + Change password Zmień hasło - + Change avatar Zmień awatar - + Administrator Administrator - + Moderator Moderator - + Registered user Zarejestrowany użytkownik - - + + Unregistered user Niezarejestrowany użytkownik - + Judge - + Sędzia - + Unknown Nieznany - + The entered password does not match your account. - + Wpisane hasło nie pasuje do twojego konta. - - - + + + Information Informacje - + User information updated. Informacje o użytkowniku zaktualizowane. - - - - - - - - - - + + + + + + + + + + Error Błąd - + User Information - + Informacje o użytkowniku - + Real Name: - + Prawdziwe imię: - + User Level: - + Rodzaj użytkownika: - + %n Year(s), amount of years (only shown if more than 0) - + %n Rok%n Lat%n Lat%n Lat - + %10%n Day(s) %20 amount of years (if more than 0), amount of days, date in local short format - + %10%n Dzień %20%10%n Dni %20%10%n Dni %20%10%n Dni %20 - + Enter Password - + Wpisz hasło - + Password verification is required in order to change your email address - + Weryfikacja hasłem jest wymagana aby zmienić twój adres email. - - - + + + An error occurred while trying to update your user information. - + Wystąpił błąd podczas aktualizacji informacji o użytkowniku. - + This server does not permit you to update your user informations. Ten serwer nie pozwala na aktualizację twoich informacji o użytkowniku. - + Password changed. Hasło zmienione. - + This server does not permit you to change your password. Ten serwer nie pozwala na zmianę twojego hasła. - + The new password is too short. Nowe hasło jest za krótkie. - + The old password is incorrect. Stare hasło jest niepoprawne. - + Avatar updated. Awatar zaktualizowany - + This server does not permit you to update your avatar. Ten serwer nie pozwala na aktualizację twojego awatara. - + An error occured while trying to updater your avatar. Wystąpił błąd podczas aktualizowania twojego awatara. @@ -6669,124 +7676,251 @@ Unikaj tego zachowania, w przeciwnym mogą zostać pozwięte dalsze czynności. UserInterfaceSettingsPage - + General interface settings Ogólne ustawienia interfejsu - + &Double-click cards to play them (instead of single-click) Zagrywaj karty po&dwójnym kliknięciem (zamiast pojedynczym) - + + &Clicking plays all selected cards (instead of just the clicked card) + Kliknięcie zagrywa wszystkie wybrane karty (zamiast tylko klikniętej karty) + + + &Play all nonlands onto the stack (not the battlefield) by default Karty nie będące lądami zagrywaj domyślnie na stos (zamiast na &pole bitwy) - + + Close card view window when last card is removed + + + + Annotate card text on tokens Dodaj tekst karty na tokenach - + Use tear-off menus, allowing right click menus to persist on screen - + Użycie odczepianych menu, pozwalające na zatrzymanie menu kontekstowego na ekranie - + Notifications settings - + Ustawienia powiadomień - + Enable notifications in taskbar Włącz &powiadomienia na pasku zadań - + Notify in the taskbar for game events while you are spectating Powiadomienia na pasku zadań dla gier, które &obserwujesz - + Notify in the taskbar when users in your buddy list connect - + Powiadamiaj na pasku zadań kiedy użytkownik z twojej listy znajomych zaloguje się. - + Animation settings Ustawienia animacji - + &Tap/untap animation Animacja &tapowania/odtapowania + + + Deck editor/storage settings + + + + + Open deck in new tab by default + Domyślnie otwórz talię w nowej karcie + + + + Prompt before converting .txt decks to .cod format + + + + + Always convert if not prompted + + + + + Use visual deck storage in game lobby + + + + + Replay settings + Ustawienia powtórek + + + + Buffer time for backwards skip via shortcut: + Buforuj czas do przeskoku w tył wywołane przez skrót: + - UserList + UserListWidget - + Users connected to server: %1 - Gracze podłączeni do serwera: %1 + - + Users in this room: %1 - Użytkownicy w tym pokoju: %1 + - + Buddies online: %1 / %2 - Znajomi online: %1 / %2 + - + Ignored users online: %1 / %2 - Ignorowani użytkownicy online: %1 / %2 + + + + + VisualDeckStorageFolderDisplayWidget + + + Deck Storage + + + + + VisualDeckStorageSearchWidget + + + Search by filename + + + + + VisualDeckStorageSortWidget + + + Sort Alphabetically (Deck Name) + + + + + Sort Alphabetically (Filename) + + + + + Sort by Last Modified + + + + + Sort by Last Loaded + + + + + VisualDeckStorageWidget + + + Loading database ... + + + + + Show Folders + + + + + Show Tag Filter + + + + + Show Tags On Deck Previews + + + + + Show Banner Card Selection Option + + + + + Include Folder Names in Search + + + + + Draw unused Color Identities + + + + + Unused Color Identities Opacity + WarningDialog - + Which warning would you like to send? Jakie ostrzeżenie chciałbyś wysłać? - + Redact all messages from this user in all rooms - + Redaguj wiadomości od tego użytkownika we wszystkich pokojach - + &OK &OK - + &Cancel &Anuluj - + Warn user for misconduct Ostrzeż gracza za złe zachowanie - - + + Error Błąd - + User name to send a warning to can not be blank, please specify a user to warn. Nazwa gracza do wysłania ostrzeżenia nie może być pusta, prosze podać gracza. - + Warning to use can not be blank, please select a valid warning to send. Ostrzeżenie nie może być puste, zaznacz poprawne ostrzeżenie do wysłania. @@ -6794,128 +7928,133 @@ Unikaj tego zachowania, w przeciwnym mogą zostać pozwięte dalsze czynności. WndSets - + Move selected set to the top Przesuń zaznaczony set do góry - + Move selected set up Przesuć wybrany dodatek w górę - + Move selected set down Przesuń wybrany dodatek w dół - + Move selected set to the bottom Przesuń zaznaczony set na dół - + Search by set name, code, or type Szukaj po nazwie dodatku, kodzie lub typie - + Default order Domyślna kolejność - + Restore original art priority order Przywróć oryginalny priorytet obrazów kart - + Enable all sets Włącz wszystkie dodatki - + Disable all sets Wyłącz wszystkie dodatki - + Enable selected set(s) Włącz zaznaczony/e set(y) - + Disable selected set(s) Wyłącz zaznaczony/e set(y) - + Deck Editor Edycja Talii - - - Use CTRL+A to select all sets in the view. - - - - - Only cards in enabled sets will appear in the card list of the deck editor. - - - - - Image priority is decided in the following order: - - + Use CTRL+A to select all sets in the view. + Użyj CTRL+A aby wybrać wszystkie dodatki w widoku. + + + + Only cards in enabled sets will appear in the card list of the deck editor. + Tylko karty z odblokowanych dodatków pojawią się w edytorze talii kart + + + + Image priority is decided in the following order: + Priorytet obrazów jest ustalany w następujący sposób: + + + first the CUSTOM Folder (%1), then the Enabled Sets in this dialog (Top to Bottom) %1 is a link to the wiki - + najpierw folder NIESTANDARDOWYCH (%1), potem Włączone Dodatki w tym oknie dialogowym (Z góry na dół) - + Card Art Obraz karty - + How to use custom card art - - - - - Hints - + Jak ustawić niestandardowe obrazy do kart + Hints + Wskazówki + + + Note - + Notatka - + Sorting by column allows you to find a set while not changing set priority. - - - - - To enable ordering again, click the column header until this message disappears. - + Sortowane kolumnami pozwala ci znaleźć dodatek nie zmieniając ich priorytetów. + To enable ordering again, click the column header until this message disappears. + Aby wyłączyć sortowanie klikaj ponownie na ten sam nagłówek kolumny aż zniknie ten komunikat. + + + Use the current sorting as the set priority instead - + Użyj tymczasowego sortowania jako priorytet dodatków - + Sorts the set priority using the same column + Sortuje priorytet dodatków korzystając z tej samej kolumny + + + + Include online-only (Arena) cards [requires restart] - + Manage sets Zarządzaj dodatkami @@ -6923,22 +8062,67 @@ Unikaj tego zachowania, w przeciwnym mogą zostać pozwięte dalsze czynności. ZoneViewWidget - - sort by name - sortuj według nazwy + + Ungrouped + Niepogrupowane - - sort by type - sortuj według typu + + Group by Type + Grupuj po typie - + + Group by Mana Value + Grupuj po wartości many + + + + Group by Color + Grupuj po kolorze + + + + Unsorted + Nieposortowane + + + + Sort by Name + Sortuj według nazwy + + + + Sort by Type + Sortuj według typu + + + + Sort by Mana Cost + Sortuj według wartości many + + + + Sort by Colors + Sortuj według koloru + + + + Sort by P/T + Sortuj według P/T + + + + Sort by Set + Sortuj według dodatku + + + shuffle when closing przetasuj przy zamykaniu - + pile view widok sterty @@ -6946,7 +8130,7 @@ Unikaj tego zachowania, w przeciwnym mogą zostać pozwięte dalsze czynności. i18n - + English Polski (Polish) @@ -6954,798 +8138,920 @@ Unikaj tego zachowania, w przeciwnym mogą zostać pozwięte dalsze czynności. main - + Connect on startup - + Zaloguj po uruchowmieniu - + Debug to file - + Debuguj do pliku shortcutsTab - + Main Window Główne okno - - + + Deck Editor Edycja Talii - + Game Lobby Poczekalnia gier - - - Card Counters - - - Player Counters - + Card Counters + Znaczniki kart + Player Counters + Znaczniki graczy + + + Power and Toughness Siła i wytrzymałość - + Game Phases Fazy gry - + Playing Area Teren gry - + Move Selected Card - + Przenieś wybraną kartę - + View Widok - - - Move Top Card - - - Move Bottom Card - + Move Top Card + Przenieś kartę z wierzchu + Move Bottom Card + Przenieś kartę z dołu + + + Gameplay Rozgrywka - - - Drawing - - - Chat Room - + Drawing + Dobieranie - Game Window - + Chat Room + Pokój czatu - Load Deck from Clipboard - + Game Window + Okno gry + Load Deck from Clipboard + Wczytaj talię ze schowka + + + + Replays + Powtórki + + + + Tabs - + Check for Card Updates... - + Uaktualnij bazę kart... - + Connect... - + Połącz… - + Disconnect Rozłącz - + Exit Wyjście - + Full screen Pełny ekran - + Register... - + Zarejestruj się... - + Settings... - + Ustawienia... - + Start a Local Game... - + Rozpocznij lokalną grę… - + Watch Replay... + Obejrzyj powtórkę… + + + + Analyze Deck (deckstats.net) + Analyze Deck - - Analyze Deck + + Analyze Deck (tappedout.net) - + Clear All Filters - + Wyczyść wszystkie filtry - + Clear Selected Filter - + Wyczyść zaznaczone filtry - + Close Zamknij - + Remove Card - + Usuń kartę - + Manage Sets... - + Zarządzaj dodatkami... - + Edit Custom Tokens... - + Edytuj niestandardowe tokeny... - - Export Deck - - - - - Add Card - - - - - Load Deck... - - - - - Load Deck from Clipboard... + + Export Deck (decklist.org) - New Deck + Export Deck (decklist.xyz) - - Open Custom Pictures Folder - + + Add Card + Dodaj kartę - - Print Deck... - + + Load Deck... + Wczytaj talię... + Load Deck from Clipboard... + Wczytaj talię ze schowka… + + + + Edit Deck in Clipboard, Annotated + + + + + Edit Deck in Clipboard + + + + + New Deck + Nowa talia + + + + Open Custom Pictures Folder + Otwórz folder z niestandardowymi obrazkami + + + + Print Deck... + Drukuj talię + + + Delete Card - + Usuń kartę - - + + Reset Layout - + Zresetuj wygląd - + Save Deck - - - - - Save Deck as... - - - - - Save Deck to Clipboard, Annotated - - - - - Save Deck to Clipboard - - - - - Load Local Deck... - - - - - Load Remote Deck... - - - - - Set Ready to Start - + Zapisz talię - Toggle Sideboard Lock - + Save Deck as... + Zapisz talię jako… - - - Add Green Counter - + + Save Deck to Clipboard, Annotated + Zapisz talię, anotowaną, do schowka - - - Remove Green Counter - - - - - - Set Green Counters... + + Save Deck to Clipboard, Annotated (No Set Info) + Save Deck to Clipboard + Zapisz talię do schowka + + + + Save Deck to Clipboard (No Set Info) + + + + + Load Local Deck... + Wczytaj talię lokalną... + + + + Load Remote Deck... + Wczytaj zdalną talię... + + + + Set Ready to Start + Ustaw gotowość do rozpoczęcia + + + + Toggle Sideboard Lock + Przełącz blokadę sideboard + + + + + Add Green Counter + Dodaj Zielony Znacznik + + + + + Remove Green Counter + Odejmij Zielony Znacznik + + + + + Set Green Counters... + Ustaw Zielony Znacznik... + + + Add Yellow Counter - + Dodaj Żółty Znacznik - + Remove Yellow Counter - + Odejmij Żółty Znacznik - + Set Yellow Counters... - + Ustaw Żółty Znacznik... - - + + Add Red Counter - + Dodaj Czerwony Znacznik - - + + Remove Red Counter - + Odejmij Czerwony Znacznik - - + + Set Red Counters... - + Ustaw Czerwony Znacznik - + Add Life Counter - + Dodaj do Znacznika Życia - + Remove Life Counter - + Odejmij od Znacznika Życia - + Set Life Counters... - + Ustaw Znacznik Życia... - + Add White Counter - + Dodaj Biały Znacznik - + Remove White Counter - + Odejmij Biały Znacznik - + Set White Counters... - + Ustaw Biały Znacznik... - + Add Blue Counter - + Dodaj Niebieski Znacznik - + Remove Blue Counter - + Odejmij Niebieski Znacznik - + Set Blue Counters... - + Ustaw Niebieski Znacznik... - + Add Black Counter - + Dodaj Czarny Znacznik - + Remove Black Counter - + Odejmij Czarny Znacznik - + Set Black Counters... - + Ustaw Czarny Znacznik... - + Add Colorless Counter - + Dodaj Bezkolorowy Znacznik - + Remove Colorless Counter - + Odejmij Bezkolorowy Znacznik - + Set Colorless Counters... - + Ustaw Bezkolorowy Znacznik... - + Add Other Counter - + Dodaj Inny Znacznik - + Remove Other Counter - + Odejmij Inny Znacznik - + Set Other Counters... - + Ustaw Inny Znacznik... - + Add Power (+1/+0) - + Dodaj Power (+1/+0) - + Remove Power (-1/-0) - + Odejmij Power (-1/-0) - + Move Toughness to Power (+1/-1) - + Przesuń Toughness do Power (+1/-1) - + Add Toughness (+0/+1) - + Dodaj Toughness (+0/+1) - + Remove Toughness (-0/-1) - + Odejmij Toughness (-0/-1) - + Move Power to Toughness (-1/+1) - + Przesuń Power do Toughness (-1/+1) - + Add Power and Toughness (+1/+1) - + Dodaj Power i Toughness (+1/+1) - + Remove Power and Toughness (-1/-1) - + Odejmij Power i Toughness (-1/-1) - + Set Power and Toughness... - + Ustaw Power i Toughness... - + Reset Power and Toughness - + Resetuj Power i Toughness - + Untap Odtapuj - + Upkeep Utrzymanie - + Draw Dobranie - + First Main Phase - + Pierwsza Faza Główna - + Start Combat - + Początek Walki - + Attack Atak - + Block Blok - + Damage Obrażenia - + End Combat - + Koniec Walki - + Second Main Phase - + Druga Faza Główna - + End Koniec - + Next Phase - + Następna Faza - + Next Phase Action - + Następna Akcja Fazowa - + Next Turn + Następna Tura + + + + Hide Card in Reveal Window - + Tap / Untap Card Zatapuj / Odtapuj kartę - + Untap All - + Odtapuj Wszystkie - + Toggle Untap - + Odtapuj - + Turn Card Over - + Obróć Kartę - + Peek Card - + Podglądnij Kartę - + Play Card - + Zagraj Kartę - + Attach Card... - + Dołącz Kartę.. - + Unattach Card - + Odłącz Kartę - + Clone Card - + Sklonuj Kartę - + Create Token... - + Utwórz token… - + Create All Related Tokens - + Stwórz Wszystkie Pokrewne Tokeny - + Create Another Token - + Stwórz Kolejny Token - + Set Annotation... - + Ustaw Adnotację… - - + + Select All Cards in Zone + Zaznacz Wszystkie Karty w Strefie + + + + Select All Cards in Row + Zaznacz Wszystkie Karty w Rzędzie + + + + Select All Cards in Column + Zaznacz Wszystkie Karty w Kolumnie + + + + Bottom of Library - + Dno biblioteki - - - - + + + + Exile Wygnanie - - - - + + + + Graveyard Grób - - + + Hand Ręka - - + + Top of Library - + Wierzch biblioteki - - - + + + Battlefield, Face Down - + Pole Bitwy, Koszulką do Góry - + Battlefield - + Pole Bitwy - + Library Biblioteka - + Sideboard Biblioteka poboczna - + Top Cards of Library + Karty z Wierzchu Biblioteki + + + + Bottom Cards of Library - + Close Recent View - + Zamknij Ostatni Widok - - + + Stack - + Stos - - + + Graveyard (Multiple) - - - - - - Exile (Multiple) - - - - - Stack Until Found - - - - - Draw Bottom Card - - - - - Draw Multiple Cards from Bottom... - - - - - Draw Arrow... - - - - - Remove Local Arrows - - - - - Leave Game - + Cmentarz (Wiele) + + Exile (Multiple) + Wygnanie (Wiele) + + + + Stack Until Found + Stos aż do Znalezienia + + + + Draw Bottom Card + Dobierz Kartę z Dna + + + + Draw Multiple Cards from Bottom... + Dobierz Wiele Kart z Dna... + + + + Draw Arrow... + Narysuj wskaźnik... + + + + Remove Local Arrows + Usuń Lokalne Wskaźniki + + + + Leave Game + Opuść Grę + + + Concede Poddanie - + Roll Dice... - + Rzuć Kośćmi... - + Shuffle Library + Potasuj Bibliotekę + + + + Shuffle Top Cards of Library - + + Shuffle Bottom Cards of Library + + + + Mulligan Mulligan - + Draw a Card - + Dobierz Kartę - + Draw Multiple Cards... - + Dobierz Wiele Kart... - + Undo Draw - + Cofnij Dobranie - + Always Reveal Top Card - + Zawsze Odkrywaj Górną Kartę - + Always Look At Top Card - + Zawsze Patrz na Górną Kartę - + Rotate View Clockwise - + Obróć widok zgodnie z ruchem wskazówek zegara - + Rotate View Counterclockwise - + Obróć widok przeciwnie do ruchu wskazówek zegara - + Unfocus Text Box - + Nie skupiaj Tekstu - + Focus Chat - + Skup Czat - + Clear Chat - + Wyczyść czat - + Refresh - + Odśwież - + + Skip Forward + Przeskocz do Przodu + + + + Skip Backward + Przeskocz do Tyłu + + + + Skip Forward by a lot + Bardzo Przeskocz do Przodu + + + + Skip Backward by a lot + Bardzo Przeskocz do Tyłu + + + Play/Pause + Odtwórz/Pauza + + + + Toggle Fast Forward + Przełącz przyspieszenie + + + + Visual Deck Storage - - Toggle Fast Forward + + Deck Storage + + + + + Server + + + + + Account + + + + + Administration + + + + + Logs diff --git a/cockatrice/translations/cockatrice_pt.ts b/cockatrice/translations/cockatrice_pt.ts index 214cdbace..8e242f776 100644 --- a/cockatrice/translations/cockatrice_pt.ts +++ b/cockatrice/translations/cockatrice_pt.ts @@ -2,7 +2,7 @@ AbstractCounter - + &Set counter... Definir marcador @@ -10,95 +10,247 @@ AbstractCounterDialog - + Set counter - + New value for counter '%1': + + AbstractDlgDeckTextEdit + + + &Refresh + + + + + Parse Set Name and Number (if available) + + + + + AbstractTabDeckEditor + + + Open in new tab + + + + + Are you sure? + + + + + The decklist has been modified. +Do you want to save the changes? + + + + + + + + + + + Error + + + + + Could not open deck at %1 + + + + + Could not save remote deck + + + + + + The deck could not be saved. +Please check that the directory is writable and try again. + + + + + Save deck + + + + + The deck could not be saved. + + + + + There are no cards in your deck to be exported + + + + + No deck was selected to be exported. + + + + + AdminNotesDialog + + + Update Notes + + + + + Admin Notes for %1 + + + + + AllZonesCardAmountWidget + + + Mainboard + + + + + Sideboard + + + AppearanceSettingsPage - + Error - + Could not create themes directory at '%1'. - + Theme settings Definições do Tema - + Current theme: Tema actual: - + Open themes folder - + + Menu settings + + + + + Show keyboard shortcuts in right-click menus + + + + Card rendering Rendering da carta - + Display card names on cards having a picture Mostrar o nome em cartas com imagem - + + Auto-Rotate cards with sideways layout + + + + + Override all card art with personal set preference (Pre-ProviderID change behavior) [Requires Client restart] + + + + + Bump sets that the deck contains cards from to the top in the printing selector + + + + Scale cards on mouse over Aumentar as cartas ao passar o rato - + + Use rounded card corners + + + + Minimum overlap percentage of cards on the stack and in vertical hand - + + Maximum initial height for card view window: + + + + + + rows + + + + + Maximum expanded height for card view window: + + + + Hand layout Disposição da Mão - + Display hand horizontally (wastes space) Mostrar mão horizontalmente (desperdiça espaço) - + Enable left justification Permitir justificação de texto à esquerda - + Table grid layout Esquema da mesa - + Invert vertical coordinate Inverter coordenada vertical - + Minimum player count for multi-column layout: Número mínimo de kogadores para layout com múltiplas colunas: - + Maximum font size for information displayed on cards: @@ -106,112 +258,112 @@ BanDialog - + ban &user name Banir nome de &utilizador - + ban &IP address Banir endereço de &IP - + ban client I&D banir I&D do cliente - + Ban type Tipo de Ban - + &permanent ban Ban &permanente - + &temporary ban Ban &temporário - + &Days: &Dias: - + &Hours: &Horas: - + &Minutes: &Minutos: - + Duration of the ban Duração do ban - + Please enter the reason for the ban. This is only saved for moderators and cannot be seen by the banned person. Por favor introduza o motivo do ban. Isto apenas é guardado para os moderadores e não é visível para a pessoa banida. - + Please enter the reason for the ban that will be visible to the banned person. Por favor introduza o motivo do ban que será visível à pessoa banida. - + Redact all messages from this user in all rooms - + &OK &OK - + &Cancel &Cancelar - + Ban user from server Banir utilizador do servidor - - - - + + + + Error Erro - + You have to select a name-based, IP-based, clientId based, or some combination of the three to place a ban. Tem de seleccionar um banimento baseado no nome, no IP ou ambos. - + You must have a value in the name ban when selecting the name ban checkbox. Deve preencher o campo de nome a banir quando selecciona a caixa de verificação para banir um nome. - + You must have a value in the ip ban when selecting the ip ban checkbox. Deve preencher o campo de IP a banir quando selecciona a caixa de verificação para banir um IP. - + You must have a value in the clientid ban when selecting the clientid ban checkbox. Deve preencher o campo de identificação de cliente quando selecciona a caixa de verificação para banir uma identificação de cliente. @@ -219,22 +371,22 @@ Isto apenas é guardado para os moderadores e não é visível para a pessoa ban BetaReleaseChannel - - Beta Releases + + Beta - + No reply received from the release update server. - + Invalid reply received from the release update server. - + No reply received from the file update server. @@ -242,32 +394,32 @@ Isto apenas é guardado para os moderadores e não é visível para a pessoa ban CardDatabaseModel - + Name Nome - + Sets Expansões - + Mana cost Custo de mana - + Card type Tipo de carta - + P/T P/R - + Color(s) Cor(es) @@ -360,37 +512,65 @@ Isto apenas é guardado para os moderadores e não é visível para a pessoa ban - CardFrame + CardInfoFrameWidget - + Image - Imagem + - + Description - Descrição + - + Both - Ambos + + + + + View transformation + - CardInfoText + CardInfoPictureWidget - - Name: - Nome: + + View related cards + - + + Add card to deck + + + + + Mainboard + + + + + Sideboard + + + + + CardInfoTextWidget + + + Name: + + + + Related cards: - + Unknown card: @@ -398,287 +578,555 @@ Isto apenas é guardado para os moderadores e não é visível para a pessoa ban CardItem - + &Move to &Mover para - + &Power / toughness &Poder / resistência + + CardSizeWidget + + + Card Size + + + CardZone - + their hand nominative sua mão - + %1's hand nominative mão de %1 - + their library look at zone seu grimório - + %1's library look at zone grimório de %1 - + of their library top cards of zone, do seu grimório - + of %1's library top cards of zone do grimório de %1 - + their library reveal zone seu grimório - + %1's library reveal zone grimório de %1 - + their library shuffle seu grimório - + %1's library shuffle grimório de %1 - + their library nominative seu grimório - + %1's library nominative grimório de %1 - + their graveyard nominative seu cemitério - + %1's graveyard nominative cemitério de %1 - + their exile nominative seu exílio - + %1's exile nominative exílio de %1 - + their sideboard look at zone seu sideboard - + %1's sideboard look at zone sideboard de %1 - + their sideboard nominative seu sideboard - + %1's sideboard nominative sideboard de %1 + + DeckEditorCardInfoDockWidget + + + Card Info + + + + + DeckEditorDatabaseDisplayWidget + + + Search by card name (or search expressions) + + + + + Add to Deck + + + + + Add to Sideboard + + + + + Select Printing + + + + + Show on EDHREC (Commander) + + + + + Show on EDHREC (Card) + + + + + Show Related cards + + + + + Add card to &maindeck + + + + + Add card to &sideboard + + + + + DeckEditorDeckDockWidget + + + Banner Card + + + + + Select Printing + + + + + Deck + + + + + Deck &name: + + + + + &Comments: + + + + + Hash: + + + + + &Increment number + + + + + &Decrement number + + + + + &Remove row + + + + + Swap card to/from sideboard + + + + + DeckEditorFilterDockWidget + + + Filters + + + + + &Clear all filters + + + + + Delete selected + + + + + DeckEditorMenu + + + &Deck Editor + + + + + &New deck + + + + + &Load deck... + + + + + Load recent deck... + + + + + Clear + + + + + &Save deck + + + + + Save deck &as... + + + + + Load deck from cl&ipboard... + + + + + Edit deck in clipboard + + + + + + Annotated + + + + + + Not Annotated + + + + + Save deck to clipboard + + + + + Annotated (No set info) + + + + + Not Annotated (No set info) + + + + + &Print deck... + + + + + &Send deck to online service + + + + + Create decklist (decklist.org) + + + + + Create decklist (decklist.xyz) + + + + + Analyze deck (deckstats.net) + + + + + Analyze deck (tappedout.net) + + + + + &Close + + + + + DeckEditorPrintingSelectorDockWidget + + + Printing Selector + + + DeckEditorSettingsPage - - + + Update Spoilers - - + + Success - + Download URLs have been reset. - + Downloaded card pictures have been reset. - + Error - + One or more downloaded card pictures could not be cleared. - + Add URL - - + + URL: - - + + Edit URL - + + Network Cache Size: + + + + + Redirect Cache TTL: + + + + + How long cached redirects for urls are valid for. + + + + + Picture Cache Size: + + + + Add New URL - + Remove URL - + + Day(s) + + + + Updating... - + Choose path - + URL Download Priority - + Spoilers - + Download Spoilers Automatically - + Spoiler Location: - + Last Change - + Spoilers download automatically on launch - + Press the button to manually update without relaunching - + Do not close settings until manual update is complete - + Download card pictures on the fly - + How to add a custom URL - + Delete Downloaded Images - + Reset Download URLs - - Downloaded images directory size: - - - - + On-disk cache for downloaded pictures - - Picture cache size: - - - - + In-memory cache for pictures not currently on screen @@ -686,16 +1134,218 @@ Isto apenas é guardado para os moderadores e não é visível para a pessoa ban DeckListModel - + + Count + + + + + Set + + + + Number Número - + + Provider ID + + + + Card Carta + + DeckLoader + + + Common deck formats (%1) + + + + + All files (*.*) + + + + + DeckPreviewColorIdentityFilterWidget + + + Mode: Exact Match + + + + + Mode: Includes + + + + + DeckPreviewDeckTagsDisplayWidget + + + Edit tags ... + + + + + DeckPreviewTagDialog + + + Deck Tags Manager + + + + + Manage your deck tags. Check or uncheck tags as needed, or add new ones: + + + + + Add a new tag (e.g., Aggro️) + + + + + Add Tag + + + + + Filter tags... + + + + + OK + + + + + Cancel + + + + + Invalid Input + + + + + Tag name cannot be empty! + + + + + Duplicate Tag + + + + + This tag already exists. + + + + + DeckPreviewWidget + + + Banner Card + + + + + Open in deck editor + + + + + Edit Tags + + + + + Rename Deck + + + + + Save Deck to Clipboard + + + + + Annotated + + + + + Annotated (No set info) + + + + + Not Annotated + + + + + Not Annotated (No set info) + + + + + Rename File + + + + + Delete File + + + + + Set Banner Card + + + + + + New name: + + + + + + Error + + + + + Rename failed + + + + + Delete file + + + + + Are you sure you want to delete the selected file? + + + + + Delete failed + + + DeckStatsInterface @@ -713,181 +1363,217 @@ Isto apenas é guardado para os moderadores e não é visível para a pessoa ban DeckViewContainer - + Load deck... &Carregar deck... - + Load remote deck... Carregar deck remoto... - + + Unload deck + Unload deck... + + + + Ready to start - + + Force start + + + + Sideboard unlocked - + Sideboard locked - - Load deck - Carregar baralho - - - + Error Erro - + The selected file could not be loaded. O ficheiro seleccionado não pôde ser carregado. + + DialogConvertDeckToCodFormat + + + Deck Format Conversion + + + + + You tried to add a tag to a .txt format deck. + Tags can only be added to .cod format decks. + Do you want to convert the deck to the .cod format? + + + + + Remember and automatically apply choice in the future + + + DlgConnect - + Downloading... - + Known Hosts - + + Delete the currently selected saved server + + + + Refresh the server list with known public servers - + New Host Novo anfitrião - + Name: - + &Host: &Servidor: - + &Port: &Porta: - + Player &name: &Nome do jogador: - + P&assword: P&assword: - + &Save password &Guardar password - + A&uto connect Conectar a&utomaticamente no arranque - + Automatically connect to the most recent login when Cockatrice opens Conectar automaticamente ao mais recente login ao abrir o Cockatrice - + If you have any trouble connecting or registering then contact the server staff for help! - - + + Webpage - + Reset Password - + + Forgot password? + + + + &Connect - + Server Servidor - + Login Login - + Server Contact - + Connect to Server - + Server URL - + Communication Port - + Unique Server Name - + Connection Warning - + You need to name your new connection profile. - + Connect Warning Aviso de conexão - + The player name can't be empty. O nome de utilizador tem de ser inserido. @@ -970,27 +1656,37 @@ Isto apenas é guardado para os moderadores e não é visível para a pessoa ban Espectadores - + + Starting life total: + + + + + Game setup options + + + + &Clear &Limpar - + Create game Criar jogo - + Game information Informação do jogo - + Error Erro - + Server error. Erro do servidor. @@ -998,92 +1694,92 @@ Isto apenas é guardado para os moderadores e não é visível para a pessoa ban DlgCreateToken - + &Name: &Nome: - + Token Ficha - + C&olor: C&or: - + white branco - + blue azul - + black preto - + red vermelho - + green verde - + multicolor multicolor - + colorless incolor - + &P/T: &P/R: - + &Annotation: &Nota: - + &Destroy token when it leaves the table &Destruir ficha quando ela deixar a mesa - + Token data Informação de ficha - + Show &all tokens Mostrar &todas as fichas - + Show tokens from this &deck Mostrar &fichas deste deck - + Choose token from list Escolha ficha da lista - + Create token Criar ficha @@ -1129,6 +1825,24 @@ Para remover o seu avatar actual, confirme SEM escolher uma nova imagem.Escolheu uma imagem inválida. + + DlgEditDeckInClipboard + + + Edit deck in clipboard + + + + + Error + + + + + Invalid deck list. + + + DlgEditPassword @@ -1171,93 +1885,93 @@ Para remover o seu avatar actual, confirme SEM escolher uma nova imagem. DlgEditTokens - + &Name: &Nome: - + C&olor: C&or: - + white branco - + blue azul - + black preto - + red vermelho - + green verde - + multicolor multicolor - + colorless incolor - + &P/T: &P/R: - + &Annotation: &Nota: - + Token data Informação de ficha - - + + Add token Adicionar ficha - + Remove token Remover ficha - + Edit custom tokens - + Please enter the name of the token: Por favor introduza o nome da ficha: - + Error Erro - + The chosen name conflicts with an existing card or token. Make sure to enable the 'Token' set in the "Manage sets" dialog to display them correctly. @@ -1294,132 +2008,132 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgFilterGames - + no limit - + 5 minutes - + 10 minutes - + 30 minutes - + 1 hour - + 2 hours - - Show '&buddies only' games - Mostrar apenas jogos de amigos - - - - Show &full games + + Hide 'buddies only' games - - Show games &that have started + + Hide full games - - Show &password protected games - Mostrar jogos protegidos por &palavra-passe - - - - Hide '&ignored user' games + + Hide games that have started - + + Hide password protected games + + + + + Hide 'ignored user' games + + + + &Newer than: - + Game &description: &Descrição de jogo: - + &Creator name: &Nome do criador: - + General - + &Game types &Tipo de jogo - + at &least: no &mínimo: - + at &most: no m&áximo: - + Maximum player count Limite máximo de jogadores - + Restrictions Restrições - + Show games only if &spectators can watch - + Show spectator password p&rotected games - + Show only if spectators can ch&at - + Show only if spectators can see &hands - + Spectators - + Filter games Filtrar jogos @@ -1602,27 +2316,28 @@ Make sure to enable the 'Token' set in the "Manage sets" dia + + DlgLoadDeck + + + Load Deck + + + DlgLoadDeckFromClipboard - - &Refresh - &Refrescar - - - + Load deck from clipboard Carregar baralho da memória - - + Error Erro - - + Invalid deck list. Lista de deck inválida. @@ -1630,11 +2345,49 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgLoadRemoteDeck - + Load deck Carregar baralho + + DlgMoveTopCardsUntil + + + Card name (or search expressions): + + + + + Number of hits: + + + + + Auto play hits + + + + + Put top cards on stack until... + + + + + No cards matching the search expression exists in the card database. Proceed anyways? + + + + + Cockatrice + + + + + Invalid filter + + + DlgRegister @@ -1748,12 +2501,12 @@ Your email will be used to verify your account. DlgSettings - + Unknown Error loading card database Erro desconhecido ao carregar a base de dados das cartas - + Your card database is invalid. Cockatrice may not function correctly with an invalid database @@ -1770,7 +2523,7 @@ Poderá necessitar de correr o Oracle para actualizar a base de dados. Gostaria de alterar a localização da base de dados? - + Your card database version is too old. This can cause problems loading card information or images @@ -1787,7 +2540,7 @@ Pode ser reparado correndo o Oracle de novo para actualizar a base de dados das Gostaria de alterar a localização da base de dados? - + Your card database did not finish loading Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues with your cards.xml attached @@ -1796,7 +2549,7 @@ Would you like to change your database location setting? - + File Error loading your card database. Would you like to change your database location setting? @@ -1805,7 +2558,7 @@ Would you like to change your database location setting? Gostaria de alterar a localização da base de dados? - + Your card database was loaded but contains no cards. Would you like to change your database location setting? @@ -1814,7 +2567,7 @@ Would you like to change your database location setting? Gostaria de alterar a localização da base de dados? - + Unknown card database load status Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues @@ -1823,59 +2576,59 @@ Would you like to change your database location setting? - - - + + + Error Erro - + The path to your deck directory is invalid. Would you like to go back and set the correct path? O directório do seu deck é inválido. Gostaria de voltar atrás e corrigir o directório? - + The path to your card pictures directory is invalid. Would you like to go back and set the correct path? O directório das imagens das cartas é inválido. Gostaria de voltar atrás e corrigir o directório? - + Settings Definições - + General Geral - + Appearance Aparência - + User Interface Interface do utilizador - + Card Sources - + Chat Chat - + Sound Som - + Shortcuts Atalhos @@ -1901,164 +2654,168 @@ Would you like to change your database location setting? DlgUpdate - + Current release channel - + Reinstall - + Cancel Download - + Open Download Page Abrir a página de downloads - + Check for Client Updates - - - - + + + + Error Erro - + Cockatrice was not built with SSL support, therefore you cannot download updates automatically! Please visit the download page to update manually. - - Downloading update... - A descarregar a actualização... + + Downloading update: %1 + Downloading update... + - + Checking for updates... A procurar actualizações... - + Finished checking for updates - + No Update Available - + Cockatrice is up to date! - + You are already running the latest version available in the chosen release channel. - + Current version - + Selected release channel - - + + Update Available - - - - A new version of Cockatrice is available! - - - - - - New version - - - Released + A new version of Cockatrice is available! + + + + + + New version - Changelog + Released + + Changelog + + + + Do you want to update now? - - Unfortunately there are no download packages available for your operating system. -You may have to build from source yourself. + + Unfortunately, the automatic updater failed to find a compatible download. +You may have to manually download the new version. + Unfortunately there are no download packages available for your operating system. +You may have to build from source yourself. - - Please check the download page manually and visit the wiki for instructions on compiling. - Por favor verifique manualmente a página de download e visite a wiki para mais informações sobre compilar. + + Please check the <a href="%1">releases page</a> on our Github and download the build for your system. + Please check the download page manually and visit the wiki for instructions on compiling. + - - - + + + Update Error Erro na actualização - + An error occurred while checking for updates: - + An error occurred while downloading an update: - + Installing... A instalar... - + Cockatrice is unable to open the installer. - + Try to update manually by closing Cockatrice and running the installer. - + Download location @@ -2076,6 +2833,14 @@ You may have to build from source yourself. Registo de erros + + EdhrecCommanderResponseCommanderDetailsDisplayWidget + + + Salt: + + + FilterBuilder @@ -2087,105 +2852,120 @@ You may have to build from source yourself. GameSelector - - - - - - - - - + + + + + + + + + Error Erro - + Please join the appropriate room first. Por favor entre na sala apropriada primeiro. - + Wrong password. Password incorrecta. - + Spectators are not allowed in this game. Não são permitidos espectadores neste jogo. - + The game is already full. O jogo já se encontra cheio. - + The game does not exist any more. O jogo já não existe. - + This game is only open to registered users. Este jogo só está aberto a utilizadores registados. - + This game is only open to its creator's buddies. Este jogo só está aberto aos amigos do seu criador. - + You are being ignored by the creator of this game. Você está a ser ignorado pelo criador deste jogo. - + + Join Game + + + + + Spectate Game + + + + + Game Information + + + + Join game Entrar no jogo - + Password: Password: - + Please join the respective room first. Por favor entre na sala respectiva primeiro. - + &Filter games &Filtrar jogos - + C&lear filter &Limpar filtros - + C&reate C&riar - + &Join &Entrar - + J&oin as spectator Entrar como &espectador - + Games shown: %1 / %2 - + Games Jogos @@ -2193,100 +2973,100 @@ You may have to build from source yourself. GamesModel - + >1 day - + %1%2 hr short age in hours - + new - + %1%2 min short age in minutes - + password senha - + buddies only amigos apenas - + reg. users only utilizadores registados apenas - - + + can chat pode falar - + see hands ver mãos - + can see hands pode ver as mãos - + not allowed não permitidos - + Room Sala - + Age Idade - + Description Descrição - + Creator Criador - + Type Tipo - + Restrictions Restrições - + Players Jogadores - + Spectators Espectadores @@ -2294,92 +3074,102 @@ You may have to build from source yourself. GeneralSettingsPage - + Reset all paths - + All paths have been reset - - + + Choose path Escolher directório - + Personal settings Defenições pessoais - + Language: Língua: - + Paths (editing disabled in portable mode) Diretórios (edição desativada em modo portátil) - + Paths Directórios - + + How to help with translations + + + + Decks directory: Directório dos decks: - + Replays directory: Directório de replays: - + Pictures directory: Directório das imagens: - + Card database: Base de dados das cartas: - + Custom database directory: - + Token database: Base de dados das fichas: - + Update channel - + + Check for client updates on startup + + + + Notify if a feature supported by the server is missing in my client - + Automatically run Oracle when running a new version of Cockatrice - + Show tips on startup @@ -2387,61 +3177,61 @@ You may have to build from source yourself. MainWindow - - + + The server has reached its maximum user capacity, please check back later. O servidor atingiu a máxima capacidade de utilizadores, por favor verifique de novo mais tarde. - + There are too many concurrent connections from your address. Há demasiadas ligações concorrentes do seu endereço. - + Banned by moderator Banido por moderador - + Expected end time: %1 Tempo previsto para o final:%1 - + This ban lasts indefinitely. Este banimento dura indefinidamente. - + Scheduled server shutdown. Encerramento do servidor agendado. - - + + Invalid username. Nome de utilizador invalido. - + You have been logged out due to logging in at another location. Você deslogado por ter entrado numa outra localização. - + Connection closed Ligação terminada - + The server has terminated your connection. Reason: %1 O servidor terminou a sua ligação. Motivo: %1 - + The server is going to be restarted in %n minute(s). All running games will be lost. Reason for shutdown: %1 @@ -2454,553 +3244,563 @@ Todos os jogos a decorrer serão perdidos. Motivo para o encerramento: %1 - + Scheduled server shutdown Encerramento do servidor agendado - - + + Success Sucesso - + Registration accepted. Will now login. Registo aceite.%s Entrará agora. - + Account activation accepted. Will now login. Activação da conta aceite. %s Entrará agora. - + Number of players Número de jogadores - + Please enter the number of players. Por favor introduza o número de jogadores. - - + + Player %1 Jogador %1 - + Load replay Carregar replay - + About Cockatrice Sobre o Cockatrice - + Version - + Cockatrice Webpage Website Cockatrice - + Project Manager: Gestor do projecto: - + Past Project Managers: Anteriores gestores do projecto: - + Developers: Desenvolvedores: - + Our Developers Os nossos desenvolvedores - + Help Develop! Ajuda Desenvolvimento! - + Translators: Tradutores: - + Our Translators - + Help Translate! Ajude a traduzir! - + Support: Ajuda técnica: - + Report an Issue Reportar um Problema - + Troubleshooting Solução de problemas - + F.A.Q. Perguntas frequentes - - - - - - - - - - - - - - - + + + + + + + + + + + + + - - - + + + + + Error Erro - + Server timeout Tempo do servidor esgotado - + Failed Login Tentativa falhada de entrada - + Your client seems to be missing features this server requires for connection. - + To update your client, go to 'Help -> Check for Client Updates'. - + Incorrect username or password. Please check your authentication information and try again. Nome de utilizador ou palavra-passe incorrecta. Por favor verifique as suas informações e tente de novo. - + There is already an active session using this user name. Please close that session first and re-login. Já existe uma sessão activa com este nome de utilizador. Por favor termine essa sessão e volte a ligar-se. - - + + You are banned until %1. Está banido até %1. - - + + You are banned indefinitely. Está banido indefinidamente. - + This server requires user registration. Do you want to register now? Este servidor requer registo de utilizador. Gostaria de se registar agora? - + This server requires client IDs. Your client is either failing to generate an ID or you are running a modified client. Please close and reopen your client to try again. - + An internal error has occurred, please close and reopen Cockatrice before trying again. If the error persists, ensure you are running the latest version of the software and if needed contact the software developers. - + Account activation Activação de conta - + Your account has not been activated yet. You need to provide the activation token received in the activation email. - + Server Full - + Unknown login error: %1 Erro de login desconhecido:%1 - - + + This usually means that your client version is out of date, and the server sent a reply your client doesn't understand. Normalmente isto significa que a sua versão de cliente está desactualizada e que o servidor enviou uma resposta que o seu cliente não percebe. - + Your username must respect these rules: O seu nome de utilizador deve respeitar as seguintes regras: - + is %1 - %2 characters long tem %1 - %2 caracteres - + can %1 contain lowercase characters %1 pode conter caracteres em minúsculas - - - - + + + + NOT NÃO - + can %1 contain uppercase characters %1 pode conter caracteres em maiúsculas - + can %1 contain numeric characters %1 pode conter caracteres numéricos - + can contain the following punctuation: %1 pode conter a seguinte pontuação: %1 - + first character can %1 be a punctuation mark o primeiro caracter %1 pode ser pontuação - + no unacceptable language as specified by these server rules: note that the following lines will not be translated - + can not contain any of the following words: %1 - + can not match any of the following expressions: %1 - + You may only use A-Z, a-z, 0-9, _, ., and - in your username. Apenas A-Z, a-z, 0-9, _, ., e - são permitidos. - - - - - - + + + + + + Registration denied Registo recusado - + Registration is currently disabled on this server Os registos encontram-se indisponívels para este servidor de momento. - + There is already an existing account with the same user name. Já existe uma conta com o mesmo nome de utilizador. - + It's mandatory to specify a valid email address when registering. É obrigatório especificar um endereço de e-mail válido quando se regista. - + It appears you are attempting to register a new account on this server yet you already have an account registered with the email provided. This server restricts the number of accounts a user can register per address. Please contact the server operator for further assistance or to obtain your credential information. - + Password too short. A password que inseriu é demasiado curta. - + Registration failed for a technical problem on the server. Falha no registo devido a um problema técnico com o servidor - + Unknown registration error: %1 Erro de registo desconhecido: %1 - + Account activation failed Falha na activação de conta - + Socket error: %1 Erro de ligação:%1 - + You are trying to connect to an obsolete server. Please downgrade your Cockatrice version or connect to a suitable server. Local version is %1, remote version is %2. Está a tentar ligar-se a um servidor obsoleto. Por favor faça downgrade à sua versão do Cockatrice ou ligue-se a servidor adequado. Versão local é %1, versão remota é %2. - + Your Cockatrice client is obsolete. Please update your Cockatrice version. Local version is %1, remote version is %2. A sua versão do Cockatrice é obsoleta. Por favor actualize-a. Versão local é %1, versão remota é %2. - + Connecting to %1... Ligando a %1... - + Registering to %1 as %2... Registando em %1 como %2... - + Disconnected Desligado - + Connected, logging in at %1 Conectado, logando em %1 - - - + + + Requesting forgotten password to %1 as %2... - + &Connect... &Ligar... - + &Disconnect &Desligar - + Start &local game... Começar &jogo local... - + &Watch replay... &Ver replay... - - &Deck editor - &Editor de decks - - - + &Full screen Ecrã &inteiro - + &Register to server... &A registar no servidor... - + &Restore password... - + &Settings... &Configurações... - + &Exit &Sair - + A&ctions A&cções - + &Cockatrice &Cockatrice - + C&ard Database B&ase de dados de cartas - + &Manage sets... - + Edit custom &tokens... - + Open custom image folder Abrir a pasta de imagens personalizadas - + Open custom sets folder Abrir a pasta de expansões personalizadas - + Add custom sets/cards Adicione cartas/expansões personalizadas - + + Reload card database + + + + + Tabs + + + + &Help &Ajuda - + &About Cockatrice S&obre o Cockatrice - + &Tip of the Day - + Check for Client Updates - + Check for Card Updates... - + View &Debug Log - + + Open Settings Folder + + + + Show/Hide - + New Version - + Congratulations on updating to Cockatrice %1! Oracle will now launch to update your card database. - + Cockatrice installed - + Congratulations on installing Cockatrice %1! Oracle will now launch to install the initial card database. - + Card database Base de dados de cartas - + Cockatrice is unable to load the card database. Do you want to update your card database now? If unsure or first time user, choose "Yes" @@ -3009,104 +3809,105 @@ Deseja actualizar a sua base de dados agora?%s Se não tem a certeza ou é a primeira vez que está a utilizar o Cockatrice, escolha "Sim" - - + + Yes Sim - - + + No Não - + Open settings Abrir definições - + New sets found Novas expansões foram encontradas - + %n new set(s) found in the card database Set code(s): %1 Do you want to enable it/them? - + View sets Ver expansões - + Welcome Bem-vindo/a - + Hi! It seems like you're running this version of Cockatrice for the first time. All the sets in the card database have been enabled. Read more about changing the set order or disabling specific sets and consequent effects in the "Manage Sets" dialog. - - + + Information Informação - + A card database update is already running. A actualização da base de dados das cartas já está a correr. - + Unable to run the card database updater: Incapaz de correr o actualizador da base de dados das cartas: - - failed to start. - falha no começo. + + Failed to start. The file might be missing, or permissions might be incorrect. + - - crashed. - falhou. + + The process crashed some time after starting successfully. + - - timed out. - esgotou o tempo. + + Timed out. The process took too long to respond. The last waitFor...() function timed out. + - - write error. - erro na escrita. + + An error occurred when attempting to write to the process. For example, the process may not be running, or it may have closed its input channel. + - - read error. - erro na leitura. + + An error occurred when attempting to read from the process. For example, the process may not be running. + - - unknown error. - erro desconhecido. + + Unknown error occurred. + - - The card database updater exited with an error: %1 - O actualizador da base de dados das cartas encerrou-se com um erro: %1 + + The card database updater exited with an error: +%1 + - + This server supports additional features that your client doesn't have. This is most likely not a problem, but this message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -3114,55 +3915,55 @@ To update your client, go to Help -> Check for Updates. - - - - - + + + + + Load sets/cards Carregar expansões/cartas - + Selected file cannot be found. O ficheiro escolhido não foi encontrado. - + You can only import XML databases at this time. - + The new sets/cards have been added successfully. Cockatrice will now reload the card database. Actualização completa com sucesso. %s O Cockatrice irá agora recarregar a base de dados das cartas. - + Sets/cards failed to import. Falha na importação de expansões/cartas. - - - + + + Reset Password - + Your password has been reset successfully, you can now log in using the new credentials. - + Failed to reset user account password, please contact the server operator to reset your password. - + Activation request received, please check your email for an activation token. @@ -3436,179 +4237,190 @@ O Cockatrice irá agora recarregar a base de dados das cartas. - %1 is looking at the top %3 card(s) %2. + %1 is looking at the %4 %3 card(s) %2. + %1 is looking at the top %3 card(s) %2. top card for singular, top %3 cards for plural - + + bottom + + + + + top + + + + %1 turns %2 face-down. - + %1 turns %2 face-up. - + The game has been closed. Este jogo foi encerrado. - + The game has started. O jogo começou. - + %1 has joined the game. %1 entrou no jogo. - + %1 is now watching the game. %1 está agora a ver o jogo. - + You have been kicked out of the game. Você foi expulso do jogo. - + %1 has left the game (%2). %1 abandonou o jogo (%2). - + %1 is not watching the game any more (%2). %1 não está mais a observar o jogo (%2). - + %1 is not ready to start the game any more. %1 já não está pronto a começar o jogo. - + %1 shuffles their deck and draws a new hand of %2 card(s). - + %1 shuffles their deck and draws a new hand. - + You are watching a replay of game #%1. Está a ver um replay do jogo #%1. - + %1 is ready to start the game. %1 está pronto a começar o jogo. - + cards an unknown amount of cards - + %1 card(s) a card for singular, %1 cards for plural - + %1 lends %2 to %3. - + %1 reveals %2 to %3. %1 revela %2 a %3. - + %1 reveals %2. %1 revela %2. - + %1 randomly reveals %2%3 to %4. %1 revela aleatoreamente %2%3. a %4. - + %1 randomly reveals %2%3. %1 revela aleatoreamente %2%3. - + %1 peeks at face down card #%2. %1 espreita a carta virada para baixo #%2. - + %1 peeks at face down card #%2: %3. %1 espreita a carta virada para baixo #%2: %3. - + %1 reveals %2%3 to %4. %1 revela %2%3 a %4. - + %1 reveals %2%3. %1 revela %2%3. - + %1 reversed turn order, now it's %2. - + reversed - + normal - + Heads Cara - + Tails Coroa - + %1 flipped a coin. It landed as %2. %1 atirou uma moeda ao ar. Calhou %2. - + %1 rolls a %2 with a %3-sided die. %1 obteve %2 com um dado de %3 faces. - + %1 flips %2 coins. There are %3 heads and %4 tails. - + %1 rolls a %2-sided dice %3 times: %4. @@ -3728,17 +4540,17 @@ O Cockatrice irá agora recarregar a base de dados das cartas. - + %1 unattaches %2. %1 desanexa %2. - + %1 undoes their last draw. %1 desfaz a sua última compra. - + %1 undoes their last draw (%2). %1 desfaz a sua última compra (%2). @@ -3746,110 +4558,110 @@ O Cockatrice irá agora recarregar a base de dados das cartas. MessagesSettingsPage - + Word1 Word2 Word3 - + Add New Message - + Edit Message - + Remove Message - + Add message Adicionar mensagem - - + + Message: Mensagem: - + Edit message - + Chat settings Definições do Chat - + Custom alert words Palavras alerta personalizáveis - + Enable chat mentions Permitir menções no chat - + Enable mention completer Permitir complementação de menções - + In-game message macros Macros de mensagem no decorrer do jogo - + How to use in-game message macros - + Ignore chat room messages sent by unregistered users Ignorar mensagens de chat enviadas por utilizadores não registados - + Ignore private messages sent by unregistered users Ignorar mensagens privadas enviadas por utilizadores não registados - - + + Invert text color Inverter cor do texto - + Enable desktop notifications for private messages Permitir notificações de mensagens privadas no Ambiente de Trabalho - + Enable desktop notification for mentions Permitir notificações de menções no ambiente de trabalho - + Enable room message history on join Permitir o histórico de mensagens de uma sala ao entrar - - + + (Color is hexadecimal) (A cor é hexadecimal) - + Separate words with a space, alphanumeric characters only Palavras separadas com espaço, apenas caracteres alfanuméricos @@ -3907,6 +4719,24 @@ O Cockatrice irá agora recarregar a base de dados das cartas. + + OverlapControlWidget + + + Cards to overlap: + + + + + Overlap percentage: + + + + + Overlap direction: + + + Phase @@ -3973,57 +4803,57 @@ O Cockatrice irá agora recarregar a base de dados das cartas. PhasesToolbar - + Untap step Etapa de Desvirar - + Upkeep step Etapa de manutenção - + Draw step Etapa de compra - + First main phase 1ª Fase Principal (pré-combate) - + Beginning of combat step Etapa de Início de Combate - + Declare attackers step Etapa de Declaração de Atacantes - + Declare blockers step Etapa de Declaração de Bloqueadores - + Combat damage step Etapa de Dano de Combate - + End of combat step Etapa de Fim de Combate - + Second main phase 2ª Fase Principal (pós-combate) - + End of turn step Fase Final @@ -4031,7 +4861,7 @@ O Cockatrice irá agora recarregar a base de dados das cartas. PictureLoader - + en code for scryfall's language property, not available for all languages pt @@ -4040,647 +4870,781 @@ O Cockatrice irá agora recarregar a base de dados das cartas. Player - + Reveal top cards of library Mostrar as cartas do topo do grimório - - - - - - - - - + + + + + + + + + + + + Number of cards: (max. %1) Número de cartas: (max. %1) - + &View graveyard &Ver cemitério - + &View exile &Ver exílio - + Player "%1" Jogador "%1" - - - - + + + + &Graveyard &Cemitério - - - - + + + + &Exile &Exílio - + &Move hand to... &Mover mão para... - - - + + + &Top of library &Topo do grimório - - - + + + &Bottom of library &Fundo do grimório - + &Move graveyard to... &Mover cemitério para... - - - - + + + + &Hand &Mão - + &Move exile to... &Mover exílio para... - + &View library &Ver grimório - + &View hand - + View &top cards of library... Ver as cartas do &topo do grimório... - + Reveal &library to... &Mostrar grimório a... - + &Always reveal top card &Revelar sempre carta do topo - + &View sideboard &Ver sideboard - + &Draw card &Comprar carta - + D&raw cards... C&omprar cartas... - + &Undo last draw Desfa&zer a última compra - + Take &mulligan Fazer &mulligan - - &Shuffle - &Baralhar - - - + Play top card &face down Jogue carta do topo &virada para baixo - + Move top card to grave&yard - + Move top card to e&xile - + Move top cards to &graveyard... Mover as cartas do topo para o &cemitério... - + Move top cards to &exile... Mover as cartas do topo para o &exílio... - + Put top card on &bottom Colocar carta do topo no &fundo - + + View bottom cards of library... + + + + Lend library to... - + + Shuffle + + + + Put top cards on stack &until... Take top cards &until... - + + Shuffle top cards... + + + + + Shuffle bottom cards... + + + + &Reveal hand to... &Mostrar mão a... - + Reveal r&andom card to... Revele c&arta aleatória a... - + Reveal random card to... - + &Sideboard &Sideboard - + &Library &Grimório - + &Counters &Marcadores - + &Untap all permanents &Desvirar topas as permanentes - + R&oll die... &Lançar dado... - + &Create token... &Criar token - + C&reate another token C&riar outro token - + Cr&eate predefined token Cr&iar token pré-definido - - + + &All players Todos os &jogadores - + S&ay &Dizer - + + &Select All + + + + + S&elect Row + + + + + S&elect Column + + + + &Play &Jogar - + &Hide Esco&nder - + Play &Face Down Jogue &virada para baixo - + &Tap / Untap Turn sideways or back again - + Toggle &normal untapping A&lterar desvirar normalmente - + T&urn Over Turn face up/face down - + &Peek at card face &Espreitar a face da carta - + &Clone Copi&ar - + Attac&h to card... Ane&xar a carta... - + Unattac&h Separar as car&tas - + &Draw arrow... Desen&har seta... - + &Increase power &Aumentar poder - + &Decrease power &Diminuir poder - + I&ncrease toughness A&umentar resistência - + D&ecrease toughness Di&minuir resistência - + In&crease power and toughness Aumen&tar poder e resistência - + Dec&rease power and toughness Dimin&uir poder e resistência - + Increase power and decrease toughness - + Decrease power and increase toughness - + Set &power and toughness... Definir &poder e resistência... - + Reset p&ower and toughness - + &Set annotation... Colocar &nota... - + Red Vermelho - + Yellow Amarelo - + Green Verde - + &Add counter (%1) - + &Remove counter (%1) - + &Set counters (%1)... - + &Top of library in random order - + X cards from the top of library... X cartas do topo do grimório... - + &Bottom of library in random order - + View top cards of library Ver as cartas do topo do grimório - + + View bottom cards of library + + + + + Shuffle top cards of library + + + + + Shuffle bottom cards of library + + + + Which position should this card be placed: - + (max. %1) - + Draw hand - + 0 and lower are in comparison to current hand size - + Draw cards Comprar cartas - - + + Number: Número: - + Move top cards to grave Mover as cartas to topo para o cemitério - + Reveal &top cards to... - + &Top of library... - + &Bottom of library... - + &Always look at top card - + &Open deck in deck editor - + &Play top card - + &Draw bottom card - + D&raw bottom cards... - + &Play bottom card - + Play bottom card &face down - + Move bottom card to grave&yard - + Move bottom card to e&xile - + Move bottom cards to &graveyard... - + Move bottom cards to &exile... - + Put bottom card on &top - + Selec&ted cards - + Move top cards to exile Mover as cartas to topo para o exílio - + Move bottom cards to grave - + Move bottom cards to exile - + Draw bottom cards - - + + C&reate another %1 token C&riar outro marcador %1 - + Create tokens - + Place card X cards from top of library - + Change power/toughness - + Change stats to: - + Set annotation Colocar nota - + Please enter the new annotation: Por favor introduza a nova nota: - + Set counters Definir marcadores - + Re&veal to... - + View related cards - + Token: - + All tokens + + PrintingSelector + + + Display Navigation Buttons + + + + + <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. + + + + + PrintingSelectorCardOverlayWidget + + + Preference + + + + + Pin Printing + + + + + Unpin Printing + + + + + Show Related cards + + + + + PrintingSelectorCardSearchWidget + + + Search by set name or set code + + + + + PrintingSelectorCardSelectionWidget + + + Previous Card in Deck + + + + + Next Card in Deck + + + + + PrintingSelectorCardSortingWidget + + + Alphabetical + + + + + Preference + + + + + Release Date + + + + + + Descending + + + + + Ascending + + + QMenuBar - + Services Serviços - + Hide %1 Ocultar %1 - + Hide Others Ocultar outros - + Show All Mostrar tudo - + Preferences... Preferências... - + Quit %1 Sair %1 - + About %1 Cerca %1 @@ -4688,38 +5652,32 @@ O Cockatrice irá agora recarregar a base de dados das cartas. QObject - + Cockatrice card database (*.xml) Base de dados de cartas Cockatrice (*.xml) - - + All files (*.*) Todos os ficheiros (*.*) - + Cockatrice replays (*.cor) Replays do Cockatrice (*.cor) - - Common deck formats (*.cod *.dec *.dek *.txt *.mwDeck) - - - - + Maindeck - + Sideboard - + Tokens @@ -4727,92 +5685,92 @@ O Cockatrice irá agora recarregar a base de dados das cartas. QPlatformTheme - + OK - + Save - + Save All - + Open - + &Yes - + Yes to &All - + &No - + N&o to All - + Abort - + Retry - + Ignore - + Close - + Cancel - + Discard - + Help - + Apply - + Reset - + Restore Defaults @@ -4820,17 +5778,17 @@ O Cockatrice irá agora recarregar a base de dados das cartas. RemoteDeckList_TreeModel - + Name Nome - + ID ID - + Upload time Hora de upload @@ -4838,32 +5796,32 @@ O Cockatrice irá agora recarregar a base de dados das cartas. RemoteReplayList_TreeModel - + ID ID - + Name Nome - + Players Jogadores - + Keep Keep - + Time started Tempo de começo - + Duration (sec) Duração (seg) @@ -4909,32 +5867,32 @@ O Cockatrice irá agora recarregar a base de dados das cartas. SequenceEdit - + Choose an action from the table - + Hit the key/combination of keys you want to set for this action - - Shortcut already in use - - - - + Invalid key - + + Shortcut already in use by: + + + + Clear - + Restore default @@ -4970,72 +5928,80 @@ O Cockatrice irá agora recarregar a base de dados das cartas. ShortcutSettingsPage - - + + Restore all default shortcuts - + Do you really want to restore all default shortcuts? - + Clear all default shortcuts - + Do you really want to clear all shortcuts? - - Action - - - - - Shortcut - - - - + Section: - + Action: - + Shortcut: - + How to set custom shortcuts - + Clear all shortcuts + + + Search by shortcut name + + + + + ShortcutTreeView + + + Action + + + + + Shortcut + + ShortcutsSettings - + Your configuration file contained invalid shortcuts. Please check your shortcut settings! - + The following shortcuts have been set to default: @@ -5044,17 +6010,17 @@ Please check your shortcut settings! ShutdownDialog - + &Reason for shutdown: &Motivos para o encerramento: - + &Time until shutdown (minutes): &Tempo até ao encerramento (minutos): - + Shut down server Encerrar servidor @@ -5062,27 +6028,27 @@ Please check your shortcut settings! SoundSettingsPage - + Enable &sounds Permitir &sons - + Current sounds theme: Tema sonoro actual: - + Test system sound engine Teste motor de sistema de som - + Sound settings Definições de Som - + Master volume Volume mestre @@ -5090,48 +6056,48 @@ Please check your shortcut settings! SpoilerBackgroundUpdater - + Spoilers season has ended - + Deleting spoiler.xml. Please run Oracle - - + + Spoilers download failed - + No internet connection - + Error - + Spoilers already up to date - + No new spoilers added - + Spoilers have been updated! - + Last change: @@ -5139,638 +6105,594 @@ Please check your shortcut settings! StableReleaseChannel - - Stable Releases + + Default - + No reply received from the release update server. - + Invalid reply received from the release update server. - + No reply received from the tag update server. - + Invalid reply received from the tag update server. + + TabAccount + + + Add to Buddy List + + + + + Add to Ignore List + + + + + Account + + + TabAdmin - + Administration Administração - + Update server &message &Actualizar mensagem do servidor - + &Shut down server &Encerrar servidor - + &Reload configuration &Recarregar configuração - + Server administration functions Funções do administrador do servidor - + + Server moderator functions + + + + + Replay ID + + + + + Grant Replay Access + + + + + Username to Activate + + + + + Force Activate User + + + + &Unlock functions &Desbloquear funções - + &Lock functions &Bloquear funções + + + + Success + + + + + Replay access granted + + + + + + + + + Error + + + + + Unable to grant replay access. Replay ID invalid + + + + + Unable to grant replay access. Internal error + + + + + User successfully activated + + + + + Unable to activate user. Username invalid + + + + + Unable to activate user. User already active + + + + + Unable to activate user. Internal error + + TabDeckEditor - - Search by card name (or search expressions) - Search by card name - - - - - Add to Deck - - - - - Add to Sideboard - - - - - Show Related cards - - - - - &Clear all filters - &Limpar filtros - - - - Delete selected - Apague seleccionados - - - - Deck &name: - &Nome do deck: - - - - &Comments: - &Comentários: - - - - Hash: - Hash: - - - - &New deck - &Novo deck - - - - &Load deck... - &Carregar deck... - - - - &Save deck - &Guardar deck - - - - Save deck &as... - G&uardar deck como... - - - - Load deck from cl&ipboard... - Carregar dec&k da memória... - - - - Save deck to clipboard - - - - - Annotated - - - - - Not Annotated - - - - - &Print deck... - &Imprimir deck... - - - - &Send deck to online service - - - - - Create decklist (decklist.org) - - - - - Analyze deck (deckstats.net) - - - - - Analyze deck (tappedout.net) - - - - - &Close - &Fechar - - - - Add card to &maindeck - Adicionar carta ao &maindeck - - - - Add card to &sideboard - Adicionar carta ao &sideboard - - - - &Remove row - &Remover linha - - - - &Increment number - &Aumentar o número - - - - &Decrement number - &Diminuir o número - - - - &Deck Editor - %Editor de Decks - - - - + Card Info Informação da carta - - + Deck Baralho - - + Filters Filtros - + &View &Vista - - - + + Printing + + + + + + + Visible Visível - - - + + + + Floating Flutuando - + Reset layout Repor disposição - + Deck: %1 Deck:%1 - - - Are you sure? - Tem a certeza? - - - - The decklist has been modified. -Do you want to save the changes? - A lista foi modificada. -Gostaria de guardar as alterações? - - - - Load deck - Carregar deck - - - - - - - - - Error - Erro - - - - The deck could not be saved. - O deck não pode ser guardado. - - - - Could not save remote deck - - - - - - The deck could not be saved. -Please check that the directory is writable and try again. - O deck não pode ser guardado. -Por favor confirme se é possível escrever do directório e tente de novo. - - - - Save deck - Guardar deck - - - - There are no cards in your deck to be exported - - - - - No deck was selected to be saved. - - TabDeckStorage - - Deck storage - Armazenamento de decks - - - + Local file system Ficheiros locais - + Server deck storage Decks armazenados no servidor - - + + Open in deck editor Abrir no editor de decks - + + Rename deck or folder + + + + Upload deck Upload do deck - + Download deck Download do deck - - + + + + New folder Nova pasta - - + + Delete Apagar - - + + Open decks folder + + + + + Rename local folder + + + + + Rename local file + + + + + New name: + + + + + + + Error - + + Rename failed + + + + + Invalid deck file - + Enter deck name Introduza o nome do deck - + This decklist does not have a name. Please enter a name: Este deck nao tem um nome Por favor introduza um nome: - + Unnamed deck Deck sem nome - + Failed to upload deck to server - + Delete local file Apagar ficheiro local - - - - Are you sure you want to delete "%1"? - Tem a certeza que deseja apagar "%1"? + + Are you sure you want to delete the selected files? + - + + Delete remote decks + + + + + Are you sure you want to delete the selected decks? + + + + + Name of new folder: Nome da nova pasta: - - Delete remote folder - Eliminar pasta remota + + Deck Storage + + + + + TabDeckStorageVisual + + + Visual Deck Storage + - - Delete remote deck - Apagar deck remoto + + Error + + + + + Could not open deck at %1 + + + + + TabEdhRec + + + EDHREC: + TabGame - - - + + + Replay Repetição - - + + Game Jogo - - + + Player List Jogadores - - + + Card Info Informação da carta - - + + Messages Mensagens - - + + Replay Timeline Repetir Cronologia - + &Phases Fa&ses - + &Game &Jogo - + Next &phase Próxima &fase - + Next phase with &action - + Next &turn Próximo &turno - + Reverse turn order - + &Remove all local arrows &Remover todas as setas locais - + Rotate View Cl&ockwise Rodar a vista no se&ntido dos ponteiros - + Rotate View Co&unterclockwise rodar a vista no se&ntido contrário aos ponteiros - + Game &information &Informação do jogo - + + Un&concede + + + + &Concede &Conceder - + &Leave game Sair do &jogo - + C&lose replay &Fechar replay - + &Focus Chat - + &Say: &Dizer: - + &View &Vista - - - - + + + + Visible Visível - - - - + + + + Floating Flutuando - + Reset layout Repor disposição - + Concede Conceder - + Are you sure you want to concede this game? Tem a certeza que deseja conceder este jogo? - + Unconcede - + You have already conceded. Do you want to return to this game? - + Leave game Sair do jogo - + Are you sure you want to leave this game? Tem a certeza que deseja sair deste jogo? - + You are flooding the game. Please wait a couple of seconds. Você está a sobrecarregar o jogo. Por favor aguarte uns segundos. - + A player has joined game #%1 - + %1 has joined the game - + kicked by game host or moderator - + player left the game - + player disconnected from server - + reason unknown - + You have been kicked out of the game. Foi expulso do jogo. @@ -5783,153 +6705,153 @@ Por favor introduza um nome: Registos - - - + + + Time;SenderName;SenderIP;Message;TargetID;TargetName Tempo;NomeEnviador;IPEnviador;Mensagem;IDAlvo;NomeAlvo - + Room Logs Registos das salas - + Game Logs Registos dos jogos - + Chat Logs Registos das conversas - - + + Error Erro - + You must select at least one filter. Deve seleccionar pelo menos um filtro - + You have to select a valid number of days to locate. Deve seleccionar um número válido de dias para localizar. - + Username: Nome de utilizador: - + IP Address: Morada IP: - + Game Name: Nome do Jogo: - + GameID: ID do Jogo - + Message: Mensagem: - + Main Room Sala principal - + Game Room Sala de Jogo - + Private Chat Chat Privado - + Past X Days: Últimos X Dias: - + Today Hoje - + Last Hour Última Hora - + Maximum Results: Resultados Máximos: - + At least one filter is required. The more information you put in, the more specific your results will be. É requerido pelo menos um filtro. %s Quanta mais informação der, mais específicos serão os resultados. - + Get User Logs Obter registos dos utilizadores - + Clear Filters Limpar Filtros - + Filters Filtros: - + Log Locations Localização dos Registos - + Date Range Alcance de dados - + Maximum Results Resultados máximos - - + + Message History Histórico de mensagens - + Failed to collect message history information. Falha na obtenção do histórico de mensagens - + There are no messages for the selected filters. Não há mensagens para os filtros seleccionados @@ -5952,22 +6874,22 @@ Quanta mais informação der, mais específicos serão os resultados.%1 - Chat privado - + This user is ignoring you, they cannot see your messages in main chat and you cannot join their games. - + Private message from - + %1 has left the server. %1 abandonou o servidor. - + %1 has joined the server. %1 entrou no servidor. @@ -5975,117 +6897,153 @@ Quanta mais informação der, mais específicos serão os resultados. TabReplays - - Game replays - Repetições - - - + Local file system Ficheiros locais - + Server replay storage Armazenamento de replays no servidor - - + + Watch replay Ver replay - - + + Rename + + + + + + New folder + + + + + Delete Apagar - + + Open replays folder + + + + Download replay Download replay - + Toggle expiration lock Alternar bloqueio de expiração - + + Rename local folder + + + + + Rename local file + + + + + New name: + + + + + Error + + + + + Rename failed + + + + + Name of new folder: + + + + Delete local file Apagar ficheiro local - - Are you sure you want to delete "%1"? - Tem a certeza que deseja apagar %1? - - - - Downloading Replays + + Are you sure you want to delete the selected files? - - Folder download is not yet supported. Please download replays individually. + + Are you sure you want to delete the selected replays? - + Delete remote replay Apagar replay remoto - - Are you sure you want to delete the replay of game %1? - Tem a certeza que deseja apagar o replay do jogo %1? + + Game Replays + TabRoom - + &Say: &Dizer: - + Chat Chat - + &Room &Sala - + &Leave room &Abandonar a sala - + &Clear chat &Limpar chat - + Chat Settings... Definições do chat... - + mentioned you. mencionou-o. - + Click to view Clicar para ver - + You are flooding the chat. Please wait a couple of seconds. Estás a inundar o chat .Por favor aguarde alguns segundos. @@ -6098,30 +7056,30 @@ Quanta mais informação der, mais específicos serão os resultados.Servidor - - - - + + + + Error Erro - + Failed to join the server room: it doesn't exist on the server. - + The server thinks you are in the server room but your client is unable to display it. Try restarting your client. - + You do not have the required permission to join this server room. - + Failed to join the server room due to an unknown error: %1. @@ -6129,32 +7087,72 @@ Quanta mais informação der, mais específicos serão os resultados. TabSupervisor - + + Deck Editor + + + + + &Visual Deck Storage + + + + + Server + + + + + Account + + + + + Deck Storage + + + + + Game Replays + + + + + Administration + + + + + Logs + + + + Are you sure? Tem a certeza? - + There are still open games. Are you sure you want to quit? Ainda estão a decorrer alguns jogos. Tem a certeza que quer sair? - + Click to view - + Your buddy %1 has signed on! - + Unknown Event - + The server has sent you a message that your client does not understand. This message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -6162,62 +7160,44 @@ To update your client, go to Help -> Check for Updates. - + Idle Timeout - + You are about to be logged out due to inactivity. - + Promotion Promoção - + You have been promoted. Please log out and back in for changes to take effect. - + Warned Avisado - + You have received a warning due to %1. Please refrain from engaging in this activity or further actions may be taken against you. If you have any questions, please private message a moderator. Recebeu um aviso devido a %1. %s Por favor evite continuar essa actividade ou acções serão tomadas contra si futuramente. Se tem alguma questão, por favor envie uma mensagem privada a um moderador. - + You have received the following message from the server. (custom messages like these could be untranslated) - - TabUserLists - - - Account - Conta - - - - Add to Buddy List - Adicionar à lista de amigos - - - - Add to Ignore List - Adicionar à lista de ignorados - - TappedOutInterface @@ -6301,173 +7281,184 @@ Por favor evite continuar essa actividade ou acções serão tomadas contra si f UserContextMenu - + User &details Detalhes do &utilizador - + Private &chat Chat &privado - + Show this user's &games Mostrar os &jogos deste utilizador - + Add to &buddy list Adicionar à &lista de amigos - + Remove from &buddy list Remover da lista de &amigos - + Add to &ignore list Adicionar a lista a i&gnorar - + Remove from &ignore list Remover da lista a &ignorar - + Kick from &game Expulsar do &jogo - + Warn user Avisar utilizador - + View user's war&n history Ver o histórico de avi&sos do utilizador - + Ban from &server Banir do &servidor - + View user's &ban history Ver o histórico de &bans do utilizador - + &Promote user to moderator &Promover utilizador a moderador - + Dem&ote user from moderator &Despromover utilizador do cargo de moderador - + Promote user to &judge - + Demote user from judge - + + View admin notes + + + + %1's games jogos de %1 - - - + + + Ban History Histórico de banições - + Ban Time;Moderator;Ban Length;Ban Reason;Visible Reason Hora de Banição; Moderador; Duração da Banição; Razão da Banição; Razão visível - + User has never been banned. O utilizador nunca foi banido. - + Failed to collect ban information. - - - + + + Warning History Histórico de avisos - + Warning Time;Moderator;User Name;Reason Hora do aviso; Moderador; Nome do Utilizador; Razão - + User has never been warned. O utilizador nunca foi avisado. - + Failed to collect warning information. - - + + Failed to get admin notes. + + + + + Success Sucesso - + Successfully promoted user. Utilizador promovido com sucesso. - + Successfully demoted user. Utilizador despromovido com sucesso. - - + + + Failed Falhou - + Failed to promote user. Falha na promoção do utilizador. - + Failed to demote user. Falha na despromoção do utilizador. - + Copy hash to clipboard - + Remove this user's messages @@ -6475,173 +7466,173 @@ Por favor evite continuar essa actividade ou acções serão tomadas contra si f UserInfoBox - + Location: Localização: - + Account Age: Idade da Conta - + Edit Editar - + Change password Alterar password - + Change avatar Alterar avatar - + Administrator Administrador - + Moderator Moderador - + Registered user Utilizador registado - - + + Unregistered user Utilizador não registado - + Judge - + Unknown Desconhecido - + The entered password does not match your account. - - - + + + Information Informação - + User information updated. Informação do utilizador actualizada. - - - - - - - - - - + + + + + + + + + + Error Erro - + User Information - + Real Name: - + User Level: - + %n Year(s), amount of years (only shown if more than 0) - + %10%n Day(s) %20 amount of years (if more than 0), amount of days, date in local short format - + Enter Password - + Password verification is required in order to change your email address - - - + + + An error occurred while trying to update your user information. - + This server does not permit you to update your user informations. Este servidor não permite a actualização da sua informação pessoal. - + Password changed. Password alterada. - + This server does not permit you to change your password. Este servidor não permite a actualização da sua password. - + The new password is too short. A password nova é demasiado curta. - + The old password is incorrect. A password antiga está incorrecta. - + Avatar updated. Avatar actualizado. - + This server does not permit you to update your avatar. Este servidor não permite a actualização do seu avatar. - + An error occured while trying to updater your avatar. Um erro ocorreu ao tentar actualizar o seu avatar. @@ -6649,124 +7640,251 @@ Por favor evite continuar essa actividade ou acções serão tomadas contra si f UserInterfaceSettingsPage - + General interface settings Configurações gerais da interface - + &Double-click cards to play them (instead of single-click) Clicar &duas vezes nas cartas para as jogar (ao invés de clicar apenas uma vez) - + + &Clicking plays all selected cards (instead of just the clicked card) + + + + &Play all nonlands onto the stack (not the battlefield) by default &Jogar todos os não-terrenos para a pilha (em vez de para o campo de batalha) por definição - + + Close card view window when last card is removed + + + + Annotate card text on tokens Permitir anotações em tokens - + Use tear-off menus, allowing right click menus to persist on screen - + Notifications settings - + Enable notifications in taskbar Permitir notificações na barra de tarefas - + Notify in the taskbar for game events while you are spectating Notificar eventos de jogos na barra de tarefas - + Notify in the taskbar when users in your buddy list connect - + Animation settings Configurações de Animações - + &Tap/untap animation Animação de &virar/desvirar + + + Deck editor/storage settings + + + + + Open deck in new tab by default + + + + + Prompt before converting .txt decks to .cod format + + + + + Always convert if not prompted + + + + + Use visual deck storage in game lobby + + + + + Replay settings + + + + + Buffer time for backwards skip via shortcut: + + - UserList + UserListWidget - + Users connected to server: %1 - Utilizadores online: %1 + - + Users in this room: %1 - Utilizadores nesta sala:%1 + - + Buddies online: %1 / %2 - Amigos online: %1 / %2 + - + Ignored users online: %1 / %2 - Utilizadores ignorados online %1 / %2 + + + + + VisualDeckStorageFolderDisplayWidget + + + Deck Storage + + + + + VisualDeckStorageSearchWidget + + + Search by filename + + + + + VisualDeckStorageSortWidget + + + Sort Alphabetically (Deck Name) + + + + + Sort Alphabetically (Filename) + + + + + Sort by Last Modified + + + + + Sort by Last Loaded + + + + + VisualDeckStorageWidget + + + Loading database ... + + + + + Show Folders + + + + + Show Tag Filter + + + + + Show Tags On Deck Previews + + + + + Show Banner Card Selection Option + + + + + Include Folder Names in Search + + + + + Draw unused Color Identities + + + + + Unused Color Identities Opacity + WarningDialog - + Which warning would you like to send? Que aviso gostaria de enviar? - + Redact all messages from this user in all rooms - + &OK &OK - + &Cancel &Cancelar - + Warn user for misconduct Aviso por má conduta - - + + Error Erro - + User name to send a warning to can not be blank, please specify a user to warn. O nome de utilizador não se pode encontrar vazio, por favor especifique o utilizador que deseja avisar. - + Warning to use can not be blank, please select a valid warning to send. O aviso a enviar não se pode encontrar vazio, por favor seleccione um aviso válido para enviar. @@ -6774,128 +7892,133 @@ Por favor evite continuar essa actividade ou acções serão tomadas contra si f WndSets - + Move selected set to the top Mover expansão seleccionada para o topo - + Move selected set up Mover expansão seleccionada para cima - + Move selected set down Mover expansão seleccionada para baixo - + Move selected set to the bottom Mover expansão seleccionada para o fundo - + Search by set name, code, or type - + Default order - + Restore original art priority order - + Enable all sets Permitir todas as expansões - + Disable all sets Inactivar todas as expansões - + Enable selected set(s) - + Disable selected set(s) - + Deck Editor - + Use CTRL+A to select all sets in the view. - + Only cards in enabled sets will appear in the card list of the deck editor. - + Image priority is decided in the following order: - + first the CUSTOM Folder (%1), then the Enabled Sets in this dialog (Top to Bottom) %1 is a link to the wiki - + Card Art - + How to use custom card art - + Hints - + Note - + Sorting by column allows you to find a set while not changing set priority. - + To enable ordering again, click the column header until this message disappears. - + Use the current sorting as the set priority instead - + Sorts the set priority using the same column - + + Include online-only (Arena) cards [requires restart] + + + + Manage sets @@ -6903,22 +8026,67 @@ Por favor evite continuar essa actividade ou acções serão tomadas contra si f ZoneViewWidget - - sort by name - dispor por nome + + Ungrouped + - - sort by type - dispor por tipo + + Group by Type + - + + Group by Mana Value + + + + + Group by Color + + + + + Unsorted + + + + + Sort by Name + + + + + Sort by Type + + + + + Sort by Mana Cost + + + + + Sort by Colors + + + + + Sort by P/T + + + + + Sort by Set + + + + shuffle when closing baralhar quando terminar - + pile view vista em pilha @@ -6926,7 +8094,7 @@ Por favor evite continuar essa actividade ou acções serão tomadas contra si f i18n - + English Português (Portuguese) @@ -6934,12 +8102,12 @@ Por favor evite continuar essa actividade ou acções serão tomadas contra si f main - + Connect on startup - + Debug to file @@ -6947,786 +8115,908 @@ Por favor evite continuar essa actividade ou acções serão tomadas contra si f shortcutsTab - + Main Window Janela Principal - - + + Deck Editor Editor de Baralhos - + Game Lobby Lobby de jogo - + Card Counters - + Player Counters - + Power and Toughness Poder e Resistência - + Game Phases Etapas de jogo - + Playing Area Área de jogo - + Move Selected Card - + View Ver - + Move Top Card - + Move Bottom Card - + Gameplay Área de jogo - + Drawing - + Chat Room - + Game Window - + Load Deck from Clipboard - + + Replays - + + Tabs + + + + Check for Card Updates... - + Connect... - + Disconnect Desconectar - + Exit Sair - + Full screen Ecrã Inteiro - + Register... - + Settings... - + Start a Local Game... - + Watch Replay... - - Analyze Deck + + Analyze Deck (deckstats.net) + Analyze Deck - + + Analyze Deck (tappedout.net) + + + + Clear All Filters - + Clear Selected Filter - + Close Fechar - + Remove Card - + Manage Sets... - + Edit Custom Tokens... - - Export Deck - - - - - Add Card - - - - - Load Deck... - - - - - Load Deck from Clipboard... + + Export Deck (decklist.org) - New Deck + Export Deck (decklist.xyz) - - Open Custom Pictures Folder + + Add Card - - Print Deck... + + Load Deck... + Load Deck from Clipboard... + + + + + Edit Deck in Clipboard, Annotated + + + + + Edit Deck in Clipboard + + + + + New Deck + + + + + Open Custom Pictures Folder + + + + + Print Deck... + + + + Delete Card - - + + Reset Layout - + Save Deck - + Save Deck as... - + Save Deck to Clipboard, Annotated - - Save Deck to Clipboard - - - - - Load Local Deck... - - - - - Load Remote Deck... - - - - - Set Ready to Start - - - - - Toggle Sideboard Lock - - - - - - Add Green Counter - - - - - - Remove Green Counter - - - - - - Set Green Counters... + + Save Deck to Clipboard, Annotated (No Set Info) + Save Deck to Clipboard + + + + + Save Deck to Clipboard (No Set Info) + + + + + Load Local Deck... + + + + + Load Remote Deck... + + + + + Set Ready to Start + + + + + Toggle Sideboard Lock + + + + + + Add Green Counter + + + + + + Remove Green Counter + + + + + + Set Green Counters... + + + + Add Yellow Counter - + Remove Yellow Counter - + Set Yellow Counters... - - + + Add Red Counter - - + + Remove Red Counter - - + + Set Red Counters... - + Add Life Counter - + Remove Life Counter - + Set Life Counters... - + Add White Counter - + Remove White Counter - + Set White Counters... - + Add Blue Counter - + Remove Blue Counter - + Set Blue Counters... - + Add Black Counter - + Remove Black Counter - + Set Black Counters... - + Add Colorless Counter - + Remove Colorless Counter - + Set Colorless Counters... - + Add Other Counter - + Remove Other Counter - + Set Other Counters... - + Add Power (+1/+0) - + Remove Power (-1/-0) - + Move Toughness to Power (+1/-1) - + Add Toughness (+0/+1) - + Remove Toughness (-0/-1) - + Move Power to Toughness (-1/+1) - + Add Power and Toughness (+1/+1) - + Remove Power and Toughness (-1/-1) - + Set Power and Toughness... - + Reset Power and Toughness - + Untap Desvirar - + Upkeep Etapa de Upkeep - + Draw Etapa de compra de cartas - + First Main Phase - + Start Combat - + Attack Etapa de Ataque - + Block Etapa de Bloqueio - + Damage Etapa de atribuição de dano - + End Combat - + Second Main Phase - + End Fim de turno - + Next Phase - + Next Phase Action - + Next Turn - + + Hide Card in Reveal Window + + + + Tap / Untap Card - + Untap All - + Toggle Untap - + Turn Card Over - + Peek Card - + Play Card - + Attach Card... - + Unattach Card - + Clone Card - + Create Token... - + Create All Related Tokens - + Create Another Token - + Set Annotation... - - + + Select All Cards in Zone + + + + + Select All Cards in Row + + + + + Select All Cards in Column + + + + + Bottom of Library - - - - + + + + Exile Exílio - - - - + + + + Graveyard Cemitério - - + + Hand Mão - - + + Top of Library - - - + + + Battlefield, Face Down - + Battlefield - + Library Baralho - + Sideboard Sideboard - + Top Cards of Library - + + Bottom Cards of Library + + + + Close Recent View - - + + Stack - - + + Graveyard (Multiple) - - + + Exile (Multiple) - + Stack Until Found - + Draw Bottom Card - + Draw Multiple Cards from Bottom... - + Draw Arrow... - + Remove Local Arrows - + Leave Game - + Concede Desistir - + Roll Dice... - + Shuffle Library - + + Shuffle Top Cards of Library + + + + + Shuffle Bottom Cards of Library + + + + Mulligan Mulligan - + Draw a Card - + Draw Multiple Cards... - + Undo Draw - + Always Reveal Top Card - + Always Look At Top Card - + Rotate View Clockwise - + Rotate View Counterclockwise - + Unfocus Text Box - + Focus Chat - + Clear Chat - + Refresh - + + Skip Forward + + + + + Skip Backward + + + + + Skip Forward by a lot + + + + + Skip Backward by a lot + + + + Play/Pause - + Toggle Fast Forward + + + Visual Deck Storage + + + + + Deck Storage + + + + + Server + + + + + Account + + + + + Administration + + + + + Logs + + \ No newline at end of file diff --git a/cockatrice/translations/cockatrice_pt_BR.ts b/cockatrice/translations/cockatrice_pt_BR.ts index f8b422538..ca00d401f 100644 --- a/cockatrice/translations/cockatrice_pt_BR.ts +++ b/cockatrice/translations/cockatrice_pt_BR.ts @@ -2,7 +2,7 @@ AbstractCounter - + &Set counter... &Definir marcador... @@ -10,95 +10,247 @@ AbstractCounterDialog - + Set counter Definir marcador - + New value for counter '%1': Novo valor para marcador '%1': + + AbstractDlgDeckTextEdit + + + &Refresh + + + + + Parse Set Name and Number (if available) + + + + + AbstractTabDeckEditor + + + Open in new tab + + + + + Are you sure? + + + + + The decklist has been modified. +Do you want to save the changes? + + + + + + + + + + + Error + + + + + Could not open deck at %1 + + + + + Could not save remote deck + + + + + + The deck could not be saved. +Please check that the directory is writable and try again. + + + + + Save deck + + + + + The deck could not be saved. + + + + + There are no cards in your deck to be exported + + + + + No deck was selected to be exported. + + + + + AdminNotesDialog + + + Update Notes + Notas de Atualização + + + + Admin Notes for %1 + Notas do Administrador para %1 + + + + AllZonesCardAmountWidget + + + Mainboard + Main + + + + Sideboard + Side + + AppearanceSettingsPage - + Error Erro - + Could not create themes directory at '%1'. Não foi possível criar o diretório de temas em '%1'. - + Theme settings Configurações de Tema - + Current theme: Tema atual: - + Open themes folder Abra a pasta de temas - + + Menu settings + Configurações do Menu + + + + Show keyboard shortcuts in right-click menus + Mostrar atalhos de teclado nos menus de clique com o botão direito + + + Card rendering Renderização das cartas - + Display card names on cards having a picture Mostrar nome nas cartas que possuem imagem - + + Auto-Rotate cards with sideways layout + Rotacionar automaticamente cartas com layout horizontal + + + + Override all card art with personal set preference (Pre-ProviderID change behavior) [Requires Client restart] + Substituir todas as artes das cartas com a preferência de conjunto pessoal (comportamento anterior à mudança do ProviderID) [Requer reinício do cliente] + + + + Bump sets that the deck contains cards from to the top in the printing selector + Priorizar conjuntos que o deck contém no topo do seletor de impressão + + + Scale cards on mouse over Redimensionar cartas ao passar o mouse - - Minimum overlap percentage of cards on the stack and in vertical hand + + Use rounded card corners - + + Minimum overlap percentage of cards on the stack and in vertical hand + Porcentagem mínima de sobreposição de cartas na pilha e na mão vertical + + + + Maximum initial height for card view window: + Altura inicial máxima para a janela de visualização de cartas: + + + + + rows + linhas + + + + Maximum expanded height for card view window: + + + + Hand layout Organização da mão - + Display hand horizontally (wastes space) Mostrar a mão na horizontal (desperdiça espaço) - + Enable left justification Habilitada justificação à esquerda - + Table grid layout Organização do campo de batalha - + Invert vertical coordinate Inverter a coordenada vertical - + Minimum player count for multi-column layout: Número mínimo de jogadores para o layout de multi-colunas - + Maximum font size for information displayed on cards: Tamanho máximo da fonte para informações nas cartas: @@ -106,112 +258,112 @@ BanDialog - + ban &user name banir &usuário pelo nome - + ban &IP address banir endereço &IP - + ban client I&D banir cliente por I&D: - + Ban type Tipo de banimento - + &permanent ban &banir permanentemente - + &temporary ban &banir temporariamente - + &Days: &Dias: - + &Hours: &Horas: - + &Minutes: &Minutos: - + Duration of the ban Duração do banimento - + Please enter the reason for the ban. This is only saved for moderators and cannot be seen by the banned person. Por favor, entre com o motivo do banimento. Isto será visto somente por moderadores e não pode ser visto pela pessoa banida. - + Please enter the reason for the ban that will be visible to the banned person. Por favor, entre com o motivo para o banimento que será visível pela pessoa banida. - + Redact all messages from this user in all rooms Remover todas mensagens deste usuário em todas as salas - + &OK &OK - + &Cancel &Cancelar - + Ban user from server Banir usuário do servidor - - - - + + + + Error Erro - + You have to select a name-based, IP-based, clientId based, or some combination of the three to place a ban. Você deve selecionar um nome, IP, cliente ou alguma combinação desses três itens para banir. - + You must have a value in the name ban when selecting the name ban checkbox. Você têm que colocar um nome válido quando seleciona a caixa de banimento por nome. - + You must have a value in the ip ban when selecting the ip ban checkbox. Você têm que colocar um IP válido quando seleciona a caixa de banimento por IP. - + You must have a value in the clientid ban when selecting the clientid ban checkbox. Você têm que colocar um ID de cliente válido quando seleciona a caixa de banimento por ID de cliente. @@ -219,22 +371,22 @@ Isto será visto somente por moderadores e não pode ser visto pela pessoa banid BetaReleaseChannel - - Beta Releases - Lançamentos Beta + + Beta + Beta - + No reply received from the release update server. Nenhuma resposta recebida do servidor de atualização. - + Invalid reply received from the release update server. Resposta inválida recebida do servidor de atualização. - + No reply received from the file update server. Nenhuma resposta recebida do servidor de atualização. @@ -242,32 +394,32 @@ Isto será visto somente por moderadores e não pode ser visto pela pessoa banid CardDatabaseModel - + Name Nome - + Sets Expansões - + Mana cost Custo de mana - + Card type Tipo de carta - + P/T P/R - + Color(s) Cor(es) @@ -360,37 +512,65 @@ Isto será visto somente por moderadores e não pode ser visto pela pessoa banid - CardFrame + CardInfoFrameWidget - + Image Imagem - + Description Descrição - + Both Ambos + + + View transformation + Ver transformação + - CardInfoText + CardInfoPictureWidget - + + View related cards + Visualizar cartas relacionadas + + + + Add card to deck + Adicionar carta ao deck + + + + Mainboard + Main + + + + Sideboard + Side + + + + CardInfoTextWidget + + Name: Nome: - + Related cards: Cartas relacionadas: - + Unknown card: Carta desconhecida: @@ -398,304 +578,774 @@ Isto será visto somente por moderadores e não pode ser visto pela pessoa banid CardItem - + &Move to &Mover para - + &Power / toughness &Poder / resistência + + CardSizeWidget + + + Card Size + Tamanho da Carta + + CardZone - + their hand nominative sua mão - + %1's hand nominative mão de %1 - + their library look at zone seu grimório - + %1's library look at zone grimório de %1 - + of their library top cards of zone, do seu grimório - + of %1's library top cards of zone do grimório de %1 - + their library reveal zone seu grimório - + %1's library reveal zone grimório de %1 - + their library shuffle seu grimório - + %1's library shuffle %1º grimório - + their library nominative seu grimório - + %1's library nominative %1º grimório. - + their graveyard nominative seu cemitério - + %1's graveyard nominative %1º cemitério - + their exile nominative seu exílio - + %1's exile nominative exílio de %1 - - - their sideboard - look at zone - seu sideboard - - - - %1's sideboard - look at zone - sideboard de %1 - - - - their sideboard - nominative - seu sideboard - + their sideboard + look at zone + seu sideboard + + + + %1's sideboard + look at zone + sideboard de %1 + + + + their sideboard + nominative + seu sideboard + + + %1's sideboard nominative sideboard de %1 + + DeckEditorCardInfoDockWidget + + + Card Info + + + + + DeckEditorDatabaseDisplayWidget + + + Search by card name (or search expressions) + + + + + Add to Deck + + + + + Add to Sideboard + + + + + Select Printing + + + + + Show on EDHREC (Commander) + + + + + Show on EDHREC (Card) + + + + + Show Related cards + + + + + Add card to &maindeck + + + + + Add card to &sideboard + + + + + DeckEditorDeckDockWidget + + + Banner Card + + + + + Select Printing + + + + + Deck + + + + + Deck &name: + + + + + &Comments: + + + + + Hash: + + + + + &Increment number + + + + + &Decrement number + + + + + &Remove row + + + + + Swap card to/from sideboard + + + + + DeckEditorFilterDockWidget + + + Filters + + + + + &Clear all filters + + + + + Delete selected + + + + + DeckEditorMenu + + + &Deck Editor + + + + + &New deck + + + + + &Load deck... + + + + + Load recent deck... + + + + + Clear + + + + + &Save deck + + + + + Save deck &as... + + + + + Load deck from cl&ipboard... + + + + + Edit deck in clipboard + + + + + + Annotated + + + + + + Not Annotated + + + + + Save deck to clipboard + + + + + Annotated (No set info) + + + + + Not Annotated (No set info) + + + + + &Print deck... + + + + + &Send deck to online service + + + + + Create decklist (decklist.org) + + + + + Create decklist (decklist.xyz) + + + + + Analyze deck (deckstats.net) + + + + + Analyze deck (tappedout.net) + + + + + &Close + + + + + DeckEditorPrintingSelectorDockWidget + + + Printing Selector + + + DeckEditorSettingsPage - - + + Update Spoilers Atualizar Spoilers - - + + Success Sucesso - + Download URLs have been reset. As URLs de download foram redefinidas. - + Downloaded card pictures have been reset. As imagens de cartas baixadas foram redefinidas. - + Error Erro - + One or more downloaded card pictures could not be cleared. Uma ou mais imagens de cartas baixadas não puderam ser apagadas. - + Add URL Adicionar URL - - + + URL: URL: - - + + Edit URL Editar URL - + + Network Cache Size: + Tamanho do Cache de Rede: + + + + Redirect Cache TTL: + TTL do Cache de Redirecionamento: + + + + How long cached redirects for urls are valid for. + Por quanto tempo os redirecionamentos em cache para URLs são válidos. + + + + Picture Cache Size: + Tamanho do Cache de Imagens: + + + Add New URL Adicionar nova URL - + Remove URL Remover URL - + + Day(s) + Dia(s) + + + Updating... Atualizando... - + Choose path Selecione o caminho - + URL Download Priority Prioridade de download de URL - + Spoilers 'Spoilers' - + Download Spoilers Automatically Baixar 'spoilers' automaticamente - + Spoiler Location: Localização do 'spoiler': - + Last Change Última Alteração - + Spoilers download automatically on launch Baixar automaticamente 'spoilers' ao iniciar - + Press the button to manually update without relaunching Pressione o botão para atualizar manualmente sem reinicializar - + Do not close settings until manual update is complete Não feche as configurações até que a atualização manual termine - + Download card pictures on the fly Baixar imagens de cartas em tempo real - + How to add a custom URL Como adicionar uma URL customizada - + Delete Downloaded Images Deletar imagens baixadas - + Reset Download URLs Resetar URLs de download - - Downloaded images directory size: - - - - + On-disk cache for downloaded pictures - + Cache em disco para imagens baixadas - - Picture cache size: - - - - + In-memory cache for pictures not currently on screen - + Cache em memória para imagens que não estão atualmente na tela DeckListModel - + + Count + Contagem + + + + Set + Conjunto + + + Number Número - + + Provider ID + ID do Provedor + + + Card Card + + DeckLoader + + + Common deck formats (%1) + + + + + All files (*.*) + + + + + DeckPreviewColorIdentityFilterWidget + + + Mode: Exact Match + Modo: Correspondência Exata + + + + Mode: Includes + Modo: Inclui + + + + DeckPreviewDeckTagsDisplayWidget + + + Edit tags ... + Editar tags ... + + + + DeckPreviewTagDialog + + + Deck Tags Manager + Gerenciador de Tags de Deck + + + + Manage your deck tags. Check or uncheck tags as needed, or add new ones: + Gerencie suas tags de deck. Marque ou desmarque as tags conforme necessário ou adicione novas: + + + + Add a new tag (e.g., Aggro️) + Adicionar uma nova tag (ex.: Aggro) + + + + Add Tag + Adicionar Tag + + + + Filter tags... + Filtrar tags... + + + + OK + OK + + + + Cancel + Cancelar + + + + Invalid Input + Entrada Inválida + + + + Tag name cannot be empty! + O nome da tag não pode estar vazio! + + + + Duplicate Tag + Tag Duplicada + + + + This tag already exists. + Essa tag já existe. + + + + DeckPreviewWidget + + + Banner Card + + + + + Open in deck editor + + + + + Edit Tags + + + + + Rename Deck + + + + + Save Deck to Clipboard + + + + + Annotated + + + + + Annotated (No set info) + + + + + Not Annotated + + + + + Not Annotated (No set info) + + + + + Rename File + + + + + Delete File + + + + + Set Banner Card + + + + + + New name: + + + + + + Error + + + + + Rename failed + + + + + Delete file + + + + + Are you sure you want to delete the selected file? + + + + + Delete failed + + + DeckStatsInterface @@ -713,181 +1363,219 @@ Isto será visto somente por moderadores e não pode ser visto pela pessoa banid DeckViewContainer - + Load deck... Carregar deck... - + Load remote deck... Carregar deck remoto... - + + Unload deck + Unload deck... + Descarregar deck + + + Ready to start Preparadx para começar - + + Force start + Forçar início + + + Sideboard unlocked Sideboard aberto - + Sideboard locked Sideboard fechado - - Load deck - Carregar deck - - - + Error Erro - + The selected file could not be loaded. O arquivo selecionado não pode ser carregado + + DialogConvertDeckToCodFormat + + + Deck Format Conversion + Converter formato do deck + + + + You tried to add a tag to a .txt format deck. + Tags can only be added to .cod format decks. + Do you want to convert the deck to the .cod format? + Você tentou adicionar uma tag a um deck em formato .txt. +Tags só podem ser adicionadas a decks no formato .cod. +Deseja converter o deck para .cod? + + + + Remember and automatically apply choice in the future + Lembrar e aplicar essa escolha automaticamente no futuro + + DlgConnect - + Downloading... Baixando... - + Known Hosts Servidores conhecidos - + + Delete the currently selected saved server + Excluir o servidor salvo atualmente selecionado + + + Refresh the server list with known public servers Atualizar a lista de servidores com servidores públicos conhecidos - + New Host Novo servidor - + Name: Nome: - + &Host: &Servidor: - + &Port: &Porta: - + Player &name: &Nome do jogador: - + P&assword: S&enha: - + &Save password &Salvar senha - + A&uto connect &Conectar automaricamente - + Automatically connect to the most recent login when Cockatrice opens Conectar automaticamente com o login mais recente quando Cockatrice for iniciado - + If you have any trouble connecting or registering then contact the server staff for help! Se você tiver problemas ao se conectar ou registar entre em contato com os responsáveis pelo servidor! - - + + Webpage Página da Web - + Reset Password Resetar Senha - + + Forgot password? + Esqueceu a senha? + + + &Connect &Conectar - + Server Servidor - + Login Login - + Server Contact Salvar Contato - + Connect to Server Conectar ao Servidor - + Server URL URL do Servidor - + Communication Port Porta de comunicação - + Unique Server Name Nome Único de Servidor - + Connection Warning Aviso de conexão - + You need to name your new connection profile. Você deve nomear seu novo perfil de conexão - + Connect Warning Aviso de conexão - + The player name can't be empty. O nome do jogador é obrigatório. @@ -970,27 +1658,37 @@ Isto será visto somente por moderadores e não pode ser visto pela pessoa banid Visitantes - + + Starting life total: + Total de vida inicial: + + + + Game setup options + Opções de configuração do jogo + + + &Clear &Limpar - + Create game Criar jogo - + Game information Informação de jogo - + Error Erro - + Server error. Erro do servidor. @@ -998,92 +1696,92 @@ Isto será visto somente por moderadores e não pode ser visto pela pessoa banid DlgCreateToken - + &Name: &Nome: - + Token Ficha - + C&olor: C&or: - + white branco - + blue azul - + black preto - + red vermelho - + green verde - + multicolor multicolorido - + colorless incolor - + &P/T: &P/R: - + &Annotation: &Nota: - + &Destroy token when it leaves the table Destruir a &ficha quando ela sair do campo de batalha - + Token data Informação de ficha - + Show &all tokens Mostre &todas fichas - + Show tokens from this &deck Mostre fichas deste &deck - + Choose token from list Mostre fichas da lista - + Create token Criar ficha @@ -1129,6 +1827,24 @@ Para remover o avatar atual, confirme sem escolher uma nova imagem.Imagem inválida. + + DlgEditDeckInClipboard + + + Edit deck in clipboard + + + + + Error + + + + + Invalid deck list. + + + DlgEditPassword @@ -1171,93 +1887,93 @@ Para remover o avatar atual, confirme sem escolher uma nova imagem. DlgEditTokens - + &Name: &Nome: - + C&olor: &Cor: - + white branco - + blue azul - + black preto - + red vermelho - + green verde - + multicolor multicor - + colorless incolor - + &P/T: &P/R: - + &Annotation: &Anotação - + Token data Informação da ficha - - + + Add token Adicionar ficha - + Remove token Remover ficha - + Edit custom tokens Editar fichas personalizadas - + Please enter the name of the token: Por favor, entre com o nome da ficha: - + Error Erro - + The chosen name conflicts with an existing card or token. Make sure to enable the 'Token' set in the "Manage sets" dialog to display them correctly. O nome escolhido está em conflito com uma carta ou ficha existente. @@ -1295,132 +2011,132 @@ Certifique-se de habilitar a expansão 'Fichas' em "Gerenciar exp DlgFilterGames - + no limit sem limite - + 5 minutes 5 minutos - + 10 minutes 10 minutos - + 30 minutes 30 minutos - + 1 hour 1 hora - + 2 hours 2 horas - - Show '&buddies only' games - Exibir jogos de 'apenas amigos' + + Hide 'buddies only' games + Ocultar jogos 'apenas amigos' - - Show &full games - Mostrar &todos os jogos + + Hide full games + Ocultar jogos cheios - - Show games &that have started - Mostrar jogos &que já começaram + + Hide games that have started + Ocultar jogos já iniciados - - Show &password protected games - Mostrar jogos protegidos por &senha + + Hide password protected games + Ocultar jogos com senha - - Hide '&ignored user' games - Ocultar jogos de '&ignored user' + + Hide 'ignored user' games + Ocultar 'jogos de usuários ignorados' - + &Newer than: &Mais novos que: - + Game &description: &Descrição do jogo: - + &Creator name: Nome do &criador: - + General Geral - + &Game types Tipos de &jogos - + at &least: &ao menos: - + at &most: no &máximo: - + Maximum player count Quantidade máxima de jogadores - + Restrictions Restrições - + Show games only if &spectators can watch Mostrar jogos apenas se &espectadores puderem assistir - + Show spectator password p&rotected games Mostrar jogos p&rotegidos por senha para espectador - + Show only if spectators can ch&at Mostrar apenas se espectadores puderem interagir com o chat - + Show only if spectators can see &hands Mostrar apenas se as mãos forem visíveis para os espectadores - + Spectators Espectadores - + Filter games Filtrar jogos @@ -1603,27 +2319,28 @@ Certifique-se de habilitar a expansão 'Fichas' em "Gerenciar exp As senhas não correspondem. + + DlgLoadDeck + + + Load Deck + Carregar Deck + + DlgLoadDeckFromClipboard - - &Refresh - &Atualizar - - - + Load deck from clipboard Carregar deck da área de transferência - - + Error Erro - - + Invalid deck list. Lista de deck inválida. @@ -1631,11 +2348,49 @@ Certifique-se de habilitar a expansão 'Fichas' em "Gerenciar exp DlgLoadRemoteDeck - + Load deck Carregar deck + + DlgMoveTopCardsUntil + + + Card name (or search expressions): + Nome da carta (ou expressões de busca): + + + + Number of hits: + Número de resultados: + + + + Auto play hits + Reproduzir resultados automaticamente + + + + Put top cards on stack until... + Colocar as cartas do topo na pilha até... + + + + No cards matching the search expression exists in the card database. Proceed anyways? + Nenhuma carta correspondente à expressão de busca existe no banco de dados de cartas. Deseja continuar assim mesmo? + + + + Cockatrice + Cockatrice + + + + Invalid filter + Filtro inválido + + DlgRegister @@ -1744,18 +2499,18 @@ Seu e-mail será utilizado para verificar sua conta. Roll Dice - + Rolar dados DlgSettings - + Unknown Error loading card database Erro desconhecido ao carregar bando de dados de cartas - + Your card database is invalid. Cockatrice may not function correctly with an invalid database @@ -1772,7 +2527,7 @@ Você pode precisar rodar o Oracle novamente para atualizar o banco de dados de Você gostaria de mudar o local da configuração de seu banco de dados? - + Your card database version is too old. This can cause problems loading card information or images @@ -1789,7 +2544,7 @@ Usualmente isto pode ser consertado rodando novamente o Oracle para atualizar se Você gostaria de alterar o local da configuração de seu banco de dados? - + Your card database did not finish loading Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues with your cards.xml attached @@ -1802,7 +2557,7 @@ Por favor, envie um ticket para https://github.com/Cockatrice/Cockatrice/issues Gostaria de mudar a definição da localização da sua base de dados? - + File Error loading your card database. Would you like to change your database location setting? @@ -1811,7 +2566,7 @@ Would you like to change your database location setting? Você gostaria de alterar seu local de configuração de seu banco de dados? - + Your card database was loaded but contains no cards. Would you like to change your database location setting? @@ -1820,7 +2575,7 @@ Would you like to change your database location setting? Você gostaria de alterar seu local de configuração de banco de dados? - + Unknown card database load status Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues @@ -1833,59 +2588,59 @@ Por favor, crie um ticket em https://github.com/Cockatrice/Cockatrice/issues Você gostaria de alterar seu local de configuração do banco de dados? - - - + + + Error Erro - + The path to your deck directory is invalid. Would you like to go back and set the correct path? O caminho para a sua pasta de decks é inválido. Você gostaria de voltar e corrigir o caminho? - + The path to your card pictures directory is invalid. Would you like to go back and set the correct path? O caminho para a sua pasta de imagens de cards é inválido. Você gostaria de voltar e corrigir o caminho? - + Settings Configurações - + General Geral - + Appearance Aparência - + User Interface Interface do usuário - + Card Sources Fonte de Cartas - + Chat Conversa - + Sound Som - + Shortcuts Atalhos @@ -1911,164 +2666,168 @@ Você gostaria de alterar seu local de configuração do banco de dados? DlgUpdate - + Current release channel Canal de lançamento atual - + Reinstall Reinstalar - + Cancel Download Cancelar Download - + Open Download Page Abrir página de download - + Check for Client Updates Procurar por atualizações do cliente - - - - + + + + Error Erro - + Cockatrice was not built with SSL support, therefore you cannot download updates automatically! Please visit the download page to update manually. Cockatrice não possui suporte a SSL, por causa disso você não pode baixar updates automaticamente! Por favor visite a página de downloads para atualizar manualmente.  - - Downloading update... - Baixando atualização... + + Downloading update: %1 + Downloading update... + - + Checking for updates... Verificando atualizações... - + Finished checking for updates Busca por atualizações concluída - + No Update Available Sem atualização disponível - + Cockatrice is up to date! Cockatrice está atualizado! - + You are already running the latest version available in the chosen release channel. Você já está executando a última versão disponível no canal de lançamento escolhido. - + Current version Versão atual - + Selected release channel Canal de lançamento selecionado - - + + Update Available Atualização disponível - - + + A new version of Cockatrice is available! Uma nova versão do Cockatrice está disponível! - - + + New version Nova versão - - + + Released Lançado - - + + Changelog Log de mudanças - + Do you want to update now? Você quer atualizar agora? - - Unfortunately there are no download packages available for your operating system. -You may have to build from source yourself. - Infelizmente não há pacotes de download disponíveis para o seu sistema operacional. Você precisará montar da fonte. + + Unfortunately, the automatic updater failed to find a compatible download. +You may have to manually download the new version. + Unfortunately there are no download packages available for your operating system. +You may have to build from source yourself. + - - Please check the download page manually and visit the wiki for instructions on compiling. - Por favor verificar a página de download manualmente e acesse a wiki para instruções sobre compilação. + + Please check the <a href="%1">releases page</a> on our Github and download the build for your system. + Please check the download page manually and visit the wiki for instructions on compiling. + - - - + + + Update Error Erro na atualização - + An error occurred while checking for updates: Ocorreu um erro enquanto procurávamos por atualizações: - + An error occurred while downloading an update: Um erro ocorreu enquanto uma atualização era baixada: - + Installing... Instalando... - + Cockatrice is unable to open the installer. Cockatrice está incapaz de abrir o instalador. - + Try to update manually by closing Cockatrice and running the installer. Tente atualizar o Cockatrice manualmente fechando-o e rodando o instalador. - + Download location Localização do download @@ -2086,6 +2845,14 @@ You may have to build from source yourself. Registro de Depuração + + EdhrecCommanderResponseCommanderDetailsDisplayWidget + + + Salt: + Sal: + + FilterBuilder @@ -2097,105 +2864,120 @@ You may have to build from source yourself. GameSelector - - - - - - - - - + + + + + + + + + Error Erro - + Please join the appropriate room first. Por favor, entre na sala apropriada primeiramente - + Wrong password. Senha incorreta. - + Spectators are not allowed in this game. Não são permitidos visitantes neste jogo. - + The game is already full. O jogo está cheio. - + The game does not exist any more. O jogo não existe mais. - + This game is only open to registered users. Este jogo é aberto apenas para usuários registrados. - + This game is only open to its creator's buddies. Este jogo é aberto apenas para os amigos de quem criou o jogo. - + You are being ignored by the creator of this game. Você está sendo ignorado pelo criador deste jogo. - + + Join Game + Entrar no jogo + + + + Spectate Game + Assistir ao jogo + + + + Game Information + Informações do jogo + + + Join game Entrar no jogo - + Password: Senha: - + Please join the respective room first. Por favor, entre na respectiva sala primeiro. - + &Filter games &Filtrar jogos. - + C&lear filter L&impar Filtros - + C&reate C&riar - + &Join &Entrar - + J&oin as spectator E&ntrar como visitante - + Games shown: %1 / %2 Jogos mostrados: %1 / %2 - + Games Jogos @@ -2203,100 +2985,100 @@ You may have to build from source yourself. GamesModel - + >1 day >1 dia - + %1%2 hr short age in hours %1%2 h%1%2 h%1%2 h - + new novo - + %1%2 min short age in minutes %1%2 min%1%2 min%1%2 min - + password senha - + buddies only apenas amigos - + reg. users only usuários reg. apenas - - + + can chat pode conversar - + see hands ver mãos - + can see hands pode ver mãos - + not allowed não permitidos - + Room Sala - + Age Idade - + Description Descrição - + Creator Criador - + Type Tipo - + Restrictions Restrições - + Players Jogadores - + Spectators Visitantes @@ -2304,92 +3086,102 @@ You may have to build from source yourself. GeneralSettingsPage - + Reset all paths Redefinir todos os diretórios - + All paths have been reset Todos os caminhos para diretórios foram redefinidos - - + + Choose path Escolher caminho - + Personal settings Configurações pessoais - + Language: Idioma: - + Paths (editing disabled in portable mode) Caminhos (edição desabilitada no modo portátil) - + Paths Caminhos - + + How to help with translations + + + + Decks directory: Pasta de decks: - + Replays directory: Pasta de replays: - + Pictures directory: Pasta de imagens: - + Card database: Banco de dados de cartas: - + Custom database directory: Diretório do banco de dados de cartas customizadas - + Token database: Banco de dados de fichas: - + Update channel Canal de atualização - + + Check for client updates on startup + Verificar atualizações do cliente ao iniciar + + + Notify if a feature supported by the server is missing in my client Notificar se uma função suportada pelo servidor está faltando em meu programa. - + Automatically run Oracle when running a new version of Cockatrice Rodar Oracle automaticamente quando rodar uma nova versão de Cockatrice - + Show tips on startup Exibir dicas ao iniciar @@ -2397,61 +3189,61 @@ You may have to build from source yourself. MainWindow - - + + The server has reached its maximum user capacity, please check back later. O servidor atingiu a capacidade maxima de usuarios, tente novamente mais tarde. - + There are too many concurrent connections from your address. Há conexões concorrentes demais vinds do seu endereço. - + Banned by moderator Banido pelo moderador - + Expected end time: %1 Esperado tempo para fim: %1 - + This ban lasts indefinitely. Este banimento dura indefinidamente. - + Scheduled server shutdown. Servidor de eventos fora do ar. - - + + Invalid username. Usuário inválido - + You have been logged out due to logging in at another location. Você foi desconectado devido a um acesso em outra localidade. - + Connection closed Conexão fechada - + The server has terminated your connection. Reason: %1 O servidor terminou sua conexão. Razão: %1 - + The server is going to be restarted in %n minute(s). All running games will be lost. Reason for shutdown: %1 @@ -2464,558 +3256,568 @@ Todos os jogos serão perdidos. Motivo para desligar: %1 - + Scheduled server shutdown Servidor de eventos fora do ar - - + + Success Sucesso - + Registration accepted. Will now login. Registro aceito Irá entrar agora - + Account activation accepted. Will now login. Ativação da conta aceita. Irá entrar agora. - + Number of players Número de jogadores - + Please enter the number of players. Por favor, entre o número de jogadores. - - + + Player %1 Jogador %1 - + Load replay Carregar replay - + About Cockatrice Sobre o Cockatrice - + Version Versão - + Cockatrice Webpage Pagina da Web do Cockatrice - + Project Manager: Diretor do projeto: - + Past Project Managers: Antigos diretores do projeto: - + Developers: Desenvolvedores: - + Our Developers Nossos desenvolvedores - + Help Develop! Ajude a desenvolver! - + Translators: Tradutores: - + Our Translators Nossos tradutores. - + Help Translate! Ajude a traduzir! - + Support: Suporte: - + Report an Issue Reporte um problema: - + Troubleshooting Solução de problemas - + F.A.Q. F.A.Q. - - - - - - - - - - - - - - - + + + + + + + + + + + + + - - - + + + + + Error Erro - + Server timeout Tempo esgotado do servidor - + Failed Login Login falhou - + Your client seems to be missing features this server requires for connection. Seu cliente parece estar faltando recursos que este servidor requer para conexão. - + To update your client, go to 'Help -> Check for Client Updates'. Para atualizar seu cliente, vá para 'Ajuda -> Procurar por atualizações do cliente'. - + Incorrect username or password. Please check your authentication information and try again. Usuário ou senha incorretos. Por favor, verifique suas informações de autenticação e tente novamente. - + There is already an active session using this user name. Please close that session first and re-login. Já existe uma sessão ativa usando este nome de usuário. Por favor, feche a sessão primeiro e logue novamente. - - + + You are banned until %1. Você está banido até %1. - - + + You are banned indefinitely. Você está banido indefinidamente. - + This server requires user registration. Do you want to register now? Este servidor exige registro de usuário. Você gostaria de registrar agora? - + This server requires client IDs. Your client is either failing to generate an ID or you are running a modified client. Please close and reopen your client to try again. Este servidor requer ID de cliente. Seu cliente ou não está conseguindo gerar um ID ou você está executando um cliente modificado. Por favor feche e abra seu cliente para tentar novamente. - + An internal error has occurred, please close and reopen Cockatrice before trying again. If the error persists, ensure you are running the latest version of the software and if needed contact the software developers. Ocorreu um erro interno, por favor, feche e reabra o Cockatrice antes de tentar novamente. Se o erro persistir, verifique se você está executando a versão mais recente do software e, se necessário, entre em contato com os desenvolvedores de software. - + Account activation Ativação da conta - + Your account has not been activated yet. You need to provide the activation token received in the activation email. Sua conta não foi ativada ainda Você precisa fornecer o código de ativação recebido por email. - + Server Full Servidor cheio. - + Unknown login error: %1 Erro de login desconhecido: %1 - - + + This usually means that your client version is out of date, and the server sent a reply your client doesn't understand. Isso geralmente significa que seu cliente está obsoleto, e o servidor enviou uma resposta que seu cliente não entende. - + Your username must respect these rules: Seu nome de usuário precesia seguir essas regras: - + is %1 - %2 characters long tem entre %1 e %2 caracteres - + can %1 contain lowercase characters %1 pode conter caracteres minúsculos. - - - - + + + + NOT NÃO - + can %1 contain uppercase characters %1 pode conter caracteres maiúsculos. - + can %1 contain numeric characters %1 pode conter caracteres numéricos. - + can contain the following punctuation: %1 Pode conter a seguinte pontuação: %1 - + first character can %1 be a punctuation mark Primeiro caractere pode %1 ser um sinal de pontuação - + no unacceptable language as specified by these server rules: note that the following lines will not be translated - + Nenhuma linguagem inaceitável, conforme especificado por estas regras do servidor: - + can not contain any of the following words: %1 Não pode conter nenhuma das seguintes palavras: %1 - + can not match any of the following expressions: %1 Não pode corresponder a nenhuma das seguintes expressões: %1 - + You may only use A-Z, a-z, 0-9, _, ., and - in your username. Você pode usar apenas A-Z, a-z, 0-9, _, ., e - em seu nome de usuário. - - - - - - + + + + + + Registration denied Registro negado - + Registration is currently disabled on this server Registro está atualmente desabilitado neste servidor - + There is already an existing account with the same user name. Já existe uma conta com este nome de usuário. - + It's mandatory to specify a valid email address when registering. É obrigatório especificar um endereço de email válido para registrar. - + It appears you are attempting to register a new account on this server yet you already have an account registered with the email provided. This server restricts the number of accounts a user can register per address. Please contact the server operator for further assistance or to obtain your credential information. Aparentemente você está tentando registrar uma nova conta nesse servidor, porém você já tem uma conta registrada com o e-mail fornecido. Este servidor restringe a quantidade de contas que podem ser registradas por endereço. Por favor contate o operador do servidor para assistência posterior ou para obter informações das suas credenciais. - + Password too short. Senha muito curta - + Registration failed for a technical problem on the server. Registro falhou devido a um problema tecnico no servidor. - + Unknown registration error: %1 Erro de registro desconhecido: %1 - + Account activation failed A ativação de conta falhou - + Socket error: %1 Erro de ligação:%1 - + You are trying to connect to an obsolete server. Please downgrade your Cockatrice version or connect to a suitable server. Local version is %1, remote version is %2. Você está tentando conectar a um servidor obsoleto. Por favor, faça um downgrade na versão do seu Cockatrice ou conecte-se ao servidor correto. A versão local é %1 e a versão remota é %2. - + Your Cockatrice client is obsolete. Please update your Cockatrice version. Local version is %1, remote version is %2. A versão do seu Cockatrice é obsoleta. Por favor, atualize a sua versão. A versão local é %1 e a versão remota é %2. - + Connecting to %1... Conectando a %1... - + Registering to %1 as %2... Registrando em %1 como %2... - + Disconnected Desconectado - + Connected, logging in at %1 Conectado, logando em %1 - - - + + + Requesting forgotten password to %1 as %2... Solicitando senha esquecida para %1 como %2... - + &Connect... &Conectar... - + &Disconnect &Desconectar - + Start &local game... Iniciar jogo &local... - + &Watch replay... &Assistir replay... - - &Deck editor - Editor de &decks - - - + &Full screen Tela &cheia - + &Register to server... &Registrar ao servidor: - + &Restore password... &Recuperar senha... - + &Settings... &Configurações... - + &Exit &Sair - + A&ctions Açõ&es - + &Cockatrice &Cockatrice - + C&ard Database Base de Cartas - + &Manage sets... &Gerenciar expansões... - + Edit custom &tokens... Editar &fichas personalizadas - + Open custom image folder Abrir pasta customizada de imagens. - + Open custom sets folder Abrir pasta de sets personalizados - + Add custom sets/cards Adicionar sets/cartas personalizadas - + + Reload card database + Recarregar banco de dados de cartas + + + + Tabs + Abas + + + &Help &Ajuda - + &About Cockatrice So&bre o Cockatrice - + &Tip of the Day &Dica do Dia - + Check for Client Updates Procurar por atualizações do cliente - + Check for Card Updates... Procurar por atualizações de cartas... - + View &Debug Log Ver registro de &depuração - - Show/Hide - + + Open Settings Folder + Abrir Pasta de Configurações - + + Show/Hide + Mostrar/Ocultar + + + New Version Nova Versão - + Congratulations on updating to Cockatrice %1! Oracle will now launch to update your card database. Parabéns por atualizar o Cockatrice %1! Oracle será inicializado agora para atualizar sua base de cartas - + Cockatrice installed Cockatrice instalado - + Congratulations on installing Cockatrice %1! Oracle will now launch to install the initial card database. Parabéns pela instalação do Cockatrice %1! O Oracle agora será iniciado para instalar o banco de dados inicial de cartas. - + Card database Base de cartas - + Cockatrice is unable to load the card database. Do you want to update your card database now? If unsure or first time user, choose "Yes" @@ -3024,29 +3826,29 @@ Gostaria de atualizar seu banco de dados de cartas agora? Se estiver em dúvida ou for um novo usuário selecione "Sim" - - + + Yes Sim - - + + No Não - + Open settings Abrir configurações - + New sets found Novas expansões encontradas - + %n new set(s) found in the card database Set code(s): %1 Do you want to enable it/them? @@ -3059,17 +3861,17 @@ Códigos das coleções: %1 Deseja ativá-las? - + View sets Visualizar expansões - + Welcome Bem vindo - + Hi! It seems like you're running this version of Cockatrice for the first time. All the sets in the card database have been enabled. Read more about changing the set order or disabling specific sets and consequent effects in the "Manage Sets" dialog. @@ -3078,58 +3880,59 @@ Todas as expansões do banco de dados de cartas foram habilitadas. Leia mais sobre mudar a ordem das expansões ou desabilitando expansões específicas na aba "Gerenciar Expansões". - - + + Information Informação - + A card database update is already running. Uma atualização da base de dados de cartas já está em andamento. - + Unable to run the card database updater: Não foi possível executar a atualização da base de dados de cartas. - - failed to start. - Falha ao iniciar + + Failed to start. The file might be missing, or permissions might be incorrect. + - - crashed. - erro. + + The process crashed some time after starting successfully. + - - timed out. - Tempo expirado. + + Timed out. The process took too long to respond. The last waitFor...() function timed out. + - - write error. - erro na escrita. + + An error occurred when attempting to write to the process. For example, the process may not be running, or it may have closed its input channel. + - - read error. - erro na leitura. + + An error occurred when attempting to read from the process. For example, the process may not be running. + - - unknown error. - erro desconhecido. + + Unknown error occurred. + - - The card database updater exited with an error: %1 - O atualizador do banco de dados de cartas fechou com um erro: %1 + + The card database updater exited with an error: +%1 + - + This server supports additional features that your client doesn't have. This is most likely not a problem, but this message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -3140,55 +3943,55 @@ Provavelmente não é um problema, mas esta mensagem pode significar que existe Para atualizar seu cliente, vá para Ajuda -> Verificar atualizações. - - - - - + + + + + Load sets/cards Carregar expansões/cards - + Selected file cannot be found. Arquivo selecionado não pode ser encontrado - + You can only import XML databases at this time. Você só pode importar bancos de dados XML neste momento. - + The new sets/cards have been added successfully. Cockatrice will now reload the card database. As novas cartas/coleções foram adicionadas com sucesso. O Cockatrice reiniciará imediatamente o banco de dados de cartas. - + Sets/cards failed to import. Falha ao importar expansões/cartas. - - - + + + Reset Password Redefinir Senha - + Your password has been reset successfully, you can now log in using the new credentials. Sua senha foi alterada com sucesso, você pode se conectar usando suas novas credenciais - + Failed to reset user account password, please contact the server operator to reset your password. Falha ao redefinir a senha da conta do usuário, entre em contato com o operador do servidor para redefinir sua senha. - + Activation request received, please check your email for an activation token. Solicitação de ativação recebida, verifique seu e-mail para um token de ativação. @@ -3448,7 +4251,7 @@ O Cockatrice reiniciará imediatamente o banco de dados de cartas. %1 tries to draw from an empty library - + %1 tenta comprar de uma biblioteca vazia @@ -3462,181 +4265,192 @@ O Cockatrice reiniciará imediatamente o banco de dados de cartas. - %1 is looking at the top %3 card(s) %2. + %1 is looking at the %4 %3 card(s) %2. + %1 is looking at the top %3 card(s) %2. top card for singular, top %3 cards for plural - %1 está olhando %3 carta %2%1 está olhando as %3 carta(s) %2%1 está olhando as %3 carta(s) %2 + %1 está olhando para a(s) %3 carta(s) %4 %2.%1 está olhando para a(s) %3 carta(s) %4 %2.%1 está olhando para a(s) %3 carta(s) %4 %2. - + + bottom + de fundo + + + + top + de topo + + + %1 turns %2 face-down. %1 turnos %2 cartas viradas para baixo. - + %1 turns %2 face-up. %1 turnos %2 cartas viradas para cima. - + The game has been closed. O jogo foi fechado. - + The game has started. O jogo começou. - + %1 has joined the game. %1 entrou no jogo. - + %1 is now watching the game. %1 está assistindo o jogo agora. - + You have been kicked out of the game. Você foi 'chutado' do jogo. - + %1 has left the game (%2). %1 deixou o jogo (%2) - + %1 is not watching the game any more (%2). %1 não está mais assistindo o jogo (%2). - + %1 is not ready to start the game any more. %1 não está mais pronto para inciar o jogo. - + %1 shuffles their deck and draws a new hand of %2 card(s). %1 embaralhou seu grimório e comprou uma nova mão com %2 card.%1 embaralhou seu grimório e comprou uma nova mão com %2 cards.%1 embaralhou seu grimório e comprou uma nova mão com %2 cards. - + %1 shuffles their deck and draws a new hand. %1 embaralhou seu grimório e comprou uma nova mão. - + You are watching a replay of game #%1. Você está assistindo o replay do jogo #%1. - + %1 is ready to start the game. %1 está pronto para inciar o jogo. - + cards an unknown amount of cards cartas - + %1 card(s) a card for singular, %1 cards for plural %1 carta%1 carta(s)%1 carta(s) - + %1 lends %2 to %3. - + %1 empresta %2 para %3. - + %1 reveals %2 to %3. %1 revela %2 para %3. - + %1 reveals %2. %1 revela %2. - + %1 randomly reveals %2%3 to %4. %1 revela aleatoriamente %2%3 para %4. - + %1 randomly reveals %2%3. %1 revela aleatoriamente %2%3. - + %1 peeks at face down card #%2. %1 olha a carta virada para baixo #%2. - + %1 peeks at face down card #%2: %3. %1 espia a face para baixo da carta #%2: %3. - + %1 reveals %2%3 to %4. %1 revela %2 %3 para %4. - + %1 reveals %2%3. %1 revela %2%3. - + %1 reversed turn order, now it's %2. %1 reverteu a ordem dos turnos, agora é a vez de %2. - + reversed revertido - + normal normal - + Heads Cara - + Tails Coroa - + %1 flipped a coin. It landed as %2. %1 jogou uma moeda. Saiu %2. - + %1 rolls a %2 with a %3-sided die. %1 tirou %2 em um D%3. - + %1 flips %2 coins. There are %3 heads and %4 tails. - + %1 joga %2 moedas. São %3 caras e %4 coroas. - + %1 rolls a %2-sided dice %3 times: %4. - + %1 rola um dado de %2 lados %3 vezes: %4. @@ -3754,17 +4568,17 @@ O Cockatrice reiniciará imediatamente o banco de dados de cartas. %1 embaralha as cartas %3 - %4 de %2 - + %1 unattaches %2. %1 desanexa %2. - + %1 undoes their last draw. %1 desfaz sua última compra. - + %1 undoes their last draw (%2). %1 desfaz sua última compra (%2). @@ -3772,110 +4586,110 @@ O Cockatrice reiniciará imediatamente o banco de dados de cartas. MessagesSettingsPage - + Word1 Word2 Word3 Palavra1 Palavra2 Palavra3 - + Add New Message Adicionar nova mensagem - + Edit Message Editar mensagem - + Remove Message Remover mensagem - + Add message Adicionar mensagem - - + + Message: Mensagem: - + Edit message Editar mensagem - + Chat settings Configurações de Conversa - + Custom alert words Palavras de alertas customizadas. - + Enable chat mentions Habilitar menções em conversa - + Enable mention completer Habilitar completador de menções. - + In-game message macros Mensagem no jogo - + How to use in-game message macros - + Como usar macros de mensagens no jogo - + Ignore chat room messages sent by unregistered users Ignorar mensagens na sala enviadas por usuários não registrados - + Ignore private messages sent by unregistered users Ignorar mensagens privadas enviadas por usuários não registrados - - + + Invert text color Inverter cor do texto - + Enable desktop notifications for private messages Ativar notificações da área de trabalho para mensagens privadas. - + Enable desktop notification for mentions Habilitar notificações de desktop para menções. - + Enable room message history on join Ativar histórico de mensagens da sala ao entrar - - + + (Color is hexadecimal) (Cor em hexadecimal) - + Separate words with a space, alphanumeric characters only Separar palavras com um espaço, apenas caracteres alfanuméricos @@ -3933,6 +4747,24 @@ O Cockatrice reiniciará imediatamente o banco de dados de cartas. Identidade de Cor + + OverlapControlWidget + + + Cards to overlap: + Cartas para sobrepor: + + + + Overlap percentage: + Porcentagem de sobreposição: + + + + Overlap direction: + Direção da sobreposição: + + Phase @@ -3999,57 +4831,57 @@ O Cockatrice reiniciará imediatamente o banco de dados de cartas. PhasesToolbar - + Untap step Etapa de desvirar - + Upkeep step Etapa de manutenção - + Draw step Etapa de compra - + First main phase Primeira fase principal - + Beginning of combat step Etapa de início de combate - + Declare attackers step Etapa de declaracão de atacantes - + Declare blockers step Etapa de declaração de bloqueadores - + Combat damage step Etapa de dano de combate - + End of combat step Etapa de fim de combate - + Second main phase Segunda fase principal - + End of turn step Etapa de fim de combate @@ -4057,7 +4889,7 @@ O Cockatrice reiniciará imediatamente o banco de dados de cartas. PictureLoader - + en code for scryfall's language property, not available for all languages pt @@ -4066,647 +4898,781 @@ O Cockatrice reiniciará imediatamente o banco de dados de cartas. Player - + Reveal top cards of library Revelar cartas do topo do grimório - - - - - - - - - + + + + + + + + + + + + Number of cards: (max. %1) Número de cartas: (no máximo %1) - + &View graveyard V&er cemitério - + &View exile &Ver exílio - + Player "%1" Jogador "%1" - - - - + + + + &Graveyard &Cemitério - - - - + + + + &Exile &Exílio - + &Move hand to... &Mover mão para... - - - + + + &Top of library &Topo do grimório - - - + + + &Bottom of library &Fundo do grimório - + &Move graveyard to... &Mover cemitério para... - - - - + + + + &Hand &Mão - + &Move exile to... &Mover exílio para... - + &View library &Ver grimório - + &View hand &Ver mão - + View &top cards of library... Ver os cards do to&po do grimório... - + Reveal &library to... Revelar &grimório para... - + &Always reveal top card &Sempre revelar a carta do topo - + &View sideboard &Ver sideboard - + &Draw card Co&mprar card - + D&raw cards... Comprar car&ds... - + &Undo last draw Desfa&zer última compra - + Take &mulligan Pedir mu&lligan - - &Shuffle - &Embaralhar - - - + Play top card &face down Coloque em jogo a primeira carta do topo do grimório com a face para baixo - + Move top card to grave&yard Move a carta do topo para o cemitério - + Move top card to e&xile Exila a carta do topo - + Move top cards to &graveyard... Mover os cards do topo para o ce&mitério... - + Move top cards to &exile... Mover os cards do topo para o e&xílio... - + Put top card on &bottom Colocar o card do topo no &fundo - + + View bottom cards of library... + Visualizar cartas do fundo da biblioteca...  + + + Lend library to... + Emprestar biblioteca para... + + + + Shuffle - + Put top cards on stack &until... Take top cards &until... + Colocar as cartas do topo na pilha até... + + + + Shuffle top cards... - + + Shuffle bottom cards... + + + + &Reveal hand to... &Revelar carta para... - + Reveal r&andom card to... Revelar uma carta &aleatória para... - + Reveal random card to... Revele uma carta aleatória a... - + &Sideboard &Sideboard - + &Library &Grimório - + &Counters &Marcadores - + &Untap all permanents Des&virar todas as permanentes - + R&oll die... &Jogar dado... - + &Create token... Criar fich&a... - + C&reate another token Criar &outra ficha - + Cr&eate predefined token Cr&iar uma ficha predefinida - - + + &All players To&dos os jogadores - + S&ay &Falar - + + &Select All + Selecionar Tudo + + + + S&elect Row + Selecionar Linha + + + + S&elect Column + Selecionar Coluna + + + &Play &Jogar - + &Hide &Ocultar - + Play &Face Down Jogar com a &Face-para-Baixo - + &Tap / Untap Turn sideways or back again &Virar / Desvirar - + Toggle &normal untapping &Trocar o modo de desvirar - + T&urn Over Turn face up/face down &Virar - + &Peek at card face &Olhar a face da carta - + &Clone Clo&nar - + Attac&h to card... Ane&xar na carta... - + Unattac&h De&sanexar - + &Draw arrow... &Desenhar seta... - + &Increase power Au&mentar poder - + &Decrease power Dimi&nuir poder - + I&ncrease toughness A&umentar resistência - + D&ecrease toughness D&iminuir resistência - + In&crease power and toughness Aumen&tar poder e resistência - + Dec&rease power and toughness Diminuir p&oder e resistência - + Increase power and decrease toughness Aumentar o poder e diminuir a resistência - + Decrease power and increase toughness Diminuir o poder e aumentar a resistência - + Set &power and toughness... Definir poder e resis&tência... - + Reset p&ower and toughness Reinicia o poder e resistência - + &Set annotation... Definir &nota... - + Red Vermelho - + Yellow Amarelho - + Green Verde - + &Add counter (%1) &Adicionar marcador(%1) - + &Remove counter (%1) &Remover marcador (%1) - + &Set counters (%1)... &Definir marcadores (%1)... - + &Top of library in random order - + Topo da biblioteca em ordem aleatória - + X cards from the top of library... X cartas do topo do grimório... - + &Bottom of library in random order &Fundo do grimório em ordem aleatória - + View top cards of library Ver os cards do topo do grimório - + + View bottom cards of library + Visualizar cartas do fundo da biblioteca + + + + Shuffle top cards of library + + + + + Shuffle bottom cards of library + + + + Which position should this card be placed: Em que posição esta carta deve ser colocada: - + (max. %1) (máx. %1) - + Draw hand Comprar mão - + 0 and lower are in comparison to current hand size Zero ou menos é a quantidade em relação à mão atual - + Draw cards Comprar cards - - + + Number: Número: - + Move top cards to grave Mover os cards do topo para o cemitério - + Reveal &top cards to... Revelar as cartas do &topo para... - + &Top of library... &Topo do grimório... - + &Bottom of library... &Fundo do grimório... - + &Always look at top card &Sempre olhar a carta do topo - + &Open deck in deck editor Abrir no edit&or de decks - + &Play top card Jogar a carta do to&po - + &Draw bottom card Comprar carta do fun&do - + D&raw bottom cards... Comprar cartas do fu&ndo... - + &Play bottom card &Jogar carta do fundo - + Play bottom card &face down Jogar carta do fundo com a &face voltada para baixo - + Move bottom card to grave&yard Mover a carta do fundo para o cem&itério - + Move bottom card to e&xile Mover a carta do fundo para o e&xílio - + Move bottom cards to &graveyard... Mover as cartas do fundo para &cemitério - + Move bottom cards to &exile... Mover as cartas do fundo para o &exílio - + Put bottom card on &top Colocar carta do fundo no &topo - + Selec&ted cards Cartas &selecionadas - + Move top cards to exile Mover as cartas do topo para o exílio - + Move bottom cards to grave Mover as cartas do fundo para o cemitério - + Move bottom cards to exile Mover as cartas do fundo para o exílio - + Draw bottom cards Comprar cards do fundo do grimório - - + + C&reate another %1 token Criar outra ficha %1 - + Create tokens Criar fichas - + Place card X cards from top of library Colocar a carta X cartas do topo do grimório - + Change power/toughness Mudar o poder/resistêcia - + Change stats to: Mudar estatísticas para: - + Set annotation Alterar nota - + Please enter the new annotation: Por favor, entre com a nova nota: - + Set counters Alterar marcadores - + Re&veal to... - + Revelar para... - + View related cards Ver cartas relacionadas - + Token: Ficha: - + All tokens Todas as fichas + + PrintingSelector + + + Display Navigation Buttons + + + + + <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. + + + + + PrintingSelectorCardOverlayWidget + + + Preference + Preferência + + + + Pin Printing + Fixar Impressão + + + + Unpin Printing + Desafixar Impressão + + + + Show Related cards + Mostrar Cartas Relacionadas + + + + PrintingSelectorCardSearchWidget + + + Search by set name or set code + Pesquisar pelo nome ou código do conjunto + + + + PrintingSelectorCardSelectionWidget + + + Previous Card in Deck + Carta Anterior no Deck + + + + Next Card in Deck + Próxima Carta no Deck + + + + PrintingSelectorCardSortingWidget + + + Alphabetical + Alfabética + + + + Preference + Preferência + + + + Release Date + Data de Lançamento + + + + + Descending + Decrescente + + + + Ascending + Crescente + + QMenuBar - + Services Serviços - + Hide %1 Esconder %1 - + Hide Others Esconder outros - + Show All Mostrar tudo - + Preferences... Preferências - + Quit %1 Sair %1 - + About %1 Sobre %1 @@ -4714,131 +5680,125 @@ O Cockatrice reiniciará imediatamente o banco de dados de cartas. QObject - + Cockatrice card database (*.xml) Banco de dados de cartas do Cockatrice (*.xml) - - + All files (*.*) Todos arquivos (*.*) - + Cockatrice replays (*.cor) Cockatrice replays (*.cor) - - Common deck formats (*.cod *.dec *.dek *.txt *.mwDeck) - Formatos de deck utilizados (*.cod *.dec *.dek *.txt *.mwDeck) - - - + Maindeck - + Main - + Sideboard - + Side - + Tokens - + Fichas QPlatformTheme - + OK OK - + Save Salvar - + Save All Salvar Todos - + Open Abrir - + &Yes &Sim - + Yes to &All Sim para &Todos - + &No &Não - + N&o to All Não para &Todos - + Abort Abortar - + Retry Tentar Novamente - + Ignore Ignorar - + Close Fechar - + Cancel Cancelar - + Discard Descartar - + Help Ajuda - + Apply Aplicar - + Reset Resetar - + Restore Defaults Restaurar Padrão @@ -4846,17 +5806,17 @@ O Cockatrice reiniciará imediatamente o banco de dados de cartas. RemoteDeckList_TreeModel - + Name Nome - + ID ID - + Upload time Hora do upload @@ -4864,32 +5824,32 @@ O Cockatrice reiniciará imediatamente o banco de dados de cartas. RemoteReplayList_TreeModel - + ID Código - + Name Nome - + Players Jogadores - + Keep Manter - + Time started Tempo iniciado - + Duration (sec) Duração (seg) @@ -4935,34 +5895,34 @@ O Cockatrice reiniciará imediatamente o banco de dados de cartas. SequenceEdit - + Choose an action from the table - + Escolha uma ação da tabela - + Hit the key/combination of keys you want to set for this action - + Pressione a tecla/combinação de teclas que deseja definir para esta ação - - Shortcut already in use - - - - + Invalid key - + Tecla inválida - + + Shortcut already in use by: + Atalho já está sendo usado por: + + + Clear - + Limpar - + Restore default - + Restaurar padrão @@ -4996,73 +5956,81 @@ O Cockatrice reiniciará imediatamente o banco de dados de cartas. ShortcutSettingsPage - - + + Restore all default shortcuts Restaurar todos atalhos padrões - + Do you really want to restore all default shortcuts? Você tem certeza que deseja restaurar todos atalhos padrões? - + Clear all default shortcuts Limpar todos os atalhos padrão - + Do you really want to clear all shortcuts? Você realmente quer limpar todos os atalhos? - - Action - Ação - - - - Shortcut - Atalho - - - + Section: Seção: - + Action: Ação: - + Shortcut: Atalho: - + How to set custom shortcuts Como definir atalhos personalizados - + Clear all shortcuts - + Limpar todos os atalhos + + + + Search by shortcut name + Pesquisar pelo nome do atalho + + + + ShortcutTreeView + + + Action + Ação + + + + Shortcut + Atalho ShortcutsSettings - + Your configuration file contained invalid shortcuts. Please check your shortcut settings! Seu arquivo de configurações contém atalhos inválidos. Por favor, verifique suas configurações de atalho! - + The following shortcuts have been set to default: Os seguintes atalhos foram configurados como padrão: @@ -5072,17 +6040,17 @@ Por favor, verifique suas configurações de atalho! ShutdownDialog - + &Reason for shutdown: &Motivo para desligar: - + &Time until shutdown (minutes): &Tempo até o desligamento (minutos): - + Shut down server Servidor desligado @@ -5090,27 +6058,27 @@ Por favor, verifique suas configurações de atalho! SoundSettingsPage - + Enable &sounds Habilitar &sons - + Current sounds theme: Tema sonoro atual: - + Test system sound engine Testar sistema de som - + Sound settings Configurações de som - + Master volume Volume principal @@ -5118,48 +6086,48 @@ Por favor, verifique suas configurações de atalho! SpoilerBackgroundUpdater - + Spoilers season has ended Temporada de 'spoilers' encerrada - + Deleting spoiler.xml. Please run Oracle Deletando spoiler.xml. Favor rodar o Oracle - - + + Spoilers download failed Falha no download de 'spoilers' - + No internet connection Sem conexão com a internet - + Error Erro - + Spoilers already up to date 'Spoilers' já atualizados - + No new spoilers added Nenhum 'spoiler' novo adicionado - + Spoilers have been updated! 'Spoilers' foram adicionados! - + Last change: Última alteração: @@ -5167,638 +6135,594 @@ Por favor, verifique suas configurações de atalho! StableReleaseChannel - - Stable Releases - Lançamentos Estáveis + + Default + Padrão - + No reply received from the release update server. Sem resposta do servidor de atualização - + Invalid reply received from the release update server. Resposta inválida do servidor de atualização - + No reply received from the tag update server. Nenhuma resposta recebida do servidor de atualização de tags. - + Invalid reply received from the tag update server. Resposta inválida recebida do servidor de atualização de tags. + + TabAccount + + + Add to Buddy List + Adicionar à lista de amigos + + + + Add to Ignore List + Adicionar à lista de ignorados + + + + Account + Conta + + TabAdmin - + Administration Administração - + Update server &message &Atualizar mensagem do servidor - + &Shut down server &Servidor desligado - + &Reload configuration &Recarregar configuração - + Server administration functions Funções do administrador do servidor - + + Server moderator functions + Funções de moderador do servidor + + + + Replay ID + ID da Repetição + + + + Grant Replay Access + Conceder Acesso à Repetição + + + + Username to Activate + Nome de Usuário para Ativar + + + + Force Activate User + Forçar Ativação do Usuário + + + &Unlock functions &Desbloquear funções - + &Lock functions &Bloquear funções + + + + Success + Sucesso + + + + Replay access granted + Acesso à repetição concedido + + + + + + + + Error + Erro + + + + Unable to grant replay access. Replay ID invalid + Não foi possível conceder acesso à repetição. ID da Repetição inválido + + + + Unable to grant replay access. Internal error + Não foi possível conceder acesso à repetição. Erro interno + + + + User successfully activated + Usuário ativado com sucesso + + + + Unable to activate user. Username invalid + Não foi possível ativar o usuário. Nome de usuário inválido + + + + Unable to activate user. User already active + Não foi possível ativar o usuário. Usuário já está ativo + + + + Unable to activate user. Internal error + Não foi possível ativar o usuário. Erro interno + TabDeckEditor - - Search by card name (or search expressions) - Search by card name - - - - - Add to Deck - Adicionar ao deck - - - - Add to Sideboard - Adicionar ao sideboard - - - - Show Related cards - Exibir cartas relacionadas - - - - &Clear all filters - &Limpar todos os filtros - - - - Delete selected - Apagar selecionados - - - - Deck &name: - Nome do &deck: - - - - &Comments: - &Comentários: - - - - Hash: - Hash: - - - - &New deck - &Novo deck - - - - &Load deck... - &Carregar deck - - - - &Save deck - &Salvar deck - - - - Save deck &as... - Salvar deck &como... - - - - Load deck from cl&ipboard... - Carregar deck da área de &transferência... - - - - Save deck to clipboard - Salvar grimório da área de colagem - - - - Annotated - Anotado - - - - Not Annotated - Não anotado - - - - &Print deck... - &Imprimir deck... - - - - &Send deck to online service - &Enviar grimório para serviço online - - - - Create decklist (decklist.org) - Criar decklist (decklist.org) - - - - Analyze deck (deckstats.net) - Analisar grimório (deckstats.net) - - - - Analyze deck (tappedout.net) - Analisar grimório (tappedout.net) - - - - &Close - &Fechar - - - - Add card to &maindeck - Adicionar carta ao &maindeck - - - - Add card to &sideboard - Adicionar carta ao &sideboard - - - - &Remove row - &Remover linha - - - - &Increment number - &Aumentar número - - - - &Decrement number - &Diminuir número - - - - &Deck Editor - Editor de &Decks - - - - + Card Info Informação da carta. - - + Deck Deck - - + Filters Filtros - + &View &Visualizar - - - + + Printing + Impressão + + + + + + Visible Visível - - - + + + + Floating Flutuante - + Reset layout Reiniciar layout - + Deck: %1 Deck: %1 - - - Are you sure? - Você tem certeza? - - - - The decklist has been modified. -Do you want to save the changes? - O deck foi modificado. -Você deseja salvar as alterações? - - - - Load deck - Carregar deck - - - - - - - - - Error - Erro - - - - The deck could not be saved. - O deck não pode ser salvo. - - - - Could not save remote deck - Não foi possível salvar o deck remoto - - - - - The deck could not be saved. -Please check that the directory is writable and try again. - O deck não pôde ser salvo. -Por favor, verifique se a pasta não é somente leitura e tente novamente. - - - - Save deck - Salvar deck - - - - There are no cards in your deck to be exported - Não há cartas em seu grimório a serem exportadas - - - - No deck was selected to be saved. - Nenhum grimório selecionado para ser salvo. - TabDeckStorage - - Deck storage - Armazenamento de decks - - - + Local file system Sistema de arquivos local - + Server deck storage Armazenamento de decks no servidor - - + + Open in deck editor Abrir no editor de decks - + + Rename deck or folder + + + + Upload deck Upload do deck - + Download deck Download do deck - - + + + + New folder Nova pasta - - + + Delete Apagar - - + + Open decks folder + Abrir pasta de decks + + + + Rename local folder + + + + + Rename local file + + + + + New name: + + + + + + + Error Error - + + Rename failed + + + + + Invalid deck file Arquivo de deck inválido - + Enter deck name Entre o nome do deck - + This decklist does not have a name. Please enter a name: Este deck não tem um nome. Por favor, entre um nome: - + Unnamed deck Deck sem nome - + Failed to upload deck to server Falha ao enviar deck ao servidor - + Delete local file Excluir arquivo local - - - - Are you sure you want to delete "%1"? - Você tem certeza que quer excluir "%1"? + + Are you sure you want to delete the selected files? + Tem certeza de que deseja excluir os arquivos selecionados? - + + Delete remote decks + Excluir decks remotos + + + + Are you sure you want to delete the selected decks? + Tem certeza de que deseja excluir os decks selecionados? + + + + Name of new folder: Nome da nova pasta: - - Delete remote folder - Excluir pasta remota + + Deck Storage + Armazenamento de Deck + + + + TabDeckStorageVisual + + + Visual Deck Storage + Decks Visuais - - Delete remote deck - Excluir deck remoto + + Error + + + + + Could not open deck at %1 + + + + + TabEdhRec + + + EDHREC: + EDHREC: TabGame - - - + + + Replay Replay - - + + Game Jogo - - + + Player List Lista de jogadores. - - + + Card Info Informação da carta - - + + Messages Mensagens - - + + Replay Timeline Linha do tempo do Replay - + &Phases &Etapas - + &Game &Jogo - + Next &phase Próxima &etapa - + Next phase with &action Próxima etapa com &acao - + Next &turn Próximo &turno - + Reverse turn order Ordem dos turnos revertida - + &Remove all local arrows &Apagar todas as setas locais - + Rotate View Cl&ockwise Girar visualização em sentido h&orário - + Rotate View Co&unterclockwise Girar visualização em sentido a&nti-horário - + Game &information &Informação de jogo - + + Un&concede + Cancelar Rendição + + + &Concede &Conceder - + &Leave game &Sair do jogo - + C&lose replay &Fechar replay - + &Focus Chat &Focar no chat - + &Say: &Falar: - + &View &Visualizar - - - - + + + + Visible Visível - - - - + + + + Floating Flutuante - + Reset layout Reiniciar layout - + Concede Conceder - + Are you sure you want to concede this game? Você tem certeza que deseja conceder este jogo? - + Unconcede Defazer concessão - + You have already conceded. Do you want to return to this game? Você já concedeu. Quer retornar a esse jogo? - + Leave game Sair do jogo - + Are you sure you want to leave this game? Você tem certeza que deseja sair deste jogo? - + You are flooding the game. Please wait a couple of seconds. Você está 'flodando' o chat (jogo). Por favor, espere alguns segundos. - + A player has joined game #%1 Um jogador entrou no jogo #%1 - + %1 has joined the game %1 entrou no jogo - + kicked by game host or moderator expulso pelo hospedador do jogo ou moderador. - + player left the game jogador deixou o jogo - + player disconnected from server jogador desconectado do servidor - + reason unknown razão desconhecida - + You have been kicked out of the game. Você foi 'chutado' do jogo. @@ -5811,153 +6735,153 @@ Por favor, entre um nome: Logs - - - + + + Time;SenderName;SenderIP;Message;TargetID;TargetName Tempo;RemetenteNome;RemetenteIP;Mensagem;AlvoID;AlvoNome - + Room Logs Logs de salas - + Game Logs Logs de jogos - + Chat Logs Logs de conversas - - + + Error Erro - + You must select at least one filter. Você precisa selecionar pelo menos um filtro. - + You have to select a valid number of days to locate. Você precisa selecionar um número de dias válido para localizar - + Username: Nome de usuário. - + IP Address: Endereço de IP: - + Game Name: Nome do jogo: - + GameID: ID do Jogo: - + Message: Mensagem: - + Main Room Sala Principal - + Game Room Sala de Jogo - + Private Chat Chat Privado. - + Past X Days: X dias anteriores: - + Today Hoje - + Last Hour Última hora - + Maximum Results: Máximo de resultados: - + At least one filter is required. The more information you put in, the more specific your results will be. Ao menos um filtro deve ser selecionado. Quanto mais informação você inserir, mais específicos seus resultados serão. - + Get User Logs Obter logs de usuário - + Clear Filters Limpar Filtros - + Filters Filtros - + Log Locations Logs de localização - + Date Range Intervalo de data - + Maximum Results Máximo de resultados - - + + Message History Histórico de Mensagens - + Failed to collect message history information. Falha ao coletar informação do histórico de mensagens. - + There are no messages for the selected filters. Não existe mensagem que corresponda aos filtros selecionados. @@ -5980,22 +6904,22 @@ Quanto mais informação você inserir, mais específicos seus resultados serão %1 - Conversa privada - + This user is ignoring you, they cannot see your messages in main chat and you cannot join their games. Este usuário esta ignorando você, ele não pode ver suas mensagens no chat principal e você não pode entrar nos jogos dele. - + Private message from Mensagem privada de - + %1 has left the server. %1 saiu do servidor. - + %1 has joined the server. %1 entrou no servidor. @@ -6003,117 +6927,153 @@ Quanto mais informação você inserir, mais específicos seus resultados serão TabReplays - - Game replays - Replay de jogos - - - + Local file system Sistema de arquivos local - + Server replay storage Servidor de armazenamento de replays - - + + Watch replay Assistir replay - - + + Rename + Renomear + + + + + New folder + Nova pasta + + + + Delete Excluir - + + Open replays folder + Abrir pasta de repetições + + + Download replay Baixar replay - + Toggle expiration lock Trocar expiração da trava - + + Rename local folder + Renomear pasta local + + + + Rename local file + Renomear arquivo local + + + + New name: + Novo nome: + + + + Error + Erro + + + + Rename failed + Falha ao renomear + + + + Name of new folder: + Nome da nova pasta: + + + Delete local file Excluir arquivo local - - Are you sure you want to delete "%1"? - ^Você tem certeza que quer excluir "%1"? + + Are you sure you want to delete the selected files? + Tem certeza de que deseja excluir os arquivos selecionados? - - Downloading Replays - Baixando Replays + + Are you sure you want to delete the selected replays? + Tem certeza de que deseja excluir as repetições selecionadas? - - Folder download is not yet supported. Please download replays individually. - Baixar pastas ainda não é suportado. Por favor baixe os replays individualmente. - - - + Delete remote replay Excluir replay remoto - - Are you sure you want to delete the replay of game %1? - Você tem certeza que quer excluir o replay do jogo %1? + + Game Replays + Repetições de jogo TabRoom - + &Say: &Falar: - + Chat Chat - + &Room &Sala - + &Leave room S&air da sala - + &Clear chat Limpar conversa - + Chat Settings... Configurações de Conversa - + mentioned you. mencionou você. - + Click to view Clique para ver - + You are flooding the chat. Please wait a couple of seconds. Você está flodando o chat. Por favor, espere alguns segundos. @@ -6126,30 +7086,30 @@ Quanto mais informação você inserir, mais específicos seus resultados serão Servidor - - - - + + + + Error Erro - + Failed to join the server room: it doesn't exist on the server. Falha ao entrar na sala: ela não existe no servidor. - + The server thinks you are in the server room but your client is unable to display it. Try restarting your client. O servidor pensa que você está na sala, mas seu cliente não consegue exibi-la. Tente reiniciar o seu cliente. - + You do not have the required permission to join this server room. Você não tem permissão para entrar nesta sala. - + Failed to join the server room due to an unknown error: %1. Falha ao entrar na sala devido a um erro desconhecido: %1. @@ -6157,32 +7117,72 @@ Quanto mais informação você inserir, mais específicos seus resultados serão TabSupervisor - + + Deck Editor + Editor de Deck + + + + &Visual Deck Storage + &Decks Visuais + + + + Server + Servidor + + + + Account + Conta + + + + Deck Storage + Armazenamento de Deck + + + + Game Replays + Repetições de jogo + + + + Administration + Administração + + + + Logs + Registros + + + Are you sure? Você tem certeza? - + There are still open games. Are you sure you want to quit? Ainda existem jogos abertos. Você tem certeza que deseja sair? - + Click to view Clique para visualizar - + Your buddy %1 has signed on! Seu amigo %1 entrou no servidor! - + Unknown Event Evento desconhecido. - + The server has sent you a message that your client does not understand. This message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -6193,63 +7193,45 @@ Esta mensagem pode significar que existe uma nova versão do Cockatrice disponí Para atualizar seu cliente, vá para Ajuda -> Verificar atualizações. - + Idle Timeout Tempo esgotado de inatividade - + You are about to be logged out due to inactivity. Você irá ser desconectado por inatividade - + Promotion Promoção - + You have been promoted. Please log out and back in for changes to take effect. Você foi promovido a moderador(a). Por favor faça logout e depois acesse novamente para que as mudanças sejam efetivadas. - + Warned Advertido - + You have received a warning due to %1. Please refrain from engaging in this activity or further actions may be taken against you. If you have any questions, please private message a moderator. Você recebeu uma advertência por %1. Por favor evite tais ações ou outras medidas poderão ser tomadas contra você. Se você tem qualquer dúvida, por favor envie uma mensagem privada um dos moderadores. - + You have received the following message from the server. (custom messages like these could be untranslated) Você recebeu a seguinte mensagem do servidor. (mensagens personalizadas como estas podem não ser traduzidas) - - TabUserLists - - - Account - Conta - - - - Add to Buddy List - Adicionado a Lista de Amigos - - - - Add to Ignore List - Adicionados a Lista de Ignorados - - TappedOutInterface @@ -6335,173 +7317,184 @@ Por favor evite tais ações ou outras medidas poderão ser tomadas contra você UserContextMenu - + User &details &Detalhes do usuário - + Private &chat Conversa privada - + Show this user's &games Mostrar este usuário de jogo - + Add to &buddy list Adicionar à &lista de amigos - + Remove from &buddy list Remover da li&sta de amigos - + Add to &ignore list Adicionar à li&sta dos ignorados - + Remove from &ignore list Remover da lista dos i&gnorados - + Kick from &game C&hutar do jogo - + Warn user Advertir usuário - + View user's war&n history Visualizar o histórico de advertê&ncias do usuário. - + Ban from &server Ban&ir do servidor - + View user's &ban history Visualizar o histórico de %banimento do usuário. - + &Promote user to moderator &Promover usuário a moderador - + Dem&ote user from moderator Rev&ogar as permissões de moderação do usuário - + Promote user to &judge Promover usuário a &juiz - + Demote user from judge Remover usuário da posição de juiz - + + View admin notes + Visualizar notas do administrador + + + %1's games Jogos de %1 - - - + + + Ban History Histórico de banimento - + Ban Time;Moderator;Ban Length;Ban Reason;Visible Reason Tempo de banimento;Moderador;Duração do Banimento;Razão do Banimento;Razão Visível - + User has never been banned. O usuário nunca foi banido. - + Failed to collect ban information. Falha ao coletar informações de banidas. - - - + + + Warning History Histórico de advertências - + Warning Time;Moderator;User Name;Reason Tempo de advertência;Moderador;Nome de Usuário;Razão - + User has never been warned. Usuário nunca foi advertido. - + Failed to collect warning information. Falha ao coletar informações de aviso. - - + + Failed to get admin notes. + Falha ao obter as notas do administrador. + + + + Success Sucesso - + Successfully promoted user. Usuário promovido com sucesso. - + Successfully demoted user. Permissões de moderação revogadas com sucesso. - - + + + Failed Falha - + Failed to promote user. Falha ao promover usuário. - + Failed to demote user. Falha ao revogar permissões de moderação do usuário. - + Copy hash to clipboard Copiar hash para área de transferência - + Remove this user's messages Remover mensagens deste usuário @@ -6509,173 +7502,173 @@ Por favor evite tais ações ou outras medidas poderão ser tomadas contra você UserInfoBox - + Location: Localização: - + Account Age: Idade conta: - + Edit Editar - + Change password Alterar senha - + Change avatar Alterar avatar - + Administrator Administrador - + Moderator Moderador - + Registered user Usuário registrado - - + + Unregistered user Usuário não registrado - + Judge Juíz - + Unknown Desconhecido - + The entered password does not match your account. A senha inserida não coincide com de sua conta. - - - + + + Information Informação - + User information updated. Informações do usuário atualizadas - - - - - - - - - - + + + + + + + + + + Error Erro - + User Information Informação do usuário - + Real Name: Nome real: - + User Level: Nível do usuário: - + %n Year(s), amount of years (only shown if more than 0) %n ano(s),%n ano(s),%n ano(s), - + %10%n Day(s) %20 amount of years (if more than 0), amount of days, date in local short format %10%n dia(s) %20%10%n dia(s) %20%10%n dia(s) %20 - + Enter Password Insira a senha - + Password verification is required in order to change your email address Uma verificação de senha é obrigatória para alterar seu endereço de e-mail - - - + + + An error occurred while trying to update your user information. Ocorreu um erro ao tentar atualizar suas informações de usuário. - + This server does not permit you to update your user informations. Este servidor não permite que você atualize suas informações de usuário. - + Password changed. Senha alterada. - + This server does not permit you to change your password. Este servidor não permite que você altere sua senha. - + The new password is too short. A nova senha é muito curta. - + The old password is incorrect. A senha antiga está incorreta. - + Avatar updated. Avatar alterado. - + This server does not permit you to update your avatar. Este servidor não permite que você altere seu avatar. - + An error occured while trying to updater your avatar. Um erro ocorreu enquanto seu avatar era atualizado. @@ -6683,124 +7676,251 @@ Por favor evite tais ações ou outras medidas poderão ser tomadas contra você UserInterfaceSettingsPage - + General interface settings Configurações gerais de interface - + &Double-click cards to play them (instead of single-click) &Duplo clique nos cards para jogá-los (ao invés de clique simples) - + + &Clicking plays all selected cards (instead of just the clicked card) + Clicar reproduz todas as cartas selecionadas (em vez de apenas a carta clicada) + + + &Play all nonlands onto the stack (not the battlefield) by default &Jogar todos não terrenos na pilha (não campo de batalha) por padrão - + + Close card view window when last card is removed + Fechar janela de visualização ao remover a última carta + + + Annotate card text on tokens Anotar texto de card nas fichas - + Use tear-off menus, allowing right click menus to persist on screen Usar menus tear-off, permitindo que menus de clique com botão direito persistam na tela - + Notifications settings Configurações de Notificações - + Enable notifications in taskbar Habilitar notificações na barra de tarefas - + Notify in the taskbar for game events while you are spectating Notificar na barra de tarefas para eventos do jogo enquanto você está como espectador - + Notify in the taskbar when users in your buddy list connect Notificar na barra de tarefas quando usuários na sua lista de amigos se conectar - + Animation settings Configurações de animação - + &Tap/untap animation Animação de &virar/desvirar + + + Deck editor/storage settings + Configurações do editor/armazenamento de decks + + + + Open deck in new tab by default + Abrir deck em uma nova aba por padrão + + + + Prompt before converting .txt decks to .cod format + Perguntar antes de converter decks .txt para .cod + + + + Always convert if not prompted + Sempre converter se não perguntar + + + + Use visual deck storage in game lobby + Usar decks visuais no lobby do jogo + + + + Replay settings + Configurações de repetição + + + + Buffer time for backwards skip via shortcut: + Tempo de buffer para retroceder usando atalho: + - UserList + UserListWidget - + Users connected to server: %1 Usuários conectados ao servidor: %1 - + Users in this room: %1 Usuários nesta sala: %1 - + Buddies online: %1 / %2 Amigos online: %1 / %2 - + Ignored users online: %1 / %2 Usuários ignorados online: %1 / %2 + + VisualDeckStorageFolderDisplayWidget + + + Deck Storage + Decks Visuais + + + + VisualDeckStorageSearchWidget + + + Search by filename + Pesquisar pelo nome do arquivo + + + + VisualDeckStorageSortWidget + + + Sort Alphabetically (Deck Name) + Ordenar Alfabeticamente (Nome do Deck) + + + + Sort Alphabetically (Filename) + Ordenar Alfabeticamente (Nome do Arquivo) + + + + Sort by Last Modified + Ordenar por Última Modificação + + + + Sort by Last Loaded + Ordenar por Último Carregamento + + + + VisualDeckStorageWidget + + + Loading database ... + Carregando banco de dados ... + + + + Show Folders + + + + + Show Tag Filter + + + + + Show Tags On Deck Previews + + + + + Show Banner Card Selection Option + + + + + Include Folder Names in Search + + + + + Draw unused Color Identities + + + + + Unused Color Identities Opacity + + + WarningDialog - + Which warning would you like to send? Qual advertência você gostaria de enviar? - + Redact all messages from this user in all rooms Remover todas mensagens deste usuário em todas salas - + &OK %OK - + &Cancel &Cancelar - + Warn user for misconduct Advertir usuario por mau comportamento. - - + + Error Erro - + User name to send a warning to can not be blank, please specify a user to warn. O campo "nome de usuário" ao qual se deseja enviar uma advertência não pode permanecer em branco, por favor especifique um usuário para ser advertido. - + Warning to use can not be blank, please select a valid warning to send. O campo "advertência" não pode permanecer em branco, por favor selecione uma advertência para ser enviada. @@ -6808,128 +7928,133 @@ Por favor evite tais ações ou outras medidas poderão ser tomadas contra você WndSets - + Move selected set to the top Mover o selecionado para o topo - + Move selected set up Mover o selecionado para cima - + Move selected set down Mover o selecionado para baixo - + Move selected set to the bottom Mover o selecionado para o fundo - + Search by set name, code, or type Procurar por expansão, código ou tipo - + Default order Ordem padrão - + Restore original art priority order Restaurar prioridade da ordem de arte original - + Enable all sets Habilitar todas expansões - + Disable all sets Desabilitar todas expansões - + Enable selected set(s) Habilitar expansão/expansões selecionada(s) - + Disable selected set(s) Desabilitar expansão/expansões selecionada(s) - + Deck Editor Editor de Deck - + Use CTRL+A to select all sets in the view. Use CTRL+A para selecionar todas expansões visíveis. - + Only cards in enabled sets will appear in the card list of the deck editor. Apenas cartas em expansões ativadas irão aparecer na lista de cartas do editor de deck. - + Image priority is decided in the following order: Prioridade da imagem é decidida na seguinte ordem: - + first the CUSTOM Folder (%1), then the Enabled Sets in this dialog (Top to Bottom) %1 is a link to the wiki primeiro a pasta de PERSONALIDADAS (%1), depois as expansões habilitadas neste diálogo (de cima para baixo) - + Card Art Arte da Carta - + How to use custom card art Como usar arte de carta personalizada - + Hints Dicas - + Note Nota - + Sorting by column allows you to find a set while not changing set priority. Classificar por coluna te permite encontrar um set sem mudar a prioridade de sets - + To enable ordering again, click the column header until this message disappears. Para reabilitar a ordenação, clique no cabeçalho da coluna até que esta mensagem desapareça - + Use the current sorting as the set priority instead Usar a classificação atual como a prioridade de sets - + Sorts the set priority using the same column Classificar a prioridade de sets usando a mesma coluna - + + Include online-only (Arena) cards [requires restart] + + + + Manage sets Gerenciar expansões @@ -6937,22 +8062,67 @@ Por favor evite tais ações ou outras medidas poderão ser tomadas contra você ZoneViewWidget - - sort by name - ordenar por nome + + Ungrouped + Não agrupado - - sort by type - ordenar por tipo + + Group by Type + Agrupar por Tipo - + + Group by Mana Value + Agrupar por Valor de Mana + + + + Group by Color + Agrupar por Cor + + + + Unsorted + Não ordenado + + + + Sort by Name + Ordenar por Nome + + + + Sort by Type + Ordenar por Tipo + + + + Sort by Mana Cost + Ordenar por Custo de Mana + + + + Sort by Colors + Ordenar por Cores + + + + Sort by P/T + Ordenar por P/T + + + + Sort by Set + Ordenar por Conjunto + + + shuffle when closing embaralhar quando fechar - + pile view visualização da pilha @@ -6960,7 +8130,7 @@ Por favor evite tais ações ou outras medidas poderão ser tomadas contra você i18n - + English Português do Brasil (Brazilian Portuguese) @@ -6968,12 +8138,12 @@ Por favor evite tais ações ou outras medidas poderão ser tomadas contra você main - + Connect on startup Conectar ao iniciar - + Debug to file Depurar para um arquivo @@ -6981,786 +8151,908 @@ Por favor evite tais ações ou outras medidas poderão ser tomadas contra você shortcutsTab - + Main Window Janela Principal - - + + Deck Editor Editor de Deck - + Game Lobby Saguão de jogo - + Card Counters Marcadores de carta - + Player Counters Marcadores de jogador - + Power and Toughness Poder e Resistência - + Game Phases Etapas de Jogo - + Playing Area Área de jogo - + Move Selected Card Mover Carta Selecionada - + View Visualizar - + Move Top Card Mover A Carta Do Topo - + Move Bottom Card Mover A Carta Do Fundo - + Gameplay Jogabilidade - + Drawing Comprando - + Chat Room Sala de Conversa - + Game Window Janela de Jogo - + Load Deck from Clipboard Carregar deck da área de transferência - + + Replays - + Repetições - + + Tabs + Abas + + + Check for Card Updates... Procurar por atualizações de cartas... - + Connect... Conectar... - + Disconnect Desconectar - + Exit Sair - + Full screen Tela cheia - + Register... Registrar... - + Settings... Configurações... - + Start a Local Game... Iniciar um Jogo Local... - + Watch Replay... Assistir Replay... - - Analyze Deck - Analisar Deck + + Analyze Deck (deckstats.net) + Analyze Deck + - + + Analyze Deck (tappedout.net) + + + + Clear All Filters Limpar todos os FIltros - + Clear Selected Filter Limpar Filtro Selecionado - + Close Fechar - + Remove Card Remover Carta - + Manage Sets... Gerenciar expansões... - + Edit Custom Tokens... Editar fichas customizadas... - - Export Deck - Exportar Deck + + Export Deck (decklist.org) + - + + Export Deck (decklist.xyz) + + + + Add Card Adicionar Carta - + Load Deck... Carregar Deck... - + Load Deck from Clipboard... Carregar deck da área de transferência... - + + Edit Deck in Clipboard, Annotated + + + + + Edit Deck in Clipboard + + + + New Deck Novo Deck - + Open Custom Pictures Folder Abrir pasta de imagens customizadas - + Print Deck... Imprimir Deck... - + Delete Card Deletar carta - - + + Reset Layout Redefinir layout - + Save Deck Salvar Deck - + Save Deck as... Salvar Deck Como... - + Save Deck to Clipboard, Annotated Salvar deck na área de transferência, anotado - + + Save Deck to Clipboard, Annotated (No Set Info) + + + + Save Deck to Clipboard Salvar deck na área de transferência - + + Save Deck to Clipboard (No Set Info) + + + + Load Local Deck... Carregar deck local... - + Load Remote Deck... Carregar deck remoto... - + Set Ready to Start Preparade para começar - + Toggle Sideboard Lock Alternar trava de sideboard - - + + Add Green Counter Adicionar marcador verde - - + + Remove Green Counter Remover marcador verde - - + + Set Green Counters... Definir marcadores verde... - + Add Yellow Counter Adicionar marcador amarelo - + Remove Yellow Counter Remover marcador amarelo - + Set Yellow Counters... Definir marcadores amarelos... - - + + Add Red Counter Adicionar marcador vermelho - - + + Remove Red Counter Remover marcador vermelho - - + + Set Red Counters... Definir marcadores vermelhos - + Add Life Counter Adicionar ponto de vida - + Remove Life Counter Remover ponto de vida - + Set Life Counters... Definir pontos de vida... - + Add White Counter Adicionar marcador branco - + Remove White Counter Remover marcador branco - + Set White Counters... Definir marcadores brancos... - + Add Blue Counter Adicionar marcador azul - + Remove Blue Counter Remover marcador azul - + Set Blue Counters... Definir marcadores azul - + Add Black Counter Adicionar marcador preto - + Remove Black Counter Remover marcador preto - + Set Black Counters... Definir marcadores pretos... - + Add Colorless Counter Adicionar marcador incolor - + Remove Colorless Counter Remover marcador incolor - + Set Colorless Counters... Definir marcadores incolor... - + Add Other Counter Adicionar outro marcador - + Remove Other Counter Remover outro marcador - + Set Other Counters... Definir outros marcadores... - + Add Power (+1/+0) adicionar Poder (+1/+0) - + Remove Power (-1/-0) Remover Poder (-1/-0) - + Move Toughness to Power (+1/-1) Mover Resistência Para Poder (+1/-1) - + Add Toughness (+0/+1) Adicionar Resistência (+0/+1) - + Remove Toughness (-0/-1) Remover Resistência (-0/-1) - + Move Power to Toughness (-1/+1) Mover Poder Para Resistência (-1/+1) - + Add Power and Toughness (+1/+1) Adicionar Poder e Resistência (+1/+1) - + Remove Power and Toughness (-1/-1) Remover Poder e Resistência (-1/-1) - + Set Power and Toughness... Definir poder e resistência... - + Reset Power and Toughness Redefinir poder e resistência - + Untap Desvirar - + Upkeep Manutenção - + Draw Compra - + First Main Phase Primeira Fase Principal - + Start Combat Iniciar Combate - + Attack Atacar - + Block Bloquear - + Damage Dano - + End Combat Terminar Combate - + Second Main Phase Segunda Fase Principal - + End Fim - + Next Phase Próxima Fase - + Next Phase Action Ação de Próxima Etapa - + Next Turn Próximo Turno - + + Hide Card in Reveal Window + Ocultar Carta na Janela de Revelação + + + Tap / Untap Card Virar / Desvirar Carta - + Untap All Desvirar Tudo - + Toggle Untap Alternar modo de desvirar - + Turn Card Over Virar carta para cima - + Peek Card Olhar carta - + Play Card Jogar Carta - + Attach Card... Anexar carta... - + Unattach Card Desanexar carta... - + Clone Card Duplicar Carta - + Create Token... Criar Ficha - + Create All Related Tokens Criar todas as fichas relacionadas - + Create Another Token Criar Outra Ficha - + Set Annotation... Definir anotação - - + + Select All Cards in Zone + Selecionar Todas as Cartas na Zona + + + + Select All Cards in Row + Selecionar Todas as Cartas na Linha + + + + Select All Cards in Column + Selecionar Todas as Cartas na Coluna + + + + Bottom of Library Fundo do Grimório - - - - + + + + Exile Exilar - - - - + + + + Graveyard Cemitério - - + + Hand Mão - - + + Top of Library Topo do Grimório - - - + + + Battlefield, Face Down Campo de batalha, Voltada para baixo - + Battlefield Campo de Batalha - + Library Grimório - + Sideboard Sideboard - + Top Cards of Library Cartas do topo do grimório - + + Bottom Cards of Library + Cartas do Fundo da Biblioteca + + + Close Recent View Fechar visualização recente - - + + Stack Pilha - - + + Graveyard (Multiple) Cemitério (Vários) - - + + Exile (Multiple) Exílio (Vários) - + Stack Until Found - + Empilhar Até Encontrar - + Draw Bottom Card Comprar carta do fundo - + Draw Multiple Cards from Bottom... Comprar várias cartas do fundo... - + Draw Arrow... Desenhar seta... - + Remove Local Arrows Remover setas locais - + Leave Game Sair do jogo - + Concede Conceder - + Roll Dice... Rolar dados... - + Shuffle Library Embaralhar grimório - + + Shuffle Top Cards of Library + + + + + Shuffle Bottom Cards of Library + + + + Mulligan Mulligan - + Draw a Card Comprar uma carta - + Draw Multiple Cards... Comprar várias cartas - + Undo Draw Desfazer compra - + Always Reveal Top Card Sempre revelar a carta do topo do grimório - + Always Look At Top Card Sempre olhar para a carta do topo do grimório - + Rotate View Clockwise Rotacionar visão no sentido horário - + Rotate View Counterclockwise Rotacionar visão no sentido anti-horário - + Unfocus Text Box Desfocar caixa de texto - + Focus Chat Focar no chat - + Clear Chat Limpar chat - + Refresh Atualizar - - Play/Pause - + + Skip Forward + Avançar - + + Skip Backward + Retroceder + + + + Skip Forward by a lot + Avançar Bastante + + + + Skip Backward by a lot + Retroceder Bastante + + + + Play/Pause + Reproduzir/Pausar + + + Toggle Fast Forward - + Ativar/Desativar Avanço Rápido + + + + Visual Deck Storage + Decks Visuais + + + + Deck Storage + Armazenamento de Deck + + + + Server + Servidor + + + + Account + Servidor + + + + Administration + Administração + + + + Logs + Registros \ No newline at end of file diff --git a/cockatrice/translations/cockatrice_ru.ts b/cockatrice/translations/cockatrice_ru.ts index 226bf114d..f3ebed977 100644 --- a/cockatrice/translations/cockatrice_ru.ts +++ b/cockatrice/translations/cockatrice_ru.ts @@ -2,7 +2,7 @@ AbstractCounter - + &Set counter... &Установить жетоны... @@ -10,95 +10,247 @@ AbstractCounterDialog - + Set counter Установить жетоны - + New value for counter '%1': Новое значение жетонов '%1': + + AbstractDlgDeckTextEdit + + + &Refresh + + + + + Parse Set Name and Number (if available) + + + + + AbstractTabDeckEditor + + + Open in new tab + + + + + Are you sure? + + + + + The decklist has been modified. +Do you want to save the changes? + + + + + + + + + + + Error + + + + + Could not open deck at %1 + + + + + Could not save remote deck + + + + + + The deck could not be saved. +Please check that the directory is writable and try again. + + + + + Save deck + + + + + The deck could not be saved. + + + + + There are no cards in your deck to be exported + + + + + No deck was selected to be exported. + + + + + AdminNotesDialog + + + Update Notes + + + + + Admin Notes for %1 + + + + + AllZonesCardAmountWidget + + + Mainboard + + + + + Sideboard + + + AppearanceSettingsPage - + Error Ошибка - + Could not create themes directory at '%1'. Не удалость создать папку для тем в '%1'. - + Theme settings Настройки темы - + Current theme: Текущая тема: - + Open themes folder Открыть папку с темами - + + Menu settings + + + + + Show keyboard shortcuts in right-click menus + + + + Card rendering Отрисовка карт - + Display card names on cards having a picture Отображать название карты поверх изображения - + + Auto-Rotate cards with sideways layout + + + + + Override all card art with personal set preference (Pre-ProviderID change behavior) [Requires Client restart] + + + + + Bump sets that the deck contains cards from to the top in the printing selector + + + + Scale cards on mouse over Увеличивать карты при наведении мыши - + + Use rounded card corners + + + + Minimum overlap percentage of cards on the stack and in vertical hand - + + Maximum initial height for card view window: + + + + + + rows + + + + + Maximum expanded height for card view window: + + + + Hand layout Раскладка руки - + Display hand horizontally (wastes space) Отображать руку горизонтально (требует места) - + Enable left justification Выравнивание по левому краю - + Table grid layout Сетка - + Invert vertical coordinate Инвертировать вертикальные координаты - + Minimum player count for multi-column layout: Минимальное количество игроков для столбчатого расположения: - + Maximum font size for information displayed on cards: Максимальный размер шрифта для текста карт: @@ -106,112 +258,112 @@ BanDialog - + ban &user name Забанить по &Имени игрока - + ban &IP address Забанить по &адресу IP - + ban client I&D Забанить по I&D клиента - + Ban type Тип бана - + &permanent ban &постоянный бан - + &temporary ban &временный бан - + &Days: &Дни - + &Hours: &Часы - + &Minutes: &Минуты - + Duration of the ban Длительность бана - + Please enter the reason for the ban. This is only saved for moderators and cannot be seen by the banned person. Пожалуйста, назовите причину бана. Эта информация сохраняется для модераторов и не будет видна забаненному игроку - + Please enter the reason for the ban that will be visible to the banned person. Пожалуйста, назовите причину бана, которая будет видна забаненному игроку. - + Redact all messages from this user in all rooms Отредактировать все сообщения от этого пользователя во всех комнатах - + &OK &ОК - + &Cancel &Отмена - + Ban user from server Забанить игрока на этом сервере - - - - + + + + Error Ошибка - + You have to select a name-based, IP-based, clientId based, or some combination of the three to place a ban. Необходимо выбрать тип бана: по имени, по адресу IP, по ID клиента или комбинацию этих трех типов. - + You must have a value in the name ban when selecting the name ban checkbox. Необходимо ввести имя игрока при выборе опции бана по имени. - + You must have a value in the ip ban when selecting the ip ban checkbox. Необходимо ввести адрес IP при выборе опции бана по IP. - + You must have a value in the clientid ban when selecting the clientid ban checkbox. Необходимо ввести ID клиента при выборе опции бана по ID. @@ -219,22 +371,22 @@ This is only saved for moderators and cannot be seen by the banned person. BetaReleaseChannel - - Beta Releases - Бета-версии + + Beta + - + No reply received from the release update server. Нет ответа от сервера обновления версий. - + Invalid reply received from the release update server. Неправильный ответ от сервера обновления версий. - + No reply received from the file update server. Нет ответа от сервера обновления файлов. @@ -242,32 +394,32 @@ This is only saved for moderators and cannot be seen by the banned person. CardDatabaseModel - + Name Название - + Sets Издания - + Mana cost Мана-стоимость - + Card type Тип - + P/T Сила/Выносливость: - + Color(s) Цвет(а) @@ -360,325 +512,621 @@ This is only saved for moderators and cannot be seen by the banned person. - CardFrame + CardInfoFrameWidget - + Image - Изображение + - + Description - Описание + - + Both - Все + + + + + View transformation + - CardInfoText + CardInfoPictureWidget - + + View related cards + + + + + Add card to deck + + + + + Mainboard + + + + + Sideboard + + + + + CardInfoTextWidget + + Name: - Название: + - + Related cards: - Связанные карты: + - + Unknown card: - Неизвестная карта: + CardItem - + &Move to &Переместить... - + &Power / toughness &Сила / выносливость + + CardSizeWidget + + + Card Size + + + CardZone - + their hand nominative их руки - + %1's hand nominative рука %1 - + their library look at zone их библиотеку - + %1's library look at zone библиотеку %1 - + of their library top cards of zone, их библиотеки - + of %1's library top cards of zone библиотеки %1 - + their library reveal zone их библиотеку - - - %1's library - reveal zone - библиотеку %1 - - - - their library - shuffle - их библиотеку - - - - %1's library - shuffle - библиотеку %1 - + %1's library + reveal zone + библиотеку %1 + + + + their library + shuffle + их библиотеку + + + + %1's library + shuffle + библиотеку %1 + + + their library nominative их библиотека - + %1's library nominative библиотека %1 - + their graveyard nominative их кладбище - + %1's graveyard nominative кладбище %1 - + their exile nominative их изгнание - + %1's exile nominative изгнание %1 - + their sideboard look at zone их сайдборд - + %1's sideboard look at zone сайдборд %1 - + their sideboard nominative их сайдборд - + %1's sideboard nominative сайдборд %1 + + DeckEditorCardInfoDockWidget + + + Card Info + + + + + DeckEditorDatabaseDisplayWidget + + + Search by card name (or search expressions) + + + + + Add to Deck + + + + + Add to Sideboard + + + + + Select Printing + + + + + Show on EDHREC (Commander) + + + + + Show on EDHREC (Card) + + + + + Show Related cards + + + + + Add card to &maindeck + + + + + Add card to &sideboard + + + + + DeckEditorDeckDockWidget + + + Banner Card + + + + + Select Printing + + + + + Deck + + + + + Deck &name: + + + + + &Comments: + + + + + Hash: + + + + + &Increment number + + + + + &Decrement number + + + + + &Remove row + + + + + Swap card to/from sideboard + + + + + DeckEditorFilterDockWidget + + + Filters + + + + + &Clear all filters + + + + + Delete selected + + + + + DeckEditorMenu + + + &Deck Editor + + + + + &New deck + + + + + &Load deck... + + + + + Load recent deck... + + + + + Clear + + + + + &Save deck + + + + + Save deck &as... + + + + + Load deck from cl&ipboard... + + + + + Edit deck in clipboard + + + + + + Annotated + + + + + + Not Annotated + + + + + Save deck to clipboard + + + + + Annotated (No set info) + + + + + Not Annotated (No set info) + + + + + &Print deck... + + + + + &Send deck to online service + + + + + Create decklist (decklist.org) + + + + + Create decklist (decklist.xyz) + + + + + Analyze deck (deckstats.net) + + + + + Analyze deck (tappedout.net) + + + + + &Close + + + + + DeckEditorPrintingSelectorDockWidget + + + Printing Selector + + + DeckEditorSettingsPage - - + + Update Spoilers Обновить спойлеры - - + + Success Успешно - + Download URLs have been reset. Адреса URL загрузки были сброшены. - + Downloaded card pictures have been reset. Загруженные изображения карт были сброшены. - + Error Ошибка - + One or more downloaded card pictures could not be cleared. Одно или более загруженных изображений карт не может быть удалено. - + Add URL Добавить URL - - + + URL: URL: - - + + Edit URL Изменить URL: - + + Network Cache Size: + + + + + Redirect Cache TTL: + + + + + How long cached redirects for urls are valid for. + + + + + Picture Cache Size: + + + + Add New URL Добавить новый URL - + Remove URL Удалить URL - + + Day(s) + + + + Updating... Обновление... - + Choose path Выбрать путь - + URL Download Priority Приоритет загрузки URL - + Spoilers Спойлеры - + Download Spoilers Automatically Скачивать спойлеры автоматически - + Spoiler Location: Расположение спойлеров: - + Last Change Последние изменения - + Spoilers download automatically on launch Спойлеры скачиваются автоматически при запуске - + Press the button to manually update without relaunching Нажмите, чтобы обновить вручную без перезапуска - + Do not close settings until manual update is complete Не закрывать настройки до завершения ручного обновления - + Download card pictures on the fly Синхронная загрузка изображений карт - + How to add a custom URL Как добавить пользовательский URL - + Delete Downloaded Images Удалить загруженные изображения - + Reset Download URLs Обнулить загруженные URL - - Downloaded images directory size: - - - - + On-disk cache for downloaded pictures - - Picture cache size: - - - - + In-memory cache for pictures not currently on screen @@ -686,16 +1134,218 @@ This is only saved for moderators and cannot be seen by the banned person. DeckListModel - + + Count + + + + + Set + + + + Number Номер - + + Provider ID + + + + Card Название + + DeckLoader + + + Common deck formats (%1) + + + + + All files (*.*) + + + + + DeckPreviewColorIdentityFilterWidget + + + Mode: Exact Match + + + + + Mode: Includes + + + + + DeckPreviewDeckTagsDisplayWidget + + + Edit tags ... + + + + + DeckPreviewTagDialog + + + Deck Tags Manager + + + + + Manage your deck tags. Check or uncheck tags as needed, or add new ones: + + + + + Add a new tag (e.g., Aggro️) + + + + + Add Tag + + + + + Filter tags... + + + + + OK + + + + + Cancel + + + + + Invalid Input + + + + + Tag name cannot be empty! + + + + + Duplicate Tag + + + + + This tag already exists. + + + + + DeckPreviewWidget + + + Banner Card + + + + + Open in deck editor + + + + + Edit Tags + + + + + Rename Deck + + + + + Save Deck to Clipboard + + + + + Annotated + + + + + Annotated (No set info) + + + + + Not Annotated + + + + + Not Annotated (No set info) + + + + + Rename File + + + + + Delete File + + + + + Set Banner Card + + + + + + New name: + + + + + + Error + + + + + Rename failed + + + + + Delete file + + + + + Are you sure you want to delete the selected file? + + + + + Delete failed + + + DeckStatsInterface @@ -713,181 +1363,217 @@ This is only saved for moderators and cannot be seen by the banned person. DeckViewContainer - + Load deck... Загрузить колоду... - + Load remote deck... Загрузить колоду удаленно... - + + Unload deck + Unload deck... + + + + Ready to start &Готов - + + Force start + + + + Sideboard unlocked Сайдборд разблокирован - + Sideboard locked Сайдборд заблокирован - - Load deck - Загрузить колоду - - - + Error Ошибка - + The selected file could not be loaded. Выбранный файл не может быть загружен + + DialogConvertDeckToCodFormat + + + Deck Format Conversion + + + + + You tried to add a tag to a .txt format deck. + Tags can only be added to .cod format decks. + Do you want to convert the deck to the .cod format? + + + + + Remember and automatically apply choice in the future + + + DlgConnect - + Downloading... Загрузка... - + Known Hosts Известные Хосты - + + Delete the currently selected saved server + + + + Refresh the server list with known public servers Обновить список известных публичных серверов - + New Host Новый хост - + Name: Название: - + &Host: &Хост: - + &Port: &Порт: - + Player &name: &Имя пользователя: - + P&assword: П&ароль: - + &Save password &Сохранить пароль - + A&uto connect Присоединяться автоматически - + Automatically connect to the most recent login when Cockatrice opens Автоматически подключится с последним логином при открытии Кокатрис - + If you have any trouble connecting or registering then contact the server staff for help! Если у вас появилась проблема при присоединении или регистрации - свяжитесь с сотрудниками для помощи! - - + + Webpage Веб-страница - + Reset Password Обнулить пароль - + + Forgot password? + + + + &Connect &Подключиться - + Server Сервер - + Login Логин - + Server Contact Соединение с сервером - + Connect to Server Подключиться к серверу - + Server URL URL сервера - + Communication Port Порт связи - + Unique Server Name Уникальное имя сервера - + Connection Warning Предупреждение при соединении - + You need to name your new connection profile. Назовите ваш профиль для соединения. - + Connect Warning Ошибка при соединении - + The player name can't be empty. Необходимо указать имя пользователя. @@ -970,27 +1656,37 @@ This is only saved for moderators and cannot be seen by the banned person.Зрители - + + Starting life total: + + + + + Game setup options + + + + &Clear &Очистить - + Create game Создать игру - + Game information Информация об игре - + Error Ошибка - + Server error. Ошибка сервера. @@ -998,92 +1694,92 @@ This is only saved for moderators and cannot be seen by the banned person. DlgCreateToken - + &Name: &Название: - + Token Фишка - + C&olor: &Цвет: - + white белый - + blue синий - + black черный - + red красный - + green зеленый - + multicolor многоцветный - + colorless бесцветный - + &P/T: &Сила/Выносливость: - + &Annotation: &Примечание: - + &Destroy token when it leaves the table &Уничтожить фишку, когда она покинет поле битвы - + Token data Описание фишки - + Show &all tokens Показать &все фишки - + Show tokens from this &deck Показать фишки из указанной &колоды - + Choose token from list Выбрать фишку из списка - + Create token Создать фишку @@ -1129,6 +1825,24 @@ To remove your current avatar, confirm without choosing a new image. Выбрано не подходящее изображение. + + DlgEditDeckInClipboard + + + Edit deck in clipboard + + + + + Error + + + + + Invalid deck list. + + + DlgEditPassword @@ -1171,93 +1885,93 @@ To remove your current avatar, confirm without choosing a new image. DlgEditTokens - + &Name: &Название - + C&olor: &Цвет - + white белый - + blue синий - + black чёрный - + red красный - + green зелёный - + multicolor мультицветный - + colorless бесцветный - + &P/T: &Сила/Выносливость: - + &Annotation: &Примечание: - + Token data Описание фишки - - + + Add token Добавить фишку - + Remove token Удалить фишку - + Edit custom tokens Редактировать пользовательские фишки - + Please enter the name of the token: Введите имя фишки: - + Error Ошибка - + The chosen name conflicts with an existing card or token. Make sure to enable the 'Token' set in the "Manage sets" dialog to display them correctly. Выбранное имя конфликтует с существующей картой или токеном. @@ -1295,132 +2009,132 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgFilterGames - + no limit без лимита - + 5 minutes 5 минут - + 10 minutes 10 минут - + 30 minutes 30 минут - + 1 hour 1 час - + 2 hours 2 часа - - Show '&buddies only' games - Показывать игры "только для друзей" + + Hide 'buddies only' games + - - Show &full games - Показывать &полные игры + + Hide full games + - - Show games &that have started - Показывать игры &которые уже начались + + Hide games that have started + - - Show &password protected games - Показывать игры с &паролем + + Hide password protected games + - - Hide '&ignored user' games - Прятать игры &игнорируемых пользователей + + Hide 'ignored user' games + - + &Newer than: &Новее чем: - + Game &description: &Описание игры - + &Creator name: Имя &создателя - + General Основные - + &Game types Форматы &игры - + at &least: не &менее - + at &most: не &более - + Maximum player count Максимальное количество игроков - + Restrictions Ограничения - + Show games only if &spectators can watch Показывать игры со &зрителями - + Show spectator password p&rotected games Показывать &игры с паролем для зрителей - + Show only if spectators can ch&at Показывать игры где зрители могут &писать в чат - + Show only if spectators can see &hands Показывать игры где зрители могут видеть &руки игроков - + Spectators Зрители - + Filter games Фильтровать игры @@ -1603,27 +2317,28 @@ Make sure to enable the 'Token' set in the "Manage sets" dia Введенные пароли не совпадают. + + DlgLoadDeck + + + Load Deck + + + DlgLoadDeckFromClipboard - - &Refresh - &Обновить - - - + Load deck from clipboard Загрузить колоду из буфера - - + Error Ошибка - - + Invalid deck list. Неверный деклист. @@ -1631,11 +2346,49 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgLoadRemoteDeck - + Load deck Загрузить колоду + + DlgMoveTopCardsUntil + + + Card name (or search expressions): + + + + + Number of hits: + + + + + Auto play hits + + + + + Put top cards on stack until... + + + + + No cards matching the search expression exists in the card database. Proceed anyways? + + + + + Cockatrice + + + + + Invalid filter + + + DlgRegister @@ -1749,12 +2502,12 @@ Your email will be used to verify your account. DlgSettings - + Unknown Error loading card database Неизвестная ошибка при загрузке базы карт. - + Your card database is invalid. Cockatrice may not function correctly with an invalid database @@ -1768,7 +2521,7 @@ Cockatrice не может корректно работать с испорче Вы хотите сменить расположение файла базы карт? - + Your card database version is too old. This can cause problems loading card information or images @@ -1782,7 +2535,7 @@ Would you like to change your database location setting? Вы хотите сменить расположение файла базы карт? - + Your card database did not finish loading Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues with your cards.xml attached @@ -1791,7 +2544,7 @@ Would you like to change your database location setting? - + File Error loading your card database. Would you like to change your database location setting? @@ -1799,7 +2552,7 @@ Would you like to change your database location setting? Вы хотите сменить расположение файла базы карт? - + Your card database was loaded but contains no cards. Would you like to change your database location setting? @@ -1807,7 +2560,7 @@ Would you like to change your database location setting? Вы хотите сменить расположение файла базы карт? - + Unknown card database load status Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues @@ -1816,59 +2569,59 @@ Would you like to change your database location setting? - - - + + + Error Ошибка - + The path to your deck directory is invalid. Would you like to go back and set the correct path? Ваши колоды отсутствуют в указанной папке. Вернуться и задать правильный путь? - + The path to your card pictures directory is invalid. Would you like to go back and set the correct path? Изображения карт не найдены. Вернуться и задать правильный путь? - + Settings Настройки - + General Основные - + Appearance Вид - + User Interface Интерфейс - + Card Sources - + Chat Чат - + Sound Звук - + Shortcuts Горячие клавиши @@ -1894,40 +2647,40 @@ Would you like to change your database location setting? DlgUpdate - + Current release channel Канал актуального обновления - + Reinstall Переустановить - + Cancel Download Отменить загрузку - + Open Download Page Открыть страницу загрузки - + Check for Client Updates - - - - + + + + Error Ошибка - + Cockatrice was not built with SSL support, therefore you cannot download updates automatically! Please visit the download page to update manually. В Cockatrice нет поддержки SSL и вы не можете обновиться автоматически! @@ -1935,125 +2688,129 @@ Please visit the download page to update manually. Пожалуйста, посетите страницу загрузки для получения обновления. - - Downloading update... - Идет скачивание обновлений... + + Downloading update: %1 + Downloading update... + - + Checking for updates... Проверка обновлений... - + Finished checking for updates Закончена проверка обновлений - + No Update Available Нет доступных обновлений - + Cockatrice is up to date! Установлена последняя версия Cockatrice! - + You are already running the latest version available in the chosen release channel. Уже установлена последняя версия, доступная в выбранной ветке обновлений. - + Current version Текущая версия - + Selected release channel Выбранная ветка обновлений - - + + Update Available Обновление доступно - - + + A new version of Cockatrice is available! Доступна новая версия Cockatrice! - - + + New version Новая версия - - + + Released Выпущена - - + + Changelog Список изменений - + Do you want to update now? Вы хотите обновить сейчас? - - Unfortunately there are no download packages available for your operating system. -You may have to build from source yourself. + + Unfortunately, the automatic updater failed to find a compatible download. +You may have to manually download the new version. + Unfortunately there are no download packages available for your operating system. +You may have to build from source yourself. - - Please check the download page manually and visit the wiki for instructions on compiling. + + Please check the <a href="%1">releases page</a> on our Github and download the build for your system. + Please check the download page manually and visit the wiki for instructions on compiling. - - - + + + Update Error Ошибка при оновлении - + An error occurred while checking for updates: В процессе проверки обновлений произошла ошибка: - + An error occurred while downloading an update: В процессе загрузки обновления произошла ошибка: - + Installing... Установка... - + Cockatrice is unable to open the installer. - + Try to update manually by closing Cockatrice and running the installer. Попробуйте обновить вручную, закрыв Cockatrice и запустив инсталлятор. - + Download location @@ -2071,6 +2828,14 @@ You may have to build from source yourself. Лог + + EdhrecCommanderResponseCommanderDetailsDisplayWidget + + + Salt: + + + FilterBuilder @@ -2082,105 +2847,120 @@ You may have to build from source yourself. GameSelector - - - - - - - - - + + + + + + + + + Error Ошибка - + Please join the appropriate room first. Пожалуйста, сперва войдите в соответствующую комнату. - + Wrong password. Неверный пароль. - + Spectators are not allowed in this game. Зрители не допускаются в эту игру. - + The game is already full. Все места уже заняты! - + The game does not exist any more. Эта игра больше не существует. - + This game is only open to registered users. Доступно только для зарегистрированных. - + This game is only open to its creator's buddies. Доступно только для друзей. - + You are being ignored by the creator of this game. Вы добавлены в игнор-лист данного игрока. - + + Join Game + + + + + Spectate Game + + + + + Game Information + + + + Join game Присоединиться - + Password: Пароль: - + Please join the respective room first. Пожалуйста, сначала присоединитесь к соответствующей комнате. - + &Filter games &Фильтр игр - + C&lear filter О&чистить фильтр - + C&reate &Создать - + &Join &Присоединиться - + J&oin as spectator Присоединиться как &зритель - + Games shown: %1 / %2 - + Games Игры @@ -2188,100 +2968,100 @@ You may have to build from source yourself. GamesModel - + >1 day - + %1%2 hr short age in hours - + new - + %1%2 min short age in minutes - + password пароль - + buddies only только друзья - + reg. users only только для зарегистрированных пользователей - - + + can chat зрители могут комментировать в чате - + see hands видеть руки игроков - + can see hands могут видеть руки - + not allowed не допускаются - + Room Комната - + Age Возраст - + Description Описание - + Creator Создатель - + Type Тип - + Restrictions Ограничения - + Players Количество игроков - + Spectators Зрители @@ -2289,92 +3069,102 @@ You may have to build from source yourself. GeneralSettingsPage - + Reset all paths - + All paths have been reset - - + + Choose path Путь - + Personal settings Настройки пользователя - + Language: Язык: - + Paths (editing disabled in portable mode) - + Paths Пути расположения - + + How to help with translations + + + + Decks directory: Колоды: - + Replays directory: Повторы: - + Pictures directory: Изображения карт: - + Card database: База данных карт: - + Custom database directory: - + Token database: База данных токенов: - + Update channel Обновить канал - + + Check for client updates on startup + + + + Notify if a feature supported by the server is missing in my client Предупреждать, если функции, используемые сервером, отсутствуют в этом клиенте. - + Automatically run Oracle when running a new version of Cockatrice - + Show tips on startup @@ -2382,61 +3172,61 @@ You may have to build from source yourself. MainWindow - - + + The server has reached its maximum user capacity, please check back later. Этот сервер достиг максимального количества пользователей, попробуйте позже. - + There are too many concurrent connections from your address. Слишком много одновременных подключений с Вашего адреса. - + Banned by moderator Забанен модератором - + Expected end time: %1 Ожидаемое время: %1 - + This ban lasts indefinitely. Этот бан не ограничен по времени. - + Scheduled server shutdown. Плановый перерыв в работе сервера. - - + + Invalid username. Неверное имя пользователя. - + You have been logged out due to logging in at another location. Вы вышли из аккаунта, так как был выполнен вход из другого расположения. - + Connection closed Соединение прервано - + The server has terminated your connection. Reason: %1 Ваше подключение было прервано сервером. Причина: %1 - + The server is going to be restarted in %n minute(s). All running games will be lost. Reason for shutdown: %1 @@ -2451,657 +3241,668 @@ Reason for shutdown: %1 Причина перезагрузки: %1 - + Scheduled server shutdown Плановый перерыв в работе сервера - - + + Success Успешно - + Registration accepted. Will now login. Регистрация прошла успешно. Идет соединение. - + Account activation accepted. Will now login. Активация аккаунта прошла успешно. Идет соединение. - + Number of players Количество игроков - + Please enter the number of players. Введите количество игроков. - - + + Player %1 Игрок %1 - + Load replay Загрузить повтор - + About Cockatrice О программе - + Version - + Cockatrice Webpage Страница Cockatrice - + Project Manager: Руководитель проекта: - + Past Project Managers: Предыдущие руководители проекта: - + Developers: Разработчики: - + Our Developers Наши разработчики - + Help Develop! Помочь в разработке! - + Translators: Переводчики: - + Our Translators Наши переводчики - + Help Translate! Помочь с переводом! - + Support: Поддержать проект: - + Report an Issue Сообщить о проблеме - + Troubleshooting Устранение неполадок - + F.A.Q. ЧАВо - - - - - - - - - - - - - - - + + + + + + + + + + + + + - - - + + + + + Error Ошибка - + Server timeout Нет связи с сервером - + Failed Login Не могу подключиться - + Your client seems to be missing features this server requires for connection. Ваш клиент не поддерживает некоторые функции, необходимые для соединения с сервером. - + To update your client, go to 'Help -> Check for Client Updates'. - + Incorrect username or password. Please check your authentication information and try again. Неверное имя пользователя или пароль. Пожалуйста, уточните регистрационные данные и попробуйте снова. - + There is already an active session using this user name. Please close that session first and re-login. Пользователь с таким именем уже подключен. Пожалуйста, закройте это подключение и войдите заново. - - + + You are banned until %1. Вы забанены до %1. - - + + You are banned indefinitely. Вы забанены бессрочно. - + This server requires user registration. Do you want to register now? На этом сервере требуется регистрация. Вы хотите зарегистрироваться сейчас? - + This server requires client IDs. Your client is either failing to generate an ID or you are running a modified client. Please close and reopen your client to try again. - + An internal error has occurred, please close and reopen Cockatrice before trying again. If the error persists, ensure you are running the latest version of the software and if needed contact the software developers. - + Account activation Активация аккаунта - + Your account has not been activated yet. You need to provide the activation token received in the activation email. Ваш аккаунт ещё не активирован. Вам нужно предоставить жетон/код активации, полученный по электронной почте. - + Server Full Сервер заполнен - + Unknown login error: %1 Неизвестная ошибка входа: %1 - - + + This usually means that your client version is out of date, and the server sent a reply your client doesn't understand. Обычно это означает, что Вы используете устаревшую версию клиента, и тот не отвечает запросам сервера. - + Your username must respect these rules: Имя пользователя должно удовлетворять следующим условиям: - + is %1 - %2 characters long длиной в %1 - %2 символов - + can %1 contain lowercase characters %1 может содержать строчные буквы - - - - + + + + NOT НЕ - + can %1 contain uppercase characters %1 может содержать заглавные буквы - + can %1 contain numeric characters %1 может содержать цифры - + can contain the following punctuation: %1 Может содержать следующие символы: %1 - + first character can %1 be a punctuation mark Первый знак %1 может быть знаком пунктуации - + no unacceptable language as specified by these server rules: note that the following lines will not be translated - + can not contain any of the following words: %1 не может содержать следующие слова: %1 - + can not match any of the following expressions: %1 не может соответствовать следующим выражениям: %1 - + You may only use A-Z, a-z, 0-9, _, ., and - in your username. Имя пользователя может включать только следующие символы: A-Z, a-z, 0-9, "_", "." и "-" - - - - - - + + + + + + Registration denied Запрос на регистрацию отклонен - + Registration is currently disabled on this server Регистрация на данном сервере в настоящий момент недоступна - + There is already an existing account with the same user name. Аккаунт с таким именем уже существует. - + It's mandatory to specify a valid email address when registering. При регистрации необходимо указать действующий e-mail. - + It appears you are attempting to register a new account on this server yet you already have an account registered with the email provided. This server restricts the number of accounts a user can register per address. Please contact the server operator for further assistance or to obtain your credential information. - + Password too short. Слишком короткий пароль. - + Registration failed for a technical problem on the server. Регистрация не удалась из-за технических неполадок на сервере. - + Unknown registration error: %1 Неизвестная ошибка при регистрации: %1 - + Account activation failed Не удалось активировать аккаунт - + Socket error: %1 Ошибка сокета: %1 - + You are trying to connect to an obsolete server. Please downgrade your Cockatrice version or connect to a suitable server. Local version is %1, remote version is %2. Вы пытаетесь подключиться к несуществующему серверу. Пожалуйста, обновите Cockatrice или выберите другой сервер. Локальная версия %1, удаленная версия %2. - + Your Cockatrice client is obsolete. Please update your Cockatrice version. Local version is %1, remote version is %2. Ваш клиент Cockatrice устарел. Пожалуйста, обновите Cockatrice. Локальная версия %1, удаленная версия %2. - + Connecting to %1... Подключение к %1... - + Registering to %1 as %2... Регистрация на %1 как %2... - + Disconnected Подключение прервано - + Connected, logging in at %1 Соединение установлено, выполняется вход на %1 - - - + + + Requesting forgotten password to %1 as %2... - + &Connect... Подключение... &С - + &Disconnect П&рервать подключение - + Start &local game... &Начать локальную игру... - + &Watch replay... &Смотреть повтор... - - &Deck editor - Редактор &колод - - - + &Full screen Полный экран &F - + &Register to server... &Регистрация на сервере... - + &Restore password... - + &Settings... &Настройки - + &Exit &Выход - + A&ctions &Действия - + &Cockatrice &Cockatrice - + C&ard Database &База карт - + &Manage sets... - + Edit custom &tokens... - + Open custom image folder Открыть папку с изображениями - + Open custom sets folder Открыть папку с изданиями - + Add custom sets/cards Добавить издания/карты - + + Reload card database + + + + + Tabs + + + + &Help &Справка - + &About Cockatrice О про&грамме - + &Tip of the Day - + Check for Client Updates - + Check for Card Updates... - + View &Debug Log - + + Open Settings Folder + + + + Show/Hide - + New Version - + Congratulations on updating to Cockatrice %1! Oracle will now launch to update your card database. - + Cockatrice installed - + Congratulations on installing Cockatrice %1! Oracle will now launch to install the initial card database. - + Card database База карт - + Cockatrice is unable to load the card database. Do you want to update your card database now? If unsure or first time user, choose "Yes" Cockatrice не может загрузить базу карт. Обновить вашу базу карт сейчас? Если Вы не уверены, или впервые запустили программу, нажмите "Да" - - + + Yes Да - - + + No Нет - + Open settings Открыть настройки - + New sets found Найдены новые издания - + %n new set(s) found in the card database Set code(s): %1 Do you want to enable it/them? - + View sets Просмотреть издания - + Welcome Добро пожаловать - + Hi! It seems like you're running this version of Cockatrice for the first time. All the sets in the card database have been enabled. Read more about changing the set order or disabling specific sets and consequent effects in the "Manage Sets" dialog. - - + + Information Информация - + A card database update is already running. Обновление базы карт уже идет. - + Unable to run the card database updater: Не удалось запустить обновление базы карт: - - failed to start. - ошибка при запуске. + + Failed to start. The file might be missing, or permissions might be incorrect. + - - crashed. - прервано. + + The process crashed some time after starting successfully. + - - timed out. - превышен лимит ожидания. + + Timed out. The process took too long to respond. The last waitFor...() function timed out. + - - write error. - ошибка записи. + + An error occurred when attempting to write to the process. For example, the process may not be running, or it may have closed its input channel. + - - read error. - ошибка чтения. + + An error occurred when attempting to read from the process. For example, the process may not be running. + - - unknown error. - неизвестная ошибка. + + Unknown error occurred. + - - The card database updater exited with an error: %1 - Обновление базы карт прервано с ошибкой: %1 + + The card database updater exited with an error: +%1 + - + This server supports additional features that your client doesn't have. This is most likely not a problem, but this message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -3112,55 +3913,55 @@ To update your client, go to Help -> Check for Updates. Чтобы обновить клиент, перейдите в Справка-> Проверка обновлений. - - - - - + + + + + Load sets/cards Загрузить издания/карты - + Selected file cannot be found. Выбранный файл не найден. - + You can only import XML databases at this time. На данный момент вы только можете предостовлять базы данных в формате XML. - + The new sets/cards have been added successfully. Cockatrice will now reload the card database. Новые издания/карты успешно добавлены. Сейчас Cockatrice перезагрузит базу карт. - + Sets/cards failed to import. Не удалось импортировать издания/карты. - - - + + + Reset Password - + Your password has been reset successfully, you can now log in using the new credentials. - + Failed to reset user account password, please contact the server operator to reset your password. Невозможно сбросить пароль. Пожалуйста, свяжитесь с оператором сервера. - + Activation request received, please check your email for an activation token. Запрос активации получен. Вам отправлен код активации. Пожалуйста, проверьте свой e-mail. @@ -3434,179 +4235,190 @@ Cockatrice will now reload the card database. - %1 is looking at the top %3 card(s) %2. + %1 is looking at the %4 %3 card(s) %2. + %1 is looking at the top %3 card(s) %2. top card for singular, top %3 cards for plural - + + bottom + + + + + top + + + + %1 turns %2 face-down. - + %1 turns %2 face-up. - + The game has been closed. Игра закрыта. - + The game has started. Игра началась. - + %1 has joined the game. %1 присоединился к игре. - + %1 is now watching the game. %1 вошел как зритель. - + You have been kicked out of the game. Вы были отключены от игры - + %1 has left the game (%2). - + %1 is not watching the game any more (%2). - + %1 is not ready to start the game any more. %1 пока не готов начать игру. - + %1 shuffles their deck and draws a new hand of %2 card(s). - + %1 shuffles their deck and draws a new hand. - + You are watching a replay of game #%1. Вы просматриваете запись игры #%1. - + %1 is ready to start the game. %1 готов начать игру. - + cards an unknown amount of cards - + %1 card(s) a card for singular, %1 cards for plural - + %1 lends %2 to %3. - + %1 reveals %2 to %3. %1 открывает %2 для %3. - + %1 reveals %2. %1 открывает %2. - + %1 randomly reveals %2%3 to %4. %1 случайным образом открывает %2%3 для %4. - + %1 randomly reveals %2%3. %1 случайным образом открывает %2%3. - + %1 peeks at face down card #%2. %1 указывает на перевернутую рубашкой вверх карту #%2. - + %1 peeks at face down card #%2: %3. %1 указывает на перевернутую рубашкой вверх карту #%2: %3. - + %1 reveals %2%3 to %4. %1 открывает %2%3 для %4. - + %1 reveals %2%3. %1 открывает %2%3. - + %1 reversed turn order, now it's %2. - + reversed - + normal - + Heads Орел - + Tails Решка - + %1 flipped a coin. It landed as %2. %1 подбросил монету. Выпал(а) %2. - + %1 rolls a %2 with a %3-sided die. %1 выбросил %2 на %3-гранном кубике. - + %1 flips %2 coins. There are %3 heads and %4 tails. - + %1 rolls a %2-sided dice %3 times: %4. @@ -3726,17 +4538,17 @@ Cockatrice will now reload the card database. - + %1 unattaches %2. %1 открепляет %2. - + %1 undoes their last draw. %1 отменяет последние взятия. - + %1 undoes their last draw (%2). %1 отменяет последние взятия (%2). @@ -3744,110 +4556,110 @@ Cockatrice will now reload the card database. MessagesSettingsPage - + Word1 Word2 Word3 - + Add New Message - + Edit Message - + Remove Message - + Add message Добавить сообщение - - + + Message: Сообщение: - + Edit message - + Chat settings Настройки чата - + Custom alert words Пользовательские предупреждения - + Enable chat mentions Включить упоминания в чате - + Enable mention completer Включить автозавершение упоминаний - + In-game message macros Макросы сообщений игрового чата - + How to use in-game message macros - + Ignore chat room messages sent by unregistered users Не уведомлять о сообщениях в чате от незарегистрированных пользователей - + Ignore private messages sent by unregistered users Не уведомлять о личных сообщениях от незарегистрированных пользователей - - + + Invert text color Инвертировать цвет текста - + Enable desktop notifications for private messages Включить оповещения о личных сообщениях - + Enable desktop notification for mentions Включить оповещения об упоминаниях - + Enable room message history on join Отображать историю сообщений при присоединении к комнате. - - + + (Color is hexadecimal) (Шестнадцатеричный цвет) - + Separate words with a space, alphanumeric characters only Разделять слова пробелом, только для букв и цифр @@ -3905,6 +4717,24 @@ Cockatrice will now reload the card database. + + OverlapControlWidget + + + Cards to overlap: + + + + + Overlap percentage: + + + + + Overlap direction: + + + Phase @@ -3971,57 +4801,57 @@ Cockatrice will now reload the card database. PhasesToolbar - + Untap step Шаг разворота - + Upkeep step Шаг поддержки - + Draw step Шаг взятия карты - + First main phase Первая главная фаза - + Beginning of combat step Шаг начала боя - + Declare attackers step Шаг назначения атакующих - + Declare blockers step Шаг назначения блокирующих - + Combat damage step Шаг нанесения повреждений - + End of combat step Шаг завершения боя - + Second main phase Вторая главная фаза - + End of turn step Заключительный шаг @@ -4029,7 +4859,7 @@ Cockatrice will now reload the card database. PictureLoader - + en code for scryfall's language property, not available for all languages ru @@ -4038,647 +4868,781 @@ Cockatrice will now reload the card database. Player - + Reveal top cards of library Показать верхние карты библиотеки - - - - - - - - - + + + + + + + + + + + + Number of cards: (max. %1) Количество карт: (макс. %1) - + &View graveyard &Посмотреть кладбище - + &View exile По&смотреть карты в изгнании - + Player "%1" Игрок "%1" - - - - + + + + &Graveyard &Кладбище - - - - + + + + &Exile &Изгнание - + &Move hand to... &Поместить руку.. - - - + + + &Top of library &на верх библиотеки - - - + + + &Bottom of library &вниз библиотеки - + &Move graveyard to... &Поместить карты кладбища.. - - - - + + + + &Hand Р&ука - + &Move exile to... &Поместить карты из изгнания.. - + &View library Просмортеть &библиотеку - + &View hand - + View &top cards of library... Посмтореть верхние карт&ы... - + Reveal &library to... Показать &библиотеку.. - + &Always reveal top card &Всегда показывать верхнюю карту - + &View sideboard Просмотреть &сайд - + &Draw card В&зять карту - + D&raw cards... Вз&ять карты... - + &Undo last draw &Отменить последнее взятие - + Take &mulligan Взять стра&ховку - - &Shuffle - Переме&шать - - - + Play top card &face down Разыграть верхнюю карту &рубашкой вверх - + Move top card to grave&yard - + Move top card to e&xile - + Move top cards to &graveyard... Поместить верхние карты на кладби&ще... - + Move top cards to &exile... Поместить верхние карты в и&згнание... - + Put top card on &bottom Поместить верхн&юю карту на дно - + + View bottom cards of library... + + + + Lend library to... - + + Shuffle + + + + Put top cards on stack &until... Take top cards &until... - + + Shuffle top cards... + + + + + Shuffle bottom cards... + + + + &Reveal hand to... Показать руку игроку.. &R - + Reveal r&andom card to... Показать случайную карту игроку... - + Reveal random card to... - + &Sideboard &Сайд - + &Library &Библиотека - + &Counters &Жетоны - + &Untap all permanents &Развернуть все перманенты - + R&oll die... Бросить &кубик... - + &Create token... Создать &фишку... - + C&reate another token Создать &еще одну фишку - + Cr&eate predefined token Создать готовый токен - - + + &All players &Все игроки - + S&ay Ска&зать - + + &Select All + + + + + S&elect Row + + + + + S&elect Column + + + + &Play Разыграть - + &Hide Скрыть - + Play &Face Down Разыграть рубашкой вверх - + &Tap / Untap Turn sideways or back again - + Toggle &normal untapping Переключить &разворот как обычно - + T&urn Over Turn face up/face down - + &Peek at card face Посмотреть &лицевую сторону - + &Clone &Копировать - + Attac&h to card... При&соединить - + Unattac&h &Открепить - + &Draw arrow... &Нарисовать стрелку - + &Increase power Увеличить силу - + &Decrease power Уменьшить силу - + I&ncrease toughness Увеличить защиту - + D&ecrease toughness Уменьшить защиту - + In&crease power and toughness У&величить силу и защиту - + Dec&rease power and toughness У&меньшить силу и защиту - + Increase power and decrease toughness - + Decrease power and increase toughness - + Set &power and toughness... Установить силу и защиту - + Reset p&ower and toughness - + &Set annotation... Добавить пометку - + Red Красный - + Yellow Желтый - + Green Зеленый - + &Add counter (%1) &Добавить жетон (%1) - + &Remove counter (%1) &Удалить жетон (%1) - + &Set counters (%1)... &Установить жетоны (%1)... - + &Top of library in random order - + X cards from the top of library... - + &Bottom of library in random order - + View top cards of library Просмотр верхних карт - + + View bottom cards of library + + + + + Shuffle top cards of library + + + + + Shuffle bottom cards of library + + + + Which position should this card be placed: - + (max. %1) - + Draw hand - + 0 and lower are in comparison to current hand size - + Draw cards Взять карты - - + + Number: Количество: - + Move top cards to grave Поместить верхние карты на кладбище - + Reveal &top cards to... - + &Top of library... - + &Bottom of library... - + &Always look at top card - + &Open deck in deck editor - + &Play top card - + &Draw bottom card - + D&raw bottom cards... - + &Play bottom card - + Play bottom card &face down - + Move bottom card to grave&yard - + Move bottom card to e&xile - + Move bottom cards to &graveyard... - + Move bottom cards to &exile... - + Put bottom card on &top - + Selec&ted cards - + Move top cards to exile Поместить верхние карты в изгнание - + Move bottom cards to grave - + Move bottom cards to exile - + Draw bottom cards - - + + C&reate another %1 token - + Create tokens - + Place card X cards from top of library - + Change power/toughness - + Change stats to: - + Set annotation Пометка - + Please enter the new annotation: Введите текст: - + Set counters Установить жетоны - + Re&veal to... - + View related cards - + Token: Код: - + All tokens Все фишки + + PrintingSelector + + + Display Navigation Buttons + + + + + <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. + + + + + PrintingSelectorCardOverlayWidget + + + Preference + + + + + Pin Printing + + + + + Unpin Printing + + + + + Show Related cards + + + + + PrintingSelectorCardSearchWidget + + + Search by set name or set code + + + + + PrintingSelectorCardSelectionWidget + + + Previous Card in Deck + + + + + Next Card in Deck + + + + + PrintingSelectorCardSortingWidget + + + Alphabetical + + + + + Preference + + + + + Release Date + + + + + + Descending + + + + + Ascending + + + QMenuBar - + Services Сервисы - + Hide %1 Скрыть %1 - + Hide Others Скрыть остальные - + Show All Показать все - + Preferences... Параметры... - + Quit %1 Выйти %1 - + About %1 О %1 @@ -4686,38 +5650,32 @@ Cockatrice will now reload the card database. QObject - + Cockatrice card database (*.xml) База карт Cockatrice (*.xml) - - + All files (*.*) Все файлы (*.*) - + Cockatrice replays (*.cor) Записи игр Cockatrice (*.cor) - - Common deck formats (*.cod *.dec *.dek *.txt *.mwDeck) - - - - + Maindeck - + Sideboard - + Tokens @@ -4725,92 +5683,92 @@ Cockatrice will now reload the card database. QPlatformTheme - + OK - + Save - + Save All - + Open - + &Yes - + Yes to &All - + &No - + N&o to All - + Abort - + Retry - + Ignore - + Close - + Cancel - + Discard - + Help - + Apply - + Reset - + Restore Defaults @@ -4818,17 +5776,17 @@ Cockatrice will now reload the card database. RemoteDeckList_TreeModel - + Name Название - + ID ID - + Upload time Дата загрузки @@ -4836,32 +5794,32 @@ Cockatrice will now reload the card database. RemoteReplayList_TreeModel - + ID ID - + Name Имя - + Players Игроки - + Keep Хранение - + Time started Время начала - + Duration (sec) Длительность (сек) @@ -4907,32 +5865,32 @@ Cockatrice will now reload the card database. SequenceEdit - + Choose an action from the table - + Hit the key/combination of keys you want to set for this action - - Shortcut already in use - - - - + Invalid key - + + Shortcut already in use by: + + + + Clear - + Restore default @@ -4968,72 +5926,80 @@ Cockatrice will now reload the card database. ShortcutSettingsPage - - + + Restore all default shortcuts - + Do you really want to restore all default shortcuts? - + Clear all default shortcuts - + Do you really want to clear all shortcuts? - - Action - - - - - Shortcut - - - - + Section: - + Action: - + Shortcut: - + How to set custom shortcuts - + Clear all shortcuts + + + Search by shortcut name + + + + + ShortcutTreeView + + + Action + + + + + Shortcut + + ShortcutsSettings - + Your configuration file contained invalid shortcuts. Please check your shortcut settings! - + The following shortcuts have been set to default: @@ -5042,17 +6008,17 @@ Please check your shortcut settings! ShutdownDialog - + &Reason for shutdown: &Причина отключения: - + &Time until shutdown (minutes): &Время до отключения (в минутах): - + Shut down server Отключение сервера @@ -5060,27 +6026,27 @@ Please check your shortcut settings! SoundSettingsPage - + Enable &sounds Включить &звуки - + Current sounds theme: Звуковая тема: - + Test system sound engine Протестировать системный звук - + Sound settings Настройки звука - + Master volume Громкости @@ -5088,48 +6054,48 @@ Please check your shortcut settings! SpoilerBackgroundUpdater - + Spoilers season has ended - + Deleting spoiler.xml. Please run Oracle - - + + Spoilers download failed - + No internet connection - + Error - + Spoilers already up to date - + No new spoilers added - + Spoilers have been updated! - + Last change: @@ -5137,638 +6103,594 @@ Please check your shortcut settings! StableReleaseChannel - - Stable Releases + + Default - + No reply received from the release update server. Сервер обновлений версии не отвечает. - + Invalid reply received from the release update server. Ошибка отклика сервера обновлений версии. - + No reply received from the tag update server. Сервер обновлений меток не отвечает. - + Invalid reply received from the tag update server. Ошибка отклика сервера обновлений меток. + + TabAccount + + + Add to Buddy List + + + + + Add to Ignore List + + + + + Account + + + TabAdmin - + Administration Управление - + Update server &message Обновить сооб&щения сервера - + &Shut down server &Отключение сервера - + &Reload configuration &Обновить конфигурацию - + Server administration functions Функции администрирования сервера - + + Server moderator functions + + + + + Replay ID + + + + + Grant Replay Access + + + + + Username to Activate + + + + + Force Activate User + + + + &Unlock functions &Разблокировать функции - + &Lock functions &Заблокировать функции + + + + Success + + + + + Replay access granted + + + + + + + + + Error + + + + + Unable to grant replay access. Replay ID invalid + + + + + Unable to grant replay access. Internal error + + + + + User successfully activated + + + + + Unable to activate user. Username invalid + + + + + Unable to activate user. User already active + + + + + Unable to activate user. Internal error + + TabDeckEditor - - Search by card name (or search expressions) - Search by card name - - - - - Add to Deck - - - - - Add to Sideboard - - - - - Show Related cards - - - - - &Clear all filters - Очистить все фильтры - - - - Delete selected - Удалить выбранное - - - - Deck &name: - Имя колоды: - - - - &Comments: - Комментарии: - - - - Hash: - Хэш: - - - - &New deck - Новая колода - - - - &Load deck... - Загрузить колоду... - - - - &Save deck - Сохранить колоду - - - - Save deck &as... - Сохранить колоду как... - - - - Load deck from cl&ipboard... - Загрузить колоду из буфера... - - - - Save deck to clipboard - - - - - Annotated - - - - - Not Annotated - - - - - &Print deck... - Распечатать колоду... - - - - &Send deck to online service - - - - - Create decklist (decklist.org) - - - - - Analyze deck (deckstats.net) - - - - - Analyze deck (tappedout.net) - - - - - &Close - Закрыть - - - - Add card to &maindeck - Добавить карту в основную колоду - - - - Add card to &sideboard - Добавить карту в сайдборд - - - - &Remove row - Удалить строку - - - - &Increment number - Увеличить количество - - - - &Decrement number - Уменьшить количество - - - - &Deck Editor - Редактор колод - - - - + Card Info Информация о карте - - + Deck Колода - - + Filters Фильтры - + &View Вид - - - + + Printing + + + + + + + Visible Видимый - - - + + + + Floating Плавающий - + Reset layout Сбросить слой - + Deck: %1 Колода: %1 - - - Are you sure? - Вы уверены? - - - - The decklist has been modified. -Do you want to save the changes? - Деклист был изменен. -Вы хотите сохранить изменения? - - - - Load deck - Загрузить колоду - - - - - - - - - Error - Ошибка - - - - The deck could not be saved. - Колода не может быть сохранена. - - - - Could not save remote deck - - - - - - The deck could not be saved. -Please check that the directory is writable and try again. - Колода не может быть сохранена. -Пожалуйста, проверьте расположение файла и попробуйте снова. - - - - Save deck - Сохранить колоду - - - - There are no cards in your deck to be exported - - - - - No deck was selected to be saved. - Для сохранения не выбрано ни одной колоды - TabDeckStorage - - Deck storage - Хранилище колод - - - + Local file system Локальная файловая система - + Server deck storage Хранилище колод на сервере - - + + Open in deck editor Открыть колоду в редакторе - + + Rename deck or folder + + + + Upload deck Загрузить колоду в хранилище - + Download deck Загрузить колоду из хранилища - - + + + + New folder Новая папка - - + + Delete Удалить - - + + Open decks folder + + + + + Rename local folder + + + + + Rename local file + + + + + New name: + + + + + + + Error - + + Rename failed + + + + + Invalid deck file - + Enter deck name Введите название колоды - + This decklist does not have a name. Please enter a name: У этой колоды нет названия. Пожалуйста, введите имя: - + Unnamed deck Безымянная колода - + Failed to upload deck to server - + Delete local file Удалить файл - - - - Are you sure you want to delete "%1"? - Вы действительно хотите удалить "%1"? + + Are you sure you want to delete the selected files? + - + + Delete remote decks + + + + + Are you sure you want to delete the selected decks? + + + + + Name of new folder: Название новой папки: - - Delete remote folder - Удалить папку + + Deck Storage + + + + + TabDeckStorageVisual + + + Visual Deck Storage + - - Delete remote deck - Удалить колоду + + Error + + + + + Could not open deck at %1 + + + + + TabEdhRec + + + EDHREC: + TabGame - - - + + + Replay Запись игры - - + + Game Игра - - + + Player List Список игроков - - + + Card Info Информация о карте - - + + Messages Сообщения - - + + Replay Timeline Полоса прокрутки записи - + &Phases &Фазы - + &Game &Игра - + Next &phase Следующая &фаза - + Next phase with &action - + Next &turn Следующий &ход - + Reverse turn order - + &Remove all local arrows &Удалить все указатели - + Rotate View Cl&ockwise Повернуть вид по часовой стрелке - + Rotate View Co&unterclockwise Повернуть вид против часовой стрелки - + Game &information Информация &об игре - + + Un&concede + + + + &Concede &Сдаться... - + &Leave game Покинуть и&гру - + C&lose replay З&акрыть повтор - + &Focus Chat - + &Say: Ска&зать: - + &View Вид - - - - + + + + Visible Видимый - - - - + + + + Floating Плавающий - + Reset layout Сбросить слой - + Concede Сдаться - + Are you sure you want to concede this game? Вы точно хотите сдаться? - + Unconcede - + You have already conceded. Do you want to return to this game? - + Leave game Покинуть игру - + Are you sure you want to leave this game? Вы уверены, что хотите уйти? - + You are flooding the game. Please wait a couple of seconds. Кажется, Вы нафлудили. Пожалуйста, подождите пару секунд. - + A player has joined game #%1 - + %1 has joined the game - + kicked by game host or moderator - + player left the game игрок покинул игру - + player disconnected from server игрок отсоединился от сервера - + reason unknown причина неизвестна - + You have been kicked out of the game. Вы были отключены от игры @@ -5781,153 +6703,153 @@ Please enter a name: Логи - - - + + + Time;SenderName;SenderIP;Message;TargetID;TargetName Время;ИмяОтправителя;IPОтправителя;Сообщение;IDПолучателя;ИмяПолучателя - + Room Logs Логи Комнаты - + Game Logs Логи Игры - + Chat Logs Логи Чата - - + + Error Ошибка - + You must select at least one filter. Вы должны выбрать хотя бы один фильтр - + You have to select a valid number of days to locate. Вы должны указать верное количество дней для размещения. - + Username: Имя пользователя: - + IP Address: IP адрес - + Game Name: Имя игры: - + GameID: ID Игры: - + Message: Сообщение: - + Main Room Основная комната - + Game Room Игровая комната - + Private Chat Приватный чат - + Past X Days: Последние X Дней: - + Today Сегодня - + Last Hour За последний час - + Maximum Results: Максимальный результат: - + At least one filter is required. The more information you put in, the more specific your results will be. Должен быть как минимум один фильтр. Чем больше информации Вы укажете, тем точнее будет результат. - + Get User Logs Получить логи пользователя - + Clear Filters Очистить фильтры - + Filters Фильтры - + Log Locations Размещение логов - + Date Range Интервал дат - + Maximum Results Максимальный результат - - + + Message History История сообщений - + Failed to collect message history information. Не удалось собрать историю сообщений. - + There are no messages for the selected filters. Нет сообщений для указанных фильтров. @@ -5950,22 +6872,22 @@ The more information you put in, the more specific your results will be.%1 - Приватный чат - + This user is ignoring you, they cannot see your messages in main chat and you cannot join their games. Это пользователь добавил Вас в игнор-лист и не видит ваши сообщения в главном чате, а Вы не можете присоединяться к созданным им (ею) играм. - + Private message from Личное сообщение от - + %1 has left the server. %1 отключился. - + %1 has joined the server. %1 зашел на сервер. @@ -5973,117 +6895,153 @@ The more information you put in, the more specific your results will be. TabReplays - - Game replays - Повторы игр - - - + Local file system Локальная файловая система - + Server replay storage Хранилище записей игр на сервере - - + + Watch replay Посмотреть повтор - - + + Rename + + + + + + New folder + + + + + Delete Удалить - + + Open replays folder + + + + Download replay Скачать запись игры - + Toggle expiration lock Переключить блокировку срока - + + Rename local folder + + + + + Rename local file + + + + + New name: + + + + + Error + + + + + Rename failed + + + + + Name of new folder: + + + + Delete local file Удалить локальный файл - - Are you sure you want to delete "%1"? - Вы действительно хотите удалить "%1"? - - - - Downloading Replays - Идет скачивание записей игр... - - - - Folder download is not yet supported. Please download replays individually. + + Are you sure you want to delete the selected files? - + + Are you sure you want to delete the selected replays? + + + + Delete remote replay Удалить запись игры - - Are you sure you want to delete the replay of game %1? - Вы действительно хотите удалить запись игры "%1"? + + Game Replays + TabRoom - + &Say: &Сказать: - + Chat Чат - + &Room &Комната - + &Leave room &Покинуть комнату - + &Clear chat & Обновить чат - + Chat Settings... Установки чата - + mentioned you. упомянул(а) Вас. - + Click to view Нажмите для просмотра - + You are flooding the chat. Please wait a couple of seconds. Кажется, Вы нафлудили. Пожалуйста, подождите пару секунд. @@ -6096,30 +7054,30 @@ The more information you put in, the more specific your results will be.Сервер - - - - + + + + Error Ошибка - + Failed to join the server room: it doesn't exist on the server. - + The server thinks you are in the server room but your client is unable to display it. Try restarting your client. - + You do not have the required permission to join this server room. - + Failed to join the server room due to an unknown error: %1. @@ -6127,32 +7085,72 @@ The more information you put in, the more specific your results will be. TabSupervisor - + + Deck Editor + + + + + &Visual Deck Storage + + + + + Server + + + + + Account + + + + + Deck Storage + + + + + Game Replays + + + + + Administration + + + + + Logs + + + + Are you sure? Вы уверены? - + There are still open games. Are you sure you want to quit? У вас все еще есть активные игры. Уверены, что хотите выйти? - + Click to view - + Your buddy %1 has signed on! - + Unknown Event Неопознанное событие - + The server has sent you a message that your client does not understand. This message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -6163,62 +7161,44 @@ To update your client, go to Help -> Check for Updates. Чтобы обновить клиент, перейдите в Справка-> Проверка обновлений. - + Idle Timeout Исчерпано время бездействия - + You are about to be logged out due to inactivity. Вы были отключены из-за отсутствия активности. - + Promotion Повышение - + You have been promoted. Please log out and back in for changes to take effect. - + Warned Предупрежден - + You have received a warning due to %1. Please refrain from engaging in this activity or further actions may be taken against you. If you have any questions, please private message a moderator. Вы получили предупреждение по причине %1. Пожалуйста, воздержитесь от подобных действий, иначе в ваш адрес будут применены дальнейшие меры. При возникновении каких-либо вопросов напишите личное сообщение любому модератору. - + You have received the following message from the server. (custom messages like these could be untranslated) Вы получили сообщение от сервера. (сообщения вроде этого могут быть непереведены) - - TabUserLists - - - Account - Учетная запись - - - - Add to Buddy List - Добавить в список друзей - - - - Add to Ignore List - Добавить в игнор-лист - - TappedOutInterface @@ -6302,173 +7282,184 @@ Please refrain from engaging in this activity or further actions may be taken ag UserContextMenu - + User &details &Детали пользователя - + Private &chat &Приватный чат - + Show this user's &games Показать &игры этого пользователя - + Add to &buddy list Добавить в &список друзей - + Remove from &buddy list Удалить из &списка друзей - + Add to &ignore list Добавить в &игнор-лист - + Remove from &ignore list Удалить из &игнор-листа - + Kick from &game Выкинуть из &игры - + Warn user Дать предупреждение пользователю - + View user's war&n history Просмотреть &историю предупреждений пользователя - + Ban from &server Забанить на &сервере - + View user's &ban history Просмотреть &историю банов пользователя - + &Promote user to moderator &Повысить пользователя до модератора - + Dem&ote user from moderator &Разжаловать пользователя из модератора - + Promote user to &judge - + Demote user from judge - + + View admin notes + + + + %1's games Игры %1 - - - + + + Ban History История банов - + Ban Time;Moderator;Ban Length;Ban Reason;Visible Reason ВремяБана;Модератор;ПродолжительностьБана;ПричинаБана;ВидимаяПричина - + User has never been banned. Пользователя никогда не банили. - + Failed to collect ban information. - - - + + + Warning History История предупреждений - + Warning Time;Moderator;User Name;Reason ВремяПредупреждения;Модератор;ИмяПольозователя;Причина - + User has never been warned. Пользователя никогда не предупреждали. - + Failed to collect warning information. - - + + Failed to get admin notes. + + + + + Success Успешно - + Successfully promoted user. Пользователь успешно повышен. - + Successfully demoted user. Пользователь успешно понижен. - - + + + Failed Не удалось - + Failed to promote user. Не удалось повысить пользователя. - + Failed to demote user. Не удалось понизить пользователя. - + Copy hash to clipboard - + Remove this user's messages @@ -6476,173 +7467,173 @@ Please refrain from engaging in this activity or further actions may be taken ag UserInfoBox - + Location: Местонахождение: - + Account Age: Возраст учетной записи: - + Edit Редактировать - + Change password Сменить пароль - + Change avatar Сменить аватар - + Administrator Администратор - + Moderator Модератор - + Registered user Зарегистрированный пользователь - - + + Unregistered user Рандом - + Judge - + Unknown Неизвестный - + The entered password does not match your account. - - - + + + Information Информация - + User information updated. Информация о пользователе обновлена. - - - - - - - - - - + + + + + + + + + + Error Ошибка - + User Information - + Real Name: - + User Level: - + %n Year(s), amount of years (only shown if more than 0) - + %10%n Day(s) %20 amount of years (if more than 0), amount of days, date in local short format - + Enter Password - + Password verification is required in order to change your email address - - - + + + An error occurred while trying to update your user information. - + This server does not permit you to update your user informations. Этот сервер не позволяет обновить пользовательскую информацию. - + Password changed. Пароль изменен. - + This server does not permit you to change your password. Этот сервер не позволяет Вам сменить пароль. - + The new password is too short. Новый пароль слишком короткий. - + The old password is incorrect. Предыдущий пароль неверен. - + Avatar updated. Аватар изменен. - + This server does not permit you to update your avatar. Этот сервер не позволяет Вам изменить аватар. - + An error occured while trying to updater your avatar. При изменении аватара произошла ошибка. @@ -6650,124 +7641,251 @@ Please refrain from engaging in this activity or further actions may be taken ag UserInterfaceSettingsPage - + General interface settings Основные настройки интерфейса - + &Double-click cards to play them (instead of single-click) &Двойной клик, чтобы разыграть карту (вместо одинарного) - + + &Clicking plays all selected cards (instead of just the clicked card) + + + + &Play all nonlands onto the stack (not the battlefield) by default &Помещать все заклинания в стек при разыгрывании (вместо поля боя) - + + Close card view window when last card is removed + + + + Annotate card text on tokens Изменить текст карты на токенах - + Use tear-off menus, allowing right click menus to persist on screen - + Notifications settings - + Enable notifications in taskbar Включить оповещения в панели задач. - + Notify in the taskbar for game events while you are spectating Уведомлять об игровых событиях в панели задач, когда вы наблюдаете за игрой - + Notify in the taskbar when users in your buddy list connect - + Animation settings Настройки анимации - + &Tap/untap animation &Анимировать поворот/разворот карты + + + Deck editor/storage settings + + + + + Open deck in new tab by default + + + + + Prompt before converting .txt decks to .cod format + + + + + Always convert if not prompted + + + + + Use visual deck storage in game lobby + + + + + Replay settings + + + + + Buffer time for backwards skip via shortcut: + + - UserList + UserListWidget - + Users connected to server: %1 - Подключенных к серверу пользователей: %1 + - + Users in this room: %1 - Пользователей в этой комнате: %1 + - + Buddies online: %1 / %2 - Друзей онлайн: %1 / %2 + - + Ignored users online: %1 / %2 - Игнорируемых пользователей онлайн: %1 / %2 + + + + + VisualDeckStorageFolderDisplayWidget + + + Deck Storage + + + + + VisualDeckStorageSearchWidget + + + Search by filename + + + + + VisualDeckStorageSortWidget + + + Sort Alphabetically (Deck Name) + + + + + Sort Alphabetically (Filename) + + + + + Sort by Last Modified + + + + + Sort by Last Loaded + + + + + VisualDeckStorageWidget + + + Loading database ... + + + + + Show Folders + + + + + Show Tag Filter + + + + + Show Tags On Deck Previews + + + + + Show Banner Card Selection Option + + + + + Include Folder Names in Search + + + + + Draw unused Color Identities + + + + + Unused Color Identities Opacity + WarningDialog - + Which warning would you like to send? Какое предупреждение Вы бы хотели отправить? - + Redact all messages from this user in all rooms - + &OK &ОК - + &Cancel &Отменить - + Warn user for misconduct Дать предупреждение пользователю за проступок - - + + Error Ошибка - + User name to send a warning to can not be blank, please specify a user to warn. Поле не может быть пустым. Пожалуйста, уточните имя пользователя для выдачи предупреждения. - + Warning to use can not be blank, please select a valid warning to send. Поле предупреждения не может быть пустым. Пожалуйста, выберите корректную причину. @@ -6775,128 +7893,133 @@ Please refrain from engaging in this activity or further actions may be taken ag WndSets - + Move selected set to the top Переместить выбранное издание наверх - + Move selected set up Переместить выбранное издание вверх - + Move selected set down Переместить выбранное издание вниз - + Move selected set to the bottom Переместить выбранное издание в конец. - + Search by set name, code, or type - + Default order - + Restore original art priority order - + Enable all sets Включить все издания - + Disable all sets Отключить все издания - + Enable selected set(s) Включить выбранные издания - + Disable selected set(s) Отключить выбранные издания - + Deck Editor Редактор колод - + Use CTRL+A to select all sets in the view. - + Only cards in enabled sets will appear in the card list of the deck editor. - + Image priority is decided in the following order: - + first the CUSTOM Folder (%1), then the Enabled Sets in this dialog (Top to Bottom) %1 is a link to the wiki - + Card Art - + How to use custom card art - + Hints - + Note - + Sorting by column allows you to find a set while not changing set priority. - + To enable ordering again, click the column header until this message disappears. - + Use the current sorting as the set priority instead - + Sorts the set priority using the same column - + + Include online-only (Arena) cards [requires restart] + + + + Manage sets @@ -6904,22 +8027,67 @@ Please refrain from engaging in this activity or further actions may be taken ag ZoneViewWidget - - sort by name - Сортировать по имени + + Ungrouped + - - sort by type - сортировать по типу + + Group by Type + - + + Group by Mana Value + + + + + Group by Color + + + + + Unsorted + + + + + Sort by Name + + + + + Sort by Type + + + + + Sort by Mana Cost + + + + + Sort by Colors + + + + + Sort by P/T + + + + + Sort by Set + + + + shuffle when closing Перемешать после просмотра - + pile view просмотр вразнобой @@ -6927,7 +8095,7 @@ Please refrain from engaging in this activity or further actions may be taken ag i18n - + English Русский (Russian) @@ -6935,12 +8103,12 @@ Please refrain from engaging in this activity or further actions may be taken ag main - + Connect on startup - + Debug to file @@ -6948,786 +8116,908 @@ Please refrain from engaging in this activity or further actions may be taken ag shortcutsTab - + Main Window Главное окно - - + + Deck Editor Редактор колоды - + Game Lobby Вкладка игры - + Card Counters - + Player Counters - + Power and Toughness Сила и Выносливость - + Game Phases Игровые фазы - + Playing Area Игровое поле - + Move Selected Card - + View Вид - + Move Top Card - + Move Bottom Card - + Gameplay Процесс игры - + Drawing - + Chat Room - + Game Window - + Load Deck from Clipboard - + + Replays - + + Tabs + + + + Check for Card Updates... - + Connect... - + Disconnect Прервать подключение - + Exit Выход - + Full screen На весь экран - + Register... - + Settings... - + Start a Local Game... - + Watch Replay... - - Analyze Deck + + Analyze Deck (deckstats.net) + Analyze Deck - + + Analyze Deck (tappedout.net) + + + + Clear All Filters - + Clear Selected Filter - + Close Закрыть - + Remove Card - + Manage Sets... - + Edit Custom Tokens... - - Export Deck - - - - - Add Card - - - - - Load Deck... - - - - - Load Deck from Clipboard... + + Export Deck (decklist.org) - New Deck + Export Deck (decklist.xyz) - - Open Custom Pictures Folder + + Add Card - - Print Deck... + + Load Deck... + Load Deck from Clipboard... + + + + + Edit Deck in Clipboard, Annotated + + + + + Edit Deck in Clipboard + + + + + New Deck + + + + + Open Custom Pictures Folder + + + + + Print Deck... + + + + Delete Card - - + + Reset Layout - + Save Deck - + Save Deck as... - + Save Deck to Clipboard, Annotated - - Save Deck to Clipboard - - - - - Load Local Deck... - - - - - Load Remote Deck... - - - - - Set Ready to Start - - - - - Toggle Sideboard Lock - - - - - - Add Green Counter - - - - - - Remove Green Counter - - - - - - Set Green Counters... + + Save Deck to Clipboard, Annotated (No Set Info) + Save Deck to Clipboard + + + + + Save Deck to Clipboard (No Set Info) + + + + + Load Local Deck... + + + + + Load Remote Deck... + + + + + Set Ready to Start + + + + + Toggle Sideboard Lock + + + + + + Add Green Counter + + + + + + Remove Green Counter + + + + + + Set Green Counters... + + + + Add Yellow Counter - + Remove Yellow Counter - + Set Yellow Counters... - - + + Add Red Counter - - + + Remove Red Counter - - + + Set Red Counters... - + Add Life Counter - + Remove Life Counter - + Set Life Counters... - + Add White Counter - + Remove White Counter - + Set White Counters... - + Add Blue Counter - + Remove Blue Counter - + Set Blue Counters... - + Add Black Counter - + Remove Black Counter - + Set Black Counters... - + Add Colorless Counter - + Remove Colorless Counter - + Set Colorless Counters... - + Add Other Counter - + Remove Other Counter - + Set Other Counters... - + Add Power (+1/+0) - + Remove Power (-1/-0) - + Move Toughness to Power (+1/-1) - + Add Toughness (+0/+1) - + Remove Toughness (-0/-1) - + Move Power to Toughness (-1/+1) - + Add Power and Toughness (+1/+1) - + Remove Power and Toughness (-1/-1) - + Set Power and Toughness... - + Reset Power and Toughness - + Untap Шаг разворота - + Upkeep Шаг поддержки - + Draw Шаг взятия карты - + First Main Phase - + Start Combat - + Attack Атака - + Block Блок - + Damage Повреждения - + End Combat - + Second Main Phase - + End Конец хода - + Next Phase - + Next Phase Action - + Next Turn - + + Hide Card in Reveal Window + + + + Tap / Untap Card Повернуть / развернуть карту - + Untap All - + Toggle Untap - + Turn Card Over - + Peek Card - + Play Card - + Attach Card... - + Unattach Card - + Clone Card - + Create Token... - + Create All Related Tokens - + Create Another Token - + Set Annotation... - - + + Select All Cards in Zone + + + + + Select All Cards in Row + + + + + Select All Cards in Column + + + + + Bottom of Library - - - - + + + + Exile Изгнание - - - - + + + + Graveyard Кладбище - - + + Hand Рука - - + + Top of Library - - - + + + Battlefield, Face Down - + Battlefield - + Library Библиотека - + Sideboard Сайдборд - + Top Cards of Library - + + Bottom Cards of Library + + + + Close Recent View - - + + Stack - - + + Graveyard (Multiple) - - + + Exile (Multiple) - + Stack Until Found - + Draw Bottom Card - + Draw Multiple Cards from Bottom... - + Draw Arrow... - + Remove Local Arrows - + Leave Game - + Concede Сдаться - + Roll Dice... - + Shuffle Library - + + Shuffle Top Cards of Library + + + + + Shuffle Bottom Cards of Library + + + + Mulligan Муллиган - + Draw a Card - + Draw Multiple Cards... - + Undo Draw - + Always Reveal Top Card - + Always Look At Top Card - + Rotate View Clockwise - + Rotate View Counterclockwise - + Unfocus Text Box - + Focus Chat - + Clear Chat - + Refresh - + + Skip Forward + + + + + Skip Backward + + + + + Skip Forward by a lot + + + + + Skip Backward by a lot + + + + Play/Pause - + Toggle Fast Forward + + + Visual Deck Storage + + + + + Deck Storage + + + + + Server + + + + + Account + + + + + Administration + + + + + Logs + + \ No newline at end of file diff --git a/cockatrice/translations/cockatrice_sr.ts b/cockatrice/translations/cockatrice_sr.ts index 517540431..6f2dd3122 100644 --- a/cockatrice/translations/cockatrice_sr.ts +++ b/cockatrice/translations/cockatrice_sr.ts @@ -2,7 +2,7 @@ AbstractCounter - + &Set counter... Podesi brojač... @@ -10,95 +10,247 @@ AbstractCounterDialog - + Set counter - + New value for counter '%1': + + AbstractDlgDeckTextEdit + + + &Refresh + + + + + Parse Set Name and Number (if available) + + + + + AbstractTabDeckEditor + + + Open in new tab + + + + + Are you sure? + + + + + The decklist has been modified. +Do you want to save the changes? + + + + + + + + + + + Error + + + + + Could not open deck at %1 + + + + + Could not save remote deck + + + + + + The deck could not be saved. +Please check that the directory is writable and try again. + + + + + Save deck + + + + + The deck could not be saved. + + + + + There are no cards in your deck to be exported + + + + + No deck was selected to be exported. + + + + + AdminNotesDialog + + + Update Notes + + + + + Admin Notes for %1 + + + + + AllZonesCardAmountWidget + + + Mainboard + + + + + Sideboard + + + AppearanceSettingsPage - + Error - + Could not create themes directory at '%1'. - + Theme settings Podešavanja teme - + Current theme: Sadašnja tema: - + Open themes folder - + + Menu settings + + + + + Show keyboard shortcuts in right-click menus + + + + Card rendering - + Display card names on cards having a picture Pokazuj imena karti na kartama koje imaju sliku - + + Auto-Rotate cards with sideways layout + + + + + Override all card art with personal set preference (Pre-ProviderID change behavior) [Requires Client restart] + + + + + Bump sets that the deck contains cards from to the top in the printing selector + + + + Scale cards on mouse over - + + Use rounded card corners + + + + Minimum overlap percentage of cards on the stack and in vertical hand - + + Maximum initial height for card view window: + + + + + + rows + + + + + Maximum expanded height for card view window: + + + + Hand layout Raspored ruke - + Display hand horizontally (wastes space) Prikazuj ruku horizontalno (troši prostor) - + Enable left justification - + Table grid layout - + Invert vertical coordinate - + Minimum player count for multi-column layout: - + Maximum font size for information displayed on cards: @@ -106,112 +258,112 @@ BanDialog - + ban &user name zabrani ime igrača - + ban &IP address zabrani IP adresa - + ban client I&D zabranite ID klijenta - + Ban type Tip zabrane - + &permanent ban stalna zabrana - + &temporary ban privremena zabrana - + &Days: Dani: - + &Hours: Sati: - + &Minutes: Minuti: - + Duration of the ban Dužina trajanja zabrane - + Please enter the reason for the ban. This is only saved for moderators and cannot be seen by the banned person. Molimo Vas ukucajte razlog zabrane. Ovo je samo sačuvano za moderatore i ne može biti viđeno od strane zabranjene osobe. - + Please enter the reason for the ban that will be visible to the banned person. Molimo Vas ukucajte razlog zabrane koji će biti vidljiv zabranjenoj osobi. - + Redact all messages from this user in all rooms - + &OK U redu - + &Cancel Poništi - + Ban user from server Zabranite ulaz korisniku u server - - - - + + + + Error Greška - + You have to select a name-based, IP-based, clientId based, or some combination of the three to place a ban. - + You must have a value in the name ban when selecting the name ban checkbox. - + You must have a value in the ip ban when selecting the ip ban checkbox. - + You must have a value in the clientid ban when selecting the clientid ban checkbox. @@ -219,22 +371,22 @@ Ovo je samo sačuvano za moderatore i ne može biti viđeno od strane zabranjene BetaReleaseChannel - - Beta Releases + + Beta - + No reply received from the release update server. - + Invalid reply received from the release update server. - + No reply received from the file update server. @@ -242,32 +394,32 @@ Ovo je samo sačuvano za moderatore i ne može biti viđeno od strane zabranjene CardDatabaseModel - + Name Ime - + Sets Setovi - + Mana cost Cena mane - + Card type Tip karte - + P/T - + Color(s) Boja (boje) @@ -360,37 +512,65 @@ Ovo je samo sačuvano za moderatore i ne može biti viđeno od strane zabranjene - CardFrame + CardInfoFrameWidget - + Image - Slika + - + Description - Opis + - + Both - Oba + + + + + View transformation + - CardInfoText + CardInfoPictureWidget - - Name: - Ime: + + View related cards + - + + Add card to deck + + + + + Mainboard + + + + + Sideboard + + + + + CardInfoTextWidget + + + Name: + + + + Related cards: - + Unknown card: @@ -398,287 +578,555 @@ Ovo je samo sačuvano za moderatore i ne može biti viđeno od strane zabranjene CardItem - + &Move to Pomeri na - + &Power / toughness Moć / Izdržljivost + + CardSizeWidget + + + Card Size + + + CardZone - + their hand nominative njihova ruka - + %1's hand nominative Ruka od %1 - + their library look at zone - + %1's library look at zone - + of their library top cards of zone, - + of %1's library top cards of zone - + their library reveal zone - + %1's library reveal zone - + their library shuffle - + %1's library shuffle - + their library nominative - + %1's library nominative - + their graveyard nominative njihovo groblje - + %1's graveyard nominative groblje od %1 - + their exile nominative njihovo izgnanstvo - + %1's exile nominative izgnanstvo od %1 - - - their sideboard - look at zone - - - - - %1's sideboard - look at zone - - - - - their sideboard - nominative - - + their sideboard + look at zone + + + + + %1's sideboard + look at zone + + + + + their sideboard + nominative + + + + %1's sideboard nominative + + DeckEditorCardInfoDockWidget + + + Card Info + + + + + DeckEditorDatabaseDisplayWidget + + + Search by card name (or search expressions) + + + + + Add to Deck + + + + + Add to Sideboard + + + + + Select Printing + + + + + Show on EDHREC (Commander) + + + + + Show on EDHREC (Card) + + + + + Show Related cards + + + + + Add card to &maindeck + + + + + Add card to &sideboard + + + + + DeckEditorDeckDockWidget + + + Banner Card + + + + + Select Printing + + + + + Deck + + + + + Deck &name: + + + + + &Comments: + + + + + Hash: + + + + + &Increment number + + + + + &Decrement number + + + + + &Remove row + + + + + Swap card to/from sideboard + + + + + DeckEditorFilterDockWidget + + + Filters + + + + + &Clear all filters + + + + + Delete selected + + + + + DeckEditorMenu + + + &Deck Editor + + + + + &New deck + + + + + &Load deck... + + + + + Load recent deck... + + + + + Clear + + + + + &Save deck + + + + + Save deck &as... + + + + + Load deck from cl&ipboard... + + + + + Edit deck in clipboard + + + + + + Annotated + + + + + + Not Annotated + + + + + Save deck to clipboard + + + + + Annotated (No set info) + + + + + Not Annotated (No set info) + + + + + &Print deck... + + + + + &Send deck to online service + + + + + Create decklist (decklist.org) + + + + + Create decklist (decklist.xyz) + + + + + Analyze deck (deckstats.net) + + + + + Analyze deck (tappedout.net) + + + + + &Close + + + + + DeckEditorPrintingSelectorDockWidget + + + Printing Selector + + + DeckEditorSettingsPage - - + + Update Spoilers - - + + Success - + Download URLs have been reset. - + Downloaded card pictures have been reset. - + Error - + One or more downloaded card pictures could not be cleared. - + Add URL - - + + URL: - - + + Edit URL - + + Network Cache Size: + + + + + Redirect Cache TTL: + + + + + How long cached redirects for urls are valid for. + + + + + Picture Cache Size: + + + + Add New URL - + Remove URL - + + Day(s) + + + + Updating... - + Choose path - + URL Download Priority - + Spoilers - + Download Spoilers Automatically - + Spoiler Location: - + Last Change - + Spoilers download automatically on launch - + Press the button to manually update without relaunching - + Do not close settings until manual update is complete - + Download card pictures on the fly - + How to add a custom URL - + Delete Downloaded Images - + Reset Download URLs - - Downloaded images directory size: - - - - + On-disk cache for downloaded pictures - - Picture cache size: - - - - + In-memory cache for pictures not currently on screen @@ -686,16 +1134,218 @@ Ovo je samo sačuvano za moderatore i ne može biti viđeno od strane zabranjene DeckListModel - + + Count + + + + + Set + + + + Number Broj - + + Provider ID + + + + Card Karta + + DeckLoader + + + Common deck formats (%1) + + + + + All files (*.*) + + + + + DeckPreviewColorIdentityFilterWidget + + + Mode: Exact Match + + + + + Mode: Includes + + + + + DeckPreviewDeckTagsDisplayWidget + + + Edit tags ... + + + + + DeckPreviewTagDialog + + + Deck Tags Manager + + + + + Manage your deck tags. Check or uncheck tags as needed, or add new ones: + + + + + Add a new tag (e.g., Aggro️) + + + + + Add Tag + + + + + Filter tags... + + + + + OK + + + + + Cancel + + + + + Invalid Input + + + + + Tag name cannot be empty! + + + + + Duplicate Tag + + + + + This tag already exists. + + + + + DeckPreviewWidget + + + Banner Card + + + + + Open in deck editor + + + + + Edit Tags + + + + + Rename Deck + + + + + Save Deck to Clipboard + + + + + Annotated + + + + + Annotated (No set info) + + + + + Not Annotated + + + + + Not Annotated (No set info) + + + + + Rename File + + + + + Delete File + + + + + Set Banner Card + + + + + + New name: + + + + + + Error + + + + + Rename failed + + + + + Delete file + + + + + Are you sure you want to delete the selected file? + + + + + Delete failed + + + DeckStatsInterface @@ -713,181 +1363,217 @@ Ovo je samo sačuvano za moderatore i ne može biti viđeno od strane zabranjene DeckViewContainer - + Load deck... Učitaj špil... - + Load remote deck... Učitaj daljinski špil... - + + Unload deck + Unload deck... + + + + Ready to start - + + Force start + + + + Sideboard unlocked - + Sideboard locked - - Load deck - Učitaj špil - - - + Error Greška - + The selected file could not be loaded. Izabrani fajl nije mogao biti učitan. + + DialogConvertDeckToCodFormat + + + Deck Format Conversion + + + + + You tried to add a tag to a .txt format deck. + Tags can only be added to .cod format decks. + Do you want to convert the deck to the .cod format? + + + + + Remember and automatically apply choice in the future + + + DlgConnect - + Downloading... - + Known Hosts - + + Delete the currently selected saved server + + + + Refresh the server list with known public servers - + New Host Novi Domaćin - + Name: - + &Host: Domaćin: - + &Port: - + Player &name: Ime igrača: - + P&assword: Lozinka: - + &Save password Sačuvajte lozinku: - + A&uto connect Automatsko povezivanje - + Automatically connect to the most recent login when Cockatrice opens Automatski se poveži na najskoriju prijavu kada se Cockatrice otvori - + If you have any trouble connecting or registering then contact the server staff for help! - - + + Webpage - + Reset Password - + + Forgot password? + + + + &Connect - + Server Server - + Login Prijava - + Server Contact - + Connect to Server - + Server URL - + Communication Port - + Unique Server Name - + Connection Warning - + You need to name your new connection profile. - + Connect Warning Upozorenje o povezivanju - + The player name can't be empty. Ime igrača ne može biti prazno. @@ -970,27 +1656,37 @@ Ovo je samo sačuvano za moderatore i ne može biti viđeno od strane zabranjene Posmatrači - + + Starting life total: + + + + + Game setup options + + + + &Clear Očisti - + Create game Započni igru - + Game information Informacije o igri - + Error Greška - + Server error. Greška servera. @@ -998,92 +1694,92 @@ Ovo je samo sačuvano za moderatore i ne može biti viđeno od strane zabranjene DlgCreateToken - + &Name: Ime: - + Token Žeton - + C&olor: Boja: - + white belo - + blue plavo - + black crno - + red crveno - + green zeleno - + multicolor višebojno - + colorless bezbojno - + &P/T: M/I: - + &Annotation: Anotacija: - + &Destroy token when it leaves the table Uništi token kada napusti sto - + Token data Podaci o tokenima - + Show &all tokens Prikaži sve tokene - + Show tokens from this &deck Prikaži tokene iz ovog špila - + Choose token from list Izaberi token iz liste - + Create token Napravi token @@ -1129,6 +1825,24 @@ Da izbrišete vaš sadašnji avatar, potvrdite bez biranja nove slike.Neispravna slika izabrana. + + DlgEditDeckInClipboard + + + Edit deck in clipboard + + + + + Error + + + + + Invalid deck list. + + + DlgEditPassword @@ -1171,93 +1885,93 @@ Da izbrišete vaš sadašnji avatar, potvrdite bez biranja nove slike. DlgEditTokens - + &Name: Ime: - + C&olor: Boja: - + white belo - + blue plavo - + black crno - + red crveno - + green zeleno - + multicolor višebojno - + colorless bezbojno - + &P/T: M/I: - + &Annotation: Anotacija: - + Token data Podaci o tokenima - - + + Add token Dodaj token - + Remove token Uklonite token - + Edit custom tokens - + Please enter the name of the token: Molim Vas, ukucajte ime tokena: - + Error Greška - + The chosen name conflicts with an existing card or token. Make sure to enable the 'Token' set in the "Manage sets" dialog to display them correctly. @@ -1294,132 +2008,132 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgFilterGames - + no limit - + 5 minutes - + 10 minutes - + 30 minutes - + 1 hour - + 2 hours - - Show '&buddies only' games - Prikaži 'samo prijatelji' igre - - - - Show &full games + + Hide 'buddies only' games - - Show games &that have started + + Hide full games - - Show &password protected games - Prikaži igre zaštićene lozinkom - - - - Hide '&ignored user' games + + Hide games that have started - + + Hide password protected games + + + + + Hide 'ignored user' games + + + + &Newer than: - + Game &description: Opis igre: - + &Creator name: Ime kreatora: - + General - + &Game types Tipovi igara - + at &least: barem: - + at &most: najviše: - + Maximum player count Maksimalni broj igrača - + Restrictions Ograničenja - + Show games only if &spectators can watch - + Show spectator password p&rotected games - + Show only if spectators can ch&at - + Show only if spectators can see &hands - + Spectators - + Filter games Filtriraj igre @@ -1602,27 +2316,28 @@ Make sure to enable the 'Token' set in the "Manage sets" dia + + DlgLoadDeck + + + Load Deck + + + DlgLoadDeckFromClipboard - - &Refresh - Osveži - - - + Load deck from clipboard - - + Error Greška - - + Invalid deck list. @@ -1630,11 +2345,49 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgLoadRemoteDeck - + Load deck Učitaj špil + + DlgMoveTopCardsUntil + + + Card name (or search expressions): + + + + + Number of hits: + + + + + Auto play hits + + + + + Put top cards on stack until... + + + + + No cards matching the search expression exists in the card database. Proceed anyways? + + + + + Cockatrice + + + + + Invalid filter + + + DlgRegister @@ -1748,12 +2501,12 @@ Your email will be used to verify your account. DlgSettings - + Unknown Error loading card database Nepoznata Greška učitavanja baze podataka karte - + Your card database is invalid. Cockatrice may not function correctly with an invalid database @@ -1764,7 +2517,7 @@ Would you like to change your database location setting? - + Your card database version is too old. This can cause problems loading card information or images @@ -1775,7 +2528,7 @@ Would you like to change your database location setting? - + Your card database did not finish loading Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues with your cards.xml attached @@ -1784,14 +2537,14 @@ Would you like to change your database location setting? - + File Error loading your card database. Would you like to change your database location setting? - + Your card database was loaded but contains no cards. Would you like to change your database location setting? @@ -1800,7 +2553,7 @@ Would you like to change your database location setting? Da li bi ste želeli da promenite vaše podešavanje lokacije baze podataka? - + Unknown card database load status Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues @@ -1809,59 +2562,59 @@ Would you like to change your database location setting? - - - + + + Error Greška - + The path to your deck directory is invalid. Would you like to go back and set the correct path? - + The path to your card pictures directory is invalid. Would you like to go back and set the correct path? - + Settings Podešavanja - + General Opšto - + Appearance Izgled - + User Interface Korisnički interfejs - + Card Sources - + Chat Ćaskanje - + Sound Zvuk - + Shortcuts Prečice @@ -1887,164 +2640,168 @@ Would you like to change your database location setting? DlgUpdate - + Current release channel - + Reinstall - + Cancel Download - + Open Download Page Otvori Stranicu Za Preuzimanje - + Check for Client Updates - - - - + + + + Error Greška - + Cockatrice was not built with SSL support, therefore you cannot download updates automatically! Please visit the download page to update manually. - - Downloading update... - Preuzimanje ažuriranja + + Downloading update: %1 + Downloading update... + - + Checking for updates... Provera za ažuriranja... - + Finished checking for updates - + No Update Available - + Cockatrice is up to date! - + You are already running the latest version available in the chosen release channel. - + Current version - + Selected release channel - - + + Update Available - - - - A new version of Cockatrice is available! - - - - - - New version - - - Released + A new version of Cockatrice is available! + + + + + + New version - Changelog + Released + + Changelog + + + + Do you want to update now? - - Unfortunately there are no download packages available for your operating system. -You may have to build from source yourself. + + Unfortunately, the automatic updater failed to find a compatible download. +You may have to manually download the new version. + Unfortunately there are no download packages available for your operating system. +You may have to build from source yourself. - - Please check the download page manually and visit the wiki for instructions on compiling. + + Please check the <a href="%1">releases page</a> on our Github and download the build for your system. + Please check the download page manually and visit the wiki for instructions on compiling. - - - + + + Update Error Greška Ažuriranja - + An error occurred while checking for updates: - + An error occurred while downloading an update: - + Installing... Instalacija... - + Cockatrice is unable to open the installer. - + Try to update manually by closing Cockatrice and running the installer. - + Download location @@ -2062,6 +2819,14 @@ You may have to build from source yourself. + + EdhrecCommanderResponseCommanderDetailsDisplayWidget + + + Salt: + + + FilterBuilder @@ -2073,105 +2838,120 @@ You may have to build from source yourself. GameSelector - - - - - - - - - + + + + + + + + + Error Greška - + Please join the appropriate room first. - + Wrong password. Pogrešna lozinka. - + Spectators are not allowed in this game. Posmatrači nisu dozvoljeni u ovoj igri. - + The game is already full. Igra je već puna. - + The game does not exist any more. Ova igra više ne postoji. - + This game is only open to registered users. Ova igra je samo otvorena za registrovane korisnike. - + This game is only open to its creator's buddies. Ova igra je samo otvorena za prijatelje njenog kreatora. - + You are being ignored by the creator of this game. Vi ste ignorisani od strane kreatore ove igre. - + + Join Game + + + + + Spectate Game + + + + + Game Information + + + + Join game Pridruži se igri - + Password: Lozinka: - + Please join the respective room first. - + &Filter games Filtriraj igre - + C&lear filter Očisti filter - + C&reate Kreiraj - + &Join Pridruži se - + J&oin as spectator Pridruži se kao posmatrač - + Games shown: %1 / %2 - + Games Igre @@ -2179,100 +2959,100 @@ You may have to build from source yourself. GamesModel - + >1 day - + %1%2 hr short age in hours - + new - + %1%2 min short age in minutes - + password lozinka - + buddies only samo prijatelji - + reg. users only samo reg. korisnici - - + + can chat može da ćaska - + see hands vidi ruke - + can see hands može da vidi ruke - + not allowed nije dozvoljeno - + Room Soba - + Age Starost - + Description Opis - + Creator Kreator - + Type Tip - + Restrictions Ograničenja - + Players Igrači - + Spectators Posmatrači @@ -2280,92 +3060,102 @@ You may have to build from source yourself. GeneralSettingsPage - + Reset all paths - + All paths have been reset - - + + Choose path Izaberi put - + Personal settings Lična podešavanja - + Language: Jezik: - + Paths (editing disabled in portable mode) - + Paths Putevi - + + How to help with translations + + + + Decks directory: - + Replays directory: - + Pictures directory: - + Card database: Baza podataka karata: - + Custom database directory: - + Token database: Baza podataka tokena: - + Update channel - + + Check for client updates on startup + + + + Notify if a feature supported by the server is missing in my client - + Automatically run Oracle when running a new version of Cockatrice - + Show tips on startup @@ -2373,711 +3163,722 @@ You may have to build from source yourself. MainWindow - - + + The server has reached its maximum user capacity, please check back later. - + There are too many concurrent connections from your address. - + Banned by moderator Zabranjen od strane moderatora - + Expected end time: %1 - + This ban lasts indefinitely. - + Scheduled server shutdown. - - + + Invalid username. Nevažeće korisničko ime. - + You have been logged out due to logging in at another location. - + Connection closed Povezanost prekinuta - + The server has terminated your connection. Reason: %1 - + The server is going to be restarted in %n minute(s). All running games will be lost. Reason for shutdown: %1 - + Scheduled server shutdown - - + + Success Uspeh - + Registration accepted. Will now login. - + Account activation accepted. Will now login. - + Number of players - + Please enter the number of players. - - + + Player %1 - + Load replay - + About Cockatrice - + Version - + Cockatrice Webpage - + Project Manager: - + Past Project Managers: - + Developers: - + Our Developers - + Help Develop! - + Translators: - + Our Translators - + Help Translate! - + Support: - + Report an Issue - + Troubleshooting - + F.A.Q. - - - - - - - - - - - - - - - + + + + + + + + + + + + + - - - + + + + + Error - + Server timeout - + Failed Login - + Your client seems to be missing features this server requires for connection. - + To update your client, go to 'Help -> Check for Client Updates'. - + Incorrect username or password. Please check your authentication information and try again. - + There is already an active session using this user name. Please close that session first and re-login. - - + + You are banned until %1. - - + + You are banned indefinitely. - + This server requires user registration. Do you want to register now? - + This server requires client IDs. Your client is either failing to generate an ID or you are running a modified client. Please close and reopen your client to try again. - + An internal error has occurred, please close and reopen Cockatrice before trying again. If the error persists, ensure you are running the latest version of the software and if needed contact the software developers. - + Account activation - + Your account has not been activated yet. You need to provide the activation token received in the activation email. - + Server Full - + Unknown login error: %1 - - + + This usually means that your client version is out of date, and the server sent a reply your client doesn't understand. - + Your username must respect these rules: - + is %1 - %2 characters long - + can %1 contain lowercase characters - - - - + + + + NOT - + can %1 contain uppercase characters - + can %1 contain numeric characters - + can contain the following punctuation: %1 - + first character can %1 be a punctuation mark - + no unacceptable language as specified by these server rules: note that the following lines will not be translated - + can not contain any of the following words: %1 - + can not match any of the following expressions: %1 - + You may only use A-Z, a-z, 0-9, _, ., and - in your username. - - - - - - + + + + + + Registration denied - + Registration is currently disabled on this server - + There is already an existing account with the same user name. - + It's mandatory to specify a valid email address when registering. - + It appears you are attempting to register a new account on this server yet you already have an account registered with the email provided. This server restricts the number of accounts a user can register per address. Please contact the server operator for further assistance or to obtain your credential information. - + Password too short. - + Registration failed for a technical problem on the server. - + Unknown registration error: %1 - + Account activation failed - + Socket error: %1 - + You are trying to connect to an obsolete server. Please downgrade your Cockatrice version or connect to a suitable server. Local version is %1, remote version is %2. - + Your Cockatrice client is obsolete. Please update your Cockatrice version. Local version is %1, remote version is %2. - + Connecting to %1... - + Registering to %1 as %2... - + Disconnected - + Connected, logging in at %1 - - - + + + Requesting forgotten password to %1 as %2... - + &Connect... - + &Disconnect - + Start &local game... - + &Watch replay... - - &Deck editor - - - - + &Full screen - + &Register to server... - + &Restore password... - + &Settings... - + &Exit - + A&ctions - + &Cockatrice - + C&ard Database - + &Manage sets... - + Edit custom &tokens... - + Open custom image folder - + Open custom sets folder - + Add custom sets/cards - + + Reload card database + + + + + Tabs + + + + &Help - + &About Cockatrice - + &Tip of the Day - + Check for Client Updates - + Check for Card Updates... - + View &Debug Log - + + Open Settings Folder + + + + Show/Hide - + New Version - + Congratulations on updating to Cockatrice %1! Oracle will now launch to update your card database. - + Cockatrice installed - + Congratulations on installing Cockatrice %1! Oracle will now launch to install the initial card database. - + Card database - + Cockatrice is unable to load the card database. Do you want to update your card database now? If unsure or first time user, choose "Yes" - - + + Yes - - + + No - + Open settings - + New sets found - + %n new set(s) found in the card database Set code(s): %1 Do you want to enable it/them? - + View sets - + Welcome - + Hi! It seems like you're running this version of Cockatrice for the first time. All the sets in the card database have been enabled. Read more about changing the set order or disabling specific sets and consequent effects in the "Manage Sets" dialog. - - + + Information - + A card database update is already running. - + Unable to run the card database updater: - - failed to start. + + Failed to start. The file might be missing, or permissions might be incorrect. - - crashed. + + The process crashed some time after starting successfully. - - timed out. + + Timed out. The process took too long to respond. The last waitFor...() function timed out. - - write error. + + An error occurred when attempting to write to the process. For example, the process may not be running, or it may have closed its input channel. - - read error. + + An error occurred when attempting to read from the process. For example, the process may not be running. - - unknown error. + + Unknown error occurred. - - The card database updater exited with an error: %1 + + The card database updater exited with an error: +%1 - + This server supports additional features that your client doesn't have. This is most likely not a problem, but this message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -3085,54 +3886,54 @@ To update your client, go to Help -> Check for Updates. - - - - - + + + + + Load sets/cards - + Selected file cannot be found. - + You can only import XML databases at this time. - + The new sets/cards have been added successfully. Cockatrice will now reload the card database. - + Sets/cards failed to import. - - - + + + Reset Password - + Your password has been reset successfully, you can now log in using the new credentials. - + Failed to reset user account password, please contact the server operator to reset your password. - + Activation request received, please check your email for an activation token. @@ -3406,179 +4207,190 @@ Cockatrice will now reload the card database. - %1 is looking at the top %3 card(s) %2. + %1 is looking at the %4 %3 card(s) %2. + %1 is looking at the top %3 card(s) %2. top card for singular, top %3 cards for plural - + + bottom + + + + + top + + + + %1 turns %2 face-down. - + %1 turns %2 face-up. - + The game has been closed. - + The game has started. - + %1 has joined the game. - + %1 is now watching the game. - + You have been kicked out of the game. - + %1 has left the game (%2). - + %1 is not watching the game any more (%2). - + %1 is not ready to start the game any more. - + %1 shuffles their deck and draws a new hand of %2 card(s). - + %1 shuffles their deck and draws a new hand. - + You are watching a replay of game #%1. - + %1 is ready to start the game. - + cards an unknown amount of cards - + %1 card(s) a card for singular, %1 cards for plural - + %1 lends %2 to %3. - + %1 reveals %2 to %3. - + %1 reveals %2. - + %1 randomly reveals %2%3 to %4. - + %1 randomly reveals %2%3. - + %1 peeks at face down card #%2. - + %1 peeks at face down card #%2: %3. - + %1 reveals %2%3 to %4. - + %1 reveals %2%3. - + %1 reversed turn order, now it's %2. - + reversed - + normal - + Heads - + Tails - + %1 flipped a coin. It landed as %2. - + %1 rolls a %2 with a %3-sided die. - + %1 flips %2 coins. There are %3 heads and %4 tails. - + %1 rolls a %2-sided dice %3 times: %4. @@ -3698,17 +4510,17 @@ Cockatrice will now reload the card database. - + %1 unattaches %2. - + %1 undoes their last draw. - + %1 undoes their last draw (%2). @@ -3716,110 +4528,110 @@ Cockatrice will now reload the card database. MessagesSettingsPage - + Word1 Word2 Word3 - + Add New Message - + Edit Message - + Remove Message - + Add message - - + + Message: - + Edit message - + Chat settings - + Custom alert words - + Enable chat mentions - + Enable mention completer - + In-game message macros - + How to use in-game message macros - + Ignore chat room messages sent by unregistered users - + Ignore private messages sent by unregistered users - - + + Invert text color - + Enable desktop notifications for private messages - + Enable desktop notification for mentions - + Enable room message history on join - - + + (Color is hexadecimal) - + Separate words with a space, alphanumeric characters only @@ -3877,6 +4689,24 @@ Cockatrice will now reload the card database. + + OverlapControlWidget + + + Cards to overlap: + + + + + Overlap percentage: + + + + + Overlap direction: + + + Phase @@ -3943,57 +4773,57 @@ Cockatrice will now reload the card database. PhasesToolbar - + Untap step - + Upkeep step - + Draw step - + First main phase - + Beginning of combat step - + Declare attackers step - + Declare blockers step - + Combat damage step - + End of combat step - + Second main phase - + End of turn step @@ -4001,7 +4831,7 @@ Cockatrice will now reload the card database. PictureLoader - + en code for scryfall's language property, not available for all languages @@ -4010,647 +4840,781 @@ Cockatrice will now reload the card database. Player - + Reveal top cards of library - - - - - - - - - + + + + + + + + + + + + Number of cards: (max. %1) - + &View graveyard - + &View exile - + Player "%1" - - - - + + + + &Graveyard - - - - + + + + &Exile - + &Move hand to... - - - + + + &Top of library - - - + + + &Bottom of library - + &Move graveyard to... - - - - + + + + &Hand - + &Move exile to... - + &View library - + &View hand - + View &top cards of library... - + Reveal &library to... - + &Always reveal top card - + &View sideboard - + &Draw card - + D&raw cards... - + &Undo last draw - + Take &mulligan - - &Shuffle - - - - + Play top card &face down - + Move top card to grave&yard - + Move top card to e&xile - + Move top cards to &graveyard... - + Move top cards to &exile... - + Put top card on &bottom - + + View bottom cards of library... + + + + Lend library to... - + + Shuffle + + + + Put top cards on stack &until... Take top cards &until... - + + Shuffle top cards... + + + + + Shuffle bottom cards... + + + + &Reveal hand to... - + Reveal r&andom card to... - + Reveal random card to... - + &Sideboard - + &Library - + &Counters - + &Untap all permanents - + R&oll die... - + &Create token... - + C&reate another token - + Cr&eate predefined token - - + + &All players - + S&ay - + + &Select All + + + + + S&elect Row + + + + + S&elect Column + + + + &Play - + &Hide - + Play &Face Down - + &Tap / Untap Turn sideways or back again - + Toggle &normal untapping - + T&urn Over Turn face up/face down - + &Peek at card face - + &Clone - + Attac&h to card... - + Unattac&h - + &Draw arrow... - + &Increase power - + &Decrease power - + I&ncrease toughness - + D&ecrease toughness - + In&crease power and toughness - + Dec&rease power and toughness - + Increase power and decrease toughness - + Decrease power and increase toughness - + Set &power and toughness... - + Reset p&ower and toughness - + &Set annotation... - + Red - + Yellow - + Green - + &Add counter (%1) - + &Remove counter (%1) - + &Set counters (%1)... - + &Top of library in random order - + X cards from the top of library... - + &Bottom of library in random order - + View top cards of library - + + View bottom cards of library + + + + + Shuffle top cards of library + + + + + Shuffle bottom cards of library + + + + Which position should this card be placed: - + (max. %1) - + Draw hand - + 0 and lower are in comparison to current hand size - + Draw cards - - + + Number: - + Move top cards to grave - + Reveal &top cards to... - + &Top of library... - + &Bottom of library... - + &Always look at top card - + &Open deck in deck editor - + &Play top card - + &Draw bottom card - + D&raw bottom cards... - + &Play bottom card - + Play bottom card &face down - + Move bottom card to grave&yard - + Move bottom card to e&xile - + Move bottom cards to &graveyard... - + Move bottom cards to &exile... - + Put bottom card on &top - + Selec&ted cards - + Move top cards to exile - + Move bottom cards to grave - + Move bottom cards to exile - + Draw bottom cards - - + + C&reate another %1 token - + Create tokens - + Place card X cards from top of library - + Change power/toughness - + Change stats to: - + Set annotation - + Please enter the new annotation: - + Set counters - + Re&veal to... - + View related cards - + Token: - + All tokens + + PrintingSelector + + + Display Navigation Buttons + + + + + <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. + + + + + PrintingSelectorCardOverlayWidget + + + Preference + + + + + Pin Printing + + + + + Unpin Printing + + + + + Show Related cards + + + + + PrintingSelectorCardSearchWidget + + + Search by set name or set code + + + + + PrintingSelectorCardSelectionWidget + + + Previous Card in Deck + + + + + Next Card in Deck + + + + + PrintingSelectorCardSortingWidget + + + Alphabetical + + + + + Preference + + + + + Release Date + + + + + + Descending + + + + + Ascending + + + QMenuBar - + Services - + Hide %1 - + Hide Others - + Show All - + Preferences... - + Quit %1 - + About %1 @@ -4658,38 +5622,32 @@ Cockatrice will now reload the card database. QObject - + Cockatrice card database (*.xml) - - + All files (*.*) - + Cockatrice replays (*.cor) - - Common deck formats (*.cod *.dec *.dek *.txt *.mwDeck) - - - - + Maindeck - + Sideboard - + Tokens @@ -4697,92 +5655,92 @@ Cockatrice will now reload the card database. QPlatformTheme - + OK - + Save - + Save All - + Open - + &Yes - + Yes to &All - + &No - + N&o to All - + Abort - + Retry - + Ignore - + Close - + Cancel - + Discard - + Help - + Apply - + Reset - + Restore Defaults @@ -4790,17 +5748,17 @@ Cockatrice will now reload the card database. RemoteDeckList_TreeModel - + Name - + ID - + Upload time @@ -4808,32 +5766,32 @@ Cockatrice will now reload the card database. RemoteReplayList_TreeModel - + ID - + Name - + Players - + Keep - + Time started - + Duration (sec) @@ -4879,32 +5837,32 @@ Cockatrice will now reload the card database. SequenceEdit - + Choose an action from the table - + Hit the key/combination of keys you want to set for this action - - Shortcut already in use - - - - + Invalid key - + + Shortcut already in use by: + + + + Clear - + Restore default @@ -4940,72 +5898,80 @@ Cockatrice will now reload the card database. ShortcutSettingsPage - - + + Restore all default shortcuts - + Do you really want to restore all default shortcuts? - + Clear all default shortcuts - + Do you really want to clear all shortcuts? - - Action - - - - - Shortcut - - - - + Section: - + Action: - + Shortcut: - + How to set custom shortcuts - + Clear all shortcuts + + + Search by shortcut name + + + + + ShortcutTreeView + + + Action + + + + + Shortcut + + ShortcutsSettings - + Your configuration file contained invalid shortcuts. Please check your shortcut settings! - + The following shortcuts have been set to default: @@ -5014,17 +5980,17 @@ Please check your shortcut settings! ShutdownDialog - + &Reason for shutdown: - + &Time until shutdown (minutes): - + Shut down server @@ -5032,27 +5998,27 @@ Please check your shortcut settings! SoundSettingsPage - + Enable &sounds - + Current sounds theme: - + Test system sound engine - + Sound settings - + Master volume @@ -5060,48 +6026,48 @@ Please check your shortcut settings! SpoilerBackgroundUpdater - + Spoilers season has ended - + Deleting spoiler.xml. Please run Oracle - - + + Spoilers download failed - + No internet connection - + Error - + Spoilers already up to date - + No new spoilers added - + Spoilers have been updated! - + Last change: @@ -5109,635 +6075,593 @@ Please check your shortcut settings! StableReleaseChannel - - Stable Releases + + Default - + No reply received from the release update server. - + Invalid reply received from the release update server. - + No reply received from the tag update server. - + Invalid reply received from the tag update server. + + TabAccount + + + Add to Buddy List + + + + + Add to Ignore List + + + + + Account + + + TabAdmin - + Administration - + Update server &message - + &Shut down server - + &Reload configuration - + Server administration functions - + + Server moderator functions + + + + + Replay ID + + + + + Grant Replay Access + + + + + Username to Activate + + + + + Force Activate User + + + + &Unlock functions - + &Lock functions + + + + Success + + + + + Replay access granted + + + + + + + + + Error + + + + + Unable to grant replay access. Replay ID invalid + + + + + Unable to grant replay access. Internal error + + + + + User successfully activated + + + + + Unable to activate user. Username invalid + + + + + Unable to activate user. User already active + + + + + Unable to activate user. Internal error + + TabDeckEditor - - Search by card name (or search expressions) - Search by card name - - - - - Add to Deck - - - - - Add to Sideboard - - - - - Show Related cards - - - - - &Clear all filters - - - - - Delete selected - - - - - Deck &name: - - - - - &Comments: - - - - - Hash: - - - - - &New deck - - - - - &Load deck... - - - - - &Save deck - - - - - Save deck &as... - - - - - Load deck from cl&ipboard... - - - - - Save deck to clipboard - - - - - Annotated - - - - - Not Annotated - - - - - &Print deck... - - - - - &Send deck to online service - - - - - Create decklist (decklist.org) - - - - - Analyze deck (deckstats.net) - - - - - Analyze deck (tappedout.net) - - - - - &Close - - - - - Add card to &maindeck - - - - - Add card to &sideboard - - - - - &Remove row - - - - - &Increment number - - - - - &Decrement number - - - - - &Deck Editor - - - - - + Card Info - - + Deck - - + Filters - + &View - - - + + Printing + + + + + + + Visible - - - + + + + Floating - + Reset layout - + Deck: %1 - - - Are you sure? - - - - - The decklist has been modified. -Do you want to save the changes? - - - - - Load deck - - - - - - - - - - Error - - - - - The deck could not be saved. - - - - - Could not save remote deck - - - - - - The deck could not be saved. -Please check that the directory is writable and try again. - - - - - Save deck - - - - - There are no cards in your deck to be exported - - - - - No deck was selected to be saved. - - TabDeckStorage - - Deck storage - - - - + Local file system - + Server deck storage - - + + Open in deck editor - + + Rename deck or folder + + + + Upload deck - + Download deck - - + + + + New folder - - + + Delete - - + + Open decks folder + + + + + Rename local folder + + + + + Rename local file + + + + + New name: + + + + + + + Error - + + Rename failed + + + + + Invalid deck file - + Enter deck name - + This decklist does not have a name. Please enter a name: - + Unnamed deck - + Failed to upload deck to server - + Delete local file - - - - Are you sure you want to delete "%1"? + + Are you sure you want to delete the selected files? - + + Delete remote decks + + + + + Are you sure you want to delete the selected decks? + + + + + Name of new folder: - - Delete remote folder + + Deck Storage + + + + + TabDeckStorageVisual + + + Visual Deck Storage - - Delete remote deck + + Error + + + + + Could not open deck at %1 + + + + + TabEdhRec + + + EDHREC: TabGame - - - + + + Replay - - + + Game - - + + Player List - - + + Card Info - - + + Messages - - + + Replay Timeline - + &Phases - + &Game - + Next &phase - + Next phase with &action - + Next &turn - + Reverse turn order - + &Remove all local arrows - + Rotate View Cl&ockwise - + Rotate View Co&unterclockwise - + Game &information - + + Un&concede + + + + &Concede - + &Leave game - + C&lose replay - + &Focus Chat - + &Say: - + &View - - - - + + + + Visible - - - - + + + + Floating - + Reset layout - + Concede - + Are you sure you want to concede this game? - + Unconcede - + You have already conceded. Do you want to return to this game? - + Leave game - + Are you sure you want to leave this game? - + You are flooding the game. Please wait a couple of seconds. - + A player has joined game #%1 - + %1 has joined the game - + kicked by game host or moderator - + player left the game - + player disconnected from server - + reason unknown - + You have been kicked out of the game. @@ -5750,152 +6674,152 @@ Please enter a name: - - - + + + Time;SenderName;SenderIP;Message;TargetID;TargetName - + Room Logs - + Game Logs - + Chat Logs - - + + Error - + You must select at least one filter. - + You have to select a valid number of days to locate. - + Username: - + IP Address: - + Game Name: - + GameID: - + Message: - + Main Room - + Game Room - + Private Chat - + Past X Days: - + Today - + Last Hour - + Maximum Results: - + At least one filter is required. The more information you put in, the more specific your results will be. - + Get User Logs - + Clear Filters - + Filters - + Log Locations - + Date Range - + Maximum Results - - + + Message History - + Failed to collect message history information. - + There are no messages for the selected filters. @@ -5918,22 +6842,22 @@ The more information you put in, the more specific your results will be. - + This user is ignoring you, they cannot see your messages in main chat and you cannot join their games. - + Private message from - + %1 has left the server. - + %1 has joined the server. @@ -5941,117 +6865,153 @@ The more information you put in, the more specific your results will be. TabReplays - - Game replays - - - - + Local file system - + Server replay storage - - + + Watch replay - - + + Rename + + + + + + New folder + + + + + Delete - + + Open replays folder + + + + Download replay - + Toggle expiration lock - + + Rename local folder + + + + + Rename local file + + + + + New name: + + + + + Error + + + + + Rename failed + + + + + Name of new folder: + + + + Delete local file - - Are you sure you want to delete "%1"? + + Are you sure you want to delete the selected files? - - Downloading Replays + + Are you sure you want to delete the selected replays? - - Folder download is not yet supported. Please download replays individually. - - - - + Delete remote replay - - Are you sure you want to delete the replay of game %1? + + Game Replays TabRoom - + &Say: - + Chat - + &Room - + &Leave room - + &Clear chat - + Chat Settings... - + mentioned you. - + Click to view - + You are flooding the chat. Please wait a couple of seconds. @@ -6064,30 +7024,30 @@ The more information you put in, the more specific your results will be. - - - - + + + + Error - + Failed to join the server room: it doesn't exist on the server. - + The server thinks you are in the server room but your client is unable to display it. Try restarting your client. - + You do not have the required permission to join this server room. - + Failed to join the server room due to an unknown error: %1. @@ -6095,32 +7055,72 @@ The more information you put in, the more specific your results will be. TabSupervisor - + + Deck Editor + + + + + &Visual Deck Storage + + + + + Server + + + + + Account + + + + + Deck Storage + + + + + Game Replays + + + + + Administration + + + + + Logs + + + + Are you sure? - + There are still open games. Are you sure you want to quit? - + Click to view - + Your buddy %1 has signed on! - + Unknown Event - + The server has sent you a message that your client does not understand. This message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -6128,61 +7128,43 @@ To update your client, go to Help -> Check for Updates. - + Idle Timeout - + You are about to be logged out due to inactivity. - + Promotion - + You have been promoted. Please log out and back in for changes to take effect. - + Warned - + You have received a warning due to %1. Please refrain from engaging in this activity or further actions may be taken against you. If you have any questions, please private message a moderator. - + You have received the following message from the server. (custom messages like these could be untranslated) - - TabUserLists - - - Account - - - - - Add to Buddy List - - - - - Add to Ignore List - - - TappedOutInterface @@ -6266,173 +7248,184 @@ Please refrain from engaging in this activity or further actions may be taken ag UserContextMenu - + User &details - + Private &chat - + Show this user's &games - + Add to &buddy list - + Remove from &buddy list - + Add to &ignore list - + Remove from &ignore list - + Kick from &game - + Warn user - + View user's war&n history - + Ban from &server - + View user's &ban history - + &Promote user to moderator - + Dem&ote user from moderator - + Promote user to &judge - + Demote user from judge - + + View admin notes + + + + %1's games - - - + + + Ban History - + Ban Time;Moderator;Ban Length;Ban Reason;Visible Reason - + User has never been banned. - + Failed to collect ban information. - - - + + + Warning History - + Warning Time;Moderator;User Name;Reason - + User has never been warned. - + Failed to collect warning information. - - + + Failed to get admin notes. + + + + + Success - + Successfully promoted user. - + Successfully demoted user. - - + + + Failed - + Failed to promote user. - + Failed to demote user. - + Copy hash to clipboard - + Remove this user's messages @@ -6440,173 +7433,173 @@ Please refrain from engaging in this activity or further actions may be taken ag UserInfoBox - + Location: - + Account Age: - + Edit - + Change password - + Change avatar - + Administrator - + Moderator - + Registered user - - + + Unregistered user - + Judge - + Unknown - + The entered password does not match your account. - - - + + + Information - + User information updated. - - - - - - - - - - + + + + + + + + + + Error - + User Information - + Real Name: - + User Level: - + %n Year(s), amount of years (only shown if more than 0) - + %10%n Day(s) %20 amount of years (if more than 0), amount of days, date in local short format - + Enter Password - + Password verification is required in order to change your email address - - - + + + An error occurred while trying to update your user information. - + This server does not permit you to update your user informations. - + Password changed. - + This server does not permit you to change your password. - + The new password is too short. - + The old password is incorrect. - + Avatar updated. - + This server does not permit you to update your avatar. - + An error occured while trying to updater your avatar. @@ -6614,124 +7607,251 @@ Please refrain from engaging in this activity or further actions may be taken ag UserInterfaceSettingsPage - + General interface settings - + &Double-click cards to play them (instead of single-click) - + + &Clicking plays all selected cards (instead of just the clicked card) + + + + &Play all nonlands onto the stack (not the battlefield) by default - + + Close card view window when last card is removed + + + + Annotate card text on tokens - + Use tear-off menus, allowing right click menus to persist on screen - + Notifications settings - + Enable notifications in taskbar - + Notify in the taskbar for game events while you are spectating - + Notify in the taskbar when users in your buddy list connect - + Animation settings - + &Tap/untap animation + + + Deck editor/storage settings + + + + + Open deck in new tab by default + + + + + Prompt before converting .txt decks to .cod format + + + + + Always convert if not prompted + + + + + Use visual deck storage in game lobby + + + + + Replay settings + + + + + Buffer time for backwards skip via shortcut: + + - UserList + UserListWidget - + Users connected to server: %1 - + Users in this room: %1 - + Buddies online: %1 / %2 - Aktivni prijatelji: %1 / %2 + - + Ignored users online: %1 / %2 - Aktivni ignorisani korisnici: %1 / %2 + + + + + VisualDeckStorageFolderDisplayWidget + + + Deck Storage + + + + + VisualDeckStorageSearchWidget + + + Search by filename + + + + + VisualDeckStorageSortWidget + + + Sort Alphabetically (Deck Name) + + + + + Sort Alphabetically (Filename) + + + + + Sort by Last Modified + + + + + Sort by Last Loaded + + + + + VisualDeckStorageWidget + + + Loading database ... + + + + + Show Folders + + + + + Show Tag Filter + + + + + Show Tags On Deck Previews + + + + + Show Banner Card Selection Option + + + + + Include Folder Names in Search + + + + + Draw unused Color Identities + + + + + Unused Color Identities Opacity + WarningDialog - + Which warning would you like to send? - + Redact all messages from this user in all rooms - + &OK U redu - + &Cancel - + Warn user for misconduct - - + + Error - + User name to send a warning to can not be blank, please specify a user to warn. - + Warning to use can not be blank, please select a valid warning to send. @@ -6739,128 +7859,133 @@ Please refrain from engaging in this activity or further actions may be taken ag WndSets - + Move selected set to the top - + Move selected set up - + Move selected set down - + Move selected set to the bottom - + Search by set name, code, or type - + Default order - + Restore original art priority order - + Enable all sets - + Disable all sets - + Enable selected set(s) - + Disable selected set(s) - + Deck Editor - + Use CTRL+A to select all sets in the view. - + Only cards in enabled sets will appear in the card list of the deck editor. - + Image priority is decided in the following order: - + first the CUSTOM Folder (%1), then the Enabled Sets in this dialog (Top to Bottom) %1 is a link to the wiki - + Card Art - + How to use custom card art - + Hints - + Note - + Sorting by column allows you to find a set while not changing set priority. - + To enable ordering again, click the column header until this message disappears. - + Use the current sorting as the set priority instead - + Sorts the set priority using the same column - + + Include online-only (Arena) cards [requires restart] + + + + Manage sets @@ -6868,22 +7993,67 @@ Please refrain from engaging in this activity or further actions may be taken ag ZoneViewWidget - - sort by name - sortiraj po imenu + + Ungrouped + - - sort by type - sortiraj po tipu + + Group by Type + - + + Group by Mana Value + + + + + Group by Color + + + + + Unsorted + + + + + Sort by Name + + + + + Sort by Type + + + + + Sort by Mana Cost + + + + + Sort by Colors + + + + + Sort by P/T + + + + + Sort by Set + + + + shuffle when closing - + pile view @@ -6891,7 +8061,7 @@ Please refrain from engaging in this activity or further actions may be taken ag i18n - + English Srpski (Serbian) @@ -6899,12 +8069,12 @@ Please refrain from engaging in this activity or further actions may be taken ag main - + Connect on startup - + Debug to file @@ -6912,786 +8082,908 @@ Please refrain from engaging in this activity or further actions may be taken ag shortcutsTab - + Main Window - - + + Deck Editor - + Game Lobby - + Card Counters - + Player Counters - + Power and Toughness Moć i Izdržljivost - + Game Phases Faze Igre - + Playing Area - + Move Selected Card - + View Pogled - + Move Top Card - + Move Bottom Card - + Gameplay - + Drawing - + Chat Room - + Game Window - + Load Deck from Clipboard - + + Replays - + + Tabs + + + + Check for Card Updates... - + Connect... - + Disconnect - + Exit Izađite - + Full screen Ceo ekran - + Register... - + Settings... - + Start a Local Game... - + Watch Replay... - - Analyze Deck + + Analyze Deck (deckstats.net) + Analyze Deck - + + Analyze Deck (tappedout.net) + + + + Clear All Filters - + Clear Selected Filter - + Close Zatvorite - + Remove Card - + Manage Sets... - + Edit Custom Tokens... - - Export Deck - - - - - Add Card - - - - - Load Deck... - - - - - Load Deck from Clipboard... + + Export Deck (decklist.org) - New Deck + Export Deck (decklist.xyz) - - Open Custom Pictures Folder + + Add Card - - Print Deck... + + Load Deck... + Load Deck from Clipboard... + + + + + Edit Deck in Clipboard, Annotated + + + + + Edit Deck in Clipboard + + + + + New Deck + + + + + Open Custom Pictures Folder + + + + + Print Deck... + + + + Delete Card - - + + Reset Layout - + Save Deck - + Save Deck as... - + Save Deck to Clipboard, Annotated - - Save Deck to Clipboard - - - - - Load Local Deck... - - - - - Load Remote Deck... - - - - - Set Ready to Start - - - - - Toggle Sideboard Lock - - - - - - Add Green Counter - - - - - - Remove Green Counter - - - - - - Set Green Counters... + + Save Deck to Clipboard, Annotated (No Set Info) + Save Deck to Clipboard + + + + + Save Deck to Clipboard (No Set Info) + + + + + Load Local Deck... + + + + + Load Remote Deck... + + + + + Set Ready to Start + + + + + Toggle Sideboard Lock + + + + + + Add Green Counter + + + + + + Remove Green Counter + + + + + + Set Green Counters... + + + + Add Yellow Counter - + Remove Yellow Counter - + Set Yellow Counters... - - + + Add Red Counter - - + + Remove Red Counter - - + + Set Red Counters... - + Add Life Counter - + Remove Life Counter - + Set Life Counters... - + Add White Counter - + Remove White Counter - + Set White Counters... - + Add Blue Counter - + Remove Blue Counter - + Set Blue Counters... - + Add Black Counter - + Remove Black Counter - + Set Black Counters... - + Add Colorless Counter - + Remove Colorless Counter - + Set Colorless Counters... - + Add Other Counter - + Remove Other Counter - + Set Other Counters... - + Add Power (+1/+0) - + Remove Power (-1/-0) - + Move Toughness to Power (+1/-1) - + Add Toughness (+0/+1) - + Remove Toughness (-0/-1) - + Move Power to Toughness (-1/+1) - + Add Power and Toughness (+1/+1) - + Remove Power and Toughness (-1/-1) - + Set Power and Toughness... - + Reset Power and Toughness - + Untap - + Upkeep - + Draw Izvlačenje - + First Main Phase - + Start Combat - + Attack Napad - + Block - + Damage - + End Combat - + Second Main Phase - + End Kraj - + Next Phase - + Next Phase Action - + Next Turn - + + Hide Card in Reveal Window + + + + Tap / Untap Card - + Untap All - + Toggle Untap - + Turn Card Over - + Peek Card - + Play Card - + Attach Card... - + Unattach Card - + Clone Card - + Create Token... - + Create All Related Tokens - + Create Another Token - + Set Annotation... - - + + Select All Cards in Zone + + + + + Select All Cards in Row + + + + + Select All Cards in Column + + + + + Bottom of Library - - - - + + + + Exile Izgnanstvo - - - - + + + + Graveyard Groblje - - + + Hand Ruka - - + + Top of Library - - - + + + Battlefield, Face Down - + Battlefield - + Library Špil - + Sideboard - + Top Cards of Library - + + Bottom Cards of Library + + + + Close Recent View - - + + Stack - - + + Graveyard (Multiple) - - + + Exile (Multiple) - + Stack Until Found - + Draw Bottom Card - + Draw Multiple Cards from Bottom... - + Draw Arrow... - + Remove Local Arrows - + Leave Game - + Concede - + Roll Dice... - + Shuffle Library - + + Shuffle Top Cards of Library + + + + + Shuffle Bottom Cards of Library + + + + Mulligan - + Draw a Card - + Draw Multiple Cards... - + Undo Draw - + Always Reveal Top Card - + Always Look At Top Card - + Rotate View Clockwise - + Rotate View Counterclockwise - + Unfocus Text Box - + Focus Chat - + Clear Chat - + Refresh - + + Skip Forward + + + + + Skip Backward + + + + + Skip Forward by a lot + + + + + Skip Backward by a lot + + + + Play/Pause - + Toggle Fast Forward + + + Visual Deck Storage + + + + + Deck Storage + + + + + Server + + + + + Account + + + + + Administration + + + + + Logs + + \ No newline at end of file diff --git a/cockatrice/translations/cockatrice_sv.ts b/cockatrice/translations/cockatrice_sv.ts index 32b5e9262..a57f43d39 100644 --- a/cockatrice/translations/cockatrice_sv.ts +++ b/cockatrice/translations/cockatrice_sv.ts @@ -2,7 +2,7 @@ AbstractCounter - + &Set counter... &Placera polett... @@ -10,95 +10,247 @@ AbstractCounterDialog - + Set counter - + New value for counter '%1': + + AbstractDlgDeckTextEdit + + + &Refresh + + + + + Parse Set Name and Number (if available) + + + + + AbstractTabDeckEditor + + + Open in new tab + + + + + Are you sure? + + + + + The decklist has been modified. +Do you want to save the changes? + + + + + + + + + + + Error + + + + + Could not open deck at %1 + + + + + Could not save remote deck + + + + + + The deck could not be saved. +Please check that the directory is writable and try again. + + + + + Save deck + + + + + The deck could not be saved. + + + + + There are no cards in your deck to be exported + + + + + No deck was selected to be exported. + + + + + AdminNotesDialog + + + Update Notes + + + + + Admin Notes for %1 + + + + + AllZonesCardAmountWidget + + + Mainboard + + + + + Sideboard + + + AppearanceSettingsPage - + Error - + Could not create themes directory at '%1'. - + Theme settings Temainställningar - + Current theme: Aktuellt tema: - + Open themes folder - + + Menu settings + + + + + Show keyboard shortcuts in right-click menus + + + + Card rendering Kortrendering - + Display card names on cards having a picture Visa kortnamn på kort som har bilder - + + Auto-Rotate cards with sideways layout + + + + + Override all card art with personal set preference (Pre-ProviderID change behavior) [Requires Client restart] + + + + + Bump sets that the deck contains cards from to the top in the printing selector + + + + Scale cards on mouse over Skala kort på musen över - + + Use rounded card corners + + + + Minimum overlap percentage of cards on the stack and in vertical hand - + + Maximum initial height for card view window: + + + + + + rows + + + + + Maximum expanded height for card view window: + + + + Hand layout Handlayout - + Display hand horizontally (wastes space) Visa hand horisontellt (slösar plats) - + Enable left justification Aktivera vänsterjustering - + Table grid layout Bordets rutnätlayout - + Invert vertical coordinate Invertera vertikal koordinat - + Minimum player count for multi-column layout: Minst antal spelare för multi-kolumnlayout: - + Maximum font size for information displayed on cards: Maximal teckenstorlek för information som visas på kort: @@ -106,112 +258,112 @@ BanDialog - + ban &user name bannlys användar&namn - + ban &IP address bannlys &IP address - + ban client I&D förbjuda klienten I & D - + Ban type Typ av bannlysning - + &permanent ban &permanent bannlysning - + &temporary ban &temporär bannlysning - + &Days: &Dagar: - + &Hours: &Timmar: - + &Minutes: &Minuter: - + Duration of the ban Bannlysningens längd - + Please enter the reason for the ban. This is only saved for moderators and cannot be seen by the banned person. Vänligen ange anledningen till bannlysningen. Detta sparas endast för moderatorer och kan inte ses av den bannlysta personen. - + Please enter the reason for the ban that will be visible to the banned person. Vänligen ange en anledning för bannlysningen som kommer att visas för den bannlysta personen. - + Redact all messages from this user in all rooms - + &OK &OK - + &Cancel &Avbryt - + Ban user from server Bannlys användaren från servern - - - - + + + + Error Fel - + You have to select a name-based, IP-based, clientId based, or some combination of the three to place a ban. Du måste välja en namnbaserad, IP-baserad, clientId-baserad eller en kombination av de tre för att lägga ett förbud. - + You must have a value in the name ban when selecting the name ban checkbox. Du måste ha ett värde i namnförbudet när du markerar kryssrutan för namnförbud. - + You must have a value in the ip ban when selecting the ip ban checkbox. Du måste ha ett värde i ip-förbudet när du markerar kryssrutan ip-banan. - + You must have a value in the clientid ban when selecting the clientid ban checkbox. Du måste ha ett värde i clientidförbudet när du markerar kryssrutan för klientidförbud. @@ -219,22 +371,22 @@ Detta sparas endast för moderatorer och kan inte ses av den bannlysta personen. BetaReleaseChannel - - Beta Releases - Betaversioner + + Beta + - + No reply received from the release update server. Inget svar mottaget från uppdateringsservern. - + Invalid reply received from the release update server. Ogiltigt svar mottaget från uppdateringsserveren. - + No reply received from the file update server. Inget svar mottaget från filuppdateringsservern. @@ -242,32 +394,32 @@ Detta sparas endast för moderatorer och kan inte ses av den bannlysta personen. CardDatabaseModel - + Name Namn - + Sets Utgåvor - + Mana cost Manakostnad - + Card type Korttyp - + P/T P/T - + Color(s) Färg(er) @@ -360,325 +512,621 @@ Detta sparas endast för moderatorer och kan inte ses av den bannlysta personen. - CardFrame + CardInfoFrameWidget - + Image - Bild + - + Description - Beskrivning + - + Both - Både + + + + + View transformation + - CardInfoText + CardInfoPictureWidget - + + View related cards + + + + + Add card to deck + + + + + Mainboard + + + + + Sideboard + + + + + CardInfoTextWidget + + Name: - Namn: + - + Related cards: - Relaterade kort: + - + Unknown card: - Okänt kort: + CardItem - + &Move to Fl&ytta till - + &Power / toughness Po&wer / toughness + + CardSizeWidget + + + Card Size + + + CardZone - + their hand nominative Deras hand - + %1's hand nominative %1s hand - + their library look at zone Deras lek - + %1's library look at zone %1s bibliotek - + of their library top cards of zone, av deras lek - + of %1's library top cards of zone av %1s %1s bibliotek - + their library reveal zone Deras lek - + %1's library reveal zone %1s %1s bibliotek - + their library shuffle deras lek - + %1's library shuffle %1s lek - + their library nominative deras lek - + %1's library nominative %1s lek - + their graveyard nominative deras gravhög - + %1's graveyard nominative %1s gravhög - + their exile nominative deras exil - + %1's exile nominative %1s exil - + their sideboard look at zone deras skänk - + %1's sideboard look at zone - + their sideboard nominative deras skänk - + %1's sideboard nominative + + DeckEditorCardInfoDockWidget + + + Card Info + + + + + DeckEditorDatabaseDisplayWidget + + + Search by card name (or search expressions) + + + + + Add to Deck + + + + + Add to Sideboard + + + + + Select Printing + + + + + Show on EDHREC (Commander) + + + + + Show on EDHREC (Card) + + + + + Show Related cards + + + + + Add card to &maindeck + + + + + Add card to &sideboard + + + + + DeckEditorDeckDockWidget + + + Banner Card + + + + + Select Printing + + + + + Deck + + + + + Deck &name: + + + + + &Comments: + + + + + Hash: + + + + + &Increment number + + + + + &Decrement number + + + + + &Remove row + + + + + Swap card to/from sideboard + + + + + DeckEditorFilterDockWidget + + + Filters + + + + + &Clear all filters + + + + + Delete selected + + + + + DeckEditorMenu + + + &Deck Editor + + + + + &New deck + + + + + &Load deck... + + + + + Load recent deck... + + + + + Clear + + + + + &Save deck + + + + + Save deck &as... + + + + + Load deck from cl&ipboard... + + + + + Edit deck in clipboard + + + + + + Annotated + + + + + + Not Annotated + + + + + Save deck to clipboard + + + + + Annotated (No set info) + + + + + Not Annotated (No set info) + + + + + &Print deck... + + + + + &Send deck to online service + + + + + Create decklist (decklist.org) + + + + + Create decklist (decklist.xyz) + + + + + Analyze deck (deckstats.net) + + + + + Analyze deck (tappedout.net) + + + + + &Close + + + + + DeckEditorPrintingSelectorDockWidget + + + Printing Selector + + + DeckEditorSettingsPage - - + + Update Spoilers Uppdatera Spoilers - - + + Success - + Download URLs have been reset. - + Downloaded card pictures have been reset. - + Error Fel - + One or more downloaded card pictures could not be cleared. - + Add URL Lägg till webbadress - - + + URL: - - + + Edit URL - + + Network Cache Size: + + + + + Redirect Cache TTL: + + + + + How long cached redirects for urls are valid for. + + + + + Picture Cache Size: + + + + Add New URL - + Remove URL - + + Day(s) + + + + Updating... Uppdaterar ... - + Choose path Välj sökväg - + URL Download Priority - + Spoilers Spoilers - + Download Spoilers Automatically Ladda ner Spoilers automatiskt - + Spoiler Location: Spoiler Plats: - + Last Change Senaste ändring - + Spoilers download automatically on launch Spoilers laddas ner automatiskt vid lanseringen - + Press the button to manually update without relaunching Tryck på knappen för att manuellt uppdatera utan omstart - + Do not close settings until manual update is complete - + Download card pictures on the fly Ladda enkelt ned bilder på kort - + How to add a custom URL Hur man lägger till en valfri webbadress - + Delete Downloaded Images Ta bort nedladdade bilder - + Reset Download URLs Återställ nedladdnings-webbadresser - - Downloaded images directory size: - - - - + On-disk cache for downloaded pictures - - Picture cache size: - - - - + In-memory cache for pictures not currently on screen @@ -686,16 +1134,218 @@ Detta sparas endast för moderatorer och kan inte ses av den bannlysta personen. DeckListModel - + + Count + + + + + Set + + + + Number Nummer - + + Provider ID + + + + Card Kort + + DeckLoader + + + Common deck formats (%1) + + + + + All files (*.*) + + + + + DeckPreviewColorIdentityFilterWidget + + + Mode: Exact Match + + + + + Mode: Includes + + + + + DeckPreviewDeckTagsDisplayWidget + + + Edit tags ... + + + + + DeckPreviewTagDialog + + + Deck Tags Manager + + + + + Manage your deck tags. Check or uncheck tags as needed, or add new ones: + + + + + Add a new tag (e.g., Aggro️) + + + + + Add Tag + + + + + Filter tags... + + + + + OK + + + + + Cancel + + + + + Invalid Input + + + + + Tag name cannot be empty! + + + + + Duplicate Tag + + + + + This tag already exists. + + + + + DeckPreviewWidget + + + Banner Card + + + + + Open in deck editor + + + + + Edit Tags + + + + + Rename Deck + + + + + Save Deck to Clipboard + + + + + Annotated + + + + + Annotated (No set info) + + + + + Not Annotated + + + + + Not Annotated (No set info) + + + + + Rename File + + + + + Delete File + + + + + Set Banner Card + + + + + + New name: + + + + + + Error + + + + + Rename failed + + + + + Delete file + + + + + Are you sure you want to delete the selected file? + + + + + Delete failed + + + DeckStatsInterface @@ -713,181 +1363,217 @@ Detta sparas endast för moderatorer och kan inte ses av den bannlysta personen. DeckViewContainer - + Load deck... Ladda lek... - + Load remote deck... Ladda remote lek... - + + Unload deck + Unload deck... + + + + Ready to start - + + Force start + + + + Sideboard unlocked - + Sideboard locked - - Load deck - Ladda lek - - - + Error Fel - + The selected file could not be loaded. Den valda filen kunde ej laddas. + + DialogConvertDeckToCodFormat + + + Deck Format Conversion + + + + + You tried to add a tag to a .txt format deck. + Tags can only be added to .cod format decks. + Do you want to convert the deck to the .cod format? + + + + + Remember and automatically apply choice in the future + + + DlgConnect - + Downloading... Hämtar ... - + Known Hosts Kända värdar - + + Delete the currently selected saved server + + + + Refresh the server list with known public servers Uppdatera servern listan med kända offentliga servrar - + New Host Ny värd - + Name: Namn: - + &Host: &Värd: - + &Port: &Port: - + Player &name: Spelar&namn: - + P&assword: &Lösenord: - + &Save password &Spara lösenord - + A&uto connect Anslut automatiskt - + Automatically connect to the most recent login when Cockatrice opens Koppla automatiskt till den senaste inloggningen när Cockatrice öppnas - + If you have any trouble connecting or registering then contact the server staff for help! Om du har problem med att ansluta eller registrera kontaktar du serverns personal för hjälp! - - + + Webpage Webbsida - + Reset Password - + + Forgot password? + + + + &Connect &Ansluta - + Server Server - + Login Logga in - + Server Contact Serverkontakt - + Connect to Server Anslut till servern - + Server URL Server webbadress - + Communication Port - + Unique Server Name Unikt servernamn - + Connection Warning Anslutningsvarning - + You need to name your new connection profile. Du måste ange din nya anslutningsprofil. - + Connect Warning Anslutningsvarning - + The player name can't be empty. Fältet för spelarnamn kan inte lämnas tomt. @@ -970,27 +1656,37 @@ Detta sparas endast för moderatorer och kan inte ses av den bannlysta personen. Åskådare - + + Starting life total: + + + + + Game setup options + + + + &Clear Tydlig - + Create game Skapa spel - + Game information Spelinformation - + Error Fel - + Server error. Serverfel. @@ -998,92 +1694,92 @@ Detta sparas endast för moderatorer och kan inte ses av den bannlysta personen. DlgCreateToken - + &Name: &Namn: - + Token Jetong - + C&olor: &Färg: - + white vit - + blue blå - + black svart - + red röd - + green grön - + multicolor multifärgad - + colorless färglös - + &P/T: &P/T: - + &Annotation: &Annotering: - + &Destroy token when it leaves the table &Förstör jetong när den lämnar bordet - + Token data Jetongdata - + Show &all tokens Visa alla &jetonger - + Show tokens from this &deck Visa jetonger från denna &lek - + Choose token from list Välj jetong från lista - + Create token Skapa jetong @@ -1129,6 +1825,24 @@ För att radera din nuvarande avatar, klicka "bekräfta" utan att väl Ogiltigt val av bild. + + DlgEditDeckInClipboard + + + Edit deck in clipboard + + + + + Error + + + + + Invalid deck list. + + + DlgEditPassword @@ -1171,93 +1885,93 @@ För att radera din nuvarande avatar, klicka "bekräfta" utan att väl DlgEditTokens - + &Name: &Namn: - + C&olor: &Färg: - + white vit - + blue blå - + black svart - + red röd - + green grön - + multicolor multifärgad - + colorless färglös - + &P/T: &P/T: - + &Annotation: &Annotering: - + Token data Jetongdata - - + + Add token Lägg till jetong - + Remove token Ta bort jetong - + Edit custom tokens - + Please enter the name of the token: Vänligen ange namnet på jetongen: - + Error Fel - + The chosen name conflicts with an existing card or token. Make sure to enable the 'Token' set in the "Manage sets" dialog to display them correctly. Det valda namnet står i konflikt med ett befintligt kort eller token. @@ -1295,132 +2009,132 @@ Se till att du aktiverar "Token" i dialogrutan "Hantera uppsättn DlgFilterGames - + no limit - + 5 minutes - + 10 minutes - + 30 minutes - + 1 hour - + 2 hours - - Show '&buddies only' games + + Hide 'buddies only' games - - Show &full games + + Hide full games - - Show games &that have started + + Hide games that have started - - Show &password protected games - Visa lösenordsskyddade spel: - - - - Hide '&ignored user' games + + Hide password protected games - + + Hide 'ignored user' games + + + + &Newer than: - + Game &description: Spel&beskrivning: - + &Creator name: S&kaparens namn: - + General - + &Game types &Speltyper - + at &least: &Minst: - + at &most: M&ax: - + Maximum player count Maximalt antal spelare - + Restrictions begränsningar - + Show games only if &spectators can watch - + Show spectator password p&rotected games - + Show only if spectators can ch&at - + Show only if spectators can see &hands - + Spectators - + Filter games Filtrera spel @@ -1603,27 +2317,28 @@ Se till att du aktiverar "Token" i dialogrutan "Hantera uppsättn Lösenorden stämmer inte överens. + + DlgLoadDeck + + + Load Deck + + + DlgLoadDeckFromClipboard - - &Refresh - &Uppdatera - - - + Load deck from clipboard Ladda lek från urklipp - - + Error Fel - - + Invalid deck list. Ogiltig leklista. @@ -1631,11 +2346,49 @@ Se till att du aktiverar "Token" i dialogrutan "Hantera uppsättn DlgLoadRemoteDeck - + Load deck Ladda lek + + DlgMoveTopCardsUntil + + + Card name (or search expressions): + + + + + Number of hits: + + + + + Auto play hits + + + + + Put top cards on stack until... + + + + + No cards matching the search expression exists in the card database. Proceed anyways? + + + + + Cockatrice + + + + + Invalid filter + + + DlgRegister @@ -1749,12 +2502,12 @@ Your email will be used to verify your account. DlgSettings - + Unknown Error loading card database Ett okänt fel har inträffat vid laddande av kortdatabasen - + Your card database is invalid. Cockatrice may not function correctly with an invalid database @@ -1771,7 +2524,7 @@ Möjligen kan du behöva starta om oracle för att uppdatera din kortdatabas. Vill du ändra dina databaslokaliseringsinställningar? - + Your card database version is too old. This can cause problems loading card information or images @@ -1788,7 +2541,7 @@ Detta kan oftast fixas genom att starta om oracle för att uppdatera din kortdat Vill du ändra dina kortdatabaslokaliseringsinställningar? - + Your card database did not finish loading Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues with your cards.xml attached @@ -1797,7 +2550,7 @@ Would you like to change your database location setting? - + File Error loading your card database. Would you like to change your database location setting? @@ -1806,7 +2559,7 @@ Would you like to change your database location setting? Vill du ändra dina kortdatabaslokaliseringsinställningar? - + Your card database was loaded but contains no cards. Would you like to change your database location setting? @@ -1815,7 +2568,7 @@ Would you like to change your database location setting? Vill du ändra dina kortdatabaslokaliseringsinställningar? - + Unknown card database load status Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues @@ -1824,59 +2577,59 @@ Would you like to change your database location setting? - - - + + + Error Fel - + The path to your deck directory is invalid. Would you like to go back and set the correct path? Sökvägen till din lekkatalog är ogiltig. Vill du gå tillbaka och ange den korrekta sökvägen? - + The path to your card pictures directory is invalid. Would you like to go back and set the correct path? Sökvägen till din kortbildsdatabas är ogiltig. Vill du gå tillbaka och ange den korrekta sökvägen? - + Settings Inställningar - + General Allmänt - + Appearance Utseende - + User Interface Gränssnitt - + Card Sources - + Chat Chatt - + Sound Ljud - + Shortcuts Genvägar @@ -1902,166 +2655,169 @@ Would you like to change your database location setting? DlgUpdate - + Current release channel Nuvarande släppkanal - + Reinstall Installera - + Cancel Download Avbryt Ladda ner - + Open Download Page Öppna nedladdningssidan - + Check for Client Updates - - - - + + + + Error Fel - + Cockatrice was not built with SSL support, therefore you cannot download updates automatically! Please visit the download page to update manually. Cockatrice byggdes inte med SSL-stöd, därför kan du inte ladda ner uppdateringar automatiskt! Besök sidan för nedladdning för att uppdatera manuellt. - - Downloading update... - Laddar ner uppdatering... + + Downloading update: %1 + Downloading update... + - + Checking for updates... Letar efter nya uppdateringar... - + Finished checking for updates Slutfört efter uppdateringar - + No Update Available Ingen uppdatering tillgänglig - + Cockatrice is up to date! Cockatris är aktuell! - + You are already running the latest version available in the chosen release channel. Du kör redan den senaste versionen i den valda utgåvan. - + Current version Aktuell version - + Selected release channel Vald utgivningskanal - - + + Update Available Uppdatering tillgänglig - - + + A new version of Cockatrice is available! En ny version av Cockatrice är tillgänglig! - - + + New version Ny version - - + + Released Släppte - - + + Changelog Ändringslogg - + Do you want to update now? Vill du uppdatera nu? - - Unfortunately there are no download packages available for your operating system. -You may have to build from source yourself. - Tyvärr finns inga nedladdningspaket tillgängliga för ditt operativsystem. -Du kanske måste bygga från källan själv. + + Unfortunately, the automatic updater failed to find a compatible download. +You may have to manually download the new version. + Unfortunately there are no download packages available for your operating system. +You may have to build from source yourself. + - - Please check the download page manually and visit the wiki for instructions on compiling. - Vänligen kontrollera nedladdningssidan manuellt och besök wikin för instruktioner om kompilering. + + Please check the <a href="%1">releases page</a> on our Github and download the build for your system. + Please check the download page manually and visit the wiki for instructions on compiling. + - - - + + + Update Error Uppdateringsfel - + An error occurred while checking for updates: Ett fel uppstod när du letade efter uppdateringar: - + An error occurred while downloading an update: Ett fel uppstod när du hämtade en uppdatering: - + Installing... Installerar... - + Cockatrice is unable to open the installer. Cockatrice kan inte öppna installatören. - + Try to update manually by closing Cockatrice and running the installer. Försök att uppdatera manuellt genom att stänga Cockatrice och köra installationsprogrammet. - + Download location Hämta plats @@ -2079,6 +2835,14 @@ Du kanske måste bygga från källan själv. Felsökningslogg + + EdhrecCommanderResponseCommanderDetailsDisplayWidget + + + Salt: + + + FilterBuilder @@ -2090,105 +2854,120 @@ Du kanske måste bygga från källan själv. GameSelector - - - - - - - - - + + + + + + + + + Error Fel - + Please join the appropriate room first. Vänligen anslut till det lämpliga rummet först. - + Wrong password. Fel lösenord. - + Spectators are not allowed in this game. Åskådare är ej tillåtna i detta spelet. - + The game is already full. Spelet är redan fullt. - + The game does not exist any more. Det här spelet finns inte längre. - + This game is only open to registered users. Det här spelet är bara öppet för registrerade användare. - + This game is only open to its creator's buddies. Det här spelet är bara öppet för skaparens vänner. - + You are being ignored by the creator of this game. Spelets skapare ignorerar dig. - + + Join Game + + + + + Spectate Game + + + + + Game Information + + + + Join game Anslut till spel - + Password: Lösenord: - + Please join the respective room first. Vänligen anslut till respektive rum först. - + &Filter games Specificera speltyp - + C&lear filter Rensa filter - + C&reate Skapa - + &Join Anslut - + J&oin as spectator Anslut som &åskådare - + Games shown: %1 / %2 - + Games Spel @@ -2196,100 +2975,100 @@ Du kanske måste bygga från källan själv. GamesModel - + >1 day - + %1%2 hr short age in hours - + new - + %1%2 min short age in minutes - + password lösenord - + buddies only endast vänner - + reg. users only endast reg. användare - - + + can chat kan chatta - + see hands visa händer - + can see hands Tillåts se händer - + not allowed ej tillåtna - + Room Rum - + Age Ålder - + Description Beskrivning - + Creator Skapare - + Type Typ - + Restrictions Begränsningar - + Players Spelare - + Spectators Åskådare @@ -2297,92 +3076,102 @@ Du kanske måste bygga från källan själv. GeneralSettingsPage - + Reset all paths - + All paths have been reset - - + + Choose path Välj sökväg - + Personal settings Personliga inställningar - + Language: Språk: - + Paths (editing disabled in portable mode) Sökvägar (redigering inaktiverad i bärbart läge) - + Paths Sökvägar - + + How to help with translations + + + + Decks directory: Lekkatalog: - + Replays directory: Repriskatalog: - + Pictures directory: Bildkatalog: - + Card database: Kortdatabas: - + Custom database directory: - + Token database: Token-databas: - + Update channel Uppdatera kanal - + + Check for client updates on startup + + + + Notify if a feature supported by the server is missing in my client Meddela om en funktion som stöds av servern saknas i min klient - + Automatically run Oracle when running a new version of Cockatrice - + Show tips on startup Visa tips vid uppstart @@ -2390,61 +3179,61 @@ Du kanske måste bygga från källan själv. MainWindow - - + + The server has reached its maximum user capacity, please check back later. Servern har nått sin maximala kapacitet för användare, var god försök igen vid ett senare tillfälle. - + There are too many concurrent connections from your address. Din adress har för många uppkopplingar samtidigt. - + Banned by moderator Bannlyst av moderator - + Expected end time: %1 Förväntad sluttid: %1 - + This ban lasts indefinitely. Denna bannlysning varar för evigt. - + Scheduled server shutdown. Schemalagd serverstängning. - - + + Invalid username. Ogiltigt användarnamn. - + You have been logged out due to logging in at another location. Du loggades ut på grund av att du loggade in via en annan plats. - + Connection closed Uppkoppling avslutad - + The server has terminated your connection. Reason: %1 Servern har avslutat din uppkoppling. Anledning: %1 - + The server is going to be restarted in %n minute(s). All running games will be lost. Reason for shutdown: %1 @@ -2455,656 +3244,667 @@ Alla pågående spel kommer att gå förlorade. Anledning till nedstängning: %1 - + Scheduled server shutdown Schemalagd serverstängning - - + + Success Framgång - + Registration accepted. Will now login. Registrering lyckad. Inloggning påbörjad. - + Account activation accepted. Will now login. Aktivering tillåten. Inloggning påbörjad. - + Number of players Antal spelare - + Please enter the number of players. Vänligen ange antal spelare. - - + + Player %1 Spelare %1 - + Load replay Ladda repris - + About Cockatrice Om Cockatrice - + Version - + Cockatrice Webpage - + Project Manager: Projektledare: - + Past Project Managers: Tidigare projektledare: - + Developers: utvecklare: - + Our Developers Våra utvecklare - + Help Develop! - + Translators: Översättare: - + Our Translators - + Help Translate! - + Support: - + Report an Issue - + Troubleshooting - + F.A.Q. - - - - - - - - - - - - - - - + + + + + + + + + + + + + - - - + + + + + Error Fel - + Server timeout Server timeout - + Failed Login - + Your client seems to be missing features this server requires for connection. - + To update your client, go to 'Help -> Check for Client Updates'. - + Incorrect username or password. Please check your authentication information and try again. - + There is already an active session using this user name. Please close that session first and re-login. Det finns redan en aktiv session med det användarnamnet. Vänligen stäng den sessionen först och försök igen. - - + + You are banned until %1. Du är bannlyst till %1. - - + + You are banned indefinitely. Du är bannlyst för evigt. - + This server requires user registration. Do you want to register now? - + This server requires client IDs. Your client is either failing to generate an ID or you are running a modified client. Please close and reopen your client to try again. - + An internal error has occurred, please close and reopen Cockatrice before trying again. If the error persists, ensure you are running the latest version of the software and if needed contact the software developers. - + Account activation - + Your account has not been activated yet. You need to provide the activation token received in the activation email. - + Server Full - + Unknown login error: %1 Okänt inloggningsfel: %1 - - + + This usually means that your client version is out of date, and the server sent a reply your client doesn't understand. - + Your username must respect these rules: - + is %1 - %2 characters long - + can %1 contain lowercase characters - - - - + + + + NOT - + can %1 contain uppercase characters - + can %1 contain numeric characters - + can contain the following punctuation: %1 - + first character can %1 be a punctuation mark - + no unacceptable language as specified by these server rules: note that the following lines will not be translated - + can not contain any of the following words: %1 - + can not match any of the following expressions: %1 - + You may only use A-Z, a-z, 0-9, _, ., and - in your username. - - - - - - + + + + + + Registration denied - + Registration is currently disabled on this server - + There is already an existing account with the same user name. - + It's mandatory to specify a valid email address when registering. - + It appears you are attempting to register a new account on this server yet you already have an account registered with the email provided. This server restricts the number of accounts a user can register per address. Please contact the server operator for further assistance or to obtain your credential information. - + Password too short. - + Registration failed for a technical problem on the server. - + Unknown registration error: %1 - + Account activation failed - + Socket error: %1 Socketfel: %1 - + You are trying to connect to an obsolete server. Please downgrade your Cockatrice version or connect to a suitable server. Local version is %1, remote version is %2. Du försöker koppla upp dig till en föråldrad server. Vänligen nergradera din version av Cockatrice eller koppla upp dig till en lämplig server. Lokal version är %1, avlägsen version är %2. - + Your Cockatrice client is obsolete. Please update your Cockatrice version. Local version is %1, remote version is %2. Din version av Cockatrice är föråldrad. Vänligen uppdatera din version av Cockatrice. Lokal version är %1, avlägsen version är %2. - + Connecting to %1... Ansluter till %1... - + Registering to %1 as %2... - + Disconnected Frånkopplad - + Connected, logging in at %1 Uppkopplad, loggar in hos %1 - - - + + + Requesting forgotten password to %1 as %2... - + &Connect... &Anslut... - + &Disconnect &Frånkoppla - + Start &local game... Starta &lokalt spel... - + &Watch replay... &Titta på repris... - - &Deck editor - Lek&redigeraren - - - + &Full screen &Fullskärmsläge - + &Register to server... - + &Restore password... - + &Settings... &Inställningar... - + &Exit A&vsluta - + A&ctions - + &Cockatrice &Cockatrice - + C&ard Database - + &Manage sets... - + Edit custom &tokens... - + Open custom image folder - + Open custom sets folder - + Add custom sets/cards - + + Reload card database + + + + + Tabs + + + + &Help &Hjälp - + &About Cockatrice &Om Cockatrice - + &Tip of the Day - + Check for Client Updates - + Check for Card Updates... - + View &Debug Log - + + Open Settings Folder + + + + Show/Hide - + New Version - + Congratulations on updating to Cockatrice %1! Oracle will now launch to update your card database. - + Cockatrice installed - + Congratulations on installing Cockatrice %1! Oracle will now launch to install the initial card database. - + Card database - + Cockatrice is unable to load the card database. Do you want to update your card database now? If unsure or first time user, choose "Yes" - - + + Yes - - + + No - + Open settings - + New sets found - + %n new set(s) found in the card database Set code(s): %1 Do you want to enable it/them? - + View sets - + Welcome - + Hi! It seems like you're running this version of Cockatrice for the first time. All the sets in the card database have been enabled. Read more about changing the set order or disabling specific sets and consequent effects in the "Manage Sets" dialog. - - + + Information - + A card database update is already running. - + Unable to run the card database updater: - - failed to start. + + Failed to start. The file might be missing, or permissions might be incorrect. - - crashed. + + The process crashed some time after starting successfully. - - timed out. + + Timed out. The process took too long to respond. The last waitFor...() function timed out. - - write error. + + An error occurred when attempting to write to the process. For example, the process may not be running, or it may have closed its input channel. - - read error. + + An error occurred when attempting to read from the process. For example, the process may not be running. - - unknown error. + + Unknown error occurred. - - The card database updater exited with an error: %1 + + The card database updater exited with an error: +%1 - + This server supports additional features that your client doesn't have. This is most likely not a problem, but this message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -3112,54 +3912,54 @@ To update your client, go to Help -> Check for Updates. - - - - - + + + + + Load sets/cards - + Selected file cannot be found. - + You can only import XML databases at this time. - + The new sets/cards have been added successfully. Cockatrice will now reload the card database. - + Sets/cards failed to import. - - - + + + Reset Password - + Your password has been reset successfully, you can now log in using the new credentials. - + Failed to reset user account password, please contact the server operator to reset your password. - + Activation request received, please check your email for an activation token. @@ -3433,179 +4233,190 @@ Cockatrice will now reload the card database. - %1 is looking at the top %3 card(s) %2. + %1 is looking at the %4 %3 card(s) %2. + %1 is looking at the top %3 card(s) %2. top card for singular, top %3 cards for plural - + + bottom + + + + + top + + + + %1 turns %2 face-down. - + %1 turns %2 face-up. - + The game has been closed. Spelet har stängts. - + The game has started. Spelet har börjat. - + %1 has joined the game. - + %1 is now watching the game. %1 kollar nu på spelet. - + You have been kicked out of the game. Du har blivit utsparkad från spelet. - + %1 has left the game (%2). %1 har lämnat spelet (%2) - + %1 is not watching the game any more (%2). %1 tittar inte längre på spelet (%2). - + %1 is not ready to start the game any more. - + %1 shuffles their deck and draws a new hand of %2 card(s). - + %1 shuffles their deck and draws a new hand. - + You are watching a replay of game #%1. - + %1 is ready to start the game. - + cards an unknown amount of cards - + %1 card(s) a card for singular, %1 cards for plural - + %1 lends %2 to %3. - + %1 reveals %2 to %3. - + %1 reveals %2. - + %1 randomly reveals %2%3 to %4. - + %1 randomly reveals %2%3. - + %1 peeks at face down card #%2. - + %1 peeks at face down card #%2: %3. - + %1 reveals %2%3 to %4. - + %1 reveals %2%3. - + %1 reversed turn order, now it's %2. - + reversed - + normal - + Heads - + Tails - + %1 flipped a coin. It landed as %2. - + %1 rolls a %2 with a %3-sided die. - + %1 flips %2 coins. There are %3 heads and %4 tails. - + %1 rolls a %2-sided dice %3 times: %4. @@ -3725,17 +4536,17 @@ Cockatrice will now reload the card database. - + %1 unattaches %2. - + %1 undoes their last draw. - + %1 undoes their last draw (%2). @@ -3743,110 +4554,110 @@ Cockatrice will now reload the card database. MessagesSettingsPage - + Word1 Word2 Word3 - + Add New Message - + Edit Message - + Remove Message - + Add message Lägg till meddelande - - + + Message: Meddelande: - + Edit message - + Chat settings - + Custom alert words - + Enable chat mentions - + Enable mention completer - + In-game message macros - + How to use in-game message macros - + Ignore chat room messages sent by unregistered users - + Ignore private messages sent by unregistered users - - + + Invert text color - + Enable desktop notifications for private messages - + Enable desktop notification for mentions - + Enable room message history on join - - + + (Color is hexadecimal) - + Separate words with a space, alphanumeric characters only @@ -3904,6 +4715,24 @@ Cockatrice will now reload the card database. + + OverlapControlWidget + + + Cards to overlap: + + + + + Overlap percentage: + + + + + Overlap direction: + + + Phase @@ -3970,57 +4799,57 @@ Cockatrice will now reload the card database. PhasesToolbar - + Untap step Uptappningssteget - + Upkeep step Underhållssteget - + Draw step Dragsteget - + First main phase Första huvudfasen - + Beginning of combat step Början av stridssteget - + Declare attackers step Attacksteget - + Declare blockers step Blockeringssteget - + Combat damage step Skadesteget - + End of combat step Slutet av stridssteget - + Second main phase Andra huvudfasen - + End of turn step Slutsteget @@ -4028,7 +4857,7 @@ Cockatrice will now reload the card database. PictureLoader - + en code for scryfall's language property, not available for all languages @@ -4037,647 +4866,781 @@ Cockatrice will now reload the card database. Player - + Reveal top cards of library - - - - - - - - - + + + + + + + + + + + + Number of cards: (max. %1) - + &View graveyard &Titta på kyrkogård - + &View exile &Titta på exil - + Player "%1" Spelare "%1" - - - - + + + + &Graveyard &Kyrkogård - - - - + + + + &Exile &Exil - + &Move hand to... - - - + + + &Top of library - - - + + + &Bottom of library - + &Move graveyard to... - - - - + + + + &Hand &Hand - + &Move exile to... - + &View library &Titta på leken - + &View hand - + View &top cards of library... Titta på de &översta korten i leken... - + Reveal &library to... - + &Always reveal top card &Avslöja alltid det översta kortet - + &View sideboard &Titta på sidbrädan - + &Draw card &Dra kort - + D&raw cards... D&ra kort... - + &Undo last draw &Ångra senaste drag - + Take &mulligan &Mulligan - - &Shuffle - &Blanda - - - + Play top card &face down - + Move top card to grave&yard - + Move top card to e&xile - + Move top cards to &graveyard... Flytta översta korten till &kyrkogården... - + Move top cards to &exile... Flytta översta korten till &exil... - + Put top card on &bottom Placera översta kortet &underst - + + View bottom cards of library... + + + + Lend library to... - + + Shuffle + + + + Put top cards on stack &until... Take top cards &until... - + + Shuffle top cards... + + + + + Shuffle bottom cards... + + + + &Reveal hand to... - + Reveal r&andom card to... - + Reveal random card to... - + &Sideboard S&idbräda - + &Library &Lek - + &Counters &Poletter - + &Untap all permanents Tappa upp alla perma&nenta kort - + R&oll die... Rulla t&ärning... - + &Create token... &Skapa jetong... - + C&reate another token S&kapa en till jetong - + Cr&eate predefined token Skapa fördefinierad &jetong - - + + &All players A&lla spelare - + S&ay S&äg - + + &Select All + + + + + S&elect Row + + + + + S&elect Column + + + + &Play &Spela - + &Hide &Göm - + Play &Face Down - + &Tap / Untap Turn sideways or back again - + Toggle &normal untapping Växla &normal upptappning - + T&urn Over Turn face up/face down - + &Peek at card face Kika på kort&framsida - + &Clone &Klona - + Attac&h to card... &Fäst på kort... - + Unattac&h Se&parera - + &Draw arrow... &Rita pil... - + &Increase power &Öka power - + &Decrease power &Minska power - + I&ncrease toughness Öka toug&hness - + D&ecrease toughness Minska t&oughness - + In&crease power and toughness Öka power o&ch toughness - + Dec&rease power and toughness M&inska power och toughness - + Increase power and decrease toughness - + Decrease power and increase toughness - + Set &power and toughness... An&ge power och toughness... - + Reset p&ower and toughness - + &Set annotation... Ang&e annotering... - + Red - + Yellow - + Green - + &Add counter (%1) - + &Remove counter (%1) - + &Set counters (%1)... - + &Top of library in random order - + X cards from the top of library... X kort från toppen av biblioteket... - + &Bottom of library in random order - + View top cards of library Titta på de översta korten i leken - + + View bottom cards of library + + + + + Shuffle top cards of library + + + + + Shuffle bottom cards of library + + + + Which position should this card be placed: - + (max. %1) - + Draw hand - + 0 and lower are in comparison to current hand size - + Draw cards Dra kort - - + + Number: Antal: - + Move top cards to grave Flytta översta korten till kyrkogården - + Reveal &top cards to... - + &Top of library... - + &Bottom of library... - + &Always look at top card - + &Open deck in deck editor - + &Play top card - + &Draw bottom card - + D&raw bottom cards... - + &Play bottom card - + Play bottom card &face down - + Move bottom card to grave&yard - + Move bottom card to e&xile - + Move bottom cards to &graveyard... - + Move bottom cards to &exile... - + Put bottom card on &top - + Selec&ted cards - + Move top cards to exile Flytta översta korten till exil - + Move bottom cards to grave - + Move bottom cards to exile - + Draw bottom cards - - + + C&reate another %1 token - + Create tokens - + Place card X cards from top of library Lägg kort X kort från toppen av biblioteket - + Change power/toughness - + Change stats to: - + Set annotation Ange annotering - + Please enter the new annotation: Vänligen ange den nya annoteringen: - + Set counters Placera poletter - + Re&veal to... - + View related cards - + Token: - + All tokens + + PrintingSelector + + + Display Navigation Buttons + + + + + <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. + + + + + PrintingSelectorCardOverlayWidget + + + Preference + + + + + Pin Printing + + + + + Unpin Printing + + + + + Show Related cards + + + + + PrintingSelectorCardSearchWidget + + + Search by set name or set code + + + + + PrintingSelectorCardSelectionWidget + + + Previous Card in Deck + + + + + Next Card in Deck + + + + + PrintingSelectorCardSortingWidget + + + Alphabetical + + + + + Preference + + + + + Release Date + + + + + + Descending + + + + + Ascending + + + QMenuBar - + Services - + Hide %1 - + Hide Others - + Show All - + Preferences... - + Quit %1 - + About %1 @@ -4685,38 +5648,32 @@ Cockatrice will now reload the card database. QObject - + Cockatrice card database (*.xml) - - + All files (*.*) Alla filer (*.*) - + Cockatrice replays (*.cor) Cockatricerepriser (*.cor) - - Common deck formats (*.cod *.dec *.dek *.txt *.mwDeck) - - - - + Maindeck - + Sideboard - + Tokens @@ -4724,92 +5681,92 @@ Cockatrice will now reload the card database. QPlatformTheme - + OK - + Save - + Save All - + Open - + &Yes - + Yes to &All - + &No - + N&o to All - + Abort - + Retry - + Ignore - + Close - + Cancel - + Discard - + Help - + Apply - + Reset - + Restore Defaults @@ -4817,17 +5774,17 @@ Cockatrice will now reload the card database. RemoteDeckList_TreeModel - + Name Namn - + ID ID - + Upload time Uppladdningstidpunkt @@ -4835,32 +5792,32 @@ Cockatrice will now reload the card database. RemoteReplayList_TreeModel - + ID ID - + Name Namn - + Players Spelare - + Keep Behåll - + Time started Tid startad - + Duration (sec) Varaktighet (sek) @@ -4906,32 +5863,32 @@ Cockatrice will now reload the card database. SequenceEdit - + Choose an action from the table - + Hit the key/combination of keys you want to set for this action - - Shortcut already in use - - - - + Invalid key - + + Shortcut already in use by: + + + + Clear - + Restore default @@ -4967,72 +5924,80 @@ Cockatrice will now reload the card database. ShortcutSettingsPage - - + + Restore all default shortcuts - + Do you really want to restore all default shortcuts? - + Clear all default shortcuts - + Do you really want to clear all shortcuts? - - Action - - - - - Shortcut - - - - + Section: - + Action: - + Shortcut: - + How to set custom shortcuts - + Clear all shortcuts + + + Search by shortcut name + + + + + ShortcutTreeView + + + Action + + + + + Shortcut + + ShortcutsSettings - + Your configuration file contained invalid shortcuts. Please check your shortcut settings! - + The following shortcuts have been set to default: @@ -5041,17 +6006,17 @@ Please check your shortcut settings! ShutdownDialog - + &Reason for shutdown: Anledning till &nedstängning: - + &Time until shutdown (minutes): &Tid till nedstängning (minuter): - + Shut down server Stäng ned server @@ -5059,27 +6024,27 @@ Please check your shortcut settings! SoundSettingsPage - + Enable &sounds - + Current sounds theme: - + Test system sound engine - + Sound settings - + Master volume @@ -5087,48 +6052,48 @@ Please check your shortcut settings! SpoilerBackgroundUpdater - + Spoilers season has ended - + Deleting spoiler.xml. Please run Oracle - - + + Spoilers download failed - + No internet connection - + Error - + Spoilers already up to date - + No new spoilers added - + Spoilers have been updated! - + Last change: @@ -5136,636 +6101,593 @@ Please check your shortcut settings! StableReleaseChannel - - Stable Releases + + Default - + No reply received from the release update server. - + Invalid reply received from the release update server. - + No reply received from the tag update server. - + Invalid reply received from the tag update server. + + TabAccount + + + Add to Buddy List + + + + + Add to Ignore List + + + + + Account + + + TabAdmin - + Administration - + Update server &message &Uppdatera servermeddelande - + &Shut down server &Stäng ned server - + &Reload configuration - + Server administration functions Serveradministrationsfunktioner - + + Server moderator functions + + + + + Replay ID + + + + + Grant Replay Access + + + + + Username to Activate + + + + + Force Activate User + + + + &Unlock functions &Lås upp funktioner - + &Lock functions Lås &funktioner + + + + Success + + + + + Replay access granted + + + + + + + + + Error + + + + + Unable to grant replay access. Replay ID invalid + + + + + Unable to grant replay access. Internal error + + + + + User successfully activated + + + + + Unable to activate user. Username invalid + + + + + Unable to activate user. User already active + + + + + Unable to activate user. Internal error + + TabDeckEditor - - Search by card name (or search expressions) - Search by card name - - - - - Add to Deck - - - - - Add to Sideboard - - - - - Show Related cards - - - - - &Clear all filters - - - - - Delete selected - - - - - Deck &name: - &Leknamn: - - - - &Comments: - &Kommentarer: - - - - Hash: - Hash: - - - - &New deck - &Ny lek - - - - &Load deck... - &Ladda lek... - - - - &Save deck - S&para lek - - - - Save deck &as... - Spa&ra lek som... - - - - Load deck from cl&ipboard... - Ladda lek &från urklipp... - - - - Save deck to clipboard - - - - - Annotated - - - - - Not Annotated - - - - - &Print deck... - Skri&v ut lek... - - - - &Send deck to online service - - - - - Create decklist (decklist.org) - - - - - Analyze deck (deckstats.net) - - - - - Analyze deck (tappedout.net) - - - - - &Close - S&täng - - - - Add card to &maindeck - Lägg till kort till &huvudlek - - - - Add card to &sideboard - Lägg till kort i sidbr&äda - - - - &Remove row - Ta bort ra&d - - - - &Increment number - &Öka antal - - - - &Decrement number - &Minska antal - - - - &Deck Editor - - - - - + Card Info - - + Deck - - + Filters - + &View - - - + + Printing + + + + + + + Visible - - - + + + + Floating - + Reset layout - + Deck: %1 Lek: %1 - - - Are you sure? - Är du säker? - - - - The decklist has been modified. -Do you want to save the changes? - Denna leklista har modifierats. Vill du spara ändringarna? - - - - Load deck - Ladda lek - - - - - - - - - Error - Fel - - - - The deck could not be saved. - Leken kunde inte sparas. - - - - Could not save remote deck - - - - - - The deck could not be saved. -Please check that the directory is writable and try again. - Leken kunde inte sparas. -Vänligen se till att katalogen är skrivbar och försök igen. - - - - Save deck - Spara lek - - - - There are no cards in your deck to be exported - - - - - No deck was selected to be saved. - - TabDeckStorage - - Deck storage - - - - + Local file system Lokalt filsystem - + Server deck storage Serverns leklagring - - + + Open in deck editor Öppna i lekredigeraren - + + Rename deck or folder + + + + Upload deck Ladda upp lek - + Download deck Ladda ner lek - - + + + + New folder Ny mapp - - + + Delete Radera - - + + Open decks folder + + + + + Rename local folder + + + + + Rename local file + + + + + New name: + + + + + + + Error - + + Rename failed + + + + + Invalid deck file - + Enter deck name Ange leknamn - + This decklist does not have a name. Please enter a name: Denna leklista har inget namn. Vänligen ange ett namn: - + Unnamed deck Namnlös lek - + Failed to upload deck to server - + Delete local file Radera lokal fil - - - - Are you sure you want to delete "%1"? - Är du säker på att du vill radera "%1"? + + Are you sure you want to delete the selected files? + - + + Delete remote decks + + + + + Are you sure you want to delete the selected decks? + + + + + Name of new folder: Namn på den nya mappen: - - Delete remote folder + + Deck Storage + + + + + TabDeckStorageVisual + + + Visual Deck Storage - - Delete remote deck - Radera avlägsen lek + + Error + + + + + Could not open deck at %1 + + + + + TabEdhRec + + + EDHREC: + TabGame - - - + + + Replay - - + + Game - - + + Player List - - + + Card Info - - + + Messages - - + + Replay Timeline - + &Phases &Faser - + &Game &Spel - + Next &phase Nästa &fas - + Next phase with &action - + Next &turn Nästa &tur - + Reverse turn order - + &Remove all local arrows Ta &bort alla lokala pilar - + Rotate View Cl&ockwise - + Rotate View Co&unterclockwise - + Game &information Spel&information - + + Un&concede + + + + &Concede &Ge upp - + &Leave game &Lämna spel - + C&lose replay S&täng repris - + &Focus Chat - + &Say: S&äg: - + &View - - - - + + + + Visible - - - - + + + + Floating - + Reset layout - + Concede Ge upp - + Are you sure you want to concede this game? Är du säker på att du vill ge upp detta spel? - + Unconcede - + You have already conceded. Do you want to return to this game? - + Leave game Lämna spel - + Are you sure you want to leave this game? Är du säker på att du vill lämna detta spel? - + You are flooding the game. Please wait a couple of seconds. - + A player has joined game #%1 - + %1 has joined the game - + kicked by game host or moderator - + player left the game - + player disconnected from server - + reason unknown - + You have been kicked out of the game. @@ -5778,152 +6700,152 @@ Please enter a name: - - - + + + Time;SenderName;SenderIP;Message;TargetID;TargetName - + Room Logs - + Game Logs - + Chat Logs - - + + Error - + You must select at least one filter. - + You have to select a valid number of days to locate. - + Username: - + IP Address: - + Game Name: - + GameID: - + Message: - + Main Room - + Game Room - + Private Chat - + Past X Days: - + Today - + Last Hour - + Maximum Results: - + At least one filter is required. The more information you put in, the more specific your results will be. - + Get User Logs - + Clear Filters - + Filters - + Log Locations - + Date Range - + Maximum Results - - + + Message History - + Failed to collect message history information. - + There are no messages for the selected filters. @@ -5946,22 +6868,22 @@ The more information you put in, the more specific your results will be. - + This user is ignoring you, they cannot see your messages in main chat and you cannot join their games. - + Private message from - + %1 has left the server. %1 har lämnat servern. - + %1 has joined the server. %1 har anslutit sig till servern. @@ -5969,117 +6891,153 @@ The more information you put in, the more specific your results will be. TabReplays - - Game replays - - - - + Local file system Lokalt filsystem - + Server replay storage Serverns reprislagring - - + + Watch replay Titta på repris - - + + Rename + + + + + + New folder + + + + + Delete Radera - + + Open replays folder + + + + Download replay Ladda ner repris - + Toggle expiration lock Växla utgångslås - + + Rename local folder + + + + + Rename local file + + + + + New name: + + + + + Error + + + + + Rename failed + + + + + Name of new folder: + + + + Delete local file Radera lokal fil - - Are you sure you want to delete "%1"? - Är du säker på att du vill radera "%1"? - - - - Downloading Replays + + Are you sure you want to delete the selected files? - - Folder download is not yet supported. Please download replays individually. + + Are you sure you want to delete the selected replays? - + Delete remote replay Radera avlägsen repris - - Are you sure you want to delete the replay of game %1? - Är du säker på att du vill radera reprisen av spel %1? + + Game Replays + TabRoom - + &Say: &Säg: - + Chat Chatt - + &Room &Rum - + &Leave room &Lämna rum - + &Clear chat - + Chat Settings... - + mentioned you. - + Click to view - + You are flooding the chat. Please wait a couple of seconds. Du översvämmar chatten. Var vänlig vänta ett par sekunder. @@ -6092,30 +7050,30 @@ The more information you put in, the more specific your results will be. - - - - + + + + Error - + Failed to join the server room: it doesn't exist on the server. - + The server thinks you are in the server room but your client is unable to display it. Try restarting your client. - + You do not have the required permission to join this server room. - + Failed to join the server room due to an unknown error: %1. @@ -6123,32 +7081,72 @@ The more information you put in, the more specific your results will be. TabSupervisor - + + Deck Editor + + + + + &Visual Deck Storage + + + + + Server + + + + + Account + + + + + Deck Storage + + + + + Game Replays + + + + + Administration + + + + + Logs + + + + Are you sure? - + There are still open games. Are you sure you want to quit? - + Click to view - + Your buddy %1 has signed on! - + Unknown Event - + The server has sent you a message that your client does not understand. This message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -6156,61 +7154,43 @@ To update your client, go to Help -> Check for Updates. Servern har skickat dig ett meddelande som din klient inte förstår. Det här meddelandet kan betyda att det finns en ny version av Cockatrice tillgänglig eller så kör den här servern en användarinställd eller förhandsutgiven version. För att uppdatera din klient, gå till Hjälp -> Leta efter uppdateringar. - + Idle Timeout - + You are about to be logged out due to inactivity. Du kommer snart loggas ut på grund av inaktivitet. - + Promotion - + You have been promoted. Please log out and back in for changes to take effect. - + Warned Varnad - + You have received a warning due to %1. Please refrain from engaging in this activity or further actions may be taken against you. If you have any questions, please private message a moderator. - + You have received the following message from the server. (custom messages like these could be untranslated) - - TabUserLists - - - Account - Konto - - - - Add to Buddy List - - - - - Add to Ignore List - - - TappedOutInterface @@ -6294,173 +7274,184 @@ Please refrain from engaging in this activity or further actions may be taken ag UserContextMenu - + User &details Användar&detaljer - + Private &chat - + Show this user's &games Visa denna användarens &spel - + Add to &buddy list Lägg till som &vän - + Remove from &buddy list Ta bort som &vän - + Add to &ignore list &Ignorera - + Remove from &ignore list Sluta &ignorera - + Kick from &game &Sparka från spelet - + Warn user - + View user's war&n history - + Ban from &server &Bannlys från servern - + View user's &ban history - + &Promote user to moderator - + Dem&ote user from moderator - + Promote user to &judge - + Demote user from judge - + + View admin notes + + + + %1's games %1's spel - - - + + + Ban History - + Ban Time;Moderator;Ban Length;Ban Reason;Visible Reason - + User has never been banned. - + Failed to collect ban information. - - - + + + Warning History - + Warning Time;Moderator;User Name;Reason - + User has never been warned. - + Failed to collect warning information. - - + + Failed to get admin notes. + + + + + Success - + Successfully promoted user. - + Successfully demoted user. - - + + + Failed - + Failed to promote user. - + Failed to demote user. - + Copy hash to clipboard - + Remove this user's messages @@ -6468,173 +7459,173 @@ Please refrain from engaging in this activity or further actions may be taken ag UserInfoBox - + Location: Plats: - + Account Age: - + Edit - + Change password - + Change avatar - + Administrator Administratör - + Moderator Moderator - + Registered user Registrerad användare - - + + Unregistered user Oregistrerad användare - + Judge - + Unknown - + The entered password does not match your account. - - - + + + Information - + User information updated. - - - - - - - - - - + + + + + + + + + + Error - + User Information - + Real Name: - + User Level: - + %n Year(s), amount of years (only shown if more than 0) - + %10%n Day(s) %20 amount of years (if more than 0), amount of days, date in local short format - + Enter Password - + Password verification is required in order to change your email address - - - + + + An error occurred while trying to update your user information. - + This server does not permit you to update your user informations. - + Password changed. - + This server does not permit you to change your password. - + The new password is too short. - + The old password is incorrect. - + Avatar updated. - + This server does not permit you to update your avatar. - + An error occured while trying to updater your avatar. @@ -6642,124 +7633,251 @@ Please refrain from engaging in this activity or further actions may be taken ag UserInterfaceSettingsPage - + General interface settings Allmänna gränssnittsinställningar - + &Double-click cards to play them (instead of single-click) &Dubbelklicka på kort för att spela dem (istället för enkelklick) - + + &Clicking plays all selected cards (instead of just the clicked card) + + + + &Play all nonlands onto the stack (not the battlefield) by default - + + Close card view window when last card is removed + + + + Annotate card text on tokens - + Use tear-off menus, allowing right click menus to persist on screen - + Notifications settings - + Enable notifications in taskbar - + Notify in the taskbar for game events while you are spectating - + Notify in the taskbar when users in your buddy list connect - + Animation settings Animationsinställningar - + &Tap/untap animation &Tappnings/Upptappningsanimation + + + Deck editor/storage settings + + + + + Open deck in new tab by default + + + + + Prompt before converting .txt decks to .cod format + + + + + Always convert if not prompted + + + + + Use visual deck storage in game lobby + + + + + Replay settings + + + + + Buffer time for backwards skip via shortcut: + + - UserList + UserListWidget - + Users connected to server: %1 - + Users in this room: %1 - Användare i detta rum: %1 + - + Buddies online: %1 / %2 - Vänner online: %1 / %2 + - + Ignored users online: %1 / %2 - Ignorerade användare online: %1 / %2 + + + + + VisualDeckStorageFolderDisplayWidget + + + Deck Storage + + + + + VisualDeckStorageSearchWidget + + + Search by filename + + + + + VisualDeckStorageSortWidget + + + Sort Alphabetically (Deck Name) + + + + + Sort Alphabetically (Filename) + + + + + Sort by Last Modified + + + + + Sort by Last Loaded + + + + + VisualDeckStorageWidget + + + Loading database ... + + + + + Show Folders + + + + + Show Tag Filter + + + + + Show Tags On Deck Previews + + + + + Show Banner Card Selection Option + + + + + Include Folder Names in Search + + + + + Draw unused Color Identities + + + + + Unused Color Identities Opacity + WarningDialog - + Which warning would you like to send? - + Redact all messages from this user in all rooms - + &OK - + &Cancel - + Warn user for misconduct - - + + Error - + User name to send a warning to can not be blank, please specify a user to warn. - + Warning to use can not be blank, please select a valid warning to send. @@ -6767,128 +7885,133 @@ Please refrain from engaging in this activity or further actions may be taken ag WndSets - + Move selected set to the top - + Move selected set up - + Move selected set down - + Move selected set to the bottom - + Search by set name, code, or type - + Default order - + Restore original art priority order - + Enable all sets - + Disable all sets - + Enable selected set(s) - + Disable selected set(s) - + Deck Editor - + Use CTRL+A to select all sets in the view. - + Only cards in enabled sets will appear in the card list of the deck editor. - + Image priority is decided in the following order: - + first the CUSTOM Folder (%1), then the Enabled Sets in this dialog (Top to Bottom) %1 is a link to the wiki - + Card Art - + How to use custom card art - + Hints - + Note - + Sorting by column allows you to find a set while not changing set priority. - + To enable ordering again, click the column header until this message disappears. - + Use the current sorting as the set priority instead - + Sorts the set priority using the same column - + + Include online-only (Arena) cards [requires restart] + + + + Manage sets @@ -6896,22 +8019,67 @@ Please refrain from engaging in this activity or further actions may be taken ag ZoneViewWidget - - sort by name - sortera efter namn + + Ungrouped + - - sort by type - sortera efter typ + + Group by Type + - + + Group by Mana Value + + + + + Group by Color + + + + + Unsorted + + + + + Sort by Name + + + + + Sort by Type + + + + + Sort by Mana Cost + + + + + Sort by Colors + + + + + Sort by P/T + + + + + Sort by Set + + + + shuffle when closing blanda när du stänger - + pile view @@ -6919,7 +8087,7 @@ Please refrain from engaging in this activity or further actions may be taken ag i18n - + English Svenska (Swedish) @@ -6927,12 +8095,12 @@ Please refrain from engaging in this activity or further actions may be taken ag main - + Connect on startup - + Debug to file @@ -6940,786 +8108,908 @@ Please refrain from engaging in this activity or further actions may be taken ag shortcutsTab - + Main Window - - + + Deck Editor - + Game Lobby - + Card Counters - + Player Counters - + Power and Toughness - + Game Phases - + Playing Area - + Move Selected Card - + View Se - + Move Top Card - + Move Bottom Card - + Gameplay Gameplay - + Drawing - + Chat Room - + Game Window - + Load Deck from Clipboard - + + Replays - + + Tabs + + + + Check for Card Updates... - + Connect... - + Disconnect - + Exit - + Full screen - + Register... - + Settings... - + Start a Local Game... - + Watch Replay... - - Analyze Deck + + Analyze Deck (deckstats.net) + Analyze Deck - - Clear All Filters - - - - - Clear Selected Filter - - - - - Close + + Analyze Deck (tappedout.net) - Remove Card + Clear All Filters + Clear Selected Filter + + + + + Close + + + + + Remove Card + + + + Manage Sets... - + Edit Custom Tokens... - - Export Deck - - - - - Add Card - - - - - Load Deck... - - - - - Load Deck from Clipboard... + + Export Deck (decklist.org) - New Deck + Export Deck (decklist.xyz) - - Open Custom Pictures Folder + + Add Card - - Print Deck... + + Load Deck... + Load Deck from Clipboard... + + + + + Edit Deck in Clipboard, Annotated + + + + + Edit Deck in Clipboard + + + + + New Deck + + + + + Open Custom Pictures Folder + + + + + Print Deck... + + + + Delete Card - - + + Reset Layout - + Save Deck - + Save Deck as... - + Save Deck to Clipboard, Annotated - - Save Deck to Clipboard - - - - - Load Local Deck... - - - - - Load Remote Deck... - - - - - Set Ready to Start - - - - - Toggle Sideboard Lock - - - - - - Add Green Counter - - - - - - Remove Green Counter - - - - - - Set Green Counters... + + Save Deck to Clipboard, Annotated (No Set Info) + Save Deck to Clipboard + + + + + Save Deck to Clipboard (No Set Info) + + + + + Load Local Deck... + + + + + Load Remote Deck... + + + + + Set Ready to Start + + + + + Toggle Sideboard Lock + + + + + + Add Green Counter + + + + + + Remove Green Counter + + + + + + Set Green Counters... + + + + Add Yellow Counter - + Remove Yellow Counter - + Set Yellow Counters... - - + + Add Red Counter - - + + Remove Red Counter - - + + Set Red Counters... - + Add Life Counter - + Remove Life Counter - + Set Life Counters... - + Add White Counter - + Remove White Counter - + Set White Counters... - + Add Blue Counter - + Remove Blue Counter - + Set Blue Counters... - + Add Black Counter - + Remove Black Counter - + Set Black Counters... - + Add Colorless Counter - + Remove Colorless Counter - + Set Colorless Counters... - + Add Other Counter - + Remove Other Counter - + Set Other Counters... - + Add Power (+1/+0) - + Remove Power (-1/-0) - + Move Toughness to Power (+1/-1) - + Add Toughness (+0/+1) - + Remove Toughness (-0/-1) - + Move Power to Toughness (-1/+1) - + Add Power and Toughness (+1/+1) - + Remove Power and Toughness (-1/-1) - + Set Power and Toughness... - + Reset Power and Toughness - + Untap - + Upkeep - + Draw - + First Main Phase - + Start Combat - + Attack - + Block - + Damage - + End Combat - + Second Main Phase - + End - + Next Phase - + Next Phase Action - + Next Turn - + + Hide Card in Reveal Window + + + + Tap / Untap Card - + Untap All - + Toggle Untap - + Turn Card Over - + Peek Card - + Play Card - + Attach Card... - + Unattach Card - + Clone Card - + Create Token... - + Create All Related Tokens - + Create Another Token - + Set Annotation... - - + + Select All Cards in Zone + + + + + Select All Cards in Row + + + + + Select All Cards in Column + + + + + Bottom of Library - - - - + + + + Exile Exil - - - - + + + + Graveyard Kyrkogård - - + + Hand - - + + Top of Library - - - + + + Battlefield, Face Down - + Battlefield - + Library Bibliotek - + Sideboard Skänk - + Top Cards of Library - + + Bottom Cards of Library + + + + Close Recent View - - + + Stack - - + + Graveyard (Multiple) - - + + Exile (Multiple) - + Stack Until Found - + Draw Bottom Card - + Draw Multiple Cards from Bottom... - + Draw Arrow... - + Remove Local Arrows - + Leave Game - + Concede Medge - + Roll Dice... - + Shuffle Library - + + Shuffle Top Cards of Library + + + + + Shuffle Bottom Cards of Library + + + + Mulligan - + Draw a Card - + Draw Multiple Cards... - + Undo Draw - + Always Reveal Top Card - + Always Look At Top Card - + Rotate View Clockwise - + Rotate View Counterclockwise - + Unfocus Text Box - + Focus Chat - + Clear Chat - + Refresh - + + Skip Forward + + + + + Skip Backward + + + + + Skip Forward by a lot + + + + + Skip Backward by a lot + + + + Play/Pause - + Toggle Fast Forward + + + Visual Deck Storage + + + + + Deck Storage + + + + + Server + + + + + Account + + + + + Administration + + + + + Logs + + \ No newline at end of file diff --git a/cockatrice/translations/cockatrice_tr.ts b/cockatrice/translations/cockatrice_tr.ts index 82fc02645..2ea49584a 100644 --- a/cockatrice/translations/cockatrice_tr.ts +++ b/cockatrice/translations/cockatrice_tr.ts @@ -2,7 +2,7 @@ AbstractCounter - + &Set counter... @@ -10,12 +10,12 @@ AbstractCounterDialog - + Set counter - + New value for counter '%1': @@ -23,77 +23,82 @@ AppearanceSettingsPage - + Error - + Could not create themes directory at '%1'. - + Theme settings Tema ayarları - + Current theme: Geçerli Tema - + Open themes folder - + Card rendering - + Display card names on cards having a picture - + Scale cards on mouse over - + + Minimum overlap percentage of cards on the stack and in vertical hand + + + + Hand layout - + Display hand horizontally (wastes space) Eli yatay göster (Daha fazla yer kaplar) - + Enable left justification - + Table grid layout - + Invert vertical coordinate Dikey koordinatı ters çevir - + Minimum player count for multi-column layout: - + Maximum font size for information displayed on cards: @@ -101,111 +106,111 @@ BanDialog - + ban &user name - + ban &IP address - + ban client I&D - + Ban type - + &permanent ban - + &temporary ban - + &Days: - + &Hours: - + &Minutes: - + Duration of the ban - + Please enter the reason for the ban. This is only saved for moderators and cannot be seen by the banned person. - + Please enter the reason for the ban that will be visible to the banned person. - + Redact all messages from this user in all rooms - + &OK - + &Cancel - + Ban user from server - - - - + + + + Error Hata - + You have to select a name-based, IP-based, clientId based, or some combination of the three to place a ban. - + You must have a value in the name ban when selecting the name ban checkbox. - + You must have a value in the ip ban when selecting the ip ban checkbox. - + You must have a value in the clientid ban when selecting the clientid ban checkbox. @@ -213,22 +218,22 @@ This is only saved for moderators and cannot be seen by the banned person. BetaReleaseChannel - + Beta Releases Beta Sürümü - + No reply received from the release update server. Sürüm güncelleme sunucusundan yanıt alınamadı. - + Invalid reply received from the release update server. Sürüm güncelleme sunucusundan geçersiz yanıt alındı. - + No reply received from the file update server. Dosya güncelleme sunucusundan yanıt alınamadı. @@ -236,32 +241,32 @@ This is only saved for moderators and cannot be seen by the banned person. CardDatabaseModel - + Name İsim - + Sets - + Mana cost Mana bedeli - + Card type Kart tipi - + P/T - + Color(s) Renk(ler) @@ -269,86 +274,86 @@ This is only saved for moderators and cannot be seen by the banned person. CardFilter - + AND Logical conjunction operator used in card filter VE - + OR Logical disjunction operator used in card filter VEYA - + AND NOT Negated logical conjunction operator used in card filter - + OR NOT Negated logical disjunction operator used in card filter - + Name İsim - + Type Tür - + Color Renk - + Text Yazı - + Set - + Mana Cost Mana Bedeli - + Mana Value - + Rarity Enderlik - + Power Güç - + Toughness Dayanıklılık - + Loyalty Sadakat - + Format @@ -356,17 +361,17 @@ This is only saved for moderators and cannot be seen by the banned person. CardFrame - + Image - + Description Açıklama - + Both @@ -374,17 +379,17 @@ This is only saved for moderators and cannot be seen by the banned person. CardInfoText - + Name: İsim: - + Related cards: - + Unknown card: Bilinmeyen kart: @@ -392,12 +397,12 @@ This is only saved for moderators and cannot be seen by the banned person. CardItem - + &Move to - + &Power / toughness @@ -405,121 +410,121 @@ This is only saved for moderators and cannot be seen by the banned person. CardZone - + their hand nominative - + %1's hand nominative - + their library look at zone - + %1's library look at zone - + of their library top cards of zone, - + of %1's library top cards of zone - + their library reveal zone - + %1's library reveal zone - + their library shuffle - + %1's library shuffle - + their library nominative - + %1's library nominative - + their graveyard nominative - + %1's graveyard nominative - + their exile nominative - + %1's exile nominative - + their sideboard look at zone - + %1's sideboard look at zone - + their sideboard nominative - + %1's sideboard nominative @@ -528,144 +533,164 @@ This is only saved for moderators and cannot be seen by the banned person. DeckEditorSettingsPage - - + + Update Spoilers - - + + Success Başarılı - + Download URLs have been reset. - + Downloaded card pictures have been reset. - + Error - + One or more downloaded card pictures could not be cleared. - + Add URL URL ekle - - + + URL: URL: - - + + Edit URL URL'yi düzenle - + Add New URL - + Remove URL - + Updating... Güncelleniyor... - + Choose path - + URL Download Priority - + Spoilers - + Download Spoilers Automatically - + Spoiler Location: - + Last Change - + Spoilers download automatically on launch - + Press the button to manually update without relaunching - + Do not close settings until manual update is complete - + Download card pictures on the fly - + How to add a custom URL - + Delete Downloaded Images - + Reset Download URLs + + + Downloaded images directory size: + + + + + On-disk cache for downloaded pictures + + + + + Picture cache size: + + + + + In-memory cache for pictures not currently on screen + + DeckListModel - + Number - + Card @@ -673,13 +698,13 @@ This is only saved for moderators and cannot be seen by the banned person. DeckStatsInterface - - + + Error - + The reply from the server could not be parsed. @@ -687,42 +712,42 @@ This is only saved for moderators and cannot be seen by the banned person. DeckViewContainer - + Load deck... - + Load remote deck... - + Ready to start - + Sideboard unlocked - + Sideboard locked - + Load deck - + Error - + The selected file could not be loaded. @@ -730,138 +755,138 @@ This is only saved for moderators and cannot be seen by the banned person. DlgConnect - + Downloading... - + Known Hosts - + Refresh the server list with known public servers - + New Host - + Name: - + &Host: - + &Port: - + Player &name: - + P&assword: - + &Save password - + A&uto connect - + Automatically connect to the most recent login when Cockatrice opens - + If you have any trouble connecting or registering then contact the server staff for help! - - + + Webpage - + Reset Password - + &Connect - + Server - + Login - + Server Contact - + Connect to Server - + Server URL Sunucu URL - + Communication Port - + Unique Server Name - + Connection Warning - + You need to name your new connection profile. - + Connect Warning - + The player name can't be empty. @@ -869,102 +894,102 @@ This is only saved for moderators and cannot be seen by the banned person. DlgCreateGame - + Re&member settings - + &Description: - + P&layers: - + General - + Game type - + &Password: - + Only &buddies can join - + Only &registered users can join - + Joining restrictions - + &Spectators can watch - + Spectators &need a password to watch - + Spectators can &chat - + Spectators can see &hands - + Create game as spectator - + Spectators - + &Clear - + Create game - + Game information - + Error - + Server error. @@ -972,92 +997,92 @@ This is only saved for moderators and cannot be seen by the banned person. DlgCreateToken - + &Name: - + Token - + C&olor: - + white - + blue - + black - + red - + green - + multicolor - + colorless - + &P/T: - + &Annotation: - + &Destroy token when it leaves the table - + Token data - + Show &all tokens - + Show tokens from this &deck - + Choose token from list - + Create token @@ -1065,39 +1090,39 @@ This is only saved for moderators and cannot be seen by the banned person. DlgEditAvatar - - + + No image chosen. - + To change your avatar, choose a new image. To remove your current avatar, confirm without choosing a new image. - + Browse... - + Change avatar - + Open Image - + Image Files (*.png *.jpg *.bmp) - + Invalid image chosen. @@ -1105,38 +1130,38 @@ To remove your current avatar, confirm without choosing a new image. DlgEditPassword - + Old password: - + New password: - + Confirm new password: - + Change password - - + + Error - + Your password is too short. - + The new passwords don't match. @@ -1144,93 +1169,93 @@ To remove your current avatar, confirm without choosing a new image. DlgEditTokens - + &Name: - + C&olor: - + white beyaz - + blue mavi - + black siyah - + red kırmızı - + green yeşil - + multicolor - + colorless - + &P/T: - + &Annotation: - + Token data - - + + Add token - + Remove token - + Edit custom tokens - + Please enter the name of the token: - + Error - + The chosen name conflicts with an existing card or token. Make sure to enable the 'Token' set in the "Manage sets" dialog to display them correctly. @@ -1239,27 +1264,27 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgEditUser - + Email: - + Country: - + Undefined - + Real name: - + Edit user profile @@ -1267,132 +1292,132 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgFilterGames - + no limit - + 5 minutes - + 10 minutes - + 30 minutes - + 1 hour - + 2 hours - + Show '&buddies only' games - + Show &full games - + Show games &that have started - + Show &password protected games - + Hide '&ignored user' games - + &Newer than: - + Game &description: - + &Creator name: - + General - + &Game types - + at &least: - + at &most: - + Maximum player count - + Restrictions - + Show games only if &spectators can watch - + Show spectator password p&rotected games - + Show only if spectators can ch&at - + Show only if spectators can see &hands - + Spectators - + Filter games @@ -1400,52 +1425,52 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgForgotPasswordChallenge - + Reset Password Challenge Warning - + A problem has occurred. Please try to request a new password again. - + Enter the information of the server and the account you'd like to request a new password for. - + &Host: - + &Port: - + Player &name: - + Email: - + Reset Password Challenge - + Reset Password Challenge Error - + The email address can't be empty. @@ -1453,37 +1478,37 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgForgotPasswordRequest - + Enter the information of the server you'd like to request a new password for. - + &Host: - + &Port: - + Player &name: - + Reset Password Request - + Reset Password Error - + The player name can't be empty. @@ -1491,86 +1516,86 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgForgotPasswordReset - + Reset Password Warning - + A problem has occurred. Please try to request a new password again. - + Enter the received token and the new password in order to set your new password. - + &Host: - + &Port: - + Player &name: - + Token: - - + + New Password: - + Reset Password - + The player name can't be empty. - - - - + + + + Reset Password Error - + The token can't be empty. - + The new password can't be empty. - + Error - + Your password is too short. - + The passwords do not match. @@ -1578,24 +1603,24 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgLoadDeckFromClipboard - + &Refresh - + Load deck from clipboard - - + + Error - - + + Invalid deck list. @@ -1603,7 +1628,7 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgLoadRemoteDeck - + Load deck @@ -1611,104 +1636,122 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgRegister - + Enter your information and the information of the server you'd like to register to. Your email will be used to verify your account. - + &Host: - + &Port: - + Player &name: - + P&assword: - + Password (again): - + Email: - + Email (again): - + Country: - + Undefined - + Real name: - + Register to server - - - - + + + + Registration Warning - + Your password is too short. - + Your passwords do not match, please try again. - + Your email addresses do not match, please try again. - + The player name can't be empty. + + DlgRollDice + + + Number of sides: + + + + + Number of dice: + + + + + Roll Dice + + + DlgSettings - + Unknown Error loading card database - + Your card database is invalid. Cockatrice may not function correctly with an invalid database @@ -1719,7 +1762,7 @@ Would you like to change your database location setting? - + Your card database version is too old. This can cause problems loading card information or images @@ -1730,7 +1773,7 @@ Would you like to change your database location setting? - + Your card database did not finish loading Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues with your cards.xml attached @@ -1739,21 +1782,21 @@ Would you like to change your database location setting? - + File Error loading your card database. Would you like to change your database location setting? - + Your card database was loaded but contains no cards. Would you like to change your database location setting? - + Unknown card database load status Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues @@ -1762,59 +1805,59 @@ Would you like to change your database location setting? - - - + + + Error - + The path to your deck directory is invalid. Would you like to go back and set the correct path? - + The path to your card pictures directory is invalid. Would you like to go back and set the correct path? - + Settings - + General - + Appearance - + User Interface - + Card Sources - + Chat - + Sound - + Shortcuts @@ -1822,17 +1865,17 @@ Would you like to change your database location setting? DlgTipOfTheDay - + Next - + Previous - + Tip of the Day @@ -1840,164 +1883,164 @@ Would you like to change your database location setting? DlgUpdate - + Current release channel - + Reinstall - + Cancel Download - + Open Download Page - + Check for Client Updates - - - - + + + + Error - + Cockatrice was not built with SSL support, therefore you cannot download updates automatically! Please visit the download page to update manually. - + Downloading update... - + Checking for updates... - + Finished checking for updates - + No Update Available - + Cockatrice is up to date! - + You are already running the latest version available in the chosen release channel. - + Current version - + Selected release channel - - + + Update Available - - + + A new version of Cockatrice is available! - - + + New version - - + + Released - - + + Changelog - + Do you want to update now? - + Unfortunately there are no download packages available for your operating system. You may have to build from source yourself. - + Please check the download page manually and visit the wiki for instructions on compiling. - - - + + + Update Error - + An error occurred while checking for updates: - + An error occurred while downloading an update: - + Installing... - + Cockatrice is unable to open the installer. - + Try to update manually by closing Cockatrice and running the installer. - + Download location @@ -2005,12 +2048,12 @@ You may have to build from source yourself. DlgViewLog - + Clear log when closing - + Debug Log @@ -2018,7 +2061,7 @@ You may have to build from source yourself. FilterBuilder - + Type your filter here @@ -2026,105 +2069,105 @@ You may have to build from source yourself. GameSelector - - - - - - - - - + + + + + + + + + Error - + Please join the appropriate room first. - + Wrong password. - + Spectators are not allowed in this game. - + The game is already full. - + The game does not exist any more. - + This game is only open to registered users. - + This game is only open to its creator's buddies. - + You are being ignored by the creator of this game. - + Join game - + Password: - + Please join the respective room first. - + &Filter games - + C&lear filter - + C&reate - + &Join - + J&oin as spectator - + Games shown: %1 / %2 - + Games @@ -2132,100 +2175,100 @@ You may have to build from source yourself. GamesModel - + >1 day - + %1%2 hr short age in hours - + new - + %1%2 min short age in minutes - + password - + buddies only - + reg. users only - - + + can chat - + see hands - + can see hands - + not allowed - + Room Oda - + Age Yaş - + Description Açıklama - + Creator Oluşturan - + Type Tür - + Restrictions - + Players Oyuncular - + Spectators İzleyici @@ -2233,97 +2276,92 @@ You may have to build from source yourself. GeneralSettingsPage - + Reset all paths - + All paths have been reset - - - - - - + + + + + + Choose path - + Personal settings Kişisel ayarlar - + Language: Dil: - + Paths (editing disabled in portable mode) - + Paths - + Decks directory: Deste konumu: - + Replays directory: Oyun Tekrar konumu: - + Pictures directory: Fotoğrafların Konumu - + Card database: Kart Arşivi - + Custom database directory: - + Token database: Token Arşivi - - Picture cache size: - - - - + Update channel - + Notify if a feature supported by the server is missing in my client - + Automatically run Oracle when running a new version of Cockatrice - + Show tips on startup @@ -2331,702 +2369,712 @@ You may have to build from source yourself. MainWindow - - + + The server has reached its maximum user capacity, please check back later. Bu server kapasitesi dolu,lütfen daha sonra tekrar deneyin. - + There are too many concurrent connections from your address. - + Banned by moderator Moderatör tafarından men edildi. - + Expected end time: %1 - + This ban lasts indefinitely. - + Scheduled server shutdown. - - + + Invalid username. Geçersiz kullanıcı adı - + You have been logged out due to logging in at another location. - + Connection closed Bağlantı kapatıldı - + The server has terminated your connection. Reason: %1 - + The server is going to be restarted in %n minute(s). All running games will be lost. Reason for shutdown: %1 - + Scheduled server shutdown - - + + Success - + Registration accepted. Will now login. - + Account activation accepted. Will now login. - + Number of players Oyuncu sayısı - + Please enter the number of players. Lütfen oyuncu sayısı girin. - - + + Player %1 Oyuncu %1 - + Load replay Tekrar yükle - + About Cockatrice Cockatrice hakkında - + Version - + Cockatrice Webpage Cockatrice WebSayfası - + Project Manager: Proje yöneticisi: - + Past Project Managers: - + Developers: Geliştiriciler: - + Our Developers Geliştiricilerimiz: - + Help Develop! Geliştirmemize yardım et! - + Translators: Çevirmenler: - + Our Translators - + Help Translate! Çevirilere yardım edin! - + Support: Destek: - + Report an Issue Sorun Bildir - + Troubleshooting Sorun Giderme - + F.A.Q. S.S.S - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + Error Hata - + Server timeout Server zaman aşımı - + Failed Login Başarısız Giriş - + Your client seems to be missing features this server requires for connection. - + To update your client, go to 'Help -> Check for Client Updates'. - + Incorrect username or password. Please check your authentication information and try again. Geçersiz kullanıcı adı veya şifre.Lütfen giriş bilgilerinizi kontrol edin ve tekrar deneyin. - + There is already an active session using this user name. Please close that session first and re-login. Bu hesap hala açık halde. Lütfen kapadıktan sonra tekrar giriş yapınız. - - + + You are banned until %1. %1 e kadar men edildiniz. - - + + You are banned indefinitely. Süresiz men edildiniz. - + This server requires user registration. Do you want to register now? Bu server üyelik gerektirmekte.Hemen üye olmak istermisiniz ? - + This server requires client IDs. Your client is either failing to generate an ID or you are running a modified client. Please close and reopen your client to try again. - + An internal error has occurred, please close and reopen Cockatrice before trying again. If the error persists, ensure you are running the latest version of the software and if needed contact the software developers. - + Account activation Hesap Aktivasyonu - + Your account has not been activated yet. You need to provide the activation token received in the activation email. - + Server Full Server dolu - + Unknown login error: %1 Bilinmeyen giriş hatası: %1 - - + + This usually means that your client version is out of date, and the server sent a reply your client doesn't understand. - + Your username must respect these rules: Kullanıcı adı bu kurallara uymalı : - + is %1 - %2 characters long - + can %1 contain lowercase characters - - - - + + + + NOT - + can %1 contain uppercase characters - + can %1 contain numeric characters - + can contain the following punctuation: %1 - + first character can %1 be a punctuation mark - + + no unacceptable language as specified by these server rules: + note that the following lines will not be translated + + + + can not contain any of the following words: %1 - + can not match any of the following expressions: %1 - + You may only use A-Z, a-z, 0-9, _, ., and - in your username. - - - - - - + + + + + + Registration denied - + Registration is currently disabled on this server - + There is already an existing account with the same user name. - + It's mandatory to specify a valid email address when registering. - + It appears you are attempting to register a new account on this server yet you already have an account registered with the email provided. This server restricts the number of accounts a user can register per address. Please contact the server operator for further assistance or to obtain your credential information. - + Password too short. - + Registration failed for a technical problem on the server. - + Unknown registration error: %1 - + Account activation failed - + Socket error: %1 - + You are trying to connect to an obsolete server. Please downgrade your Cockatrice version or connect to a suitable server. Local version is %1, remote version is %2. - + Your Cockatrice client is obsolete. Please update your Cockatrice version. Local version is %1, remote version is %2. - + Connecting to %1... - + Registering to %1 as %2... - + Disconnected - + Connected, logging in at %1 - - - + + + Requesting forgotten password to %1 as %2... - + &Connect... - + &Disconnect - + Start &local game... - + &Watch replay... - + &Deck editor - + &Full screen - + &Register to server... - + &Restore password... - + &Settings... - - + &Exit - + A&ctions - + &Cockatrice - + C&ard Database - + &Manage sets... - + Edit custom &tokens... - + Open custom image folder - + Open custom sets folder - + Add custom sets/cards - + &Help - + &About Cockatrice - + &Tip of the Day - + Check for Client Updates - + Check for Card Updates... - + View &Debug Log - + + Show/Hide + + + + New Version - + Congratulations on updating to Cockatrice %1! Oracle will now launch to update your card database. - + Cockatrice installed - + Congratulations on installing Cockatrice %1! Oracle will now launch to install the initial card database. - + Card database - + Cockatrice is unable to load the card database. Do you want to update your card database now? If unsure or first time user, choose "Yes" - - + + Yes - - + + No - + Open settings - + New sets found - + %n new set(s) found in the card database Set code(s): %1 Do you want to enable it/them? - + View sets - + Welcome - + Hi! It seems like you're running this version of Cockatrice for the first time. All the sets in the card database have been enabled. Read more about changing the set order or disabling specific sets and consequent effects in the "Manage Sets" dialog. - - + + Information - + A card database update is already running. - + Unable to run the card database updater: - + failed to start. - + crashed. - + timed out. - + write error. - + read error. - + unknown error. - + The card database updater exited with an error: %1 - + This server supports additional features that your client doesn't have. This is most likely not a problem, but this message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -3034,54 +3082,54 @@ To update your client, go to Help -> Check for Updates. - - - - - + + + + + Load sets/cards - + Selected file cannot be found. - + You can only import XML databases at this time. - + The new sets/cards have been added successfully. Cockatrice will now reload the card database. - + Sets/cards failed to import. - - - + + + Reset Password - + Your password has been reset successfully, you can now log in using the new credentials. - + Failed to reset user account password, please contact the server operator to reset your password. - + Activation request received, please check your email for an activation token. @@ -3089,555 +3137,575 @@ Cockatrice will now reload the card database. MessageLogWidget - + from play - + from their graveyard - + from exile - + from their hand - + the top card of %1's library - + the top card of their library - + from the top of %1's library - + from the top of their library - + the bottom card of %1's library - + the bottom card of their library - + from the bottom of %1's library - + from the bottom of their library - + from %1's library - + from their library - + from sideboard - + from the stack - + %1 is now keeping the top card %2 revealed. - + %1 is not revealing the top card %2 any longer. - + %1 can now look at top card %2 at any time. - + %1 no longer can look at top card %2 at any time. - + %1 attaches %2 to %3's %4. - + %1 has conceded the game. - + %1 has unconceded the game. - + %1 has restored connection to the game. - + %1 has lost connection to the game. - + %1 points from their %2 to themselves. - + %1 points from their %2 to %3. - + %1 points from %2's %3 to themselves. - + %1 points from %2's %3 to %4. - + %1 points from their %2 to their %3. - + %1 points from their %2 to %3's %4. - + %1 points from %2's %3 to their own %4. - + %1 points from %2's %3 to %4's %5. - + %1 creates token: %2%3. - + %1 has loaded a deck (%2). - + %1 has loaded a deck with %2 sideboard cards (%3). - + %1 destroys %2. - + a card - + %1 gives %2 control over %3. - + %1 puts %2 into play%3 face down. - + %1 puts %2 into play%3. - + %1 puts %2%3 into their graveyard. - + %1 exiles %2%3. - + %1 moves %2%3 to their hand. - + %1 puts %2%3 into their library. - + %1 puts %2%3 onto the bottom of their library. - + %1 puts %2%3 on top of their library. - + %1 puts %2%3 into their library %4 cards from the top. - + %1 moves %2%3 to sideboard. - + %1 plays %2%3. + + + %1 tries to draw from an empty library + + - + %1 draws %2 card(s). - + %1 is looking at %2. - + %1 is looking at the top %3 card(s) %2. top card for singular, top %3 cards for plural - + %1 turns %2 face-down. - + %1 turns %2 face-up. - + The game has been closed. - + The game has started. - + %1 has joined the game. - + %1 is now watching the game. - + You have been kicked out of the game. - + %1 has left the game (%2). - + %1 is not watching the game any more (%2). - + %1 is not ready to start the game any more. - + %1 shuffles their deck and draws a new hand of %2 card(s). - + %1 shuffles their deck and draws a new hand. - + You are watching a replay of game #%1. - + %1 is ready to start the game. - + cards an unknown amount of cards - + %1 card(s) a card for singular, %1 cards for plural - + + %1 lends %2 to %3. + + + + %1 reveals %2 to %3. - + %1 reveals %2. - + %1 randomly reveals %2%3 to %4. - + %1 randomly reveals %2%3. - + %1 peeks at face down card #%2. - + %1 peeks at face down card #%2: %3. - + %1 reveals %2%3 to %4. - + %1 reveals %2%3. - + %1 reversed turn order, now it's %2. - + reversed - + normal - + Heads - + Tails - + %1 flipped a coin. It landed as %2. - + %1 rolls a %2 with a %3-sided die. - + + %1 flips %2 coins. There are %3 heads and %4 tails. + + + + + %1 rolls a %2-sided dice %3 times: %4. + + + + %1's turn. - + %1 sets annotation of %2 to %3. - + %1 places %2 %3 on %4 (now %5). - + %1 removes %2 %3 from %4 (now %5). - + red counter(s) - + yellow counter(s) - + green counter(s) - + %1 sets counter %2 to %3 (%4%5). - + %1 sets %2 to not untap normally. - + %1 sets %2 to untap normally. - + %1 removes the PT of %2. - + %1 changes the PT of %2 from nothing to %4. - + %1 changes the PT of %2 from %3 to %4. - + %1 has locked their sideboard. - + %1 has unlocked their sideboard. - + %1 taps their permanents. - + %1 untaps their permanents. - + %1 taps %2. - + %1 untaps %2. - + %1 shuffles %2. - + %1 shuffles the bottom %3 cards of %2. - + %1 shuffles the top %3 cards of %2. - + %1 shuffles cards %3 - %4 of %2. - + %1 unattaches %2. - + %1 undoes their last draw. - + %1 undoes their last draw (%2). @@ -3645,105 +3713,110 @@ Cockatrice will now reload the card database. MessagesSettingsPage - + Word1 Word2 Word3 - + Add New Message - + Edit Message - + Remove Message - + Add message - - + + Message: - + Edit message - + Chat settings - + Custom alert words - + Enable chat mentions - + Enable mention completer - + In-game message macros - + + How to use in-game message macros + + + + Ignore chat room messages sent by unregistered users - + Ignore private messages sent by unregistered users - - + + Invert text color - + Enable desktop notifications for private messages - + Enable desktop notification for mentions - + Enable room message history on join - - + + (Color is hexadecimal) - + Separate words with a space, alphanumeric characters only @@ -3751,52 +3824,52 @@ Cockatrice will now reload the card database. Mtg - + Card Type - + Mana Value - + Color(s) - + Loyalty - + Main Card Type - + Mana Cost - + P/T - + Side - + Layout - + Color Identity @@ -3804,62 +3877,62 @@ Cockatrice will now reload the card database. Phase - + Unknown Phase - + Untap - + Upkeep - + Draw - + First Main - + Beginning of Combat - + Declare Attackers - + Declare Blockers - + Combat Damage - + End of Combat - + Second Main - + End/Cleanup @@ -3867,57 +3940,57 @@ Cockatrice will now reload the card database. PhasesToolbar - + Untap step - + Upkeep step - + Draw step - + First main phase - + Beginning of combat step - + Declare attackers step - + Declare blockers step - + Combat damage step - + End of combat step - + Second main phase - + End of turn step @@ -3925,7 +3998,7 @@ Cockatrice will now reload the card database. PictureLoader - + en code for scryfall's language property, not available for all languages @@ -3934,603 +4007,609 @@ Cockatrice will now reload the card database. Player - + Reveal top cards of library - - - - - - - - - + + + + + + + + + Number of cards: (max. %1) - + &View graveyard - + &View exile - + Player "%1" - - - - + + + + &Graveyard - - - - + + + + &Exile - + &Move hand to... - - - - + + + &Top of library - - - + + + &Bottom of library - + &Move graveyard to... - - - - + + + + &Hand - + &Move exile to... - + &View library - + &View hand - + View &top cards of library... - + Reveal &library to... - + &Always reveal top card - + &View sideboard - + &Draw card - + D&raw cards... - + &Undo last draw - + Take &mulligan - + &Shuffle - + Play top card &face down - + Move top card to grave&yard - + Move top card to e&xile - + Move top cards to &graveyard... - + Move top cards to &exile... - + Put top card on &bottom - + + Lend library to... + + + + + Put top cards on stack &until... + Take top cards &until... + + + + &Reveal hand to... - + Reveal r&andom card to... - + Reveal random card to... - + &Sideboard - + &Library - + &Counters - + &Untap all permanents - + R&oll die... - + &Create token... - + C&reate another token - + Cr&eate predefined token - + + &All players - + S&ay - + &Play - + &Hide - + Play &Face Down - + &Tap / Untap Turn sideways or back again - + Toggle &normal untapping - + T&urn Over Turn face up/face down - + &Peek at card face - + &Clone - + Attac&h to card... - + Unattac&h - + &Draw arrow... - + &Increase power - + &Decrease power - + I&ncrease toughness - + D&ecrease toughness - + In&crease power and toughness - + Dec&rease power and toughness - + Increase power and decrease toughness - + Decrease power and increase toughness - + Set &power and toughness... - + Reset p&ower and toughness - + &Set annotation... - + Red - + Yellow - + Green - + &Add counter (%1) - + &Remove counter (%1) - + &Set counters (%1)... - + + &Top of library in random order + + + + X cards from the top of library... - + &Bottom of library in random order - + View top cards of library - + Which position should this card be placed: - + (max. %1) - + Draw hand - + 0 and lower are in comparison to current hand size - + Draw cards - - + + Number: - + Move top cards to grave - + Reveal &top cards to... - + &Top of library... - + &Bottom of library... - + &Always look at top card - + &Open deck in deck editor - + &Play top card - + &Draw bottom card - + D&raw bottom cards... - + &Play bottom card - + Play bottom card &face down - + Move bottom card to grave&yard - + Move bottom card to e&xile - + Move bottom cards to &graveyard... - + Move bottom cards to &exile... - + Put bottom card on &top - + Selec&ted cards - + Move top cards to exile - + Move bottom cards to grave - + Move bottom cards to exile - + Draw bottom cards - - Roll die - - - - - Number of sides: - - - - - + + C&reate another %1 token - + Create tokens - + Place card X cards from top of library - + Change power/toughness - + Change stats to: - + Set annotation - + Please enter the new annotation: - + Set counters - + + Re&veal to... + + + + View related cards - + Token: - - Attach to - - - - + All tokens @@ -4538,37 +4617,37 @@ Cockatrice will now reload the card database. QMenuBar - + Services - + Hide %1 - + Hide Others - + Show All - + Preferences... - + Quit %1 - + About %1 @@ -4576,116 +4655,131 @@ Cockatrice will now reload the card database. QObject - + Cockatrice card database (*.xml) - - + + All files (*.*) - + Cockatrice replays (*.cor) - + Common deck formats (*.cod *.dec *.dek *.txt *.mwDeck) + + + Maindeck + + + + + Sideboard + + + + + Tokens + + QPlatformTheme - + OK - + Save - + Save All - + Open - + &Yes - + Yes to &All - + &No - + N&o to All - + Abort - + Retry - + Ignore - + Close - + Cancel - + Discard - + Help - + Apply - + Reset - + Restore Defaults @@ -4693,17 +4787,17 @@ Cockatrice will now reload the card database. RemoteDeckList_TreeModel - + Name - + ID - + Upload time @@ -4711,32 +4805,32 @@ Cockatrice will now reload the card database. RemoteReplayList_TreeModel - + ID - + Name - + Players - + Keep - + Time started - + Duration (sec) @@ -4744,65 +4838,98 @@ Cockatrice will now reload the card database. RoomSelector - + Rooms - + Joi&n - + Room - + Description - + Permissions - + Players - + Games + + SequenceEdit + + + Choose an action from the table + + + + + Hit the key/combination of keys you want to set for this action + + + + + Shortcut already in use + + + + + Invalid key + + + + + Clear + + + + + Restore default + + + SetsModel - + Enabled - + Set type - + Set code - + Long name - + Release date @@ -4810,66 +4937,72 @@ Cockatrice will now reload the card database. ShortcutSettingsPage - + + Restore all default shortcuts - + Do you really want to restore all default shortcuts? - + Clear all default shortcuts - + Do you really want to clear all shortcuts? - + Action - + Shortcut - + Section: - + Action: - + Shortcut: - + How to set custom shortcuts + + + Clear all shortcuts + + ShortcutsSettings - + Your configuration file contained invalid shortcuts. Please check your shortcut settings! - + The following shortcuts have been set to default: @@ -4878,17 +5011,17 @@ Please check your shortcut settings! ShutdownDialog - + &Reason for shutdown: - + &Time until shutdown (minutes): - + Shut down server @@ -4896,27 +5029,27 @@ Please check your shortcut settings! SoundSettingsPage - + Enable &sounds - + Current sounds theme: - + Test system sound engine - + Sound settings - + Master volume @@ -4924,48 +5057,48 @@ Please check your shortcut settings! SpoilerBackgroundUpdater - + Spoilers season has ended - + Deleting spoiler.xml. Please run Oracle - - + + Spoilers download failed - + No internet connection - + Error - + Spoilers already up to date - + No new spoilers added - + Spoilers have been updated! - + Last change: @@ -4973,27 +5106,27 @@ Please check your shortcut settings! StableReleaseChannel - + Stable Releases - + No reply received from the release update server. - + Invalid reply received from the release update server. - + No reply received from the tag update server. - + Invalid reply received from the tag update server. @@ -5001,37 +5134,37 @@ Please check your shortcut settings! TabAdmin - + Administration - + Update server &message - + &Shut down server - + &Reload configuration - + Server administration functions - + &Unlock functions - + &Lock functions @@ -5039,252 +5172,253 @@ Please check your shortcut settings! TabDeckEditor - - Search by card name + + Search by card name (or search expressions) + Search by card name - + Add to Deck - + Add to Sideboard - + Show Related cards - + &Clear all filters - + Delete selected - + Deck &name: - + &Comments: - + Hash: - + &New deck - + &Load deck... - + &Save deck - + Save deck &as... - + Load deck from cl&ipboard... - + Save deck to clipboard - + Annotated - + Not Annotated - + &Print deck... - + &Send deck to online service - + Create decklist (decklist.org) - + Analyze deck (deckstats.net) - + Analyze deck (tappedout.net) - + &Close - + Add card to &maindeck - + Add card to &sideboard - + &Remove row - + &Increment number - + &Decrement number - + &Deck Editor - - + + Card Info - - + + Deck - - + + Filters - + &View - - - + + + Visible - - - + + + Floating - + Reset layout - + Deck: %1 - + Are you sure? - + The decklist has been modified. Do you want to save the changes? - + Load deck - - - - - - + + + + + + Error - + The deck could not be saved. - + Could not save remote deck - - + + The deck could not be saved. Please check that the directory is writable and try again. - + Save deck - + There are no cards in your deck to be exported - + No deck was selected to be saved. @@ -5292,104 +5426,104 @@ Please check that the directory is writable and try again. TabDeckStorage - + Deck storage - + Local file system - + Server deck storage - - + + Open in deck editor - + Upload deck - + Download deck - - + + New folder - - + + Delete - - + + Error - + Invalid deck file - + Enter deck name - + This decklist does not have a name. Please enter a name: - + Unnamed deck - + Failed to upload deck to server - + Delete local file - - - + + + Are you sure you want to delete "%1"? - + Name of new folder: - + Delete remote folder - + Delete remote deck @@ -5397,210 +5531,210 @@ Please enter a name: TabGame - - - + + + Replay - - + + Game - - + + Player List - - + + Card Info - - + + Messages - - + + Replay Timeline - + &Phases - + &Game - + Next &phase - + Next phase with &action - + Next &turn - + Reverse turn order - + &Remove all local arrows - + Rotate View Cl&ockwise - + Rotate View Co&unterclockwise - + Game &information - + &Concede - + &Leave game - + C&lose replay - + &Focus Chat - + &Say: - + &View - - - - + + + + Visible - - - - + + + + Floating - + Reset layout - + Concede - + Are you sure you want to concede this game? - + Unconcede - + You have already conceded. Do you want to return to this game? - + Leave game - + Are you sure you want to leave this game? - + You are flooding the game. Please wait a couple of seconds. - + A player has joined game #%1 - + %1 has joined the game - + kicked by game host or moderator - + player left the game - + player disconnected from server - + reason unknown - + You have been kicked out of the game. @@ -5608,157 +5742,157 @@ Please enter a name: TabLog - + Logs - - - + + + Time;SenderName;SenderIP;Message;TargetID;TargetName - + Room Logs - + Game Logs - + Chat Logs - - + + Error - + You must select at least one filter. - + You have to select a valid number of days to locate. - + Username: - + IP Address: - + Game Name: - + GameID: - + Message: - + Main Room - + Game Room - + Private Chat - + Past X Days: - + Today - + Last Hour - + Maximum Results: - + At least one filter is required. The more information you put in, the more specific your results will be. - + Get User Logs - + Clear Filters - + Filters - + Log Locations - + Date Range - + Maximum Results - - + + Message History - + Failed to collect message history information. - + There are no messages for the selected filters. @@ -5766,37 +5900,37 @@ The more information you put in, the more specific your results will be. TabMessage - + Private &chat - + &Leave - + %1 - Private chat - + This user is ignoring you, they cannot see your messages in main chat and you cannot join their games. - + Private message from - + %1 has left the server. - + %1 has joined the server. @@ -5804,69 +5938,69 @@ The more information you put in, the more specific your results will be. TabReplays - + Game replays - + Local file system - + Server replay storage - - + + Watch replay - - + + Delete - + Download replay - + Toggle expiration lock - + Delete local file - + Are you sure you want to delete "%1"? - + Downloading Replays - + Folder download is not yet supported. Please download replays individually. - + Delete remote replay - + Are you sure you want to delete the replay of game %1? @@ -5874,47 +6008,47 @@ The more information you put in, the more specific your results will be. TabRoom - + &Say: - + Chat - + &Room - + &Leave room - + &Clear chat - + Chat Settings... - + mentioned you. - + Click to view - + You are flooding the chat. Please wait a couple of seconds. @@ -5922,35 +6056,35 @@ The more information you put in, the more specific your results will be. TabServer - + Server - - - - + + + + Error - + Failed to join the server room: it doesn't exist on the server. - + The server thinks you are in the server room but your client is unable to display it. Try restarting your client. - + You do not have the required permission to join this server room. - + Failed to join the server room due to an unknown error: %1. @@ -5958,32 +6092,32 @@ The more information you put in, the more specific your results will be. TabSupervisor - + Are you sure? - + There are still open games. Are you sure you want to quit? - + Click to view - + Your buddy %1 has signed on! - + Unknown Event - + The server has sent you a message that your client does not understand. This message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -5991,38 +6125,38 @@ To update your client, go to Help -> Check for Updates. - + Idle Timeout - + You are about to be logged out due to inactivity. - + Promotion - + You have been promoted. Please log out and back in for changes to take effect. - + Warned - + You have received a warning due to %1. Please refrain from engaging in this activity or further actions may be taken against you. If you have any questions, please private message a moderator. - + You have received the following message from the server. (custom messages like these could be untranslated) @@ -6031,17 +6165,17 @@ Please refrain from engaging in this activity or further actions may be taken ag TabUserLists - + Account - + Add to Buddy List - + Add to Ignore List @@ -6049,13 +6183,13 @@ Please refrain from engaging in this activity or further actions may be taken ag TappedOutInterface - - + + Error - + Unable to analyze the deck. @@ -6063,13 +6197,13 @@ Please refrain from engaging in this activity or further actions may be taken ag TipsOfTheDay - + File does not exist. - + Failed to open file. @@ -6078,42 +6212,42 @@ Please refrain from engaging in this activity or further actions may be taken ag TranslateCounterName - + Life - + White - + Blue - + Black - + Red - + Green - + Colorless - + Other @@ -6121,7 +6255,7 @@ Please refrain from engaging in this activity or further actions may be taken ag UpdateDownloader - + Could not open the file for reading. @@ -6129,173 +6263,173 @@ Please refrain from engaging in this activity or further actions may be taken ag UserContextMenu - + User &details - + Private &chat - + Show this user's &games - + Add to &buddy list - + Remove from &buddy list - + Add to &ignore list - + Remove from &ignore list - + Kick from &game - + Warn user - + View user's war&n history - + Ban from &server - + View user's &ban history - + &Promote user to moderator - + Dem&ote user from moderator - + Promote user to &judge - + Demote user from judge - + %1's games - - - + + + Ban History - + Ban Time;Moderator;Ban Length;Ban Reason;Visible Reason - + User has never been banned. - + Failed to collect ban information. - - - + + + Warning History - + Warning Time;Moderator;User Name;Reason - + User has never been warned. - + Failed to collect warning information. - - + + Success - + Successfully promoted user. - + Successfully demoted user. - - + + Failed - + Failed to promote user. - + Failed to demote user. - + Copy hash to clipboard - + Remove this user's messages @@ -6303,173 +6437,173 @@ Please refrain from engaging in this activity or further actions may be taken ag UserInfoBox - + Location: - + Account Age: - + Edit - + Change password - + Change avatar - + Administrator - + Moderator - + Registered user - - + + Unregistered user - + Judge - + Unknown - + The entered password does not match your account. - - - + + + Information - + User information updated. - - - - - - - - - - + + + + + + + + + + Error - + User Information - + Real Name: - + User Level: - + %n Year(s), amount of years (only shown if more than 0) - + %10%n Day(s) %20 amount of years (if more than 0), amount of days, date in local short format - + Enter Password - + Password verification is required in order to change your email address - - - + + + An error occurred while trying to update your user information. - + This server does not permit you to update your user informations. - + Password changed. - + This server does not permit you to change your password. - + The new password is too short. - + The old password is incorrect. - + Avatar updated. - + This server does not permit you to update your avatar. - + An error occured while trying to updater your avatar. @@ -6477,57 +6611,57 @@ Please refrain from engaging in this activity or further actions may be taken ag UserInterfaceSettingsPage - + General interface settings - + &Double-click cards to play them (instead of single-click) - + &Play all nonlands onto the stack (not the battlefield) by default - + Annotate card text on tokens - + Use tear-off menus, allowing right click menus to persist on screen - + Notifications settings - + Enable notifications in taskbar - + Notify in the taskbar for game events while you are spectating - + Notify in the taskbar when users in your buddy list connect - + Animation settings - + &Tap/untap animation @@ -6535,22 +6669,22 @@ Please refrain from engaging in this activity or further actions may be taken ag UserList - + Users connected to server: %1 - + Users in this room: %1 - + Buddies online: %1 / %2 - + Ignored users online: %1 / %2 @@ -6558,43 +6692,43 @@ Please refrain from engaging in this activity or further actions may be taken ag WarningDialog - + Which warning would you like to send? - + Redact all messages from this user in all rooms - + &OK - + &Cancel - + Warn user for misconduct - - + + Error - + User name to send a warning to can not be blank, please specify a user to warn. - + Warning to use can not be blank, please select a valid warning to send. @@ -6602,128 +6736,128 @@ Please refrain from engaging in this activity or further actions may be taken ag WndSets - + Move selected set to the top - + Move selected set up - + Move selected set down - + Move selected set to the bottom - + Search by set name, code, or type - + Default order - + Restore original art priority order - + Enable all sets - + Disable all sets - + Enable selected set(s) - + Disable selected set(s) - + Deck Editor - + Use CTRL+A to select all sets in the view. - + Only cards in enabled sets will appear in the card list of the deck editor. - + Image priority is decided in the following order: - + first the CUSTOM Folder (%1), then the Enabled Sets in this dialog (Top to Bottom) %1 is a link to the wiki - + Card Art - + How to use custom card art - + Hints - + Note - + Sorting by column allows you to find a set while not changing set priority. - + To enable ordering again, click the column header until this message disappears. - + Use the current sorting as the set priority instead - + Sorts the set priority using the same column - + Manage sets @@ -6731,22 +6865,22 @@ Please refrain from engaging in this activity or further actions may be taken ag ZoneViewWidget - + sort by name - + sort by type - + shuffle when closing - + pile view @@ -6754,7 +6888,7 @@ Please refrain from engaging in this activity or further actions may be taken ag i18n - + English Türkçe (Turkish) @@ -6762,12 +6896,12 @@ Please refrain from engaging in this activity or further actions may be taken ag main - + Connect on startup - + Debug to file @@ -6775,766 +6909,786 @@ Please refrain from engaging in this activity or further actions may be taken ag shortcutsTab - + Main Window - - + + Deck Editor - + Game Lobby - + Card Counters - + Player Counters - + Power and Toughness - + Game Phases - + Playing Area - + Move Selected Card - + View - + Move Top Card - + Move Bottom Card - + Gameplay - + Drawing - + Chat Room - + Game Window - + Load Deck from Clipboard - + + Replays + + + + Check for Card Updates... - + Connect... - + Disconnect - + Exit - + Full screen - + Register... - + Settings... - + Start a Local Game... - + Watch Replay... - + Analyze Deck - + Clear All Filters - + Clear Selected Filter - + Close - + Remove Card - + Manage Sets... - + Edit Custom Tokens... - + Export Deck - + Add Card - + Load Deck... - + Load Deck from Clipboard... - + New Deck - + Open Custom Pictures Folder - + Print Deck... - + Delete Card - - + + Reset Layout - + Save Deck - + Save Deck as... - + Save Deck to Clipboard, Annotated - + Save Deck to Clipboard - + Load Local Deck... - + Load Remote Deck... - + Set Ready to Start - + Toggle Sideboard Lock - - + + Add Green Counter - - + + Remove Green Counter - - + + Set Green Counters... - + Add Yellow Counter - + Remove Yellow Counter - + Set Yellow Counters... - - + + Add Red Counter - - + + Remove Red Counter - - + + Set Red Counters... - + Add Life Counter - + Remove Life Counter - + Set Life Counters... - + Add White Counter - + Remove White Counter - + Set White Counters... - + Add Blue Counter - + Remove Blue Counter - + Set Blue Counters... - + Add Black Counter - + Remove Black Counter - + Set Black Counters... - + Add Colorless Counter - + Remove Colorless Counter - + Set Colorless Counters... - + Add Other Counter - + Remove Other Counter - + Set Other Counters... - + Add Power (+1/+0) - + Remove Power (-1/-0) - + Move Toughness to Power (+1/-1) - + Add Toughness (+0/+1) - + Remove Toughness (-0/-1) - + Move Power to Toughness (-1/+1) - + Add Power and Toughness (+1/+1) - + Remove Power and Toughness (-1/-1) - + Set Power and Toughness... - + Reset Power and Toughness - + Untap - + Upkeep - + Draw - + First Main Phase - + Start Combat - + Attack - + Block - + Damage - + End Combat - + Second Main Phase - + End - + Next Phase - + Next Phase Action - + Next Turn - + Tap / Untap Card - + Untap All - + Toggle Untap - + Turn Card Over - + Peek Card - + Play Card - + Attach Card... - + Unattach Card - + Clone Card - + Create Token... - + Create All Related Tokens - + Create Another Token - + Set Annotation... - - + + Bottom of Library - - - - + + + + Exile - - - - + + + + Graveyard - - + + Hand - - + + Top of Library - - - + + + Battlefield, Face Down - + Battlefield - + Library - + Sideboard - + Top Cards of Library - + Close Recent View - - + + Stack - - + + Graveyard (Multiple) - - + + Exile (Multiple) - + + Stack Until Found + + + + Draw Bottom Card - + Draw Multiple Cards from Bottom... - + Draw Arrow... - + Remove Local Arrows - + Leave Game - + Concede - + Roll Dice... - + Shuffle Library - + Mulligan - + Draw a Card - + Draw Multiple Cards... - + Undo Draw - + Always Reveal Top Card - + Always Look At Top Card - + Rotate View Clockwise - + Rotate View Counterclockwise - + Unfocus Text Box - + Focus Chat - + Clear Chat - + Refresh + + + Play/Pause + + + + + Toggle Fast Forward + + \ No newline at end of file diff --git a/cockatrice/translations/cockatrice_yue.ts b/cockatrice/translations/cockatrice_yue.ts new file mode 100644 index 000000000..1299b55db --- /dev/null +++ b/cockatrice/translations/cockatrice_yue.ts @@ -0,0 +1,9044 @@ + + + AbstractCounter + + + &Set counter... + &設置指示物 + + + + AbstractCounterDialog + + + Set counter + 設置指示物 + + + + New value for counter '%1': + 數值物新價值為 '%1' + + + + AbstractDlgDeckTextEdit + + + &Refresh + + + + + Parse Set Name and Number (if available) + + + + + AbstractTabDeckEditor + + + Open in new tab + + + + + Are you sure? + + + + + The decklist has been modified. +Do you want to save the changes? + + + + + + + + + + + Error + + + + + Could not open deck at %1 + + + + + Could not save remote deck + + + + + + The deck could not be saved. +Please check that the directory is writable and try again. + + + + + Save deck + + + + + The deck could not be saved. + + + + + There are no cards in your deck to be exported + + + + + No deck was selected to be exported. + + + + + AdminNotesDialog + + + Update Notes + 更新後筆記 + + + + Admin Notes for %1 + %1 的管理筆記 + + + + AllZonesCardAmountWidget + + + Mainboard + 主牌庫 + + + + Sideboard + 副牌庫 + + + + AppearanceSettingsPage + + + Error + 出現錯誤 + + + + Could not create themes directory at '%1'. + 不能在'%1'創造主題目錄 + + + + Theme settings + 主題設定 + + + + Current theme: + 現在主題 + + + + Open themes folder + 打開主題文件夾 + + + + Menu settings + 選單設定 + + + + Show keyboard shortcuts in right-click menus + 在右擊選單中顯示鍵盤快速鍵 + + + + Card rendering + 卡面算繪 + + + + Display card names on cards having a picture + 最有圖片的牌上顯示牌名 + + + + Auto-Rotate cards with sideways layout + 將打橫佈局卡片自動向上旋轉 + + + + Override all card art with personal set preference (Pre-ProviderID change behavior) [Requires Client restart] + 將所有牌面藝術硬性改成本人喜好牌組[須重新啟動客端軟件] + + + + Bump sets that the deck contains cards from to the top in the printing selector + 將牌庫中包含的牌組推到藝術選擇視窗之頂 + + + + Scale cards on mouse over + 卡牌隨遊標縮放 + + + + Use rounded card corners + + + + + Minimum overlap percentage of cards on the stack and in vertical hand + 在疊區中和直行手牌中,盡量減少交重疊百分點 + + + + Maximum initial height for card view window: + 卡望框最高開頭高度 + + + + + rows + 橫列 + + + + Maximum expanded height for card view window: + + + + + Hand layout + 手牌佈局 + + + + Display hand horizontally (wastes space) + 打棟顯示手牌(浪費空間) + + + + Enable left justification + 啟用向左對齊 + + + + Table grid layout + 表格布局 + + + + Invert vertical coordinate + 反轉垂直坐標 + + + + Minimum player count for multi-column layout: + 界面佈局之內能夠容納的最少玩家數量: + + + + Maximum font size for information displayed on cards: + 卡牌上顯示的最大字體大小 + + + + BanDialog + + + ban &user name + 禁用 &用家名字 + + + + ban &IP address + 禁用&IP地址 + + + + ban client I&D + 禁用客户ID + + + + Ban type + 禁用類型 + + + + &permanent ban + &永久禁用 + + + + &temporary ban + &暫時禁用 + + + + &Days: + &日 + + + + &Hours: + &小時 + + + + &Minutes: + &分鐘 + + + + Duration of the ban + 禁用時間 + + + + Please enter the reason for the ban. +This is only saved for moderators and cannot be seen by the banned person. + 請記載禁用原因 +這項只保留為版主, 被禁用戶不能看到. + + + + Please enter the reason for the ban that will be visible to the banned person. + 請記載被禁用戶能看到的被禁原因. + + + + Redact all messages from this user in all rooms + 將這用戶所有室中打出的訊息完全移除 + + + + &OK + &OK + + + + &Cancel + &取消 + + + + Ban user from server + 把用戶從本伺服器禁用 + + + + + + + Error + 出現錯誤 + + + + You have to select a name-based, IP-based, clientId based, or some combination of the three to place a ban. + 若要對用戶實施禁用,請你選擇以下三種的拼合:名字,IP,或用戶id. + + + + You must have a value in the name ban when selecting the name ban checkbox. + 若用用戶名字施行禁用,必須要填寫姓名. + + + + You must have a value in the ip ban when selecting the ip ban checkbox. + 若用ip地址施行禁用,必須要寫下IP地址. + + + + You must have a value in the clientid ban when selecting the clientid ban checkbox. + 若用用戶ID施行禁用,必須要寫下用戶ID. + + + + BetaReleaseChannel + + + Beta + 測試版 + + + + No reply received from the release update server. + 從發佈更新伺服器收不到回覆. + + + + Invalid reply received from the release update server. + 從發佈更新伺服器收不到有效的回覆. + + + + No reply received from the file update server. + 從檔案更新伺服器收不到有效的回覆. + + + + CardDatabaseModel + + + Name + 名字 + + + + Sets + 牌組 + + + + Mana cost + 法力費用 + + + + Card type + 卡牌類別 + + + + P/T + 力量/防御力 + + + + Color(s) + 顏色 + + + + CardFilter + + + AND + Logical conjunction operator used in card filter + + + + + OR + Logical disjunction operator used in card filter + + + + + AND NOT + Negated logical conjunction operator used in card filter + 和不是 + + + + OR NOT + Negated logical disjunction operator used in card filter + 或不是 + + + + Name + 名稱 + + + + Type + 種類 + + + + Color + 顏色 + + + + Text + 詞語 + + + + Set + 卡組 + + + + Mana Cost + 法力費用 + + + + Mana Value + 法力數值 + + + + Rarity + 稀有度 + + + + Power + 力量 + + + + Toughness + 防御力 + + + + Loyalty + 忠诚值 + + + + Format + 賽制 + + + + CardInfoFrameWidget + + + Image + 卡圖 + + + + Description + 描述 + + + + Both + 兩個都顯示 + + + + View transformation + + + + + CardInfoPictureWidget + + + View related cards + 查看關聯卡牌 + + + + Add card to deck + + + + + Mainboard + + + + + Sideboard + + + + + CardInfoTextWidget + + + Name: + 名稱: + + + + Related cards: + 有關牌名: + + + + Unknown card: + 不知名的牌 + + + + CardItem + + + &Move to + &移到 + + + + &Power / toughness + &力量/防御力 + + + + CardSizeWidget + + + Card Size + 卡牌大小 + + + + CardZone + + + their hand + nominative + 它們的手牌 + + + + %1's hand + nominative + %1的手牌 + + + + their library + look at zone + 它們的牌庫 + + + + %1's library + look at zone + %1的牌庫 + + + + of their library + top cards of zone, + 它們的牌庫中 + + + + of %1's library + top cards of zone + %1的牌庫中 + + + + their library + reveal zone + 它們的牌庫 + + + + %1's library + reveal zone + %1的牌庫 + + + + their library + shuffle + 它們的牌庫 + + + + %1's library + shuffle + %1的牌庫 + + + + their library + nominative + 它們的牌庫 + + + + %1's library + nominative + %1的牌庫 + + + + their graveyard + nominative + 它們的墳場 + + + + %1's graveyard + nominative + %1的的墳場 + + + + their exile + nominative + 它們的放逐區 + + + + %1's exile + nominative + %1的放逐區 + + + + their sideboard + look at zone + 它們的備牌 + + + + %1's sideboard + look at zone + %1的備牌 + + + + their sideboard + nominative + 它們的備牌 + + + + %1's sideboard + nominative + %1的備牌 + + + + DeckEditorCardInfoDockWidget + + + Card Info + + + + + DeckEditorDatabaseDisplayWidget + + + Search by card name (or search expressions) + + + + + Add to Deck + + + + + Add to Sideboard + + + + + Select Printing + + + + + Show on EDHREC (Commander) + + + + + Show on EDHREC (Card) + + + + + Show Related cards + + + + + Add card to &maindeck + + + + + Add card to &sideboard + + + + + DeckEditorDeckDockWidget + + + Banner Card + + + + + Select Printing + + + + + Deck + + + + + Deck &name: + + + + + &Comments: + + + + + Hash: + + + + + &Increment number + + + + + &Decrement number + + + + + &Remove row + + + + + Swap card to/from sideboard + + + + + DeckEditorFilterDockWidget + + + Filters + + + + + &Clear all filters + + + + + Delete selected + + + + + DeckEditorMenu + + + &Deck Editor + + + + + &New deck + + + + + &Load deck... + + + + + Load recent deck... + + + + + Clear + + + + + &Save deck + + + + + Save deck &as... + + + + + Load deck from cl&ipboard... + + + + + Edit deck in clipboard + + + + + + Annotated + + + + + + Not Annotated + + + + + Save deck to clipboard + + + + + Annotated (No set info) + + + + + Not Annotated (No set info) + + + + + &Print deck... + + + + + &Send deck to online service + + + + + Create decklist (decklist.org) + + + + + Create decklist (decklist.xyz) + + + + + Analyze deck (deckstats.net) + + + + + Analyze deck (tappedout.net) + + + + + &Close + + + + + DeckEditorPrintingSelectorDockWidget + + + Printing Selector + + + + + DeckEditorSettingsPage + + + + Update Spoilers + 更新預覽卡牌 + + + + + Success + 成功 + + + + Download URLs have been reset. + 下載URL已重置。 + + + + Downloaded card pictures have been reset. + 下載的卡牌圖片已被重置。 + + + + Error + 出現錯誤 + + + + One or more downloaded card pictures could not be cleared. + 一幅或以上的卡牌圖片未能被清除。 + + + + Add URL + 添加URL + + + + + URL: + URL: + + + + + Edit URL + 更改URL + + + + Network Cache Size: + 圖片網路緩存大小: + + + + Redirect Cache TTL: + 網路緩處再定向生存時間 + + + + How long cached redirects for urls are valid for. + 再定向緩處有效時間 + + + + Picture Cache Size: + 圖片緩存大小: + + + + Add New URL + 添置新 URL + + + + Remove URL + 移除 URL. + + + + Day(s) + + + + + Updating... + 更新中... + + + + Choose path + 選擇路徑 + + + + URL Download Priority + URL下載優先次序 + + + + Spoilers + 預覽 + + + + Download Spoilers Automatically + 自動下載預覽 + + + + Spoiler Location: + 預覽位置: + + + + Last Change + 最後變更 + + + + Spoilers download automatically on launch + 開啟時自動下載預覽 + + + + Press the button to manually update without relaunching + 免重新啟動人手按制更新 + + + + Do not close settings until manual update is complete + 人手更新完成前,不能關掉设置 + + + + Download card pictures on the fly + 即時下載卡牌圖片 + + + + How to add a custom URL + 如何添加自定URL + + + + Delete Downloaded Images + 刪除已下載的圖片 + + + + Reset Download URLs + 重置下載URL + + + + On-disk cache for downloaded pictures + 下載圖片專用硬盤緩儲 + + + + In-memory cache for pictures not currently on screen + 現不在螢光幕上記憶中圖片緩儲 + + + + DeckListModel + + + Count + 數目 + + + + Set + 牌組 + + + + Number + 號碼 + + + + Provider ID + 提供者 id + + + + Card + 卡牌 + + + + DeckLoader + + + Common deck formats (%1) + + + + + All files (*.*) + + + + + DeckPreviewColorIdentityFilterWidget + + + Mode: Exact Match + + + + + Mode: Includes + + + + + DeckPreviewDeckTagsDisplayWidget + + + Edit tags ... + + + + + DeckPreviewTagDialog + + + Deck Tags Manager + + + + + Manage your deck tags. Check or uncheck tags as needed, or add new ones: + + + + + Add a new tag (e.g., Aggro️) + + + + + Add Tag + + + + + Filter tags... + + + + + OK + + + + + Cancel + + + + + Invalid Input + + + + + Tag name cannot be empty! + + + + + Duplicate Tag + + + + + This tag already exists. + + + + + DeckPreviewWidget + + + Banner Card + + + + + Open in deck editor + + + + + Edit Tags + + + + + Rename Deck + + + + + Save Deck to Clipboard + + + + + Annotated + + + + + Annotated (No set info) + + + + + Not Annotated + + + + + Not Annotated (No set info) + + + + + Rename File + + + + + Delete File + + + + + Set Banner Card + + + + + + New name: + + + + + + Error + + + + + Rename failed + + + + + Delete file + + + + + Are you sure you want to delete the selected file? + + + + + Delete failed + + + + + DeckStatsInterface + + + + Error + 出現錯誤 + + + + The reply from the server could not be parsed. + 不能分析從伺服器來的回覆 + + + + DeckViewContainer + + + Load deck... + 載入牌庫... + + + + Load remote deck... + 載入伺服器上的牌庫... + + + + Unload deck + Unload deck... + + + + + Ready to start + 準備好 + + + + Force start + 強迫開始 + + + + Sideboard unlocked + 備牌解鎖 + + + + Sideboard locked + 解鎖鎖上 + + + + Error + 出現錯誤 + + + + The selected file could not be loaded. + 選擇的檔案不能再入 + + + + DialogConvertDeckToCodFormat + + + Deck Format Conversion + + + + + You tried to add a tag to a .txt format deck. + Tags can only be added to .cod format decks. + Do you want to convert the deck to the .cod format? + + + + + Remember and automatically apply choice in the future + + + + + DlgConnect + + + Downloading... + 下載中... + + + + Known Hosts + 已知主辦機 + + + + Delete the currently selected saved server + 刪除現選儲藏伺服器. + + + + Refresh the server list with known public servers + 從已知的伺服器更新伺服器列表 + + + + New Host + 新主辦 + + + + Name: + 名稱: + + + + &Host: + &主辦機: + + + + &Port: + &端口: + + + + Player &name: + 玩家 &名字: + + + + P&assword: + &密碼: + + + + &Save password + &儲存密碼 + + + + A&uto connect + &自動連接 + + + + Automatically connect to the most recent login when Cockatrice opens + 當Cockatrice啓動時自動連接到最近的登錄 + + + + If you have any trouble connecting or registering then contact the server staff for help! + 如在連接或註冊誰中遇到問題,請聯絡伺服器職員尋求援助! + + + + + Webpage + 網頁 + + + + Reset Password + 重設密碼 + + + + Forgot password? + 密碼忘記了? + + + + &Connect + 連接 + + + + Server + 伺服器 + + + + Login + 登錄 + + + + Server Contact + 伺服器聯絡人 + + + + Connect to Server + 連接伺服器 + + + + Server URL + 伺服器URL + + + + Communication Port + 通信端口 + + + + Unique Server Name + 獨特伺服器名字 + + + + Connection Warning + 連接警告 + + + + You need to name your new connection profile. + 你必須將你的連接簡介命名 + + + + Connect Warning + 連接警告 + + + + The player name can't be empty. + 玩家名稱不能空置 + + + + DlgCreateGame + + + Re&member settings + &記住設定 + + + + &Description: + &描述: + + + + P&layers: + 玩家: + + + + General + 一般 + + + + Game type + 遊戲類型 + + + + &Password: + &密碼: + + + + Only &buddies can join + 只容許&好友加入 + + + + Only &registered users can join + 只容許&註冊用戶加入 + + + + Joining restrictions + 加入條件 + + + + &Spectators can watch + 允許觀看者 + + + + Spectators &need a password to watch + 觀看者&需要密碼 + + + + Spectators can &chat + 觀看者可以&聊天 + + + + Spectators can see &hands + 觀看者可見&手牌 + + + + Create game as spectator + 以旁觀者的身份創建遊戲 + + + + Spectators + 觀看者 + + + + Starting life total: + 開始生命總數 + + + + Game setup options + 遊戲設定選擇 + + + + &Clear + &清除 + + + + Create game + 創建遊戲 + + + + Game information + 遊戲資訊 + + + + Error + 出現錯誤 + + + + Server error. + 伺服器出現錯誤. + + + + DlgCreateToken + + + &Name: + &名稱: + + + + Token + 令牌 + + + + C&olor: + &顏色: + + + + white + 白色 + + + + blue + 藍色 + + + + black + 黑色 + + + + red + 紅色 + + + + green + 綠色 + + + + multicolor + 多色 + + + + colorless + 無色 + + + + &P/T: + &力量/防御力: + + + + &Annotation: + &註釋: + + + + &Destroy token when it leaves the table + &此令牌離場時會被摧毀 + + + + Token data + 令牌資料 + + + + Show &all tokens + 顯示&所有令牌 + + + + Show tokens from this &deck + 顯示本牌庫所有令牌 + + + + Choose token from list + 從清單中選擇令牌 + + + + Create token + 派出令牌 + + + + DlgEditAvatar + + + + No image chosen. + 未選擇圖像 + + + + To change your avatar, choose a new image. +To remove your current avatar, confirm without choosing a new image. + 若要改變頭著,請選擇新的圖片. +若要移除現有頭像, 請不要選擇新的圖片,直接確認. + + + + Browse... + 瀏覽... + + + + Change avatar + 更改頭像 + + + + Open Image + 打開圖片 + + + + Image Files (*.png *.jpg *.bmp) + 圖片文件(*.png *.jpg *.bmp格式) + + + + Invalid image chosen. + 所選圖片不能使用。 + + + + DlgEditDeckInClipboard + + + Edit deck in clipboard + + + + + Error + + + + + Invalid deck list. + + + + + DlgEditPassword + + + Old password: + 舊密碼: + + + + New password: + 新密碼: + + + + Confirm new password: + 確認新密碼: + + + + Change password + 更改密碼 + + + + + Error + 出現錯誤 + + + + Your password is too short. + 你的密碼太短。 + + + + The new passwords don't match. + 新舊密碼不一致 + + + + DlgEditTokens + + + &Name: + &名稱: + + + + C&olor: + &顏色: + + + + white + 白色 + + + + blue + 藍色 + + + + black + 黑色 + + + + red + 红色 + + + + green + 綠色 + + + + multicolor + 多色 + + + + colorless + 無色 + + + + &P/T: + &力量/防御力: + + + + &Annotation: + &註釋: + + + + Token data + 令牌資料 + + + + + Add token + 添加令牌 + + + + Remove token + 移除令牌 + + + + Edit custom tokens + 編輯自定令牌 + + + + Please enter the name of the token: + 請輸入令牌名稱: + + + + Error + 出現錯誤 + + + + The chosen name conflicts with an existing card or token. +Make sure to enable the 'Token' set in the "Manage sets" dialog to display them correctly. + 現時選擇的名字跟現有的卡牌或令牌出現衝突.請從"牌組管理"中啟動'令牌牌組',保持正確顯示. + + + + DlgEditUser + + + Email: + 電郵: + + + + Country: + 國家: + + + + Undefined + 未定義 + + + + Real name: + 實名: + + + + Edit user profile + 更改用戶資料 + + + + DlgFilterGames + + + no limit + 沒有限制 + + + + 5 minutes + 5分鐘 + + + + 10 minutes + 10分鐘 + + + + 30 minutes + 30分鐘 + + + + 1 hour + 1小時 + + + + 2 hours + 2小時 + + + + Hide 'buddies only' games + + + + + Hide full games + + + + + Hide games that have started + + + + + Hide password protected games + + + + + Hide 'ignored user' games + + + + + &Newer than: + &新過: + + + + Game &description: + 遊戲 &描述: + + + + &Creator name: + &創建者名稱: + + + + General + 一般 + + + + &Game types + &遊戲類型 + + + + at &least: + &至少: + + + + at &most: + &最多: + + + + Maximum player count + 可容納玩家最高數量 + + + + Restrictions + 限制 + + + + Show games only if &spectators can watch + 只顯示容許&觀看者 觀看的遊戲 + + + + Show spectator password p&rotected games + 顯示有觀看者&密碼保護的遊戲 + + + + Show only if spectators can ch&at + 只顯示容許觀看者&聊天的遊戲 + + + + Show only if spectators can see &hands + 只顯示容許觀看者看&手牌 的遊戲 + + + + Spectators + 觀看者 + + + + Filter games + 篩選遊戲 + + + + DlgForgotPasswordChallenge + + + Reset Password Challenge Warning + 重設密碼驗證警告 + + + + A problem has occurred. Please try to request a new password again. + 程序出現故障,請再要求新的密碼. + + + + Enter the information of the server and the account you'd like to request a new password for. + 請輸入需要新密碼的伺服器和相關戶口資料. + + + + &Host: + &主辦機: + + + + &Port: + &端口: + + + + Player &name: + 玩家 &名字: + + + + Email: + 電郵: + + + + Reset Password Challenge + 重設密碼驗證 + + + + Reset Password Challenge Error + 重設密碼驗證錯誤 + + + + The email address can't be empty. + 電郵不能空置. + + + + DlgForgotPasswordRequest + + + Enter the information of the server you'd like to request a new password for. + 請輸入需要新密碼的伺服器資料. + + + + &Host: + &主辦機: + + + + &Port: + &端口: + + + + Player &name: + 玩家 &名字: + + + + Reset Password Request + 重設密碼要求 + + + + Reset Password Error + 重設密碼錯誤 + + + + The player name can't be empty. + 玩家名稱不能空置 + + + + DlgForgotPasswordReset + + + Reset Password Warning + 重設密碼警告 + + + + A problem has occurred. Please try to request a new password again. + 程序出現故障,請再要求新的密碼. + + + + Enter the received token and the new password in order to set your new password. + 請輸入收到驗證碼和新的密碼,以便定立新的密碼. + + + + &Host: + &主辦機: + + + + &Port: + &端口: + + + + Player &name: + 玩家 &名字: + + + + Token: + 驗證碼 + + + + + New Password: + 新密碼: + + + + Reset Password + 重設密碼 + + + + The player name can't be empty. + 玩家名稱不能空置 + + + + + + + Reset Password Error + 重設密碼錯誤 + + + + The token can't be empty. + 驗證碼不能為空白 + + + + The new password can't be empty. + 新密碼不能為空白 + + + + Error + 出現錯誤 + + + + Your password is too short. + 你的新密碼太短。 + + + + The passwords do not match. + 新舊密碼不一致. + + + + DlgLoadDeck + + + Load Deck + 載入牌庫 + + + + DlgLoadDeckFromClipboard + + + Load deck from clipboard + 從剪貼板載入牌庫 + + + + Error + 出現錯誤 + + + + Invalid deck list. + 牌表不能載入 + + + + DlgLoadRemoteDeck + + + Load deck + 載入牌庫 + + + + DlgMoveTopCardsUntil + + + Card name (or search expressions): + 卡牌名稱(或搜尋表達式) + + + + Number of hits: + 因為cockatrice原生沒有ssl支援, 所以你不能自動下載更新. 請瀏覽到我們的下載網頁,進行人手更新. + + + + Auto play hits + 開關副牌上鎖 + + + + Put top cards on stack until... + 把頂牌放在重疊區, 直到... + + + + No cards matching the search expression exists in the card database. Proceed anyways? + 在卡牌資料庫中找不到符合搜尋表達式的卡牌.你想繼續嗎? + + + + Cockatrice + Cockatrice + + + + Invalid filter + 篩選無效 + + + + DlgRegister + + + Enter your information and the information of the server you'd like to register to. +Your email will be used to verify your account. + 輸入你, 和你想登記的伺服器的資料. + +我們會使用你的電郵來核實你的戶口. + + + + &Host: + &主辦機: + + + + &Port: + &端口: + + + + Player &name: + 玩家 &名字: + + + + P&assword: + &密碼: + + + + Password (again): + (再次)輸入密碼: + + + + Email: + 電郵: + + + + Email (again): + (再次)輸入電郵: + + + + Country: + 國家: + + + + Undefined + 未定義 + + + + Real name: + 實名: + + + + Register to server + 註冊加入伺服器 + + + + + + + Registration Warning + 注册警告 + + + + Your password is too short. + 你的密碼太短。 + + + + Your passwords do not match, please try again. + 兩次輸入的密碼不一致,請重新輸入, + + + + Your email addresses do not match, please try again. + 兩次輸入的電子郵箱不一致,請重新輸入。 + + + + The player name can't be empty. + 玩家名稱不能空置 + + + + DlgRollDice + + + Number of sides: + 骰子面數: + + + + Number of dice: + 骰子數目: + + + + Roll Dice + 擲骰 + + + + DlgSettings + + + Unknown Error loading card database + 讀取卡牌資料庫時出現不知名錯誤 + + + + Your card database is invalid. + +Cockatrice may not function correctly with an invalid database + +You may need to rerun oracle to update your card database. + +Would you like to change your database location setting? + 你的卡牌資料庫無效。 + +資料庫無效可能會導致Cockatrice無法正確運行。 + +你應該重新啓動Oracle,更新卡牌資料庫。 + +你想要重新設置卡牌資料庫路徑嗎? + + + + Your card database version is too old. + +This can cause problems loading card information or images + +Usually this can be fixed by rerunning oracle to to update your card database. + +Would you like to change your database location setting? + 你的卡牌資料庫過舊。 + +這可能會導致卡牌圖片或資料出現問題。 + +你應該重新啓動Oracle,更新卡牌資料庫。 + +你想要重新設置卡牌資料庫路徑嗎? + + + + Your card database did not finish loading + +Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues with your cards.xml attached + +Would you like to change your database location setting? + 你的卡牌資料庫未能完成載入 + +請瀏覽 https://github.com/Cockatrice/Cockatrice/issues , 開啟新報錯/支援票,票上加載本人的cards.xml文件 + +你想要重新設置卡牌資料庫路徑嗎? + + + + File Error loading your card database. + +Would you like to change your database location setting? + 載入卡牌資料庫時出現錯誤. +你想要重新設置卡牌資料庫路徑嗎? + + + + Your card database was loaded but contains no cards. + +Would you like to change your database location setting? + 你的卡牌資料庫中並不包含任何卡牌信息 + +你想要重新設置卡牌資料庫路徑嗎? + + + + Unknown card database load status + +Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues + +Would you like to change your database location setting? + 發現不知名卡牌資料庫載入狀態. + +請到 + +https://github.com/Cockatrice/Cockatrice/issues +上交修補票. + +你想不想更改你的資料庫位置設定? + + + + + + Error + 出現錯誤 + + + + The path to your deck directory is invalid. Would you like to go back and set the correct path? + 你的牌庫目錄路徑現在無效. 你想要重新設置套牌路徑嗎? + + + + The path to your card pictures directory is invalid. Would you like to go back and set the correct path? + 你的卡牌圖片目錄路徑現在無效. 你想要重新設置卡牌圖片資料庫路徑嗎? + + + + Settings + 設定 + + + + General + 一般 + + + + Appearance + 外觀 + + + + User Interface + 用戶界面 + + + + Card Sources + 卡牌來源 + + + + Chat + 聊天 + + + + Sound + 聲音 + + + + Shortcuts + 快捷键 + + + + DlgTipOfTheDay + + + Next + 下一個 + + + + Previous + 上一個 + + + + Tip of the Day + 每日提示 + + + + DlgUpdate + + + Current release channel + 現在發行渠道 + + + + Reinstall + 重新安裝 + + + + Cancel Download + 取消下載 + + + + Open Download Page + 打開下載頁面 + + + + Check for Client Updates + 檢查客戶端更新 + + + + + + + Error + 出現錯誤 + + + + Cockatrice was not built with SSL support, therefore you cannot download updates automatically! +Please visit the download page to update manually. + 因為Cockatrice不設立ssl支援,所以你不能自動下載更新! +請你瀏覽到我們的下載網頁進行人手更新. + + + + Downloading update: %1 + Downloading update... + + + + + Checking for updates... + 正在檢查更新... + + + + Finished checking for updates + 更新檢查已完成 + + + + No Update Available + 沒有更新可用 + + + + Cockatrice is up to date! + Cockatrice 已經達到最新版本! + + + + You are already running the latest version available in the chosen release channel. + 你運行的已經是最新的發行版本了。 + + + + Current version + 現行版本 + + + + Selected release channel + 現選發行渠道 + + + + + Update Available + 現有更新版本 + + + + + A new version of Cockatrice is available! + Cockatrice有新版本可用! + + + + + New version + 新版本 + + + + + Released + 已發行 + + + + + Changelog + 變更記載 + + + + Do you want to update now? + 你打算現在更新嗎? + + + + Unfortunately, the automatic updater failed to find a compatible download. +You may have to manually download the new version. + Unfortunately there are no download packages available for your operating system. +You may have to build from source yourself. + + + + + Please check the <a href="%1">releases page</a> on our Github and download the build for your system. + Please check the download page manually and visit the wiki for instructions on compiling. + + + + + + + Update Error + 更新出現錯誤 + + + + An error occurred while checking for updates: + 檢測更新時出現錯誤: + + + + An error occurred while downloading an update: + 下載更新時出現錯誤 + + + + Installing... + 安裝中 + + + + Cockatrice is unable to open the installer. + Cockatrice 不能啟動安裝程式 + + + + Try to update manually by closing Cockatrice and running the installer. + 請你嘗試人手更新, 關掉cockatrice, 然後重開安裝程式. + + + + Download location + 下載位置 + + + + DlgViewLog + + + Clear log when closing + 關閉時清除紀錄 + + + + Debug Log + 偵錯紀錄 + + + + EdhrecCommanderResponseCommanderDetailsDisplayWidget + + + Salt: + + + + + FilterBuilder + + + Type your filter here + 輸入你的篩選類型 + + + + GameSelector + + + + + + + + + + + Error + 出現錯誤 + + + + Please join the appropriate room first. + 請先參加合適房間 + + + + Wrong password. + 密碼錯誤. + + + + Spectators are not allowed in this game. + 這個遊戲不允許觀眾加入. + + + + The game is already full. + 這個遊戲已經滿了. + + + + The game does not exist any more. + 這個遊戲已經不存在了。 + + + + This game is only open to registered users. + 這個遊戲只開放給註冊用戶. + + + + This game is only open to its creator's buddies. + 這個遊戲只開放給房間主辦的好友。 + + + + You are being ignored by the creator of this game. + 你被這個房間的主辦屏蔽了. + + + + Join Game + + + + + Spectate Game + + + + + Game Information + + + + + Join game + 加入遊戲 + + + + Password: + 密碼: + + + + Please join the respective room first. + 請先參加相關的房間. + + + + &Filter games + &篩選遊戲 + + + + C&lear filter + &清除篩選項目 + + + + C&reate + &創建 + + + + &Join + &加入 + + + + J&oin as spectator + &作為觀看者加入 + + + + Games shown: %1 / %2 + 顯示遊戲:%1/%2 + + + + Games + 遊戲 + + + + GamesModel + + + >1 day + >1日 + + + + %1%2 hr + short age in hours + %1%2 小時 + + + + new + 新建 + + + + %1%2 min + short age in minutes + %1%2 分鐘 + + + + password + 密碼 + + + + buddies only + 僅限好友 + + + + reg. users only + 僅限註冊用户 + + + + + can chat + 允許聊天 + + + + see hands + 查看手牌 + + + + can see hands + 允許查看手牌 + + + + not allowed + 不允許 + + + + Room + 房間 + + + + Age + 時長 + + + + Description + 描述: + + + + Creator + 創建者 + + + + Type + 類型 + + + + Restrictions + 限制 + + + + Players + 玩家 + + + + Spectators + 觀看者 + + + + GeneralSettingsPage + + + Reset all paths + 重置所有路徑 + + + + All paths have been reset + 重置所有路徑 + + + + + + + + + Choose path + 選擇路徑 + + + + Personal settings + 個人設定 + + + + Language: + 語言: + + + + Paths (editing disabled in portable mode) + 路徑(在便攜模式下不能編輯) + + + + Paths + 路徑 + + + + How to help with translations + + + + + Decks directory: + 牌庫路徑: + + + + Replays directory: + 遊戲錄像目錄: + + + + Pictures directory: + 圖片目錄: + + + + Card database: + 卡牌資料庫: + + + + Custom database directory: + 自主資料庫目錄 + + + + Token database: + 令牌資料庫: + + + + Update channel + 更新渠道 + + + + Check for client updates on startup + 啟動時檢查客戶更新 + + + + Notify if a feature supported by the server is missing in my client + 如果客戶端缺少伺服器支持的更功能時,請提示我 + + + + Automatically run Oracle when running a new version of Cockatrice + 啟動新版本Cockatrice時,自動運行Oracle. + + + + Show tips on startup + 顯示啓動時提示 + + + + MainWindow + + + + The server has reached its maximum user capacity, please check back later. + 伺服器已達到最最高用戶限額,請另外選擇吉時再試. + + + + There are too many concurrent connections from your address. + 你的網路地址擁有太多同時連繫. + + + + Banned by moderator + 你已被版主禁用 + + + + Expected end time: %1 + 預計結束時間: %1 + + + + This ban lasts indefinitely. + 這是無限期禁用 + + + + Scheduled server shutdown. + 排期伺服器關機 + + + + + Invalid username. + 不可用的用戶名。 + + + + You have been logged out due to logging in at another location. + 因為你已在另外的位置登入, 所以你現有的聯繫已被登出. + + + + Connection closed + 連繫已被關閉 + + + + The server has terminated your connection. +Reason: %1 + 伺服器已經中斷你的聯繫/ +原因: %1 + + + + The server is going to be restarted in %n minute(s). +All running games will be lost. +Reason for shutdown: %1 + 伺服器將會在 %n 分鐘內重新啟動. 所有進行中的遊戲都會報銷.伺服器關閉原因: %1 + + + + Scheduled server shutdown + 排期伺服器關機 + + + + + Success + 成功 + + + + Registration accepted. +Will now login. + 註冊已成功。 +現在登入。 + + + + Account activation accepted. +Will now login. + 戶口啟動已經成功 +現在登入. + + + + Number of players + 玩家人數 + + + + Please enter the number of players. + 請輸入玩家人數 + + + + + Player %1 + 玩家 %1 + + + + Load replay + 載入遊戲錄像 + + + + About Cockatrice + 關於Cockatrice + + + + Version + 版本 + + + + Cockatrice Webpage + Cockatrice 網頁 + + + + Project Manager: + 項目經理: + + + + Past Project Managers: + 前項目經理: + + + + Developers: + 開發者: + + + + Our Developers + 我們的開發者: + + + + Help Develop! + 協助開發! + + + + Translators: + 翻譯者: + + + + Our Translators + 翻譯者名單 + + + + Help Translate! + 協助翻譯! + + + + Support: + 支援: + + + + Report an Issue + 報告問題 + + + + Troubleshooting + 排除故障 + + + + F.A.Q. + 問答 + + + + + + + + + + + + + + + + + + + + + + Error + 出現錯誤 + + + + Server timeout + 伺服器超時 + + + + Failed Login + 登入失敗 + + + + Your client seems to be missing features this server requires for connection. + 你的客戶端似乎脫小本伺服器需要用來達成連繫的功能. + + + + To update your client, go to 'Help -> Check for Client Updates'. + 請去"求助->檢查客戶端更新" 內更新你的客戶. + + + + Incorrect username or password. Please check your authentication information and try again. + 用户名或密碼錯誤。請檢查賬號信息,然後重試。 + + + + There is already an active session using this user name. +Please close that session first and re-login. + 已經有在線用户正在使用這用户名.請先關閉連線,然後重新登入. + + + + + You are banned until %1. + 你被禁止直到: %1. + + + + + You are banned indefinitely. + 你已被永久封禁. + + + + This server requires user registration. Do you want to register now? + 本伺服器要求用戶註冊. 你想不想現在登記? + + + + This server requires client IDs. Your client is either failing to generate an ID or you are running a modified client. +Please close and reopen your client to try again. + 本伺服器要求用戶擁有客戶id. 你的客戶端現在不能造成客戶id,或是你正在使用修改過的客戶端. +請你關掉現有的客戶端,然後重新啟動, 擇吉日再試。 + + + + An internal error has occurred, please close and reopen Cockatrice before trying again. +If the error persists, ensure you are running the latest version of the software and if needed contact the software developers. + 因為內部出現故障, 請關閉,重開Cockatrice 然後再次嘗試. +如果故障繼續出現,請將客戶端軟件升級為最新版本, 有需要時請聯絡軟件開發人士. + + + + Account activation + 戶口啟動 + + + + Your account has not been activated yet. +You need to provide the activation token received in the activation email. + 你的戶口還未嘗啟動. +你需要提供從啟動電郵得來的驗證碼. + + + + Server Full + 伺服器載滿 + + + + Unknown login error: %1 + 不知名登入錯誤: %1 + + + + + +This usually means that your client version is out of date, and the server sent a reply your client doesn't understand. + +這通常意味住你的客戶端版本已經過期,引起你的客戶端不能明曉伺服器的回覆. + + + + Your username must respect these rules: + 您的用戶名字必須遵守以下規則: + + + + is %1 - %2 characters long + 在%1 - %2个字符之间。 + + + + can %1 contain lowercase characters + 可以 $1 包含細楷/小寫字母 + + + + + + + NOT + 並否 + + + + can %1 contain uppercase characters + 可以 $1 包含大楷/大寫字母 + + + + can %1 contain numeric characters + 可以 $1 包含數字 + + + + can contain the following punctuation: %1 + 可以包含以下符號:%1 + + + + first character can %1 be a punctuation mark + 第一個字符可以 %1 是標點符號 + + + + no unacceptable language as specified by these server rules: + note that the following lines will not be translated + 在以下伺服器規矩中,道明不能接受的語言: + + + + can not contain any of the following words: %1 + 不能含有以下字眼: %1 + + + + can not match any of the following expressions: %1 + 不能含有以下語句: %1 + + + + You may only use A-Z, a-z, 0-9, _, ., and - in your username. + 你的用戶名字只可以用以下的字符: A-Z, a-z, 0-9, _, ., 和 - . + + + + + + + + + Registration denied + 註冊被拒絕 + + + + Registration is currently disabled on this server + 這伺服器暫時謝絕登記 + + + + There is already an existing account with the same user name. + 這個用戶名字已經被使用. + + + + It's mandatory to specify a valid email address when registering. + 登記時必要指明合能用的電郵地址. + + + + It appears you are attempting to register a new account on this server yet you already have an account registered with the email provided. This server restricts the number of accounts a user can register per address. Please contact the server operator for further assistance or to obtain your credential information. + 你看來好像在本伺服器嘗試申請新戶口, 但是你已經有跟你電郵掛鉤的戶口名字.我們的伺服器限制每用戶在每個地址可以有多少個戶口. 如果需要更多援助或是得取你的登入資料,請聯絡伺服器辦事人. + + + + Password too short. + 密碼太短。 + + + + Registration failed for a technical problem on the server. + 由於伺服器出現技術問題, 你的登記暫時失敗. + + + + Unknown registration error: %1 + 不知名登記錯誤: %1 + + + + Account activation failed + 戶口啟動失敗 + + + + Socket error: %1 + 接口錯誤: %1 + + + + You are trying to connect to an obsolete server. Please downgrade your Cockatrice version or connect to a suitable server. +Local version is %1, remote version is %2. + 你正在嘗試跟過時的伺服器連繫. 請把你的 Cockatrice 版本降級,或是連繫於合適的伺服器. 當地/離線版本維 %1, 遙距/上線版本維 %2. + + + + Your Cockatrice client is obsolete. Please update your Cockatrice version. +Local version is %1, remote version is %2. + 你的 Cockatrice 版本現已過時.請把你的版本更新. +當地/離線版本維 %1, 遙距/上線版本維 %2. + + + + Connecting to %1... + 向 %1 連接中... + + + + Registering to %1 as %2... + 正在用%2的身份登記到%1伺服器... + + + + Disconnected + 已經斷線了 + + + + Connected, logging in at %1 + 連線成功, 在%1登入 + + + + + + Requesting forgotten password to %1 as %2... + 正在用%2的身份對%1要求遺忘密碼. + + + + &Connect... + &連線... + + + + &Disconnect + &斷線 + + + + Start &local game... + 開始&離線遊戲 + + + + &Watch replay... + &觀看遊戲錄像 + + + + &Full screen + &全屏 + + + + &Register to server... + &註冊加入伺服器 + + + + &Restore password... + &弄回密碼 + + + + &Settings... + 設置⋯ + + + + &Exit + &退出 + + + + A&ctions + &動作 + + + + &Cockatrice + &Cockatrice + + + + C&ard Database + &卡牌資料庫 + + + + &Manage sets... + &管理牌組 + + + + Edit custom &tokens... + 編輯自定 &令牌... + + + + Open custom image folder + 打開自定圖片文件夾 + + + + Open custom sets folder + 打開自定牌组文件夾 + + + + Add custom sets/cards + 添加自定卡组/卡牌 + + + + Reload card database + 刷新卡牌資料庫 + + + + Tabs + + + + + &Help + &求助 + + + + &About Cockatrice + &關於Cockatrice + + + + &Tip of the Day + &每日提示 + + + + Check for Client Updates + 檢查客戶端更新 + + + + Check for Card Updates... + 檢查卡牌更新 + + + + View &Debug Log + 查看&偵錯紀錄 + + + + Open Settings Folder + 打開設置文件夾 + + + + Show/Hide + 顯示/隱藏 + + + + New Version + 新版本 + + + + Congratulations on updating to Cockatrice %1! +Oracle will now launch to update your card database. + 恭喜你將Cockatrice升級為%1. +現在會幫你啟動oracle, 更新你的卡牌資料庫. + + + + Cockatrice installed + Cockatrice 已被安裝 + + + + Congratulations on installing Cockatrice %1! +Oracle will now launch to install the initial card database. + 恭喜你成功安裝Cockatrice%1. +現在會幫你啟動oracle, 安裝你的卡牌資料庫. + + + + Card database + 卡牌資料庫 + + + + Cockatrice is unable to load the card database. +Do you want to update your card database now? +If unsure or first time user, choose "Yes" + 軟件無法載入卡牌資料庫。 +是否現在升級你的卡牌資料庫? +如果不確定, 或者是第一次使用,請選擇“是/Yes” + + + + + Yes + 是/Yes + + + + + No + 否/No + + + + Open settings + 打開設置 + + + + New sets found + 發現新卡组 + + + + %n new set(s) found in the card database +Set code(s): %1 +Do you want to enable it/them? + 卡牌資料庫中發現%n個新牌組 +牌組代碼:%1 +要啟用它們嗎? + + + + View sets + 查看卡组 + + + + Welcome + 歡迎 + + + + Hi! It seems like you're running this version of Cockatrice for the first time. +All the sets in the card database have been enabled. +Read more about changing the set order or disabling specific sets and consequent effects in the "Manage Sets" dialog. + 您好!好像是你第一次使用本版本的Cockatrice。 +所有卡牌数据库中的卡组都已生效。 +請閱讀,了解更多在“卡组设置”對話框裡.更改卡组顺序或者禁用某些卡组的功能. + + + + + Information + 資訊 + + + + A card database update is already running. + 資料庫更新現已進行. + + + + Unable to run the card database updater: + 不能運行卡牌資料附更新程式: + + + + Failed to start. The file might be missing, or permissions might be incorrect. + + + + + The process crashed some time after starting successfully. + + + + + Timed out. The process took too long to respond. The last waitFor...() function timed out. + + + + + An error occurred when attempting to write to the process. For example, the process may not be running, or it may have closed its input channel. + + + + + An error occurred when attempting to read from the process. For example, the process may not be running. + + + + + Unknown error occurred. + + + + + The card database updater exited with an error: +%1 + + + + + This server supports additional features that your client doesn't have. +This is most likely not a problem, but this message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. + +To update your client, go to Help -> Check for Updates. + 本伺服器支持你現在客戶端軟件欠缺的額外功能. + +這信息不是大問題, 但此信息可能意會現在已有新版本的 Cockatrice, 或是本伺服器運用自設或預先釋放版本. + +請到"援助->檢查更新"去更新你的客戶端. + + + + + + + + Load sets/cards + 载入卡组/卡牌 + + + + Selected file cannot be found. + 找不到選擇的文件。 + + + + You can only import XML databases at this time. + 你暫時只能導入xml資料庫. + + + + The new sets/cards have been added successfully. +Cockatrice will now reload the card database. + 已經成功添加新的卡組/卡牌. Cockatrice會重新載入卡牌資料庫. + + + + Sets/cards failed to import. + 卡組/卡牌導入失敗. + + + + + + Reset Password + 重設密碼 + + + + Your password has been reset successfully, you can now log in using the new credentials. + 你的密碼已經成功重置, 你可以用新密碼重新登入。 + + + + Failed to reset user account password, please contact the server operator to reset your password. + 重置用戶密碼失敗, 請聯絡伺服器管責人重置密碼. + + + + Activation request received, please check your email for an activation token. + 已經收到啟動要求, 請從你的電郵獲取啟動驗證碼. + + + + MessageLogWidget + + + from play + 從戰場上 + + + + from their graveyard + 從它們的墳場 + + + + from exile + 從放逐區 + + + + from their hand + 從它們的手牌 + + + + the top card of %1's library + %1牌庫的頂牌 + + + + the top card of their library + 它們的頂牌 + + + + from the top of %1's library + 從%1牌庫的頂 + + + + from the top of their library + 從它們1牌庫頂 + + + + the bottom card of %1's library + %1牌庫的底牌 + + + + the bottom card of their library + 它們牌庫的底牌 + + + + from the bottom of %1's library + 從%1的牌庫底 + + + + from the bottom of their library + 從它們的牌庫底 + + + + from %1's library + 從%1的牌庫 + + + + from their library + 從它們的牌庫 + + + + from sideboard + 從備牌中 + + + + from the stack + 從疊區中 + + + + %1 is now keeping the top card %2 revealed. + %1現在保持%2頂牌顯現. + + + + %1 is not revealing the top card %2 any longer. + %1不再展示牌庫頂牌%2。 + + + + %1 can now look at top card %2 at any time. + %1可以隨時查看牌庫頂牌 %2. + + + + %1 no longer can look at top card %2 at any time. + %1再不可以隨時查看牌庫頂牌 %2. + + + + %1 attaches %2 to %3's %4. + %1 結附 %2 指向 %3的 %4. + + + + %1 has conceded the game. + %1 在此遊戲宣佈降服. + + + + %1 has unconceded the game. + %1 收回在此遊戲降服. + + + + %1 has restored connection to the game. + %1已恢復連接。 + + + + %1 has lost connection to the game. + %1 已失去連接。 + + + + %1 points from their %2 to themselves. + %1 從它們的%2 指向自己。 + + + + %1 points from their %2 to %3. + %1 從它們的%2指向%3。 + + + + %1 points from %2's %3 to themselves. + %1 從%2的%3 指向自己。 + + + + %1 points from %2's %3 to %4. + %1 從%2的%3 指向%4。 + + + + %1 points from their %2 to their %3. + %1 從它們的%2指向它們的%3。 + + + + %1 points from their %2 to %3's %4. + %1 從它們的%2指向%3的%4。 + + + + %1 points from %2's %3 to their own %4. + %1 從%2的%3指向自己的%4。 + + + + %1 points from %2's %3 to %4's %5. + %1 從%2的%3指向%4的%5。 + + + + %1 creates token: %2%3. + %1 派出令牌: %2%3 + + + + %1 has loaded a deck (%2). + %1 已载入牌庫(%2) + + + + %1 has loaded a deck with %2 sideboard cards (%3). + %1已載入含有 %2張備牌的牌庫(%3). + + + + %1 destroys %2. + %1销毁了%2。 + + + + a card + 一張牌 + + + + %1 gives %2 control over %3. + %1將%3的控制轉移給%2. + + + + %1 puts %2 into play%3 face down. + %1將%2%3牌面朝下地放進戰場。 + + + + %1 puts %2 into play%3. + %1將%2放進戰場%3。 + + + + %1 puts %2%3 into their graveyard. + %1將%2%3置入它們的的墳場。 + + + + %1 exiles %2%3. + %1 放逐%2%3. + + + + %1 moves %2%3 to their hand. + %1將%2%3移到它們的的手牌中。 + + + + %1 puts %2%3 into their library. + %1將%2%3放入它們的牌庫。 + + + + %1 puts %2%3 onto the bottom of their library. + %1將%2%3放在它們的牌庫底部。 + + + + %1 puts %2%3 on top of their library. + %1將%2%3放在它們的牌庫頂部。 + + + + %1 puts %2%3 into their library %4 cards from the top. + %1將%2%3放在它們的牌庫頂部%4張牌以下。 + + + + %1 moves %2%3 to sideboard. + %1 將%2%3移到副牌庫. + + + + %1 plays %2%3. + %1使出 %2%3。 + + + + %1 tries to draw from an empty library + %1嘗試從空牌庫抽牌 + + + + %1 draws %2 card(s). + %1抽%2張牌。 + + + + %1 is looking at %2. + %1 正在查看%2。 + + + + %1 is looking at the %4 %3 card(s) %2. + %1 is looking at the top %3 card(s) %2. + top card for singular, top %3 cards for plural + %1 正在查看%2的 %4 %3張卡牌. + + + + bottom + 底部 + + + + top + 頂部 + + + + %1 turns %2 face-down. + %1把%2翻為面朝下。 + + + + %1 turns %2 face-up. + %1把%2翻為面朝上。 + + + + The game has been closed. + 遊戲已經關閉。 + + + + The game has started. + 遊戲已開始。 + + + + %1 has joined the game. + %1 已經加入遊戲。 + + + + %1 is now watching the game. + %1 正在旁觀遊戲。 + + + + You have been kicked out of the game. + 你已被踢出遊戲。 + + + + %1 has left the game (%2). + %1 已經離開遊戲(%2)。 + + + + %1 is not watching the game any more (%2). + %1 已經不再旁觀遊戲(%2)。 + + + + %1 is not ready to start the game any more. + %1 還未準備好開始遊戲。 + + + + %1 shuffles their deck and draws a new hand of %2 card(s). + %1將其牌庫洗牌,然後抽%2張牌作為新的起手。 + + + + %1 shuffles their deck and draws a new hand. + %1將其牌庫洗牌,然後抽起新的手牌。 + + + + You are watching a replay of game #%1. + 你正在觀看遊戲#%1的錄像。 + + + + %1 is ready to start the game. + %1已準備好開始遊戲。 + + + + cards + an unknown amount of cards + 卡牌(眾)  + + + + %1 card(s) + a card for singular, %1 cards for plural + %1 卡牌(眾)  + + + + %1 lends %2 to %3. + %1把&2借給%3. + + + + %1 reveals %2 to %3. + %1把%2展示給%3. + + + + %1 reveals %2. + %1 展示 %2。 + + + + %1 randomly reveals %2%3 to %4. + %1隨機展示%2%3給%4。 + + + + %1 randomly reveals %2%3. + %1隨機展示%2%3。 + + + + %1 peeks at face down card #%2. + %1查看面朝下的卡牌#%2。 + + + + %1 peeks at face down card #%2: %3. + %1查看面朝下的卡牌#%2:%3。 + + + + %1 reveals %2%3 to %4. + %1展示%2%3給%4。 + + + + %1 reveals %2%3. + %1展示%2%3。 + + + + %1 reversed turn order, now it's %2. + %1 逆轉回合次序,現在輪到%2。 + + + + reversed + 逆轉 + + + + normal + 正常 + + + + Heads + 正面 + + + + Tails + 翻面 + + + + %1 flipped a coin. It landed as %2. + %1擲了硬幣。結果為%2。 + + + + %1 rolls a %2 with a %3-sided die. + %1擲%3面骰子,結果為%2。 + + + + %1 flips %2 coins. There are %3 heads and %4 tails. + %1擲了%2個硬幣。結果為%3個正面和%4個反面。 + + + + %1 rolls a %2-sided dice %3 times: %4. + %1把%2面骰子擲了%3次:%4 + + + + %1's turn. + 輪到 %1. + + + + %1 sets annotation of %2 to %3. + %1給%2添加了註釋成%3。 + + + + %1 places %2 %3 on %4 (now %5). + %1 從%4上移去了%2個%3指示物 (現在是%5)。 + + + + %1 removes %2 %3 from %4 (now %5). + %1 從%4上移去了%2個%3指示物 (現在是%5)。 + + + + red counter(s) + 红色指示物 + + + + yellow counter(s) + 黄色指示物 + + + + green counter(s) + 綠色指示物 + + + + %1 sets counter %2 to %3 (%4%5). + %1 將%2指示物設置為 %3 (%4%5)。 + + + + %1 sets %2 to not untap normally. + %1 將 %2 設置為不被重置。 + + + + %1 sets %2 to untap normally. + %1 將 %2 設置為照常重置。 + + + + %1 removes the PT of %2. + %1 移除%2的力量/防禦. + + + + %1 changes the PT of %2 from nothing to %4. + %1把%2的力量/防禦從無數值轉為%4. + + + + %1 changes the PT of %2 from %3 to %4. + %1把%2的力量/防禦從%3轉為%4. + + + + %1 has locked their sideboard. + %1已經鎖定它們的的副牌庫。 + + + + %1 has unlocked their sideboard. + %1已經把它們的的副牌庫解鎖。 + + + + %1 taps their permanents. + %1横置它們的永久卡牌 + + + + %1 untaps their permanents. + %1重置它們的永久卡牌. + + + + %1 taps %2. + %1 横置 %2. + + + + %1 untaps %2. + %1 重置 %2。 + + + + %1 shuffles %2. + % 1 對 %2 洗牌 + + + + %1 shuffles the bottom %3 cards of %2. + %1 洗 %2 底的 %3 張卡牌. + + + + %1 shuffles the top %3 cards of %2. + %1 洗 %2 頂的 %3 張卡牌. + + + + %1 shuffles cards %3 - %4 of %2. + %1到%2.洗%3 - %4 + + + + %1 unattaches %2. + %1 取消了%2的結附。 + + + + %1 undoes their last draw. + %1 放回它們最後一張抽到的卡牌. + + + + %1 undoes their last draw (%2). + %1 放回它們最後一張抽到的卡牌(%2). + + + + MessagesSettingsPage + + + Word1 Word2 Word3 + 詞1 詞2 詞3 + + + + Add New Message + 添加新信息 + + + + Edit Message + 更改信息 + + + + Remove Message + 移除信息 + + + + Add message + 添加信息 + + + + + Message: + 信息: + + + + Edit message + 更改信息 + + + + Chat settings + 聊天設定 + + + + Custom alert words + 自定通知觸發詞語 + + + + Enable chat mentions + 允許聊天中提到某人 + + + + Enable mention completer + 允許自動完成提名 + + + + In-game message macros + 遊戲內短信速鍵 + + + + How to use in-game message macros + 如何使用遊戲內短信速鍵 + + + + Ignore chat room messages sent by unregistered users + 不要顯示未註冊用户的聊天消息。 + + + + Ignore private messages sent by unregistered users + 不要顯示未註冊用户的私人消息. + + + + + Invert text color + 反轉文本顏色 + + + + Enable desktop notifications for private messages + 容許桌面提醒提及私人消息 + + + + Enable desktop notification for mentions + 容許桌面提醒提及聊天提名 + + + + Enable room message history on join + 加入聊天室時開啓消息歷史 + + + + + (Color is hexadecimal) + (顏色為16進制) + + + + Separate words with a space, alphanumeric characters only + 將單詞以空格區分,僅支持字母. + + + + Mtg + + + Card Type + 卡牌類型 + + + + Mana Value + 法力數值 + + + + Color(s) + 颜色(眾)  + + + + Loyalty + 忠誠值 + + + + Main Card Type + 卡牌主類型 + + + + Mana Cost + 法力費用 + + + + P/T + 力量/防御力 + + + + Side + 正面/底面 + + + + Layout + 佈局 + + + + Color Identity + 顏色身份 + + + + OverlapControlWidget + + + Cards to overlap: + 可重疊卡牌: + + + + Overlap percentage: + 重疊百分點 + + + + Overlap direction: + 重疊方向 + + + + Phase + + + Unknown Phase + 不知名階段 + + + + Untap + 重置步 + + + + Upkeep + 維持步 + + + + Draw + 抓牌步 + + + + First Main + 第一主階步 + + + + Beginning of Combat + 開戰步 + + + + Declare Attackers + 宣告進攻步 + + + + Declare Blockers + 宣告阻擋步 + + + + Combat Damage + 戰鬥損害步 + + + + End of Combat + 戰鬥結束步 + + + + Second Main + 第二主階步 + + + + End/Cleanup + 結束/清理步 + + + + PhasesToolbar + + + Untap step + 重置步 + + + + Upkeep step + 維持步 + + + + Draw step + 抓牌步 + + + + First main phase + 第一主階段 + + + + Beginning of combat step + 戰鬥開始步 + + + + Declare attackers step + 宣告阻擋步 + + + + Declare blockers step + 宣告阻擋步 + + + + Combat damage step + 戰鬥損害步 + + + + End of combat step + 戰鬥結束步 + + + + Second main phase + 第二主階段 + + + + End of turn step + 回合完結部 + + + + PictureLoader + + + en + code for scryfall's language property, not available for all languages + + + + + Player + + + Reveal top cards of library + 顯示牌庫頂牌(眾)  + + + + + + + + + + + + + + + Number of cards: (max. %1) + 卡牌數目:(最多可以是%1) + + + + &View graveyard + &查看墳區 + + + + &View exile + 查看放逐區 + + + + Player "%1" + 玩家 "%1" + + + + + + + &Graveyard + &墳區 + + + + + + + &Exile + &放逐區 + + + + &Move hand to... + &將手牌移到... + + + + + + &Top of library + &牌庫頂 + + + + + + &Bottom of library + &牌庫底 + + + + &Move graveyard to... + &將墳場區的牌移到... + + + + + + + &Hand + &手牌 + + + + &Move exile to... + &將放逐區的卡移到 + + + + &View library + &查看牌库 + + + + &View hand + &查看手牌 + + + + View &top cards of library... + 檢視牌庫頂的牌(眾) + + + + Reveal &library to... + 將牌庫展示給... + + + + &Always reveal top card + &一直展示牌庫頂牌 + + + + &View sideboard + &查看副牌庫 + + + + &Draw card + &抽牌 + + + + D&raw cards... + &抓牌(眾)⋯ + + + + &Undo last draw + &撤銷最後抽牌 + + + + Take &mulligan + 洗回手牌&再抽 + + + + Play top card &face down + 將牌庫頂牌&面朝下使出 + + + + Move top card to grave&yard + 將牌庫頂的牌置入&墳墓區 + + + + Move top card to e&xile + 將牌庫頂的牌置入&放逐區 + + + + Move top cards to &graveyard... + 將牌庫頂的牌(眾) 置入&墳墓區... + + + + Move top cards to &exile... + 將牌庫頂的牌(眾) 置入&放逐區... + + + + Put top card on &bottom + 將牌庫頂牌放到牌庫&底 + + + + View bottom cards of library... + 查看牌庫底(眾) 的牌... + + + + Lend library to... + 將牌庫借給... + + + + Shuffle + + + + + Put top cards on stack &until... + Take top cards &until... + 把頂牌放在重疊區, &直到... + + + + Shuffle top cards... + + + + + Shuffle bottom cards... + + + + + &Reveal hand to... + 將手牌&展示給 + + + + Reveal r&andom card to... + 將手牌&隨機一張展示給... + + + + Reveal random card to... + 將手牌隨機一張展示給... + + + + &Sideboard + &副牌庫 + + + + &Library + &牌庫 + + + + &Counters + &數值物 + + + + &Untap all permanents + %重置所有永久卡牌 + + + + R&oll die... + &擲骰子... + + + + &Create token... + &派出令牌... + + + + C&reate another token + &再派出同一令牌 + + + + Cr&eate predefined token + &派出預定名牌 + + + + + &All players + &所有玩家 + + + + S&ay + &説 + + + + &Select All + &全選 + + + + S&elect Row + &選擇橫行 + + + + S&elect Column + &選擇直行 + + + + &Play + &使出 + + + + &Hide + &隱藏 + + + + Play &Face Down + &面朝下使出 + + + + &Tap / Untap + Turn sideways or back again + &横置/重置 + + + + Toggle &normal untapping + 切換&正常重置 + + + + T&urn Over + Turn face up/face down + &反轉 + + + + &Peek at card face + &查看卡牌正面 + + + + &Clone + &複製 + + + + Attac&h to card... + &结附到... + + + + Unattac&h + 取消&結附 + + + + &Draw arrow... + &畫箭咀 + + + + &Increase power + &增加力量 + + + + &Decrease power + &降低力量 + + + + I&ncrease toughness + &增加防禦 + + + + D&ecrease toughness + &降低防禦 + + + + In&crease power and toughness + &增加力量和防御 + + + + Dec&rease power and toughness + &降低力量和防御 + + + + Increase power and decrease toughness + 增加力量與降低防御 + + + + Decrease power and increase toughness + 降低力量與增加防御 + + + + Set &power and toughness... + 設置&力量和防禦為... + + + + Reset p&ower and toughness + 重置&力量和防禦 + + + + &Set annotation... + &設置注釋... + + + + Red + 紅色 + + + + Yellow + 黃色 + + + + Green + 綠色 + + + + &Add counter (%1) + &增加指示物 (%1) + + + + &Remove counter (%1) + &移除指示物 (%1) + + + + &Set counters (%1)... + &設定指示物為(%1)... + + + + &Top of library in random order + & 隨機順序置於牌庫頂 + + + + X cards from the top of library... + 牌庫頂下X張牌... + + + + &Bottom of library in random order + & 不分次序地置於牌庫底 + + + + View top cards of library + 查看牌庫頂(眾) 的牌 + + + + View bottom cards of library + 查看牌庫底(眾) 的牌 + + + + Shuffle top cards of library + + + + + Shuffle bottom cards of library + + + + + Which position should this card be placed: + 本卡牌進入戰場時應放哪種位置: + + + + (max. %1) + (最多維 %1) + + + + Draw hand + 起手 + + + + 0 and lower are in comparison to current hand size + 0或以下的數目是比較於現在手牌數目 + + + + Draw cards + 抓牌(眾) + + + + + Number: + 數目: + + + + Move top cards to grave + 將牌庫頂的牌(眾)置入墳場 + + + + Reveal &top cards to... + 將牌庫&頂牌(眾)展示給... + + + + &Top of library... + &牌庫頂... + + + + &Bottom of library... + &牌庫底... + + + + &Always look at top card + &永遠看到頂牌 + + + + &Open deck in deck editor + 在套牌編輯&打開牌庫檔案 + + + + &Play top card + &使出頂牌 + + + + &Draw bottom card + 從牌庫底抽牌(單) + + + + D&raw bottom cards... + 從牌庫底&抽牌(眾) + + + + &Play bottom card + &使出底牌 + + + + Play bottom card &face down + 將牌庫底牌&面朝下使出 + + + + Move bottom card to grave&yard + 將牌庫底的牌(單) 置入&墳場區 + + + + Move bottom card to e&xile + 將牌庫底的牌置入&放逐區 + + + + Move bottom cards to &graveyard... + 將牌庫底的牌(眾) 置入&墳場區 + + + + Move bottom cards to &exile... + 將牌庫底的牌(眾) 置入&放逐區 + + + + Put bottom card on &top + 將牌庫牌底放到牌庫頂 + + + + Selec&ted cards + 被選卡牌 + + + + Move top cards to exile + 將牌庫頂的牌(眾) 置入放逐區 + + + + Move bottom cards to grave + 將牌庫底的牌(眾) 置入墳場區 + + + + Move bottom cards to exile + 將牌庫底的牌(眾) 置入放逐區 + + + + Draw bottom cards + 從牌庫底抽牌(眾)  + + + + + C&reate another %1 token + &再派出同一%1令牌 + + + + Create tokens + 派出多個令牌 + + + + Place card X cards from top of library + 把卡牌放著牌庫頂下X張牌 + + + + Change power/toughness + 更改力量/防禦 + + + + Change stats to: + 更改力防數據至: + + + + Set annotation + 設置注釋 + + + + Please enter the new annotation: + 請輸入新的注釋: + + + + Set counters + 設定指示物為 + + + + Re&veal to... + &展示给... + + + + View related cards + 查看關聯卡牌 + + + + Token: + 令牌: + + + + All tokens + 所有令牌 + + + + PrintingSelector + + + Display Navigation Buttons + + + + + <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. + + + + + PrintingSelectorCardOverlayWidget + + + Preference + 喜好 + + + + Pin Printing + 版本上別針 + + + + Unpin Printing + 版本脫別針 + + + + Show Related cards + 顯示關聯卡牌 + + + + PrintingSelectorCardSearchWidget + + + Search by set name or set code + 用排組名稱或代碼進行搜尋 + + + + PrintingSelectorCardSelectionWidget + + + Previous Card in Deck + 牌庫次序中上一張卡牌 + + + + Next Card in Deck + 牌庫次序中下一張卡牌 + + + + PrintingSelectorCardSortingWidget + + + Alphabetical + 英文字母次序 + + + + Preference + 喜好 + + + + Release Date + 發行日期 + + + + + Descending + 由後到先 + + + + Ascending + 由先倒後 + + + + QMenuBar + + + Services + 服務 + + + + Hide %1 + 隐藏 %1 + + + + Hide Others + 隱藏其他 + + + + Show All + 全部展示 + + + + Preferences... + 喜好... + + + + Quit %1 + 退出%1 + + + + About %1 + 關於 %1 + + + + QObject + + + Cockatrice card database (*.xml) + Cockatrice卡牌資料庫 (*.xml) + + + + All files (*.*) + 所有檔案格式 (*.*) + + + + Cockatrice replays (*.cor) + Cockatrice錄像文件 (*.cor) + + + + Maindeck + 主牌庫 + + + + Sideboard + 副牌庫 + + + + Tokens + 眾令牌 + + + + QPlatformTheme + + + OK + OK/確認 + + + + Save + 保存 + + + + Save All + 全部保存 + + + + Open + 打開 + + + + &Yes + &是/Yes + + + + Yes to &All + &全選是/Yes + + + + &No + &否/No + + + + N&o to All + &全選否/No + + + + Abort + 退出 + + + + Retry + 重試 + + + + Ignore + 不理 + + + + Close + 關閉 + + + + Cancel + 取消 + + + + Discard + 棄牌 + + + + Help + 協助 + + + + Apply + 應用 + + + + Reset + 重設 + + + + Restore Defaults + 恢復常用認值 + + + + RemoteDeckList_TreeModel + + + Name + 名字 + + + + ID + ID + + + + Upload time + 上載時間 + + + + RemoteReplayList_TreeModel + + + ID + ID + + + + Name + 名字 + + + + Players + 玩家(眾) + + + + Keep + 選用 + + + + Time started + 開始時間 + + + + Duration (sec) + 維持(秒) + + + + RoomSelector + + + Rooms + 房間(眾)  + + + + Joi&n + &加入 + + + + Room + 房間 + + + + Description + 描述 + + + + Permissions + 許可 + + + + Players + 玩家(眾) + + + + Games + 遊戲(眾) + + + + SequenceEdit + + + Choose an action from the table + 從列表中選擇行動 + + + + Hit the key/combination of keys you want to set for this action + 按下以此行動設置的單鍵或組鍵 + + + + Invalid key + 設置的單鍵或組鍵無效 + + + + Shortcut already in use by: + 捷鍵已被:&使用 + + + + Clear + 清除 + + + + Restore default + 恢復常用認值 + + + + SetsModel + + + Enabled + 已啓用 + + + + Set type + 設置類型 + + + + Set code + 設置代碼 + + + + Long name + 長名 + + + + Release date + 發行日期 + + + + ShortcutSettingsPage + + + + Restore all default shortcuts + 恢復所有常用快鍵 + + + + Do you really want to restore all default shortcuts? + 確定要恢復所有常用快鍵嗎? + + + + Clear all default shortcuts + 清除所有常用快鍵 + + + + Do you really want to clear all shortcuts? + 確認清除所有快鍵嗎? + + + + Section: + 部分: + + + + Action: + 行動: + + + + Shortcut: + 快鍵: + + + + How to set custom shortcuts + 如何設置自定快鍵 + + + + Clear all shortcuts + 清除所有快键 + + + + Search by shortcut name + 按快鍵名稱搜尋 + + + + ShortcutTreeView + + + Action + 行動 + + + + Shortcut + 快鍵 + + + + ShortcutsSettings + + + Your configuration file contained invalid shortcuts. +Please check your shortcut settings! + 您的配置檔案含有無效的快捷鍵. +請檢查您的快捷鍵設定. + + + + The following shortcuts have been set to default: + + 以下快捷鍵設置已經返回常用狀態: + + + + + ShutdownDialog + + + &Reason for shutdown: + &關閉原因: + + + + &Time until shutdown (minutes): + &關閉倒數 (分鐘): + + + + Shut down server + 關閉伺服器 + + + + SoundSettingsPage + + + Enable &sounds + 啓用&聲效 + + + + Current sounds theme: + 現前聲效主題: + + + + Test system sound engine + 測試系統聲效引擎 + + + + Sound settings + 聲效設置 + + + + Master volume + 主音量 + + + + SpoilerBackgroundUpdater + + + Spoilers season has ended + 預覽季節已結束 + + + + Deleting spoiler.xml. Please run Oracle + 現在正刪除spoiler.xml。請啟動Oracle + + + + + Spoilers download failed + 預覽下載出錯 + + + + No internet connection + 沒有網絡連接 + + + + Error + 出現錯誤 + + + + Spoilers already up to date + 預覽已是最新 + + + + No new spoilers added + 沒有添加新預覽 + + + + Spoilers have been updated! + 預覽已被更新 + + + + Last change: + 最後變更: + + + + StableReleaseChannel + + + Default + 常用 + + + + No reply received from the release update server. + 從發佈更新伺服器收不到有效的回覆. + + + + Invalid reply received from the release update server. + 從發佈更新伺服器收不到有效的回覆. + + + + No reply received from the tag update server. + 未收到標籤更新伺服器的回應. + + + + Invalid reply received from the tag update server. + 從標籤更新伺服器收不到有效的回覆. + + + + TabAccount + + + Add to Buddy List + + + + + Add to Ignore List + + + + + Account + + + + + TabAdmin + + + Administration + 管理 + + + + Update server &message + 更新伺服器&信息 + + + + &Shut down server + &關閉伺服器 + + + + &Reload configuration + &重新載入設定 + + + + Server administration functions + 伺服器管理功能 + + + + Server moderator functions + + + + + Replay ID + 錄像ID + + + + Grant Replay Access + 給予遊戲錄像重溫瀏覽權 + + + + Username to Activate + 將要啟動的用戶名字 + + + + Force Activate User + 強制性啟動用戶 + + + + &Unlock functions + 功能&解鎖 + + + + &Lock functions + 功能&鎖定 + + + + + Success + 成功 + + + + Replay access granted + 已給予遊戲錄像重溫瀏覽權 + + + + + + + + Error + 出現錯誤 + + + + Unable to grant replay access. Replay ID invalid + 不能給予遊戲錄像重溫瀏覽權. Replay id無效 + + + + Unable to grant replay access. Internal error + 不能給予遊戲錄像重溫瀏覽權. 內部故障. + + + + User successfully activated + 用戶已成功啟動 + + + + Unable to activate user. Username invalid + 不能啟動用戶戶口.用戶名字無效 + + + + Unable to activate user. User already active + 不能啟動用戶戶口.用戶已被啟動 + + + + Unable to activate user. Internal error + 不能啟動用戶戶口.內部故障 + + + + TabDeckEditor + + + Card Info + 卡牌資訊 + + + + Deck + 牌庫 + + + + Filters + 篩選項目 + + + + &View + &視圖 + + + + Printing + 印刷版本 + + + + + + + Visible + 可見 + + + + + + + Floating + 浮動 + + + + Reset layout + 重置布局 + + + + Deck: %1 + 牌庫:%1 + + + + TabDeckStorage + + + Local file system + 本地檔案系統 + + + + Server deck storage + 伺服器牌庫儲藏 + + + + + Open in deck editor + 在套牌編輯打開 + + + + Rename deck or folder + + + + + Upload deck + 上載牌庫 + + + + Download deck + 下载套牌 + + + + + + + New folder + 新文件夾 + + + + + Delete + 删除 + + + + Open decks folder + 打開牌庫文件夾 + + + + Rename local folder + + + + + Rename local file + + + + + New name: + + + + + + + + Error + 出現錯誤 + + + + Rename failed + + + + + + Invalid deck file + 無效牌庫檔案 + + + + Enter deck name + 輸入牌庫名稱 + + + + This decklist does not have a name. +Please enter a name: + 本牌庫列表未有名稱.請輸入名稱: + + + + Unnamed deck + 未命名牌庫 + + + + Failed to upload deck to server + 牌庫上載於伺服器時失敗 + + + + Delete local file + 删除本地檔案 + + + + Are you sure you want to delete the selected files? + 你確定要刪除被選檔案(眾)嗎 ? + + + + Delete remote decks + 刪除伺服器上的牌庫 + + + + Are you sure you want to delete the selected decks? + 你確定要剷除所有被選的牌庫嗎? + + + + + Name of new folder: + 新建文件夾名稱: + + + + Deck Storage + + + + + TabDeckStorageVisual + + + Visual Deck Storage + + + + + Error + + + + + Could not open deck at %1 + + + + + TabEdhRec + + + EDHREC: + + + + + TabGame + + + + + Replay + 遊戲錄像重溫 + + + + + Game + 遊戲 + + + + + Player List + 玩家列表 + + + + + Card Info + 卡牌資訊 + + + + + Messages + 信息 + + + + + Replay Timeline + 錄像時間線 + + + + &Phases + 階段 + + + + &Game + 遊戲 + + + + Next &phase + 下個&階段 + + + + Next phase with &action + 下個階段&行動 + + + + Next &turn + 下個&回合 + + + + Reverse turn order + 逆轉回合次序 + + + + &Remove all local arrows + &移除所有自設箭頭 + + + + Rotate View Cl&ockwise + &順時針旋轉視角 + + + + Rotate View Co&unterclockwise + &逆時針旋轉視角 + + + + Game &information + 遊戲&資訊 + + + + Un&concede + 撤銷&投降 + + + + &Concede + &投降 + + + + &Leave game + &離開遊戲 + + + + C&lose replay + &關閉遊戲錄像 + + + + &Focus Chat + &聚焦聊天 + + + + &Say: + &説: + + + + &View + &視圖 + + + + + + + Visible + 可見 + + + + + + + Floating + 浮動 + + + + Reset layout + 重置布局 + + + + Concede + 投降 + + + + Are you sure you want to concede this game? + 你確定要宣佈投降嗎? + + + + Unconcede + 撤銷投降 + + + + You have already conceded. Do you want to return to this game? + 你已經宣佈投降,你想回到這場遊戲嗎? + + + + Leave game + 離開遊戲 + + + + Are you sure you want to leave this game? + 你確定要離開這個遊戲嗎? + + + + You are flooding the game. Please wait a couple of seconds. + 你正在刷屏。請稍等幾秒鐘。 + + + + A player has joined game #%1 + 有玩家加入遊戲 #%1 + + + + %1 has joined the game + %1 已經加入遊戲。 + + + + kicked by game host or moderator + 被遊戲主機或版主踢出 + + + + player left the game + 玩家已經離開遊戲 + + + + player disconnected from server + 玩家與伺服器斷線 + + + + reason unknown + 原因不明 + + + + You have been kicked out of the game. + 你已被踢出遊戲。 + + + + TabLog + + + Logs + 記錄 + + + + + + Time;SenderName;SenderIP;Message;TargetID;TargetName + 時間;發送者姓名;發送者IP;信息;目標ID;目標姓名 + + + + Room Logs + 房間記錄 + + + + Game Logs + 遊戲記錄 + + + + Chat Logs + 聊天記錄 + + + + + Error + 出現錯誤 + + + + You must select at least one filter. + 您必須選擇至少一個過濾項. + + + + You have to select a valid number of days to locate. + 您必須選擇一個可用的日數來作為定位. + + + + Username: + 用戶名稱: + + + + IP Address: + IP地址 + + + + Game Name: + 遊戲名稱: + + + + GameID: + 遊戲ID: + + + + Message: + 信息: + + + + Main Room + 主房 + + + + Game Room + 遊戲房間 + + + + Private Chat + 私人聊天 + + + + Past X Days: + 過去 X 天: + + + + Today + 今天 + + + + Last Hour + 1小時前 + + + + Maximum Results: + 最多結果: + + + + At least one filter is required. +The more information you put in, the more specific your results will be. + 至少需要1個篩選項。 +您輸入的信息越多,找到結果越準確。 + + + + Get User Logs + 獲取用戶記錄 + + + + Clear Filters + 清除篩選項目 + + + + Filters + 篩選項目 + + + + Log Locations + 記錄位置 + + + + Date Range + 日期範圍 + + + + Maximum Results + 最多結果 + + + + + Message History + 信息歷史 + + + + Failed to collect message history information. + 不能獲取信息歷史消息 + + + + There are no messages for the selected filters. + 沒有符合篩選條件的信息。 + + + + TabMessage + + + Private &chat + 私人&聊天 + + + + &Leave + &離開 + + + + %1 - Private chat + %1 - 私人聊天 + + + + This user is ignoring you, they cannot see your messages in main chat and you cannot join their games. + 你已被這用戶屏蔽, 它們不能再主聊天室看見你的信息,你亦不能參加它們的遊戲. + + + + Private message from + 私人信息信到, 来自 + + + + %1 has left the server. + %1 已離開伺服器. + + + + %1 has joined the server. + %1 已加入伺服器. + + + + TabReplays + + + Local file system + 本地檔案系統 + + + + Server replay storage + 伺服器錄像倉庫 + + + + + Watch replay + 觀看遊戲錄像 + + + + Rename + 改名 + + + + + New folder + 新文件夾 + + + + + Delete + 刪除 + + + + Open replays folder + 打開錄像文件夾 + + + + Download replay + 下載遊戲錄像 + + + + Toggle expiration lock + 過期鎖定 + + + + Rename local folder + 本地檔案夾改名 + + + + Rename local file + 本地檔案改名 + + + + New name: + 新名稱 + + + + Error + 出錯 + + + + Rename failed + 改名不成功 + + + + Name of new folder: + 新建文件夾名稱: + + + + Delete local file + 刪除本地檔案 + + + + Are you sure you want to delete the selected files? + 你確定要剷除所有被選的檔案嗎? + + + + Are you sure you want to delete the selected replays? + 你確定要刪除被選錄像檔案嗎 ? + + + + Delete remote replay + 刪除伺服器錄像 + + + + Game Replays + + + + + TabRoom + + + &Say: + &説: + + + + Chat + 聊天 + + + + &Room + &房間 + + + + &Leave room + &離開房間 + + + + &Clear chat + &清除聊天記錄 + + + + Chat Settings... + 聊天設置... + + + + mentioned you. + 提到了你。 + + + + Click to view + 點擊查看 + + + + You are flooding the chat. Please wait a couple of seconds. + 你正在刷屏。請稍等幾秒鐘。 + + + + TabServer + + + Server + 伺服器 + + + + + + + Error + 出現錯誤 + + + + Failed to join the server room: it doesn't exist on the server. + 加入伺服器房間失敗:在本伺服器上這房間並不存在。 + + + + The server thinks you are in the server room but your client is unable to display it. Try restarting your client. + 伺服器認為你在房間內, 但Cockatrice無法顯示。請重新啓動Cockatrice。 + + + + You do not have the required permission to join this server room. + 你未被允許進入該房間。 + + + + Failed to join the server room due to an unknown error: %1. + 因為以下不知名的錯誤,所以不能參入伺服器房間: %1. + + + + TabSupervisor + + + Deck Editor + + + + + &Visual Deck Storage + + + + + Server + + + + + Account + + + + + Deck Storage + + + + + Game Replays + + + + + Administration + + + + + Logs + + + + + Are you sure? + 你確定嗎? + + + + There are still open games. Are you sure you want to quit? + 遊戲還在繼續, 你確定要退出嗎? + + + + Click to view + 點擊查看 + + + + Your buddy %1 has signed on! + 你的好友%1已登錄! + + + + Unknown Event + 不知名事件 + + + + The server has sent you a message that your client does not understand. +This message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. + +To update your client, go to Help -> Check for Updates. + 伺服器發送了一條你的客户端無法識別的消息。 +這條消息可能是表明當前有新版本的Cockatrice軟件,或是伺服器上運行的是自定/預覽版本。 + +要升級你的客户端,請點擊“幫助”->“檢查更新” + + + + Idle Timeout + 閒置超時 + + + + You are about to be logged out due to inactivity. + 你即將因為長時間沒有活動而被退出。 + + + + Promotion + 升級 + + + + You have been promoted. Please log out and back in for changes to take effect. + 你已被升級.請登出然後再登入讓本更改生效. + + + + Warned + 被警告 + + + + You have received a warning due to %1. +Please refrain from engaging in this activity or further actions may be taken against you. If you have any questions, please private message a moderator. + 你由於$1 原因,現在接受警告. 請避免繼續本行為, 否則你將有可能會接受進一步的警誡或懲罰. 若有任何疑問, 請私信我們門下的版主. + + + + You have received the following message from the server. +(custom messages like these could be untranslated) + 你收到松本伺服器發出的以下訊息(類似本信息的自定訊息可能未經翻譯) + + + + TappedOutInterface + + + + Error + 出現錯誤 + + + + Unable to analyze the deck. + 不能分析本牌庫. + + + + TipsOfTheDay + + + File does not exist. + + 檔案並不存在. + + + + + Failed to open file. + + 不能開啟檔案. + + + + + TranslateCounterName + + + Life + 生命值 + + + + White + 白色 + + + + Blue + 藍色 + + + + Black + 黑色 + + + + Red + 紅色 + + + + Green + 綠色 + + + + Colorless + 無色 + + + + Other + 其他 + + + + UpdateDownloader + + + Could not open the file for reading. + 無法打開文件。 + + + + UserContextMenu + + + User &details + 用户&詳細信息 + + + + Private &chat + 私人&聊天 + + + + Show this user's &games + 顯示這個用户的&遊戲 + + + + Add to &buddy list + 加入到&好友列表 + + + + Remove from &buddy list + 從&好友列表移除 + + + + Add to &ignore list + 加入到&屏蔽列表 + + + + Remove from &ignore list + 從&屏蔽列表移除 + + + + Kick from &game + 從&遊戲中踢出 + + + + Warn user + 警告用戶 + + + + View user's war&n history + 查看用户警告记录 + + + + Ban from &server + 把用戶從&伺服器禁用 + + + + View user's &ban history + 查看用户&禁止記錄 + + + + &Promote user to moderator + 升$用戶作為版主 + + + + Dem&ote user from moderator + 把&用戶從版主降級 + + + + Promote user to &judge + 把&用戶升級成裁判 + + + + Demote user from judge + 把&用戶從裁判降級 + + + + View admin notes + 閱讀管理筆記 + + + + %1's games + %1 的遊戲(眾) + + + + + + Ban History + 禁用歷史 + + + + Ban Time;Moderator;Ban Length;Ban Reason;Visible Reason + 禁止時間;版主;禁止時長;禁止理由;可見理由 + + + + User has never been banned. + 用戶從未被禁 + + + + Failed to collect ban information. + 不能獲取禁用訊息 + + + + + + Warning History + 警告歷史 + + + + Warning Time;Moderator;User Name;Reason + 警告時間;版主;用戶名稱;理由 + + + + User has never been warned. + 用戶從未接收警告 + + + + Failed to collect warning information. + 不能獲取警告信息 + + + + Failed to get admin notes. + 不能獲取管理筆記 + + + + + Success + 成功 + + + + Successfully promoted user. + 用戶升級成功 + + + + Successfully demoted user. + 用戶降級成功 + + + + + + Failed + 失敗 + + + + Failed to promote user. + 用戶升級失敗 + + + + Failed to demote user. + 用戶降級失敗 + + + + Copy hash to clipboard + 把哈希覆印到用戶剪貼 + + + + Remove this user's messages + 移除這用戶訊息 + + + + UserInfoBox + + + Location: + 位置: + + + + Account Age: + 帳戶年齡 + + + + Edit + 更改 + + + + Change password + 更改密碼 + + + + Change avatar + 更改頭像 + + + + Administrator + 管理員 + + + + Moderator + 版主 + + + + Registered user + 登記用戶 + + + + + Unregistered user + 未登記用戶 + + + + Judge + 裁判 + + + + Unknown + 不知名 + + + + The entered password does not match your account. + 輸入密碼跟你戶口不匹配 + + + + + + Information + 資訊 + + + + User information updated. + 用戶資訊已更新。 + + + + + + + + + + + + + Error + 出現錯誤 + + + + User Information + 用戶資訊 + + + + Real Name: + 實名: + + + + User Level: + 用戶等級: + + + + %n Year(s), + amount of years (only shown if more than 0) + %n年(眾) , + + + + %10%n Day(s) %20 + amount of years (if more than 0), amount of days, date in local short format + %10%n日(眾) %20 + + + + Enter Password + 輸入密碼 + + + + Password verification is required in order to change your email address + 要核實密碼才能更改你的電郵地址 + + + + + + An error occurred while trying to update your user information. + 在嘗試更新你的用戶信息時出現錯誤. + + + + This server does not permit you to update your user informations. + 本伺服器不容許你更新你的用戶資訊. + + + + Password changed. + 密碼已更改 + + + + This server does not permit you to change your password. + 本伺服器不容許你更改密碼 + + + + The new password is too short. + 新的密碼太短 + + + + The old password is incorrect. + 舊的密碼不正確 + + + + Avatar updated. + 頭像已更新。 + + + + This server does not permit you to update your avatar. + 本伺服器不容許你更新你的頭像. + + + + An error occured while trying to updater your avatar. + 在更新你頭像時出現錯誤 + + + + UserInterfaceSettingsPage + + + General interface settings + 一般通用接口設置 + + + + &Double-click cards to play them (instead of single-click) + 雙點擊使出卡牌 (而不是單點擊使出) + + + + &Clicking plays all selected cards (instead of just the clicked card) + &單一點擊把所有選擇的卡牌使出(不只是被點擊的單張卡牌) + + + + &Play all nonlands onto the stack (not the battlefield) by default + &把所有使出的非地牌放入堆疊區(不是戰場) 作為常用設置 + + + + Close card view window when last card is removed + + + + + Annotate card text on tokens + 在令牌上標註牌中文字 + + + + Use tear-off menus, allowing right click menus to persist on screen + 使用浮動菜單,允許右鍵單擊, 把菜單保留在屏幕上 + + + + Notifications settings + 通告設置 + + + + Enable notifications in taskbar + 開啓任務欄通告 + + + + Notify in the taskbar for game events while you are spectating + 觀看時在任務欄提示遊戲信息 + + + + Notify in the taskbar when users in your buddy list connect + 在任務欄中提示好友連到伺服器 + + + + Animation settings + 動畫設定 + + + + &Tap/untap animation + &橫置/重置 動畫 + + + + Deck editor/storage settings + + + + + Open deck in new tab by default + 把用新頁開啟牌庫作為常用設置 + + + + Prompt before converting .txt decks to .cod format + + + + + Always convert if not prompted + + + + + Use visual deck storage in game lobby + + + + + Replay settings + 重播設置 + + + + Buffer time for backwards skip via shortcut: + 用速鍵啟動向後跳的緩衝時間: + + + + UserListWidget + + + Users connected to server: %1 + + + + + Users in this room: %1 + + + + + Buddies online: %1 / %2 + + + + + Ignored users online: %1 / %2 + + + + + VisualDeckStorageFolderDisplayWidget + + + Deck Storage + + + + + VisualDeckStorageSearchWidget + + + Search by filename + 按檔案名稱搜尋 + + + + VisualDeckStorageSortWidget + + + Sort Alphabetically (Deck Name) + + + + + Sort Alphabetically (Filename) + + + + + Sort by Last Modified + + + + + Sort by Last Loaded + + + + + VisualDeckStorageWidget + + + Loading database ... + + + + + Show Folders + + + + + Show Tag Filter + + + + + Show Tags On Deck Previews + + + + + Show Banner Card Selection Option + + + + + Include Folder Names in Search + + + + + Draw unused Color Identities + + + + + Unused Color Identities Opacity + + + + + WarningDialog + + + Which warning would you like to send? + 你想發出那種警告? + + + + Redact all messages from this user in all rooms + 把這用戶在所有房間留下的訊息刪除 + + + + &OK + &確認 + + + + &Cancel + &取消 + + + + Warn user for misconduct + 對行為不當的用戶發出警告 + + + + + Error + 出現錯誤 + + + + User name to send a warning to can not be blank, please specify a user to warn. + 接收警告的用戶名稱不能空白.請定好要接收警告的用戶名字. + + + + Warning to use can not be blank, please select a valid warning to send. + 警告名稱不能留空. 請選擇有效的警告. + + + + WndSets + + + Move selected set to the top + 將選擇的牌組送到最頂 + + + + Move selected set up + 將選擇的牌組往上一格 + + + + Move selected set down + 將選擇的牌組往下一格 + + + + Move selected set to the bottom + 將選擇的牌組送到最底 + + + + Search by set name, code, or type + 用排組名稱,代碼,或類型進行搜尋 + + + + Default order + 常用次序 + + + + Restore original art priority order + 恢復常用藝術先後次序 + + + + Enable all sets + 啟用所有牌組 + + + + Disable all sets + 禁用所有卡组 + + + + Enable selected set(s) + 啟用選擇的牌組(眾)  + + + + Disable selected set(s) + 停用選擇的牌組(眾) + + + + Deck Editor + 牌庫編輯 + + + + Use CTRL+A to select all sets in the view. + 請按 CTRL + A, 以此選擇所有看到的牌組 + + + + Only cards in enabled sets will appear in the card list of the deck editor. + 只有已經被啟用的卡牌組中的卡牌,才會在牌庫編輯中的卡牌清單出現 + + + + Image priority is decided in the following order: + 卡牌圖片先後次序由以下次序決定: + + + + first the CUSTOM Folder (%1), then the Enabled Sets in this dialog (Top to Bottom) + %1 is a link to the wiki + 自訂檔案夾(%`1)優先, 此對話窗中已被啟用的牌組為後(由頂到底) + + + + Card Art + 卡牌藝術 + + + + How to use custom card art + 如何使用自定卡牌圖片 + + + + Hints + 提示 + + + + Note + 筆記 + + + + Sorting by column allows you to find a set while not changing set priority. + 按列排序允許您在不更改系列優先級別的情況下查找牌組。 + + + + To enable ordering again, click the column header until this message disappears. + 要再次啓用排序,請單擊列標題,直到此消息消失。 + + + + Use the current sorting as the set priority instead + 用現在排序代替牌組先後 + + + + Sorts the set priority using the same column + 用同一列分牌組先後 + + + + Include online-only (Arena) cards [requires restart] + + + + + Manage sets + 管理牌組 + + + + ZoneViewWidget + + + Ungrouped + 未分組 + + + + Group by Type + 按類型分組 + + + + Group by Mana Value + 按法力數值分組 + + + + Group by Color + 按顏色排序 + + + + Unsorted + 未排序 + + + + Sort by Name + 按名稱排次序 + + + + Sort by Type + 按類型排序 + + + + Sort by Mana Cost + 按法力費用排序 + + + + Sort by Colors + 按顏色(眾) 排序 + + + + Sort by P/T + 按力量/防禦力排序 + + + + Sort by Set + 按排組排序 + + + + shuffle when closing + 關閉界面時洗牌 + + + + pile view + 棟疊觀看 + + + + i18n + + + English + + + + + main + + + Connect on startup + 啟動時自動連接 + + + + Debug to file + 偵錯紀錄寫進檔案 + + + + shortcutsTab + + + Main Window + 主視窗 + + + + + Deck Editor + 牌庫編輯 + + + + Game Lobby + 遊戲廳堂 + + + + Card Counters + 牌指示物 + + + + Player Counters + 玩家指示物 + + + + Power and Toughness + 力量和防御力 + + + + Game Phases + 遊戲階段 + + + + Playing Area + 遊戲區域 + + + + Move Selected Card + 移動所選牌張 + + + + View + 查看 + + + + Move Top Card + 移動頂牌 + + + + Move Bottom Card + 移動底牌 + + + + Gameplay + 遊戲 + + + + Drawing + 抽牌 + + + + Chat Room + 聊天室 + + + + Game Window + 遊戲視窗 + + + + Load Deck from Clipboard + 從剪貼板載入牌庫 + + + + + Replays + 遊戲錄像 + + + + Tabs + + + + + Check for Card Updates... + 檢查卡牌更新 + + + + Connect... + 連線 + + + + Disconnect + 斷開連線 + + + + Exit + 退出 + + + + Full screen + 全屏 + + + + Register... + 註冊... + + + + Settings... + 設定... + + + + Start a Local Game... + 開始離線遊戲 + + + + Watch Replay... + 觀看錄像 + + + + Analyze Deck (deckstats.net) + Analyze Deck + + + + + Analyze Deck (tappedout.net) + + + + + Clear All Filters + 清除所有篩選項目 + + + + Clear Selected Filter + 清除所選的篩選項目 + + + + Close + 關閉 + + + + Remove Card + 移除卡牌 + + + + Manage Sets... + 管理牌組 + + + + Edit Custom Tokens... + 編輯自定眾令牌... + + + + Export Deck (decklist.org) + + + + + Export Deck (decklist.xyz) + + + + + Add Card + 添加卡牌 + + + + Load Deck... + 載入牌庫... + + + + Load Deck from Clipboard... + 從剪貼板載入牌庫 + + + + Edit Deck in Clipboard, Annotated + + + + + Edit Deck in Clipboard + + + + + New Deck + 創建新牌庫 + + + + Open Custom Pictures Folder + 打開自定圖片文件夾 + + + + Print Deck... + 印刷牌庫 + + + + Delete Card + 刪除卡牌 + + + + + Reset Layout + 重置佈局 + + + + Save Deck + 保存牌庫 + + + + Save Deck as... + 牌庫另存為 + + + + Save Deck to Clipboard, Annotated + 從剪貼板儲藏牌庫, 帶注釋 + + + + Save Deck to Clipboard, Annotated (No Set Info) + + + + + Save Deck to Clipboard + 從剪貼板儲藏牌庫 + + + + Save Deck to Clipboard (No Set Info) + + + + + Load Local Deck... + 從自身檔案載入牌庫 + + + + Load Remote Deck... + 載入伺服器上的牌庫... + + + + Set Ready to Start + 設定準備開始 + + + + Toggle Sideboard Lock + 切換副牌庫鎖 + + + + + Add Green Counter + 增加綠色指示物 + + + + + Remove Green Counter + 移除綠色指示物 + + + + + Set Green Counters... + 設定綠色指示物 + + + + Add Yellow Counter + 增加黄色指示物 + + + + Remove Yellow Counter + 移除黄色指示物 + + + + Set Yellow Counters... + 設定黃色指示物 + + + + + Add Red Counter + 增加红色指示物 + + + + + Remove Red Counter + 移除紅色指示物 + + + + + Set Red Counters... + 設定紅色指示物 + + + + Add Life Counter + 增加生命指示物 + + + + Remove Life Counter + 移除生命指示物 + + + + Set Life Counters... + 設定生命指示物 + + + + Add White Counter + 增加白色指示物 + + + + Remove White Counter + 移除白色指示物 + + + + Set White Counters... + 設定白色指示物 + + + + Add Blue Counter + 增加藍色指示物 + + + + Remove Blue Counter + 移除藍色指示物 + + + + Set Blue Counters... + 設定藍色指示物 + + + + Add Black Counter + 增加黑色指示物 + + + + Remove Black Counter + 移除黑色指示物 + + + + Set Black Counters... + 設定黑色指示物 + + + + Add Colorless Counter + 增加無色指示物 + + + + Remove Colorless Counter + 移除無色指示物 + + + + Set Colorless Counters... + 設定無色指示物 + + + + Add Other Counter + 增加其他指示物 + + + + Remove Other Counter + 移除其他指示物 + + + + Set Other Counters... + 設定其他指示物 + + + + Add Power (+1/+0) + 增加力量(+1/+0) + + + + Remove Power (-1/-0) + 削減力量(-1/-0) + + + + Move Toughness to Power (+1/-1) + 防禦移到力量(+1/-1) + + + + Add Toughness (+0/+1) + 增加防御(+0/+1) + + + + Remove Toughness (-0/-1) + 削減防御(-0/-1) + + + + Move Power to Toughness (-1/+1) + 力量移到防禦(-1/+1) + + + + Add Power and Toughness (+1/+1) + 增加力量和防御(+1/+1) + + + + Remove Power and Toughness (-1/-1) + 削減力量和防御(-1/-1) + + + + Set Power and Toughness... + 設置力量和防禦 + + + + Reset Power and Toughness + 重置力量和防御 + + + + Untap + 重置 + + + + Upkeep + 維持 + + + + Draw + 抽牌 + + + + First Main Phase + 第一主階段 + + + + Start Combat + 開始戰鬥 + + + + Attack + 攻擊 + + + + Block + 擋格 + + + + Damage + 計算損害 + + + + End Combat + 戰鬥结束 + + + + Second Main Phase + 第二主階段 + + + + End + 完結 + + + + Next Phase + 下個階段 + + + + Next Phase Action + 下個行動階段 + + + + Next Turn + 下一回合 + + + + Hide Card in Reveal Window + 從揭露視窗中隱藏這牌 + + + + Tap / Untap Card + 横置/重置這牌 + + + + Untap All + 重置全牌 + + + + Toggle Untap + 鎖定重置狀態 + + + + Turn Card Over + 反轉本牌 + + + + Peek Card + 查看卡牌 + + + + Play Card + 使出本牌 + + + + Attach Card... + 结附牌 + + + + Unattach Card + 解除牌结附 + + + + Clone Card + 複製卡牌 + + + + Create Token... + 派出令牌 + + + + Create All Related Tokens + 派出所有相關令牌 + + + + Create Another Token + 再做另外令牌 + + + + Set Annotation... + 記下註解 + + + + Select All Cards in Zone + 選擇所有在區域的牌 + + + + Select All Cards in Row + 選擇所有橫行牌 + + + + Select All Cards in Column + 選擇所有直行牌 + + + + + Bottom of Library + 牌庫底 + + + + + + + Exile + 放逐 + + + + + + + Graveyard + 墳場 + + + + + Hand + 手牌 + + + + + Top of Library + 牌庫頂 + + + + + + Battlefield, Face Down + 戰場,面朝下 + + + + Battlefield + 戰場 + + + + Library + 牌庫 + + + + Sideboard + 副牌庫 + + + + Top Cards of Library + 排副多張頂牌 + + + + Bottom Cards of Library + 排副多張底牌 + + + + Close Recent View + 關閉最近查看 + + + + + Stack + 疊區 + + + + + Graveyard (Multiple) + 多張送墳場 + + + + + Exile (Multiple) + 多張放逐 + + + + Stack Until Found + 找到以下牌前全送疊區 + + + + Draw Bottom Card + 從牌庫底抽一張牌 + + + + Draw Multiple Cards from Bottom... + 從牌庫底抽超過一張牌 + + + + Draw Arrow... + 畫箭咀 + + + + Remove Local Arrows + 清除自設箭咀 + + + + Leave Game + 離開遊戲 + + + + Concede + 投降 + + + + Roll Dice... + 擲骰 + + + + Shuffle Library + 洗牌 + + + + Shuffle Top Cards of Library + + + + + Shuffle Bottom Cards of Library + + + + + Mulligan + 再調度 + + + + Draw a Card + 抽一張牌 + + + + Draw Multiple Cards... + 抽超過一張牌 + + + + Undo Draw + 撤銷抽牌 + + + + Always Reveal Top Card + 永遠顯示頂牌 + + + + Always Look At Top Card + 永遠看到頂牌 + + + + Rotate View Clockwise + 視角順時針轉 + + + + Rotate View Counterclockwise + 視角逆時針轉 + + + + Unfocus Text Box + 取消聚焦文本框 + + + + Focus Chat + 聚焦聊天 + + + + Clear Chat + 掃清聊天 + + + + Refresh + 刷新 + + + + Skip Forward + 跳前 + + + + Skip Backward + 跳後 + + + + Skip Forward by a lot + 向前大跳 + + + + Skip Backward by a lot + 向後大跳 + + + + Play/Pause + 播放/暫停 + + + + Toggle Fast Forward + 切換快進 + + + + Visual Deck Storage + + + + + Deck Storage + + + + + Server + + + + + Account + + + + + Administration + + + + + Logs + + + + \ No newline at end of file diff --git a/cockatrice/translations/cockatrice_zh-Hans.ts b/cockatrice/translations/cockatrice_zh-Hans.ts index 105627ef8..cf65e3a9e 100644 --- a/cockatrice/translations/cockatrice_zh-Hans.ts +++ b/cockatrice/translations/cockatrice_zh-Hans.ts @@ -2,274 +2,426 @@ AbstractCounter - + &Set counter... - 设置指示物为... + &设置指示物 AbstractCounterDialog - + Set counter 设置指示物 - + New value for counter '%1': - 新的指示物数值'%1': + 数值物新价值为 '%1' + + + + AbstractDlgDeckTextEdit + + + &Refresh + + + + + Parse Set Name and Number (if available) + + + + + AbstractTabDeckEditor + + + Open in new tab + + + + + Are you sure? + + + + + The decklist has been modified. +Do you want to save the changes? + + + + + + + + + + + Error + + + + + Could not open deck at %1 + + + + + Could not save remote deck + + + + + + The deck could not be saved. +Please check that the directory is writable and try again. + + + + + Save deck + + + + + The deck could not be saved. + + + + + There are no cards in your deck to be exported + + + + + No deck was selected to be exported. + + + + + AdminNotesDialog + + + Update Notes + 更新后笔记 + + + + Admin Notes for %1 + %1 的管理笔记 + + + + AllZonesCardAmountWidget + + + Mainboard + 主牌库 + + + + Sideboard + 副牌库 AppearanceSettingsPage - + Error - + 出现错误 - + Could not create themes directory at '%1'. - + 不能在'%1'创造主题目录 - + Theme settings - 主题设置 + 主题设定 - + Current theme: - 当前主题 + 现在主题 - + Open themes folder - + 打开主题文件夹 - + + Menu settings + 选单设定 + + + + Show keyboard shortcuts in right-click menus + 在右击选单中显示键盘快速键 + + + Card rendering - 牌面 + 卡面算绘 - + Display card names on cards having a picture - 显示有图卡牌的名称 + 最有图片的牌上显示牌名 - + + Auto-Rotate cards with sideways layout + 将打横佈局卡片自动向上旋转 + + + + Override all card art with personal set preference (Pre-ProviderID change behavior) [Requires Client restart] + 将所有牌面艺术硬性改成本人喜好牌组[须重新啟动客端软件] + + + + Bump sets that the deck contains cards from to the top in the printing selector + 将牌库中包含的牌组推到艺术选择视窗之顶 + + + Scale cards on mouse over - 卡牌随指针缩放 + 卡牌随游标缩放 - - Minimum overlap percentage of cards on the stack and in vertical hand + + Use rounded card corners - + + Minimum overlap percentage of cards on the stack and in vertical hand + 在迭区中和直行手牌中,尽量减少交重迭百分点 + + + + Maximum initial height for card view window: + 卡望框最高开头高度 + + + + + rows + 横列 + + + + Maximum expanded height for card view window: + + + + Hand layout - 手牌区域布局 + 手牌佈局 - + Display hand horizontally (wastes space) - 水平显示手牌区域 (浪费空间) + 打栋显示手牌(浪费空间) - + Enable left justification - 开启左对齐 + 啟用向左对齐 - + Table grid layout 表格布局 - + Invert vertical coordinate 反转垂直坐标 - + Minimum player count for multi-column layout: - 界面布局之内能够容纳的牌手栏数量: + 界面佈局之内能够容纳的最少玩家数量: - + Maximum font size for information displayed on cards: - 卡牌上显示的最大字号 + 卡牌上显示的最大字体大小 BanDialog - + ban &user name - 禁止该用户 + 禁用 &用家名字 - + ban &IP address - 禁止该IP地址 + 禁用&IP地址 - + ban client I&D - 禁止该客户端 + 禁用客户ID - + Ban type - 禁止类型 + 禁用类型 - + &permanent ban - 永久禁止 + &永久禁用 - + &temporary ban - 暂时禁止 + &暂时禁用 - + &Days: - 天: + &日 - + &Hours: - 小时: + &小时 - + &Minutes: - 分钟: + &分鐘 - + Duration of the ban - 持续时长 + 禁用时间 - + Please enter the reason for the ban. This is only saved for moderators and cannot be seen by the banned person. - 请输入禁止原因. -此项为管理缘由而保存并且对于这位被禁止的用户不可见. + 请记载禁用原因 +这项只保留为版主, 被禁用户不能看到. - + Please enter the reason for the ban that will be visible to the banned person. - 请输入对于被禁止用户可见的禁止原因 + 请记载被禁用户能看到的被禁原因. - + Redact all messages from this user in all rooms - + 将这用户所有室中打出的讯息完全移除 - + &OK - 确认 + &OK - + &Cancel - 取消 + &取消 - + Ban user from server - 禁止此用户 + 把用户从本伺服器禁用 - - - - + + + + Error - 错误 + 出现错误 - + You have to select a name-based, IP-based, clientId based, or some combination of the three to place a ban. - 你必须选择姓名、IP、客户端或任意多选组合来执行禁止操作。 + 若要对用户实施禁用,请你选择以下叁种的拼合:名字,IP,或用户id. - + You must have a value in the name ban when selecting the name ban checkbox. - 在禁用姓名时必须填写一个姓名。 + 若用用户名字施行禁用,必须要填写姓名. - + You must have a value in the ip ban when selecting the ip ban checkbox. - 在禁用IP时必须填写一个IP。 + 若用ip地址施行禁用,必须要写下IP地址. - + You must have a value in the clientid ban when selecting the clientid ban checkbox. - 在禁用客户端ID时必须填写一个客户端ID。 + 若用用户ID施行禁用,必须要写下用户ID. BetaReleaseChannel - - Beta Releases - 测试版本 + + Beta + 测试版 - + No reply received from the release update server. - 未收到更新服务器的响应。 + 从发佈更新伺服器收不到回覆. - + Invalid reply received from the release update server. - 收到更新服务器的无效响应 + 从发佈更新伺服器收不到有效的回覆. - + No reply received from the file update server. - 未收到文件更新服务器的响应 + 从档案更新伺服器收不到有效的回覆. CardDatabaseModel - + Name - 名称 + 名字 - + Sets - 系列 + 牌组 - + Mana cost - 法术力费用 + 法力费用 - + Card type 卡牌类别 - + P/T 力量/防御力 - + Color(s) - 颜色 + 顏色 @@ -306,32 +458,32 @@ This is only saved for moderators and cannot be seen by the banned person. Type - 类型 + 种类 Color - 颜色 + 顏色 Text - 文本 + 词语 Set - 系列 + 卡组 Mana Cost - 法术力费用 + 法力费用 Mana Value - + 法力数值 @@ -360,536 +512,1070 @@ This is only saved for moderators and cannot be seen by the banned person. - CardFrame + CardInfoFrameWidget - + Image 卡图 - + Description 描述 - + Both - 完整信息 + 两个都显示 + + + + View transformation + - CardInfoText + CardInfoPictureWidget - + + View related cards + 查看关联卡牌 + + + + Add card to deck + + + + + Mainboard + + + + + Sideboard + + + + + CardInfoTextWidget + + Name: 名称: - + Related cards: - 关联牌: + 有关牌名: - + Unknown card: - 未知牌: + 不知名的牌 CardItem - + &Move to - 移动到 + &移到 - + &Power / toughness - 力量/防御力 + &力量/防御力 + + + + CardSizeWidget + + + Card Size + 卡牌大小 CardZone - + their hand nominative - 他的手牌 + 它们的手牌 - + %1's hand nominative %1的手牌 - + their library look at zone - 他的牌库 + 它们的牌库 - + %1's library look at zone %1的牌库 - - - of their library - top cards of zone, - 从他的牌库中 - - - - of %1's library - top cards of zone - 从%1的牌库中 - - - - their library - reveal zone - 他的牌库 - + of their library + top cards of zone, + 它们的牌库中 + + + + of %1's library + top cards of zone + %1的牌库中 + + + + their library + reveal zone + 它们的牌库 + + + %1's library reveal zone %1的牌库 - + their library shuffle - 他的牌库 + 它们的牌库 - + %1's library shuffle %1的牌库 - + their library nominative - 他的牌库 + 它们的牌库 - + %1's library nominative %1的牌库 - + their graveyard nominative - 他的坟墓场 + 它们的坟场 - + %1's graveyard nominative - %1的坟墓场 + %1的的坟场 - + their exile nominative - 他的放逐区 + 它们的放逐区 - + %1's exile nominative %1的放逐区 - - - their sideboard - look at zone - 他的备牌 - - - - %1's sideboard - look at zone - %1的备牌 - - - - their sideboard - nominative - 他的备牌 - + their sideboard + look at zone + 它们的备牌 + + + + %1's sideboard + look at zone + %1的备牌 + + + + their sideboard + nominative + 它们的备牌 + + + %1's sideboard nominative %1的备牌 + + DeckEditorCardInfoDockWidget + + + Card Info + + + + + DeckEditorDatabaseDisplayWidget + + + Search by card name (or search expressions) + + + + + Add to Deck + + + + + Add to Sideboard + + + + + Select Printing + + + + + Show on EDHREC (Commander) + + + + + Show on EDHREC (Card) + + + + + Show Related cards + + + + + Add card to &maindeck + + + + + Add card to &sideboard + + + + + DeckEditorDeckDockWidget + + + Banner Card + + + + + Select Printing + + + + + Deck + + + + + Deck &name: + + + + + &Comments: + + + + + Hash: + + + + + &Increment number + + + + + &Decrement number + + + + + &Remove row + + + + + Swap card to/from sideboard + + + + + DeckEditorFilterDockWidget + + + Filters + + + + + &Clear all filters + + + + + Delete selected + + + + + DeckEditorMenu + + + &Deck Editor + + + + + &New deck + + + + + &Load deck... + + + + + Load recent deck... + + + + + Clear + + + + + &Save deck + + + + + Save deck &as... + + + + + Load deck from cl&ipboard... + + + + + Edit deck in clipboard + + + + + + Annotated + + + + + + Not Annotated + + + + + Save deck to clipboard + + + + + Annotated (No set info) + + + + + Not Annotated (No set info) + + + + + &Print deck... + + + + + &Send deck to online service + + + + + Create decklist (decklist.org) + + + + + Create decklist (decklist.xyz) + + + + + Analyze deck (deckstats.net) + + + + + Analyze deck (tappedout.net) + + + + + &Close + + + + + DeckEditorPrintingSelectorDockWidget + + + Printing Selector + + + DeckEditorSettingsPage - - + + Update Spoilers - 更新预览 + 更新预览卡牌 - - + + Success 成功 - + Download URLs have been reset. 下载URL已重置。 - + Downloaded card pictures have been reset. 下载的卡牌图片已被重置。 - + Error - 错误 + 出现错误 - + One or more downloaded card pictures could not be cleared. - 1个或多个卡牌图片未能被清除。 + 一幅或以上的卡牌图片未能被清除。 - + Add URL 添加URL - - + + URL: URL: - - + + Edit URL - 编辑URL + 更改URL - + + Network Cache Size: + 图片网路缓存大小: + + + + Redirect Cache TTL: + 网路缓处再定向生存时间 + + + + How long cached redirects for urls are valid for. + 再定向缓处有效时间 + + + + Picture Cache Size: + 图片缓存大小: + + + Add New URL - + 添置新 URL - + Remove URL - + 移除 URL. - + + Day(s) + + + + Updating... 更新中... - + Choose path 选择路径 - + URL Download Priority - URL下载优先级 + URL下载优先次序 - + Spoilers - 偷跑 + 预览 - + Download Spoilers Automatically 自动下载预览 - + Spoiler Location: - Spoiler位置: + 预览位置: - + Last Change - 最近的变更 + 最后变更 - + Spoilers download automatically on launch - 自动下载Spoilers运行 + 开啟时自动下载预览 - + Press the button to manually update without relaunching - 按下按钮手动更新而不重新启动 + 免重新啟动人手按制更新 - + Do not close settings until manual update is complete - 手动更新完成之前,请勿关闭设置 + 人手更新完成前,不能关掉设置 - + Download card pictures on the fly 即时下载卡牌图片 - + How to add a custom URL - 如何添加自定义URL + 如何添加自定URL - + Delete Downloaded Images 删除已下载的图片 - + Reset Download URLs - 重置用于下载的URL + 重置下载URL - - Downloaded images directory size: - - - - + On-disk cache for downloaded pictures - + 下载图片专用硬盘缓储 - - Picture cache size: - - - - + In-memory cache for pictures not currently on screen - + 现不在萤光幕上记忆中图片缓储 DeckListModel - - Number - 数量 + + Count + 数目 - + + Set + 牌组 + + + + Number + 号码 + + + + Provider ID + 提供者 id + + + Card 卡牌 + + DeckLoader + + + Common deck formats (%1) + + + + + All files (*.*) + + + + + DeckPreviewColorIdentityFilterWidget + + + Mode: Exact Match + + + + + Mode: Includes + + + + + DeckPreviewDeckTagsDisplayWidget + + + Edit tags ... + + + + + DeckPreviewTagDialog + + + Deck Tags Manager + + + + + Manage your deck tags. Check or uncheck tags as needed, or add new ones: + + + + + Add a new tag (e.g., Aggro️) + + + + + Add Tag + + + + + Filter tags... + + + + + OK + + + + + Cancel + + + + + Invalid Input + + + + + Tag name cannot be empty! + + + + + Duplicate Tag + + + + + This tag already exists. + + + + + DeckPreviewWidget + + + Banner Card + + + + + Open in deck editor + + + + + Edit Tags + + + + + Rename Deck + + + + + Save Deck to Clipboard + + + + + Annotated + + + + + Annotated (No set info) + + + + + Not Annotated + + + + + Not Annotated (No set info) + + + + + Rename File + + + + + Delete File + + + + + Set Banner Card + + + + + + New name: + + + + + + Error + + + + + Rename failed + + + + + Delete file + + + + + Are you sure you want to delete the selected file? + + + + + Delete failed + + + DeckStatsInterface Error - 错误 + 出现错误 The reply from the server could not be parsed. - 服务器的回复无法分析 + 不能分析从伺服器来的回覆 DeckViewContainer - + Load deck... - 读取套牌... + 载入牌库... - + Load remote deck... - 载入服务器上的套牌... + 载入伺服器上的牌库... - + + Unload deck + Unload deck... + + + + Ready to start - 准备好开始 + 準备好 - + + Force start + 强迫开始 + + + Sideboard unlocked - 解锁备牌 + 备牌解锁 - + Sideboard locked - 锁定备牌 + 解锁锁上 - - Load deck - 载入套牌 - - - + Error - 错误 + 出现错误 - + The selected file could not be loaded. - 此文件无法被载入 + 选择的档案不能再入 + + + + DialogConvertDeckToCodFormat + + + Deck Format Conversion + + + + + You tried to add a tag to a .txt format deck. + Tags can only be added to .cod format decks. + Do you want to convert the deck to the .cod format? + + + + + Remember and automatically apply choice in the future + DlgConnect - + Downloading... 下载中... - + Known Hosts - 已知主机: + 已知主办机 - + + Delete the currently selected saved server + 删除现选储藏伺服器. + + + Refresh the server list with known public servers - 从已知的服务器更新服务器列表 + 从已知的伺服器更新伺服器列表 - + New Host - 新主机 + 新主办 - + Name: 名称: - - - &Host: - 主机: - - &Port: - 端口: + &Host: + &主办机: - Player &name: - 玩家名字: + &Port: + &端口: + Player &name: + 玩家 &名字: + + + P&assword: - 密码: + &密码: - + &Save password - 记住密码 + &储存密码 - + A&uto connect - 自动连接 + &自动连接 - + Automatically connect to the most recent login when Cockatrice opens 当Cockatrice启动时自动连接到最近的登录 - + If you have any trouble connecting or registering then contact the server staff for help! - 如果你在连接或者注册上遇到了任何麻烦,可以联系服务器的工作人员获得帮助。 + 如在连接或註册谁中遇到问题,请联络伺服器职员寻求援助! - - + + Webpage 网页 - + Reset Password - + 重设密码 - + + Forgot password? + 密码忘记了? + + + &Connect 连接 - + Server - 服务器 + 伺服器 - + Login 登录 - + Server Contact - 联系服务器 + 伺服器联络人 - + Connect to Server - 连接服务器 + 连接伺服器 - + Server URL - 服务器URL + 伺服器URL - + Communication Port 通信端口 - + Unique Server Name - 唯一的服务器名称 + 独特伺服器名字 - + Connection Warning 连接警告 - + You need to name your new connection profile. - 你需要给连接配置文件命名。 + 你必须将你的连接简介命名 - + Connect Warning 连接警告 - + The player name can't be empty. - 玩家名称不能为空 + 玩家名称不能空置 @@ -897,12 +1583,12 @@ This is only saved for moderators and cannot be seen by the banned person. Re&member settings - 记住设定 + &记住设定 &Description: - 描述: + &描述: @@ -912,27 +1598,27 @@ This is only saved for moderators and cannot be seen by the banned person. General - 综合 + 一般 Game type - 房间类型 + 游戏类型 &Password: - 密码: + &密码: Only &buddies can join - 只有好友可以加入 + 只容许&好友加入 Only &registered users can join - 只有注册用户可以加入 + 只容许&註册用户加入 @@ -947,22 +1633,22 @@ This is only saved for moderators and cannot be seen by the banned person. Spectators &need a password to watch - 观看者需要密码 + 观看者&需要密码 Spectators can &chat - 观看者可以聊天 + 观看者可以&聊天 Spectators can see &hands - 观看者可见手牌 + 观看者可见&手牌 Create game as spectator - + 以旁观者的身份创建游戏 @@ -970,122 +1656,132 @@ This is only saved for moderators and cannot be seen by the banned person.观看者 - + + Starting life total: + 开始生命总数 + + + + Game setup options + 游戏设定选择 + + + &Clear - 清除 + &清除 - + Create game - 创建一个房间 + 创建游戏 - + Game information - 房间信息 + 游戏资讯 - + Error - 错误 + 出现错误 - + Server error. - 服务器错误. + 伺服器出现错误. DlgCreateToken - - - &Name: - 名称: - + &Name: + &名称: + + + Token - 衍生物 + 令牌 - + C&olor: - 颜色: + &顏色: - + white 白色 - + blue - + 蓝色 - + black 黑色 - + red - + 红色 - + green - 绿 + 绿色 - + multicolor 多色 - + colorless 无色 - - - &P/T: - 力量/防御力: - - &Annotation: - 注释: + &P/T: + &力量/防御力: + &Annotation: + &註释: + + + &Destroy token when it leaves the table - 此衍生物离场时会被摧毁 + &此令牌离场时会被摧毁 - + Token data - 衍生物数据资料 + 令牌资料 - + Show &all tokens - 显示所有衍生物 + 显示&所有令牌 - + Show tokens from this &deck - 显示此套牌所使用的衍生物 + 显示本牌库所有令牌 - + Choose token from list - 从列表中选择衍生物 + 从清单中选择令牌 - + Create token - 创造一个衍生物 + 派出令牌 @@ -1100,18 +1796,18 @@ This is only saved for moderators and cannot be seen by the banned person. To change your avatar, choose a new image. To remove your current avatar, confirm without choosing a new image. - 要更改你的头像,请选择一个新图片。 -要清除你当前的头像,可以不用选择新头像直接确认。 + 若要改变头着,请选择新的图片. +若要移除现有头像, 请不要选择新的图片,直接确认. Browse... - 浏览... + 瀏览... Change avatar - 改变头像 + 更改头像 @@ -1126,7 +1822,25 @@ To remove your current avatar, confirm without choosing a new image. Invalid image chosen. - 所选图片不可用。 + 所选图片不能使用。 + + + + DlgEditDeckInClipboard + + + Edit deck in clipboard + + + + + Error + + + + + Invalid deck list. + @@ -1149,119 +1863,118 @@ To remove your current avatar, confirm without choosing a new image. Change password - 修改密码: + 更改密码 Error - 错误 + 出现错误 Your password is too short. - 新密码太短。 + 你的密码太短。 The new passwords don't match. - 两次输入的新密码不一致。 + 新旧密码不一致 DlgEditTokens - + &Name: - 名称: + &名称: - + C&olor: - 颜色: + &顏色: - + white 白色 - + blue - + 蓝色 - + black 黑色 - - - red - - - green - 绿 + red + 红色 + green + 绿色 + + + multicolor 多色 - + colorless 无色 - + &P/T: - 力量/防御力: + &力量/防御力: - + &Annotation: - 注释: + &註释: - + Token data - 衍生物数据资料 + 令牌资料 - - + + Add token - 添加衍生物 + 添加令牌 - + Remove token - 删除衍生物 + 移除令牌 - + Edit custom tokens - 编辑自定义衍生物 + 编辑自定令牌 - + Please enter the name of the token: - 请输入衍生物名称: - - - - Error - 错误 + 请输入令牌名称: + Error + 出现错误 + + + The chosen name conflicts with an existing card or token. Make sure to enable the 'Token' set in the "Manage sets" dialog to display them correctly. - 所选择的名字与现有的卡牌或衍生物有冲突。 -请确保在“系列管理”中将“衍生物”启用以正常显示。 + 现时选择的名字跟现有的卡牌或令牌出现衝突.请从"牌组管理"中啟动'令牌牌组',保持正确显示. @@ -1269,7 +1982,7 @@ Make sure to enable the 'Token' set in the "Manage sets" dia Email: - 电子邮箱: + 电邮: @@ -1284,145 +1997,145 @@ Make sure to enable the 'Token' set in the "Manage sets" dia Real name: - 真实姓名: + 实名: Edit user profile - 编辑用户资料 + 更改用户资料 DlgFilterGames - + no limit - + 没有限制 + + + + 5 minutes + 5分鐘 + + + + 10 minutes + 10分鐘 - 5 minutes - 五分钟 + 30 minutes + 30分鐘 - 10 minutes - 十分钟 + 1 hour + 1小时 - 30 minutes - - - - - 1 hour - + 2 hours + 2小时 - 2 hours + Hide 'buddies only' games - - Show '&buddies only' games - 查看“仅限好友”的游戏 - - - - Show &full games + + Hide full games - - Show games &that have started + + Hide games that have started - - Show &password protected games - 显示由密码保护的房间 + + Hide password protected games + - - Hide '&ignored user' games - 隐藏有被忽略用户的房间 + + Hide 'ignored user' games + - + &Newer than: - + &新过: - + Game &description: - 房间描述: + 游戏 &描述: - + &Creator name: - 创建者名字: + &创建者名称: - + General - 综合 + 一般 - + &Game types - 房间类型 + &游戏类型 - + at &least: - 至少: + &至少: - + at &most: - 最多: + &最多: - + Maximum player count - 可容纳牌手数量 + 可容纳玩家最高数量 - + Restrictions 限制 - + Show games only if &spectators can watch - + 只显示容许&观看者 观看的游戏 + + + + Show spectator password p&rotected games + 显示有观看者&密码保护的游戏 - Show spectator password p&rotected games - + Show only if spectators can ch&at + 只显示容许观看者&聊天的游戏 - Show only if spectators can ch&at - - - - Show only if spectators can see &hands - + 只显示容许观看者看&手牌 的游戏 - + Spectators - + 观看者 - + Filter games - 过滤房间 + 筛选游戏 @@ -1430,52 +2143,52 @@ Make sure to enable the 'Token' set in the "Manage sets" dia Reset Password Challenge Warning - + 重设密码验证警告 A problem has occurred. Please try to request a new password again. - + 程序出现故障,请再要求新的密码. Enter the information of the server and the account you'd like to request a new password for. - + 请输入需要新密码的伺服器和相关户口资料. &Host: - 主机: + &主办机: &Port: - 端口: + &端口: Player &name: - 玩家名字: + 玩家 &名字: Email: - 电子邮箱: + 电邮: Reset Password Challenge - + 重设密码验证 Reset Password Challenge Error - + 重设密码验证错误 The email address can't be empty. - 电子邮箱不能为空 + 电邮不能空置. @@ -1483,37 +2196,37 @@ Make sure to enable the 'Token' set in the "Manage sets" dia Enter the information of the server you'd like to request a new password for. - + 请输入需要新密码的伺服器资料. &Host: - 主机: + &主办机: &Port: - 端口: + &端口: Player &name: - 玩家名字: + 玩家 &名字: Reset Password Request - + 重设密码要求 Reset Password Error - + 重设密码错误 The player name can't be empty. - 玩家名称不能为空 + 玩家名称不能空置 @@ -1521,32 +2234,32 @@ Make sure to enable the 'Token' set in the "Manage sets" dia Reset Password Warning - + 重设密码警告 A problem has occurred. Please try to request a new password again. - + 程序出现故障,请再要求新的密码. Enter the received token and the new password in order to set your new password. - + 请输入收到验证码和新的密码,以便定立新的密码. &Host: - 主机: + &主办机: &Port: - 端口: + &端口: Player &name: - 玩家名字: + 玩家 &名字: @@ -1562,12 +2275,12 @@ Make sure to enable the 'Token' set in the "Manage sets" dia Reset Password - + 重设密码 The player name can't be empty. - 玩家名称不能为空 + 玩家名称不能空置 @@ -1575,65 +2288,104 @@ Make sure to enable the 'Token' set in the "Manage sets" dia Reset Password Error - + 重设密码错误 The token can't be empty. - 验证码不能为空 + 验证码不能为空白 The new password can't be empty. - 新密码不能为空 + 新密码不能为空白 Error - + 出现错误 Your password is too short. - + 你的新密码太短。 The passwords do not match. - 密码不一致 + 新旧密码不一致. + + + + DlgLoadDeck + + + Load Deck + 载入牌库 DlgLoadDeckFromClipboard - - &Refresh - 刷新 - - - + Load deck from clipboard - 从剪贴板读取套牌 + 从剪贴板载入牌库 - - + Error - 错误 + 出现错误 - - + Invalid deck list. - 无效的牌表. + 牌表不能载入 DlgLoadRemoteDeck - + Load deck - 读取套牌 + 载入牌库 + + + + DlgMoveTopCardsUntil + + + Card name (or search expressions): + 卡牌名称(或搜寻表达式) + + + + Number of hits: + 因为cockatrice原生没有ssl支援, 所以你不能自动下载更新. 请瀏览到我们的下载网页,进行人手更新. + + + + Auto play hits + 开关副牌上锁 + + + + Put top cards on stack until... + 把顶牌放在重迭区, 直到... + + + + No cards matching the search expression exists in the card database. Proceed anyways? + 在卡牌资料库中找不到符合搜寻表达式的卡牌.你想继续吗? + + + + Cockatrice + Cockatrice + + + + Invalid filter + 筛选无效 @@ -1642,42 +2394,44 @@ Make sure to enable the 'Token' set in the "Manage sets" dia Enter your information and the information of the server you'd like to register to. Your email will be used to verify your account. - + 输入你, 和你想登记的伺服器的资料. + +我们会使用你的电邮来核实你的户口. &Host: - 主机: + &主办机: &Port: - 端口: + &端口: Player &name: - 玩家名字: + 玩家 &名字: P&assword: - 密码: + &密码: Password (again): - 再次输入密码: + (再次)输入密码: Email: - 电子邮箱: + 电邮: Email (again): - 再次输入电子邮箱: + (再次)输入电邮: @@ -1692,12 +2446,12 @@ Your email will be used to verify your account. Real name: - 真实姓名: + 实名: Register to server - 注册到服务器 + 註册加入伺服器 @@ -1710,7 +2464,7 @@ Your email will be used to verify your account. Your password is too short. - + 你的密码太短。 @@ -1725,7 +2479,7 @@ Your email will be used to verify your account. The player name can't be empty. - 玩家名称不能为空。 + 玩家名称不能空置 @@ -1733,28 +2487,28 @@ Your email will be used to verify your account. Number of sides: - + 骰子面数: Number of dice: - + 骰子数目: Roll Dice - + 掷骰 DlgSettings - + Unknown Error loading card database - 读取卡牌数据库时出现未知错误 + 读取卡牌资料库时出现不知名错误 - + Your card database is invalid. Cockatrice may not function correctly with an invalid database @@ -1762,16 +2516,16 @@ Cockatrice may not function correctly with an invalid database You may need to rerun oracle to update your card database. Would you like to change your database location setting? - 您的卡牌数据库无效。 + 你的卡牌资料库无效。 -数据库无效也许会导致鸡蛇无法正确运行。 +资料库无效可能会导致Cockatrice无法正确运行。 -您应该重新启动Oracle以便更新卡牌数据库。 +你应该重新启动Oracle,更新卡牌资料库。 -您想要重新设置卡牌数据库路径么? +你想要重新设置卡牌资料库路径吗? - + Your card database version is too old. This can cause problems loading card information or images @@ -1779,104 +2533,114 @@ This can cause problems loading card information or images Usually this can be fixed by rerunning oracle to to update your card database. Would you like to change your database location setting? - 您的卡牌数据库过旧。 + 你的卡牌资料库过旧。 -这也许会导致卡牌图片出现问题。 +这可能会导致卡牌图片或资料出现问题。 -您应该重新启动Oracle以便更新卡牌数据库。 +你应该重新启动Oracle,更新卡牌资料库。 -您想要重新设置卡牌数据库路径么? +你想要重新设置卡牌资料库路径吗? - + Your card database did not finish loading Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues with your cards.xml attached Would you like to change your database location setting? - + 你的卡牌资料库未能完成载入 + +请瀏览 https://github.com/Cockatrice/Cockatrice/issues , 开啟新报错/支援票,票上加载本人的cards.xml文件 + +你想要重新设置卡牌资料库路径吗? - + File Error loading your card database. Would you like to change your database location setting? - 载入卡牌数据库时出现错误 - -您想要重新设置卡牌数据库路径么? + 载入卡牌资料库时出现错误. +你想要重新设置卡牌资料库路径吗? - + Your card database was loaded but contains no cards. Would you like to change your database location setting? - 您的卡牌数据库中并不包含任何卡牌信息 + 你的卡牌资料库中并不包含任何卡牌信息 -您想要重新设置卡牌数据库路径么? +你想要重新设置卡牌资料库路径吗? - + Unknown card database load status Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues Would you like to change your database location setting? - + 发现不知名卡牌资料库载入状态. + +请到 + +https://github.com/Cockatrice/Cockatrice/issues +上交修补票. + +你想不想更改你的资料库位置设定? - - - + + + Error - 错误 + 出现错误 - + The path to your deck directory is invalid. Would you like to go back and set the correct path? - 您的套牌路径无效。您想要重新设置正确的路径么? + 你的牌库目录路径现在无效. 你想要重新设置套牌路径吗? - + The path to your card pictures directory is invalid. Would you like to go back and set the correct path? - 您想要重新设置卡牌数据库路径么? + 你的卡牌图片目录路径现在无效. 你想要重新设置卡牌图片资料库路径吗? - + Settings - 设置 + 设定 - + General - 常规 + 一般 - + Appearance 外观 - + User Interface 用户界面 - + Card Sources - 卡牌的来源 + 卡牌来源 - + Chat 聊天 - + Sound 声音 - + Shortcuts 快捷键 @@ -1891,7 +2655,7 @@ Would you like to change your database location setting? Previous - 已知 + 上一个 @@ -1902,168 +2666,171 @@ Would you like to change your database location setting? DlgUpdate - + Current release channel - 当前的发行通道 + 现在发行渠道 - + Reinstall 重新安装 - + Cancel Download 取消下载 - + Open Download Page 打开下载页面 - + Check for Client Updates - - - - - - - - Error - 错误 + 检查客户端更新 + + + + Error + 出现错误 + + + Cockatrice was not built with SSL support, therefore you cannot download updates automatically! Please visit the download page to update manually. 鸡蛇不支持建立SSL连接,因此你不能自动下载更新! 请访问下载页面以手动更新。 - - Downloading update... - 正在下载更新... + + Downloading update: %1 + Downloading update... + - + Checking for updates... 正在检查更新... - + Finished checking for updates - 更新检查完成 + 更新检查已完成 - + No Update Available 没有更新可用 - + Cockatrice is up to date! - 鸡蛇已经是最新的了! + Cockatrice 已经达到最新版本! - + You are already running the latest version available in the chosen release channel. 你运行的已经是最新的发行版本了。 - - - Current version - 当前版本 - + Current version + 现行版本 + + + Selected release channel - 选择发行渠道 + 现选发行渠道 - - + + Update Available - 更新可用 - - - - - A new version of Cockatrice is available! - 鸡蛇有新版本可用! - - - - - New version - 新版本 + 现有更新版本 - Released - 发布 + A new version of Cockatrice is available! + Cockatrice有新版本可用! + + + + + New version + 新版本 - Changelog - 更新日志 + Released + 已发行 + + Changelog + 变更记载 + + + Do you want to update now? - 你打算现在就更新吗? + 你打算现在更新吗? - - Unfortunately there are no download packages available for your operating system. -You may have to build from source yourself. - 您的鸡蛇版本太旧,但没有适合您操作系统的更新包。 -您可能需要自行从源码编译鸡蛇。 + + Unfortunately, the automatic updater failed to find a compatible download. +You may have to manually download the new version. + Unfortunately there are no download packages available for your operating system. +You may have to build from source yourself. + - - Please check the download page manually and visit the wiki for instructions on compiling. - 请点击检查下载页面,并访问wiki获取编译信息。 - - - - - - Update Error - 更新错误 - - - - An error occurred while checking for updates: - 检查更新时发生错误: - - - - An error occurred while downloading an update: - 下载更新时发生错误: - - - - Installing... - 正在安装... - - - - Cockatrice is unable to open the installer. - 鸡蛇无法打开安装程序。 - - - - Try to update manually by closing Cockatrice and running the installer. - 尝试关闭鸡蛇运行安装程序手动更新。 + + Please check the <a href="%1">releases page</a> on our Github and download the build for your system. + Please check the download page manually and visit the wiki for instructions on compiling. + + + + Update Error + 更新出现错误 + + + + An error occurred while checking for updates: + 检测更新时出现错误: + + + + An error occurred while downloading an update: + 下载更新时出现错误 + + + + Installing... + 安装中 + + + + Cockatrice is unable to open the installer. + Cockatrice 不能啟动安装程式 + + + + Try to update manually by closing Cockatrice and running the installer. + 请你尝试人手更新, 关掉cockatrice, 然后重开安装程式. + + + Download location - 下载区域信息 + 下载位置 @@ -2071,12 +2838,20 @@ You may have to build from source yourself. Clear log when closing - 关闭时清除日志 + 关闭时清除纪录 Debug Log - 问题日志 + 侦错纪录 + + + + EdhrecCommanderResponseCommanderDetailsDisplayWidget + + + Salt: + @@ -2084,111 +2859,126 @@ You may have to build from source yourself. Type your filter here - 输入你打算筛选的类型 + 输入你的筛选类型 GameSelector - - - - - - - - - + + + + + + + + + Error - 错误 + 出现错误 - + Please join the appropriate room first. - 首先请加入一个房间. + 请先参加合适房间 - + Wrong password. 密码错误. - + Spectators are not allowed in this game. - 这个游戏不允许观众加入. + 这个游戏不允许观眾加入. - + The game is already full. 这个游戏已经满了. - + The game does not exist any more. - 这个游戏不能装下啊更多了. + 这个游戏已经不存在了。 - + This game is only open to registered users. - 这个游戏只开放给注册用户. + 这个游戏只开放给註册用户. - + This game is only open to its creator's buddies. - 这个游戏只开放给好友. + 这个游戏只开放给房间主办的好友。 - + You are being ignored by the creator of this game. - 你屏蔽了这个与游戏的创造者. + 你被这个房间的主办屏蔽了. - + + Join Game + + + + + Spectate Game + + + + + Game Information + + + + Join game 加入游戏 - + Password: 密码: - - - Please join the respective room first. - 请各自加入一个房间. - - - - &Filter games - 过滤房间 - - - - C&lear filter - 清除筛选项目 - + Please join the respective room first. + 请先参加相关的房间. + + + + &Filter games + &筛选游戏 + + + + C&lear filter + &清除筛选项目 + + + C&reate - 创建 + &创建 - + &Join - 加入 + &加入 - + J&oin as spectator - 观看 + &作为观看者加入 - + Games shown: %1 / %2 - 显示游戏:%1/%2 + 显示游戏:%1/%2 - + Games 游戏 @@ -2196,100 +2986,100 @@ You may have to build from source yourself. GamesModel - + >1 day - + >1日 - + %1%2 hr short age in hours - + %1%2 小时 - + new - + 新建 - + %1%2 min short age in minutes - + %1%2 分鐘 - + password 密码 - + buddies only 仅限好友 - + reg. users only - 仅注册用户 + 仅限註册用户 - - + + can chat 允许聊天 - + see hands 查看手牌 - + can see hands 允许查看手牌 - + not allowed 不允许 - + Room 房间 - + Age 时长 - + Description - 描述 + 描述: - + Creator 创建者 - + Type 类型 - + Restrictions 限制 - + Players 玩家 - + Spectators 观看者 @@ -2297,92 +3087,102 @@ You may have to build from source yourself. GeneralSettingsPage - + Reset all paths - + 重置所有路径 - + All paths have been reset - + 重置所有路径 - - + + Choose path 选择路径 - + Personal settings - 个人设置 + 个人设定 - + Language: 语言: - + Paths (editing disabled in portable mode) - 路径(在便携模式下禁止编辑) + 路径(在便携模式下不能编辑) - + Paths 路径 - - Decks directory: - 套牌路径: + + How to help with translations + - + + Decks directory: + 牌库路径: + + + Replays directory: 游戏录像目录: - + Pictures directory: 图片目录: - + Card database: - 卡牌数据库: + 卡牌资料库: - + Custom database directory: - + 自主资料库目录 - + Token database: - 衍生物数据库: + 令牌资料库: - + Update channel - 更新通道 + 更新渠道 - + + Check for client updates on startup + 啟动时检查客户更新 + + + Notify if a feature supported by the server is missing in my client - 当客户端缺少服务器支持的特性时提示我 + 如果客户端缺少伺服器支持的更功能时,请提示我 - + Automatically run Oracle when running a new version of Cockatrice - 运行新版本鸡蛇时自动运行Oracle + 啟动新版本Cockatrice时,自动运行Oracle. - + Show tips on startup 显示启动时提示 @@ -2390,790 +3190,799 @@ You may have to build from source yourself. MainWindow - - + + The server has reached its maximum user capacity, please check back later. - 服务器已达到最大用户数,请稍微再试。 + 伺服器已达到最最高用户限额,请另外选择吉时再试. - + There are too many concurrent connections from your address. - 你的地址有太多连接. + 你的网路地址拥有太多同时连繫. - + Banned by moderator - 已被版主禁止 + 你已被版主禁用 - + Expected end time: %1 预计结束时间: %1 - + This ban lasts indefinitely. - 这个封禁是永久的. + 这是无限期禁用 - + Scheduled server shutdown. - 预定服务器关闭. + 排期伺服器关机 - - + + Invalid username. 不可用的用户名。 - + You have been logged out due to logging in at another location. - 由于在其他地点登陆,您已被登出。 + 因为你已在另外的位置登入, 所以你现有的联繫已被登出. - + Connection closed - 连接关闭 + 连繫已被关闭 - + The server has terminated your connection. Reason: %1 - 服务器中断连接. + 伺服器已经中断你的联繫/ 原因: %1 - + The server is going to be restarted in %n minute(s). All running games will be lost. Reason for shutdown: %1 - 服务器将会在 %n 分钟之后重启. -所有正在运行的游戏都会断开. -关闭原因: %1 + 伺服器将会在 %n 分鐘内重新啟动. 所有进行中的游戏都会报销.伺服器关闭原因: %1 - + Scheduled server shutdown - 预定服务器关闭 + 排期伺服器关机 - - + + Success 成功 - + Registration accepted. Will now login. - 注册已成功。 -现在登陆。 + 註册已成功。 +现在登入。 - + Account activation accepted. Will now login. - 账号已激活。 -现在登陆 + 户口啟动已经成功 +现在登入. - + Number of players 玩家人数 - + Please enter the number of players. - 请输入玩家的数量. + 请输入玩家人数 - - + + Player %1 玩家 %1 - + Load replay 载入游戏录像 - + About Cockatrice - 关于Cockatrice鸡蛇 + 关於Cockatrice - + Version 版本 - + Cockatrice Webpage - Cockatrice鸡蛇网站 + Cockatrice 网页 - + Project Manager: 项目经理: - + Past Project Managers: 前项目经理: - + Developers: 开发者: - + Our Developers 我们的开发者: - + Help Develop! 协助开发! - + Translators: 翻译者: - + Our Translators 翻译者名单 - + Help Translate! 协助翻译! - + Support: - 支持: + 支援: - + Report an Issue - 报告错误 + 报告问题 - + Troubleshooting 排除故障 - + F.A.Q. 问答 - - - - - - - - - - - - - - - + + + + + + + + + + + + + - - - + + + + + Error - 错误 + 出现错误 - + Server timeout - 服务器超时 - - - - Failed Login - 登录失败 - - - - Your client seems to be missing features this server requires for connection. - 你的客户端缺少连接这个服务器所需要的要素 - - - - To update your client, go to 'Help -> Check for Client Updates'. - 转到帮助->检查客户端更新,更新你的客户端。 - - - - Incorrect username or password. Please check your authentication information and try again. - 用户名或密码错误。请检查账号信息并重试。 + 伺服器超时 - There is already an active session using this user name. -Please close that session first and re-login. - 已经有一个在线用户正在使用这个用户名. -首先请关闭这个对话框然后重新登陆. + Failed Login + 登入失败 - - + + Your client seems to be missing features this server requires for connection. + 你的客户端似乎脱小本伺服器需要用来达成连繫的功能. + + + + To update your client, go to 'Help -> Check for Client Updates'. + 请去"求助->检查客户端更新" 内更新你的客户. + + + + Incorrect username or password. Please check your authentication information and try again. + 用户名或密码错误。请检查账号信息,然后重试。 + + + + There is already an active session using this user name. +Please close that session first and re-login. + 已经有在线用户正在使用这用户名.请先关闭连线,然后重新登入. + + + + You are banned until %1. 你被禁止直到: %1. - - + + You are banned indefinitely. - 你被永久封禁. + 你已被永久封禁. - + This server requires user registration. Do you want to register now? - 服务器需要注册。现在注册吗? + 本伺服器要求用户註册. 你想不想现在登记? - + This server requires client IDs. Your client is either failing to generate an ID or you are running a modified client. Please close and reopen your client to try again. - 服务器需要客户端ID。您的客户端未能生成ID或您正在运行修改过的客户端。 -请关闭并重新打开客户端。 + 本伺服器要求用户拥有客户id. 你的客户端现在不能造成客户id,或是你正在使用修改过的客户端. +请你关掉现有的客户端,然后重新啟动, 择吉日再试。 - + An internal error has occurred, please close and reopen Cockatrice before trying again. If the error persists, ensure you are running the latest version of the software and if needed contact the software developers. - 出现内部错误,请关闭并重启客户端再尝试。如果错误仍然出现请将您的客户端升级到最新版本,如有需要请联系您的软件提供商。 + 因为内部出现故障, 请关闭,重开Cockatrice 然后再次尝试. +如果故障继续出现,请将客户端软件升级为最新版本, 有需要时请联络软件开发人士. - + Account activation - 账号激活 + 户口啟动 - + Your account has not been activated yet. You need to provide the activation token received in the activation email. - 你的账号还未激活。 -请提供激活邮件中的验证码。 + 你的户口还未尝啟动. +你需要提供从啟动电邮得来的验证码. - + Server Full - 服务器已满 + 伺服器载满 - + Unknown login error: %1 - 未知的登陆错误: %1 - - - - - -This usually means that your client version is out of date, and the server sent a reply your client doesn't understand. - -这通常表示您的客户端已经过于陈旧,未能响应服务器提出的请求。 - - - - Your username must respect these rules: - 您的用户名须遵守以下规则: + 不知名登入错误: %1 + + +This usually means that your client version is out of date, and the server sent a reply your client doesn't understand. + +这通常意味住你的客户端版本已经过期,引起你的客户端不能明晓伺服器的回覆. + + + + Your username must respect these rules: + 您的用户名字必须遵守以下规则: + + + is %1 - %2 characters long 在%1 - %2个字符之间。 - + can %1 contain lowercase characters - 能包含 %1 个小写字母 + 可以 $1 包含细楷/小写字母 - - - - + + + + NOT - + 并否 - + can %1 contain uppercase characters - 能包含%1个小写字母 + 可以 $1 包含大楷/大写字母 - + can %1 contain numeric characters - 能包含%1个数字 + 可以 $1 包含数字 - + can contain the following punctuation: %1 - 能包含以下符号:%1 + 可以包含以下符号:%1 - + first character can %1 be a punctuation mark - 第一个字符不能为%1 标点符号 + 第一个字符可以 %1 是标点符号 - + no unacceptable language as specified by these server rules: note that the following lines will not be translated - + 在以下伺服器规矩中,道明不能接受的语言: - + can not contain any of the following words: %1 - 不能包含以下词:%1 + 不能含有以下字眼: %1 - + can not match any of the following expressions: %1 - 不能包含以下语句:%1 + 不能含有以下语句: %1 - - You may only use A-Z, a-z, 0-9, _, ., and - in your username. - 您可以使用A-Z,a-z,0-9,_,.以及-作为用户名。 - - - - - - - + You may only use A-Z, a-z, 0-9, _, ., and - in your username. + 你的用户名字只可以用以下的字符: A-Z, a-z, 0-9, _, ., 和 - . + + + + + + + + Registration denied - 注册失败 + 註册被拒绝 - + Registration is currently disabled on this server - 当前服务器无法注册 + 这伺服器暂时谢绝登记 - + There is already an existing account with the same user name. - 当前用户名已经被使用。 + 这个用户名字已经被使用. - + It's mandatory to specify a valid email address when registering. - 注册时必须提供一个有效的电子邮箱。 + 登记时必要指明合能用的电邮地址. - + It appears you are attempting to register a new account on this server yet you already have an account registered with the email provided. This server restricts the number of accounts a user can register per address. Please contact the server operator for further assistance or to obtain your credential information. - 看起来你正试图在这个服务器上注册一个新账户,但是你已经有了一个用提供的邮箱注册的账户。 此服务器限制每个邮箱地址可以注册用户帐户的数量。请联系服务器运营商以获得进一步帮助或获取您的凭据信息。 + 你看来好像在本伺服器尝试申请新户口, 但是你已经有跟你电邮掛鉤的户口名字.我们的伺服器限制每用户在每个地址可以有多少个户口. 如果需要更多援助或是得取你的登入资料,请联络伺服器办事人. - + Password too short. 密码太短。 - + Registration failed for a technical problem on the server. - 由于服务器出现技术问题,注册失败。 + 由於伺服器出现技术问题, 你的登记暂时失败. - + Unknown registration error: %1 - 未知的注册错误:%1 + 不知名登记错误: %1 - + Account activation failed - 账号激活失败 + 户口啟动失败 - + Socket error: %1 接口错误: %1 - + You are trying to connect to an obsolete server. Please downgrade your Cockatrice version or connect to a suitable server. Local version is %1, remote version is %2. - 你正在连接到一个过时的服务器,请下调你的版本或者连接到一个匹配的服务器. -你当前的版本 %1, 服务器版本 %2. - - - - Your Cockatrice client is obsolete. Please update your Cockatrice version. -Local version is %1, remote version is %2. - 你使用的客户端意见过时,请使用更新你版本. -你当前的版本 %1, 服务器版本 %2. - - - - Connecting to %1... - 连接到 %1... + 你正在尝试跟过时的伺服器连繫. 请把你的 Cockatrice 版本降级,或是连繫於合适的伺服器. 当地/离线版本维 %1, 遥距/上线版本维 %2. - Registering to %1 as %2... - 正在将 %2 注册到 %1 + Your Cockatrice client is obsolete. Please update your Cockatrice version. +Local version is %1, remote version is %2. + 你的 Cockatrice 版本现已过时.请把你的版本更新. +当地/离线版本维 %1, 遥距/上线版本维 %2. - - Disconnected - 断开连接 - - - - Connected, logging in at %1 - 连接登陆到 %1 + + Connecting to %1... + 向 %1 连接中... - - - Requesting forgotten password to %1 as %2... - 正在将%2向%1申请忘记密码流程。 + Registering to %1 as %2... + 正在用%2的身份登记到%1伺服器... - - &Connect... - 连接... + + Disconnected + 已经断线了 - - &Disconnect - 断开连接 - - - - Start &local game... - 开始本地游戏... + + Connected, logging in at %1 + 连线成功, 在%1登入 + + - &Watch replay... - 观看录像... + Requesting forgotten password to %1 as %2... + 正在用%2的身份对%1要求遗忘密码. - - &Deck editor - 套牌编辑 - - - - &Full screen - 全屏 - - - - &Register to server... - 注册到服务器 - - - - &Restore password... - - - - - &Settings... - 设置... - - - - &Exit - 退出 - - - - A&ctions - 动作 + + &Connect... + &连线... - &Cockatrice - Cockatrice鸡蛇 + &Disconnect + &断线 + + + + Start &local game... + 开始&离线游戏 + + + + &Watch replay... + &观看游戏录像 - C&ard Database - 卡牌数据库 + &Full screen + &全屏 - &Manage sets... - 系列管理 + &Register to server... + &註册加入伺服器 - Edit custom &tokens... - 编辑自定义&tokens + &Restore password... + &弄回密码 - Open custom image folder - 打开自定义图片文件夹 - - - - Open custom sets folder - 打开自定义系列文件夹 + &Settings... + 设置⋯ - Add custom sets/cards - 添加自定义系列/卡牌 - - - - &Help - 帮助 + &Exit + &退出 - &About Cockatrice - 关于Cockatrice鸡蛇 + A&ctions + &动作 - + + &Cockatrice + &Cockatrice + + + + C&ard Database + &卡牌资料库 + + + + &Manage sets... + &管理牌组 + + + + Edit custom &tokens... + 编辑自定 &令牌... + + + + Open custom image folder + 打开自定图片文件夹 + + + + Open custom sets folder + 打开自定牌组文件夹 + + + + Add custom sets/cards + 添加自定卡组/卡牌 + + + + Reload card database + 刷新卡牌资料库 + + + + Tabs + + + + + &Help + &求助 + + + + &About Cockatrice + &关於Cockatrice + + + &Tip of the Day &每日提示 - + Check for Client Updates 检查客户端更新 - + Check for Card Updates... - 对卡的更新找 + 检查卡牌更新 - + View &Debug Log - + 查看&侦错纪录 - + + Open Settings Folder + 打开设置文件夹 + + + Show/Hide - 看或者躲 + 显示/隐藏 - + New Version 新版本 - + Congratulations on updating to Cockatrice %1! Oracle will now launch to update your card database. - 恭喜鸡蛇更新到%1! -现在启动Oracle更新您的卡牌数据库。 + 恭喜你将Cockatrice升级为%1. +现在会帮你啟动oracle, 更新你的卡牌资料库. - + Cockatrice installed - 鸡蛇已安装 + Cockatrice 已被安装 - + Congratulations on installing Cockatrice %1! Oracle will now launch to install the initial card database. - 恭喜鸡蛇更新到%1! -现在启动Oracle更新您的卡牌数据库。 + 恭喜你成功安装Cockatrice%1. +现在会帮你啟动oracle, 安装你的卡牌资料库. - + Card database - 卡牌数据库 + 卡牌资料库 - + Cockatrice is unable to load the card database. Do you want to update your card database now? If unsure or first time user, choose "Yes" - 软件无法载入卡牌数据库。 -是否现在升级你的卡牌数据? -如果不确定或者您是第一次使用,请选择“是” + 软件无法载入卡牌资料库。 +是否现在升级你的卡牌资料库? +如果不确定, 或者是第一次使用,请选择“是/Yes” - - + + Yes - + 是/Yes - - + + No - + 否/No - + Open settings 打开设置 - + New sets found - 发现新系列 + 发现新卡组 - + %n new set(s) found in the card database Set code(s): %1 Do you want to enable it/them? - 卡牌数据库中发现%n个新系列 -系列代码:%1 -要将它们生效吗? + 卡牌资料库中发现%n个新牌组 +牌组代码:%1 +要啟用它们吗? - + View sets - 查看系列 + 查看卡组 - + Welcome 欢迎 - + Hi! It seems like you're running this version of Cockatrice for the first time. All the sets in the card database have been enabled. Read more about changing the set order or disabling specific sets and consequent effects in the "Manage Sets" dialog. - 您好!貌似您是第一次使用这个版本的鸡蛇。 -所有卡牌数据库中的系列都已生效。 -了解更多用“系列管理”窗口更改系列顺序或者禁用某系列的信息。 + 您好!好像是你第一次使用本版本的Cockatrice。 +所有卡牌数据库中的卡组都已生效。 +请阅读,了解更多在“卡组设置”对话框裡.更改卡组顺序或者禁用某些卡组的功能. - - + + Information - 信息 + 资讯 - + A card database update is already running. - 数据库更新已经在运行中。 + 资料库更新现已进行. - + Unable to run the card database updater: - 无法运行卡组数据库更新器: + 不能运行卡牌资料附更新程式: - - failed to start. - 无法开始。 + + Failed to start. The file might be missing, or permissions might be incorrect. + - - crashed. - 程序崩溃。 + + The process crashed some time after starting successfully. + - - timed out. - 超时 + + Timed out. The process took too long to respond. The last waitFor...() function timed out. + - - write error. - 写入错误。 + + An error occurred when attempting to write to the process. For example, the process may not be running, or it may have closed its input channel. + - - read error. - 读取错误。 + + An error occurred when attempting to read from the process. For example, the process may not be running. + - - unknown error. - 未知错误 + + Unknown error occurred. + - - The card database updater exited with an error: %1 - 卡牌数据库更新器退出,错误:%1 + + The card database updater exited with an error: +%1 + - + This server supports additional features that your client doesn't have. This is most likely not a problem, but this message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. To update your client, go to Help -> Check for Updates. - 你当前的客户端缺少该服务器所需要的特性。 -通常不会有问题,但这代表有新版本的Cockatrice可供使用,或有预览版本。 -要升级客户端,请点击 帮助->检查更新 + 本伺服器支持你现在客户端软件欠缺的额外功能. + +这信息不是大问题, 但此信息可能意会现在已有新版本的 Cockatrice, 或是本伺服器运用自设或预先释放版本. + +请到"援助->检查更新"去更新你的客户端. - - - - - + + + + + Load sets/cards - 载入系列/卡牌 + 载入卡组/卡牌 - + Selected file cannot be found. 找不到选择的文件。 - + You can only import XML databases at this time. - 当前只能导入XML格式数据库。 + 你暂时只能导入xml资料库. - + The new sets/cards have been added successfully. Cockatrice will now reload the card database. - 新的系列/卡牌已添加成功。 -Cockatrice鸡蛇现在会重新载入卡组数据库。 + 已经成功添加新的卡组/卡牌. Cockatrice会重新载入卡牌资料库. - + Sets/cards failed to import. - 系列/卡牌导入失败。 + 卡组/卡牌导入失败. - - - + + + Reset Password - + 重设密码 - + Your password has been reset successfully, you can now log in using the new credentials. - 你的密码已重置,现在你可以使用新密码登录了。 + 你的密码已经成功重置, 你可以用新密码重新登入。 - + Failed to reset user account password, please contact the server operator to reset your password. - 重置密码失败,请联系服务器管理员重置密码。 + 重置用户密码失败, 请联络伺服器管责人重置密码. - + Activation request received, please check your email for an activation token. - 激活请求已收到,请查看你的电子邮箱获取激活验证码。 + 已经收到啟动要求, 请从你的电邮获取啟动验证码. @@ -3186,7 +3995,7 @@ Cockatrice鸡蛇现在会重新载入卡组数据库。 from their graveyard - 从他的坟墓场 + 从它们的坟场 @@ -3196,37 +4005,37 @@ Cockatrice鸡蛇现在会重新载入卡组数据库。 from their hand - 从他的手牌 + 从它们的手牌 the top card of %1's library - %1牌库顶的牌 + %1牌库的顶牌 the top card of their library - 他牌库顶的卡牌 + 它们的顶牌 from the top of %1's library - 从%1的牌库顶 + 从%1牌库的顶 from the top of their library - 从他的牌库顶 + 从它们1牌库顶 the bottom card of %1's library - %1的牌库底牌 + %1牌库的底牌 the bottom card of their library - 他牌库底部的牌 + 它们牌库的底牌 @@ -3236,17 +4045,17 @@ Cockatrice鸡蛇现在会重新载入卡组数据库。 from the bottom of their library - 从他的牌库底 + 从它们的牌库底 from %1's library - 从%1的牌库中 + 从%1的牌库 from their library - 从他的牌库 + 从它们的牌库 @@ -3256,27 +4065,27 @@ Cockatrice鸡蛇现在会重新载入卡组数据库。 from the stack - 从堆叠中 + 从迭区中 %1 is now keeping the top card %2 revealed. - %1已持续展示牌库顶牌%2 + %1现在保持%2顶牌显现. %1 is not revealing the top card %2 any longer. - %1不再%2展示牌库顶牌。 + %1不再展示牌库顶牌%2。 %1 can now look at top card %2 at any time. - + %1可以随时查看牌库顶牌 %2. %1 no longer can look at top card %2 at any time. - + %1再不可以随时查看牌库顶牌 %2. @@ -3286,17 +4095,17 @@ Cockatrice鸡蛇现在会重新载入卡组数据库。 %1 has conceded the game. - %1 已放弃游戏。 + %1 在此游戏宣佈降服. %1 has unconceded the game. - %1 已放弃游戏。 + %1 收回在此游戏降服. %1 has restored connection to the game. - %1已恢复连接。 + %1已恢復连接。 @@ -3306,57 +4115,57 @@ Cockatrice鸡蛇现在会重新载入卡组数据库。 %1 points from their %2 to themselves. - %1 将%2 指向自己。 + %1 从它们的%2 指向自己。 %1 points from their %2 to %3. - %1 将%2指向%3。 + %1 从它们的%2指向%3。 %1 points from %2's %3 to themselves. - %1将%2的%3 + %1 从%2的%3 指向自己。 %1 points from %2's %3 to %4. - %1 将%2的%3指向%4。 + %1 从%2的%3 指向%4。 %1 points from their %2 to their %3. - %1将他的%2指向他的%3。 + %1 从它们的%2指向它们的%3。 %1 points from their %2 to %3's %4. - %1将他的%2指向%3的%4。 + %1 从它们的%2指向%3的%4。 %1 points from %2's %3 to their own %4. - %1将%2的%3指向他自己的%4。 + %1 从%2的%3指向自己的%4。 %1 points from %2's %3 to %4's %5. - %1 将%2的%3指向%4的%5。 + %1 从%2的%3指向%4的%5。 %1 creates token: %2%3. - %1创建衍生物:%2%3。 + %1 派出令牌: %2%3 %1 has loaded a deck (%2). - %1 已载入套牌(%2) + %1 已载入牌库(%2) %1 has loaded a deck with %2 sideboard cards (%3). - %1已载入含有 %2张备牌的套牌(%3) + %1已载入含有 %2张备牌的牌库(%3). @@ -3371,72 +4180,72 @@ Cockatrice鸡蛇现在会重新载入卡组数据库。 %1 gives %2 control over %3. - %1让%2控制%3。 + %1将%3的控制转移给%2. %1 puts %2 into play%3 face down. - + %1将%2%3牌面朝下地放进战场。 %1 puts %2 into play%3. - %1将%2%3放进战场。 + %1将%2放进战场%3。 %1 puts %2%3 into their graveyard. - %1将%2%3置入他的坟墓场。 + %1将%2%3置入它们的的坟场。 %1 exiles %2%3. - %1 %3放逐了 %2 + %1 放逐%2%3. %1 moves %2%3 to their hand. - %1将%2%3放回他的手牌中。 + %1将%2%3移到它们的的手牌中。 %1 puts %2%3 into their library. - %1将%2%3放入他的牌库。 + %1将%2%3放入它们的牌库。 %1 puts %2%3 onto the bottom of their library. - + %1将%2%3放在它们的牌库底部。 %1 puts %2%3 on top of their library. - %1将%2%3置于其牌库顶。 + %1将%2%3放在它们的牌库顶部。 %1 puts %2%3 into their library %4 cards from the top. - %1将%2%3放入牌库%4张牌放在牌库顶 + %1将%2%3放在它们的牌库顶部%4张牌以下。 %1 moves %2%3 to sideboard. - %1 将%2%3移动到备牌 + %1 将%2%3移到副牌库. %1 plays %2%3. - %1使用 %2%3。 + %1使出 %2%3。 %1 tries to draw from an empty library - + %1尝试从空牌库抽牌 %1 draws %2 card(s). - %1 抓了 %2 张牌。 + %1抽%2张牌。 @@ -3445,196 +4254,207 @@ Cockatrice鸡蛇现在会重新载入卡组数据库。 - %1 is looking at the top %3 card(s) %2. + %1 is looking at the %4 %3 card(s) %2. + %1 is looking at the top %3 card(s) %2. top card for singular, top %3 cards for plural - %1正在%3检视牌库顶的%2张牌。 + %1 正在查看%2的 %4 %3张卡牌. - + + bottom + 底部 + + + + top + 顶部 + + + %1 turns %2 face-down. - %1回合%2翻为面朝下。 + %1把%2翻为面朝下。 - + %1 turns %2 face-up. - %1回合%2翻为面朝上。 + %1把%2翻为面朝上。 - + The game has been closed. 游戏已经关闭。 - + The game has started. 游戏已开始。 - + %1 has joined the game. %1 已经加入游戏。 - + %1 is now watching the game. %1 正在旁观游戏。 - + You have been kicked out of the game. 你已被踢出游戏。 - + %1 has left the game (%2). - %1 已经离开了游戏(%2)。 + %1 已经离开游戏(%2)。 - + %1 is not watching the game any more (%2). - %1 不再旁观游戏(%2)。 + %1 已经不再旁观游戏(%2)。 - + %1 is not ready to start the game any more. - %1 还未准备好开始游戏。 + %1 还未準备好开始游戏。 - + %1 shuffles their deck and draws a new hand of %2 card(s). - %1洗牌并抓了新的%2张起手 + %1将其牌库洗牌,然后抽%2张牌作为新的起手。 - + %1 shuffles their deck and draws a new hand. - %1洗牌并抓了新的起手 + %1将其牌库洗牌,然后抽起新的手牌。 - + You are watching a replay of game #%1. 你正在观看游戏#%1的录像。 - + %1 is ready to start the game. - %1已准备好开始游戏。 + %1已準备好开始游戏。 - + cards an unknown amount of cards - + 卡牌(眾)  - + %1 card(s) a card for singular, %1 cards for plural - %1 卡牌 + %1 卡牌(眾)  - + %1 lends %2 to %3. - + %1把&2借给%3. - + %1 reveals %2 to %3. - %1将%2展示给%3。 + %1把%2展示给%3. - + %1 reveals %2. %1 展示 %2。 - + %1 randomly reveals %2%3 to %4. %1随机展示%2%3给%4。 - + %1 randomly reveals %2%3. %1随机展示%2%3。 - + %1 peeks at face down card #%2. %1查看面朝下的卡牌#%2。 - + %1 peeks at face down card #%2: %3. %1查看面朝下的卡牌#%2:%3。 - + %1 reveals %2%3 to %4. %1展示%2%3给%4。 - + %1 reveals %2%3. %1展示%2%3。 - + %1 reversed turn order, now it's %2. - %1 反转回合顺序,现在是%2的回合。 + %1 逆转回合次序,现在轮到%2。 - + reversed - 反转 + 逆转 - + normal 正常 - + Heads 正面 - - - Tails - 反面 - - %1 flipped a coin. It landed as %2. - %1掷硬币。结果为%2。 + Tails + 翻面 - + + %1 flipped a coin. It landed as %2. + %1掷了硬币。结果为%2。 + + + %1 rolls a %2 with a %3-sided die. %1掷%3面骰子,结果为%2。 - + %1 flips %2 coins. There are %3 heads and %4 tails. - + %1掷了%2个硬币。结果为%3个正面和%4个反面。 - + %1 rolls a %2-sided dice %3 times: %4. - + %1把%2面骰子掷了%3次:%4 %1's turn. - 现在是%1的回合。 + 轮到 %1. %1 sets annotation of %2 to %3. - %1给%2添加了注释%3。 + %1给%2添加了註释成%3。 %1 places %2 %3 on %4 (now %5). - %1在%4上放置了%2个%3指示物(现在是%5)。 + %1 从%4上移去了%2个%3指示物 (现在是%5)。 @@ -3664,203 +4484,203 @@ Cockatrice鸡蛇现在会重新载入卡组数据库。 %1 sets %2 to not untap normally. - %1 将 %2 设置为不会被通常重置。 + %1 将 %2 设置为不被重置。 %1 sets %2 to untap normally. - %1 将 %2 设置为可通常重置。 + %1 将 %2 设置为照常重置。 %1 removes the PT of %2. - %1 移除%2力量/防御 + %1 移除%2的力量/防御. %1 changes the PT of %2 from nothing to %4. - %1改变%2的力量/防御至%4 + %1把%2的力量/防御从无数值转为%4. %1 changes the PT of %2 from %3 to %4. - %1将%2的力量/防御从%3改变至%4 + %1把%2的力量/防御从%3转为%4. %1 has locked their sideboard. - %1已锁定他的备牌。 + %1已经锁定它们的的副牌库。 %1 has unlocked their sideboard. - %1 解除锁定他的备牌。 + %1已经把它们的的副牌库解锁。 %1 taps their permanents. - %1 横置了永久物。 + %1横置它们的永久卡牌 %1 untaps their permanents. - %1 重置了永久物。 + %1重置它们的永久卡牌. %1 taps %2. - %1 横置了 %2。 + %1 横置 %2. %1 untaps %2. - %1 重置了 %2。 + %1 重置 %2。 %1 shuffles %2. - %1 切洗了%2。 + % 1 对 %2 洗牌 %1 shuffles the bottom %3 cards of %2. - %1 洗 %2 底的 %3 张卡 + %1 洗 %2 底的 %3 张卡牌. %1 shuffles the top %3 cards of %2. - %1 洗 %2 顶的 %3 张卡 + %1 洗 %2 顶的 %3 张卡牌. %1 shuffles cards %3 - %4 of %2. - %1洗%3 - %4 到%2. + %1到%2.洗%3 - %4 - + %1 unattaches %2. %1 取消了%2的结附。 - + %1 undoes their last draw. - %1撤销了最后的抓牌。 + %1 放回它们最后一张抽到的卡牌. - + %1 undoes their last draw (%2). - %1撤销了最后的抓牌(%2)。 + %1 放回它们最后一张抽到的卡牌(%2). MessagesSettingsPage - + Word1 Word2 Word3 词1 词2 词3 - + Add New Message - 打新的信 + 添加新信息 - + Edit Message - + 更改信息 - + Remove Message - + 移除信息 - + Add message 添加信息 - - + + Message: 信息: - + Edit message - 编辑消息 + 更改信息 - + Chat settings - 聊天设置 + 聊天设定 - + Custom alert words - 自定义警告语 + 自定通知触发词语 - + Enable chat mentions 允许聊天中提到某人 - + Enable mention completer 允许自动完成提名 - + In-game message macros - 游戏内消息宏 + 游戏内短信速键 - + How to use in-game message macros - + 如何使用游戏内短信速键 - + Ignore chat room messages sent by unregistered users - 忽略未注册用户的聊天室消息。 + 不要显示未註册用户的聊天消息。 - + Ignore private messages sent by unregistered users - 忽略未注册用户发出的私人消息 + 不要显示未註册用户的私人消息. - - + + Invert text color - 反转文本颜色 + 反转文本顏色 - + Enable desktop notifications for private messages - 开启私人消息桌面提醒 + 容许桌面提醒提及私人消息 - + Enable desktop notification for mentions - 开启聊天提名桌面提醒 + 容许桌面提醒提及聊天提名 - + Enable room message history on join - 开启加入聊天室时的历史消息 + 加入聊天室时开启消息历史 - - + + (Color is hexadecimal) - (颜色为16进制) + (顏色为16进制) - + Separate words with a space, alphanumeric characters only - 将单词以空格区分,仅支持字母 + 将单词以空格区分,仅支持字母. @@ -3868,32 +4688,32 @@ Cockatrice鸡蛇现在会重新载入卡组数据库。 Card Type - 牌类型 + 卡牌类型 Mana Value - + 法力数值 Color(s) - 颜色 + 颜色(眾)  Loyalty - 忠诚 + 忠诚值 Main Card Type - 牌的主类型 + 卡牌主类型 Mana Cost - 法术力费用 + 法力费用 @@ -3903,17 +4723,35 @@ Cockatrice鸡蛇现在会重新载入卡组数据库。 Side - 旁边 + 正面/底面 Layout - 布局 + 佈局 Color Identity - 标识色 + 顏色身份 + + + + OverlapControlWidget + + + Cards to overlap: + 可重迭卡牌: + + + + Overlap percentage: + 重迭百分点 + + + + Overlap direction: + 重迭方向 @@ -3921,126 +4759,126 @@ Cockatrice鸡蛇现在会重新载入卡组数据库。 Unknown Phase - 未知阶段 + 不知名阶段 Untap - 重置阶段 + 重置步 Upkeep - 维持阶段 + 维持步 Draw - 抓牌阶段 + 抓牌步 First Main - 主要阶段1 + 第一主阶步 Beginning of Combat - 战斗开始阶段 + 开战步 Declare Attackers - 宣告进攻阶段 + 宣告进攻步 Declare Blockers - 宣告阻挡阶段 + 宣告阻挡步 Combat Damage - 战斗伤害阶段 + 战斗损害步 End of Combat - 战斗结束阶段 + 战斗结束步 Second Main - 主要阶段2 + 第二主阶步 End/Cleanup - 结束/清理阶段 + 结束/清理步 PhasesToolbar - - - Untap step - 重置阶段 - - Upkeep step - 维持阶段 + Untap step + 重置步 - Draw step - 抓牌阶段 + Upkeep step + 维持步 - First main phase - 主要阶段1 + Draw step + 抓牌步 - Beginning of combat step - 战斗开始阶段 + First main phase + 第一主阶段 - Declare attackers step - 宣告进攻阶段 + Beginning of combat step + 战斗开始步 - Declare blockers step - 宣告阻挡阶段 + Declare attackers step + 宣告阻挡步 - Combat damage step - 战斗伤害阶段 + Declare blockers step + 宣告阻挡步 - End of combat step - 战斗结束阶段 + Combat damage step + 战斗损害步 - Second main phase - 主要阶段2 + End of combat step + 战斗结束步 + Second main phase + 第二主阶段 + + + End of turn step - 结束阶段 + 回合完结部 PictureLoader - + en code for scryfall's language property, not available for all languages zhs @@ -4049,832 +4887,960 @@ Cockatrice鸡蛇现在会重新载入卡组数据库。 Player - + Reveal top cards of library - 查看牌库顶的牌 + 显示牌库顶牌(眾)  - - - - - - - - - + + + + + + + + + + + + Number of cards: (max. %1) - 卡牌数量:(最大%1) + 卡牌数目:(最多可以是%1) - + &View graveyard - 查看坟墓场 + &查看坟区 - + &View exile 查看放逐区 - + Player "%1" 玩家 "%1" - - - - - &Graveyard - 坟墓场 - - - - - - &Exile - 放逐区 - - - - &Move hand to... - 将手牌移动到 - - - - - - &Top of library - 牌库顶 - - - - - - &Bottom of library - 牌库底 - - - - &Move graveyard to... - 将坟墓场的牌移到 - - - - - - - &Hand - 手牌 - - - - &Move exile to... - 将放逐区的卡移到 + + + + &Graveyard + &坟区 + - &View library - 查看套牌 + + + &Exile + &放逐区 - - &View hand - 查看手牌 + + &Move hand to... + &将手牌移到... + + + + + + &Top of library + &牌库顶 + + + + + + &Bottom of library + &牌库底 - View &top cards of library... - 检视牌库顶的牌 - - - - Reveal &library to... - 将套牌展示给 - - - - &Always reveal top card - 一直展示牌库顶牌 - - - - &View sideboard - 查看备牌 - - - - &Draw card - 抓牌 - - - - D&raw cards... - 抓多张牌... - - - - &Undo last draw - 撤销最后抓牌 - - - - Take &mulligan - 抓起始手牌 - - - - &Shuffle - 切洗套牌 - - - - Play top card &face down - 将牌库顶牌面朝下打出 - - - - Move top card to grave&yard - 移动牌库顶牌到坟场 - - - - Move top card to e&xile - 将牌库顶的牌置入放逐区 - - - - Move top cards to &graveyard... - 将牌库顶的牌置入坟墓场... - - - - Move top cards to &exile... - 将牌库顶的牌置入放逐区... - - - - Put top card on &bottom - 将牌库顶牌放到牌库底 - - - - Lend library to... - - - - - Put top cards on stack &until... - Take top cards &until... - - - - - &Reveal hand to... - 将手牌展示给 - - - - Reveal r&andom card to... - 展示随机卡牌到 - - - - Reveal random card to... - 随机展示牌给... - - - - &Sideboard - 备牌 - - - - &Library - 牌库 - - - - &Counters - 数值 - - - - &Untap all permanents - 重置所有永久物 - - - - R&oll die... - 抛骰子... - - - - &Create token... - 创造一个衍生物... - - - - C&reate another token - 将另一个衍生物放置进场 - - - - Cr&eate predefined token - 将预设衍生物放置进场 - - - - - &All players - 全部玩家 - - - - S&ay - - - - - &Play - 开始 - - - - &Hide - 隐藏 - - - - Play &Face Down - 面朝下打出 - - - - &Tap / Untap - Turn sideways or back again - 横置/重置 - - - - Toggle &normal untapping - 锁定通常重置 - - - - T&urn Over - Turn face up/face down - 回合结束 - - - - &Peek at card face - 查看卡牌背面 - - - - &Clone - 复制 - - - - Attac&h to card... - 结附卡牌... - - - - Unattac&h - 取消结附 - - - - &Draw arrow... - 划箭头... - - - - &Increase power - 增加力量 - - - - &Decrease power - 降低力量 - - - - I&ncrease toughness - 增加防御力 - - - - D&ecrease toughness - 降低防御力 - - - - In&crease power and toughness - 增加力量和防御力 - - - - Dec&rease power and toughness - 降低力量和防御力 - - - - Increase power and decrease toughness - 增加力量降低防御力 - - - - Decrease power and increase toughness - 增加防御力降低力量 - - - - Set &power and toughness... - 设置力量和防御力为 - - - - Reset p&ower and toughness - 重置力量和防御力 - - - - &Set annotation... - 设置注释... - - - - Red - - - - - Yellow - - - - - Green - 绿 - - - - &Add counter (%1) - 增加指示物 (%1) - - - - &Remove counter (%1) - 移除指示物 (%1) - - - - &Set counters (%1)... - 设置数值 (%1)... - - - - &Top of library in random order - - - - - X cards from the top of library... - 从牌库顶X张牌... - - - - &Bottom of library in random order - &随机顺序置于牌库底 - - - - View top cards of library - 查看牌库顶的牌 - - - - Which position should this card be placed: - 卡要置于在哪儿? - - - - (max. %1) - - - - - Draw hand - 起手 - - - - 0 and lower are in comparison to current hand size - 比当前手牌数少0或者更多 - - - - Draw cards - 抓牌 - - - - - Number: - 数值: - - - - Move top cards to grave - 将牌库顶的牌置入坟墓场 + &Move graveyard to... + &将坟场区的牌移到... - Reveal &top cards to... - + + + + &Hand + &手牌 - - &Top of library... - + + &Move exile to... + &将放逐区的卡移到 - - &Bottom of library... - + + &View library + &查看牌库 - - &Always look at top card - + + &View hand + &查看手牌 - - &Open deck in deck editor - + + View &top cards of library... + 检视牌库顶的牌(眾) - &Play top card - + Reveal &library to... + 将牌库展示给... + + + + &Always reveal top card + &一直展示牌库顶牌 + + + + &View sideboard + &查看副牌库 - &Draw bottom card - + &Draw card + &抽牌 - D&raw bottom cards... - + D&raw cards... + &抓牌(眾)⋯ - &Play bottom card - + &Undo last draw + &撤销最后抽牌 - Play bottom card &face down - - - - - Move bottom card to grave&yard - - - - - Move bottom card to e&xile - - - - - Move bottom cards to &graveyard... - - - - - Move bottom cards to &exile... - + Take &mulligan + 洗回手牌&再抽 - Put bottom card on &top + Play top card &face down + 将牌库顶牌&面朝下使出 + + + + Move top card to grave&yard + 将牌库顶的牌置入&坟墓区 + + + + Move top card to e&xile + 将牌库顶的牌置入&放逐区 + + + + Move top cards to &graveyard... + 将牌库顶的牌(眾) 置入&坟墓区... + + + + Move top cards to &exile... + 将牌库顶的牌(眾) 置入&放逐区... + + + + Put top card on &bottom + 将牌库顶牌放到牌库&底 + + + + View bottom cards of library... + 查看牌库底(眾) 的牌... + + + + Lend library to... + 将牌库借给... + + + + Shuffle + + + + + Put top cards on stack &until... + Take top cards &until... + 把顶牌放在重迭区, &直到... + + + + Shuffle top cards... + + + + + Shuffle bottom cards... + &Reveal hand to... + 将手牌&展示给 + + + + Reveal r&andom card to... + 将手牌&随机一张展示给... + + + + Reveal random card to... + 将手牌随机一张展示给... + + + + &Sideboard + &副牌库 + + + + &Library + &牌库 + + + + &Counters + &数值物 + + + + &Untap all permanents + %重置所有永久卡牌 + + + + R&oll die... + &掷骰子... + + + + &Create token... + &派出令牌... + + + + C&reate another token + &再派出同一令牌 + + + + Cr&eate predefined token + &派出预定名牌 + + + + + &All players + &所有玩家 + + + + S&ay + &説 + + + + &Select All + &全选 + + + + S&elect Row + &选择横行 + + + + S&elect Column + &选择直行 + + + + &Play + &使出 + + + + &Hide + &隐藏 + + + + Play &Face Down + &面朝下使出 + + + + &Tap / Untap + Turn sideways or back again + &横置/重置 + + + + Toggle &normal untapping + 切换&正常重置 + + + + T&urn Over + Turn face up/face down + &反转 + + + + &Peek at card face + &查看卡牌正面 + + + + &Clone + &复製 + + + + Attac&h to card... + &结附到... + + + + Unattac&h + 取消&结附 + + + + &Draw arrow... + &画箭咀 + + + + &Increase power + &增加力量 + + + + &Decrease power + &降低力量 + + + + I&ncrease toughness + &增加防御 + + + + D&ecrease toughness + &降低防御 + + + + In&crease power and toughness + &增加力量和防御 + + + + Dec&rease power and toughness + &降低力量和防御 + + + + Increase power and decrease toughness + 增加力量与降低防御 + + + + Decrease power and increase toughness + 降低力量与增加防御 + + + + Set &power and toughness... + 设置&力量和防御为... + + + + Reset p&ower and toughness + 重置&力量和防御 + + + + &Set annotation... + &设置注释... + + + + Red + 红色 + + + + Yellow + 黄色 + + + + Green + 绿色 + + + + &Add counter (%1) + &增加指示物 (%1) + + + + &Remove counter (%1) + &移除指示物 (%1) + + + + &Set counters (%1)... + &设定指示物为(%1)... + + + + &Top of library in random order + & 随机顺序置於牌库顶 + + + + X cards from the top of library... + 牌库顶下X张牌... + + + + &Bottom of library in random order + & 不分次序地置於牌库底 + + + + View top cards of library + 查看牌库顶(眾) 的牌 + + + + View bottom cards of library + 查看牌库底(眾) 的牌 + + + + Shuffle top cards of library + + + + + Shuffle bottom cards of library + + + + + Which position should this card be placed: + 本卡牌进入战场时应放哪种位置: + + + + (max. %1) + (最多维 %1) + + + + Draw hand + 起手 + + + + 0 and lower are in comparison to current hand size + 0或以下的数目是比较於现在手牌数目 + + + + Draw cards + 抓牌(眾) + + + + + Number: + 数目: + + + + Move top cards to grave + 将牌库顶的牌(眾)置入坟场 + + + + Reveal &top cards to... + 将牌库&顶牌(眾)展示给... + + + + &Top of library... + &牌库顶... + + + + &Bottom of library... + &牌库底... + + + + &Always look at top card + &永远看到顶牌 + + + + &Open deck in deck editor + 在套牌编辑&打开牌库档案 + + + + &Play top card + &使出顶牌 + + + + &Draw bottom card + 从牌库底抽牌(单) + + + + D&raw bottom cards... + 从牌库底&抽牌(眾) + + + + &Play bottom card + &使出底牌 + + + + Play bottom card &face down + 将牌库底牌&面朝下使出 + + + + Move bottom card to grave&yard + 将牌库底的牌(单) 置入&坟场区 + + + + Move bottom card to e&xile + 将牌库底的牌置入&放逐区 + + + + Move bottom cards to &graveyard... + 将牌库底的牌(眾) 置入&坟场区 + + + + Move bottom cards to &exile... + 将牌库底的牌(眾) 置入&放逐区 + + + + Put bottom card on &top + 将牌库牌底放到牌库顶 + + + Selec&ted cards - + 被选卡牌 - + Move top cards to exile - 将牌库顶的牌置入放逐区 + 将牌库顶的牌(眾) 置入放逐区 - + Move bottom cards to grave - + 将牌库底的牌(眾) 置入坟场区 - + Move bottom cards to exile - + 将牌库底的牌(眾) 置入放逐区 - + Draw bottom cards - + 从牌库底抽牌(眾)  - - + + C&reate another %1 token - 创造%1个衍生物 + &再派出同一%1令牌 - + Create tokens - 创造一个衍生物 + 派出多个令牌 - + Place card X cards from top of library - 查看牌库顶X张牌 + 把卡牌放着牌库顶下X张牌 - + Change power/toughness - 改变力量/防御 + 更改力量/防御 - + Change stats to: - 改变状态至: + 更改力防数据至: - + Set annotation 设置注释 - + Please enter the new annotation: - 请输入注释: + 请输入新的注释: - + Set counters - 设置数值 + 设定指示物为 - + Re&veal to... + &展示给... + + + + View related cards + 查看关联卡牌 + + + + Token: + 令牌: + + + + All tokens + 所有令牌 + + + + PrintingSelector + + + Display Navigation Buttons - - View related cards - 查看关联牌 + + <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. + + + + + PrintingSelectorCardOverlayWidget + + + Preference + 喜好 - - Token: - 衍生物: + + Pin Printing + 版本上别针 - - All tokens - 所有衍生物 + + Unpin Printing + 版本脱别针 + + + + Show Related cards + 显示关联卡牌 + + + + PrintingSelectorCardSearchWidget + + + Search by set name or set code + 用排组名称或代码进行搜寻 + + + + PrintingSelectorCardSelectionWidget + + + Previous Card in Deck + 牌库次序中上一张卡牌 + + + + Next Card in Deck + 牌库次序中下一张卡牌 + + + + PrintingSelectorCardSortingWidget + + + Alphabetical + 英文字母次序 + + + + Preference + 喜好 + + + + Release Date + 发行日期 + + + + + Descending + 由后到先 + + + + Ascending + 由先倒后 QMenuBar - + Services 服务 - + Hide %1 隐藏 %1 - + Hide Others 隐藏其他 - + Show All - 展示所有 + 全部展示 - + Preferences... - 参数 + 喜好... - + Quit %1 退出%1 - + About %1 - 关于 %1 + 关於 %1 QObject - + Cockatrice card database (*.xml) - Cockatrice鸡蛇卡牌数据库 (*.xml) + Cockatrice卡牌资料库 (*.xml) - - + All files (*.*) - 全部文件 (*.*) + 所有档案格式 (*.*) - + Cockatrice replays (*.cor) - 鸡蛇录像文件 (*.cor) + Cockatrice录像文件 (*.cor) - - Common deck formats (*.cod *.dec *.dek *.txt *.mwDeck) - 通用套牌格式(*.cod *.dec *.dek *.txt *.mwDeck) - - - + Maindeck - + 主牌库 - + Sideboard - + 副牌库 - + Tokens - + 眾令牌 QPlatformTheme - + OK - 确定 + OK/确认 - + Save 保存 - + Save All - 保存全部 + 全部保存 - + Open 打开 - + &Yes - + &是/Yes - + Yes to &All - 全选是 + &全选是/Yes - + &No - + &否/No - + N&o to All - 全选否 + &全选否/No - + Abort 退出 - + Retry 重试 - + Ignore - 忽略 + 不理 - + Close 关闭 - + Cancel 取消 - + Discard 弃牌 - + Help - 帮助 + 协助 - + Apply 应用 - + Reset - 重置 + 重设 - + Restore Defaults - 恢复为默认值 + 恢復常用认值 RemoteDeckList_TreeModel - + Name - 名称 + 名字 - + ID ID - + Upload time - 上传时间 + 上载时间 RemoteReplayList_TreeModel - + ID ID - + Name - 名称 + 名字 + + + + Players + 玩家(眾) - Players - 玩家 + Keep + 选用 - Keep - 维持 + Time started + 开始时间 - Time started - 游戏时间 - - - Duration (sec) - 持续(离开) + 维持(秒) @@ -4882,12 +5848,12 @@ Cockatrice鸡蛇现在会重新载入卡组数据库。 Rooms - 房间 + 房间(眾)  Joi&n - 加入 + &加入 @@ -4897,55 +5863,55 @@ Cockatrice鸡蛇现在会重新载入卡组数据库。 Description - 信息 + 描述 Permissions - 许可证 + 许可 Players - 玩家 + 玩家(眾) Games - 游戏 + 游戏(眾) SequenceEdit - + Choose an action from the table - + 从列表中选择行动 - + Hit the key/combination of keys you want to set for this action - + 按下以此行动设置的单键或组键 - - Shortcut already in use - - - - + Invalid key - + 设置的单键或组键无效 - + + Shortcut already in use by: + 捷键已被:&使用 + + + Clear - 走开 + 清除 - + Restore default - + 恢復常用认值 @@ -4968,132 +5934,140 @@ Cockatrice鸡蛇现在会重新载入卡组数据库。 Long name - 长的名称 + 长名 Release date - 发布日期 + 发行日期 ShortcutSettingsPage - - + + Restore all default shortcuts - 恢复所有默认快捷键 + 恢復所有常用快键 - + Do you really want to restore all default shortcuts? - 确定要恢复所有默认快捷键吗? + 确定要恢復所有常用快键吗? - + Clear all default shortcuts - 清楚所有默认快捷键 + 清除所有常用快键 - + Do you really want to clear all shortcuts? - 确定要清楚所有快捷键? + 确认清除所有快键吗? - - Action - 行动 - - - - Shortcut - 快捷键 - - - + Section: - 部分: + 部分: - + Action: 行动: - + Shortcut: - 快捷键: + 快键: - + How to set custom shortcuts - 如何设置自定义快捷键 + 如何设置自定快键 - + Clear all shortcuts - + 清除所有快键 + + + + Search by shortcut name + 按快键名称搜寻 + + + + ShortcutTreeView + + + Action + 行动 + + + + Shortcut + 快键 ShortcutsSettings - + Your configuration file contained invalid shortcuts. Please check your shortcut settings! - 您的配置文件包涵了无效的快捷键 -请检查您的快捷键设置 + 您的配置档案含有无效的快捷键. +请检查您的快捷键设定. - + The following shortcuts have been set to default: - 以下快捷方式被设置为默认状态: + 以下快捷键设置已经返回常用状态: ShutdownDialog - + &Reason for shutdown: - 关闭原因: + &关闭原因: - + &Time until shutdown (minutes): - 关闭倒计时 (分钟): + &关闭倒数 (分鐘): - + Shut down server - 关闭服务器 + 关闭伺服器 SoundSettingsPage - + Enable &sounds - 启用声效 + 启用&声效 - + Current sounds theme: - 当前声效主题: + 现前声效主题: - + Test system sound engine - 测试系统声效 + 测试系统声效引擎 - + Sound settings 声效设置 - + Master volume 主音量 @@ -5101,48 +6075,48 @@ Please check your shortcut settings! SpoilerBackgroundUpdater - + Spoilers season has ended - 偷跑时间已结束 + 预览季节已结束 - + Deleting spoiler.xml. Please run Oracle - 删除spoiler.xml。请运行Oracle + 现在正删除spoiler.xml。请啟动Oracle - - + + Spoilers download failed - Spoilers下载错误 + 预览下载出错 - + No internet connection 没有网络连接 - + Error - 错误 + 出现错误 - + Spoilers already up to date - Spoilers更新已准备好 + 预览已是最新 - + No new spoilers added - 没有新的spoilers被添加 + 没有添加新预览 - + Spoilers have been updated! - Spoilers已更新! + 预览已被更新 - + Last change: 最后变更: @@ -5150,638 +6124,593 @@ Please check your shortcut settings! StableReleaseChannel - - Stable Releases - 稳定版本 + + Default + 常用 - + No reply received from the release update server. - 更新服务器未响应。 + 从发佈更新伺服器收不到有效的回覆. - + Invalid reply received from the release update server. - 收到更新服务器的无效回应。 + 从发佈更新伺服器收不到有效的回覆. - + No reply received from the tag update server. - 未收到标签更新服务器的响应 + 未收到标籤更新伺服器的回应. - + Invalid reply received from the tag update server. - 收到标签更新服务器的无效响应 + 从标籤更新伺服器收不到有效的回覆. + + + + TabAccount + + + Add to Buddy List + + + + + Add to Ignore List + + + + + Account + TabAdmin - + Administration - 管理员 + 管理 - + Update server &message - 更新服务器信息 + 更新伺服器&信息 - + &Shut down server - 关闭服务器 + &关闭伺服器 - + &Reload configuration - 重新载入设置 + &重新载入设定 - + Server administration functions - 服务器管理功能 + 伺服器管理功能 - + + Server moderator functions + + + + + Replay ID + 录像ID + + + + Grant Replay Access + 给予游戏录像重温瀏览权 + + + + Username to Activate + 将要啟动的用户名字 + + + + Force Activate User + 强制性啟动用户 + + + &Unlock functions - 解锁功能 + 功能&解锁 - + &Lock functions - 锁定功能 + 功能&锁定 + + + + + Success + 成功 + + + + Replay access granted + 已给予游戏录像重温瀏览权 + + + + + + + + Error + 出现错误 + + + + Unable to grant replay access. Replay ID invalid + 不能给予游戏录像重温瀏览权. Replay id无效 + + + + Unable to grant replay access. Internal error + 不能给予游戏录像重温瀏览权. 内部故障. + + + + User successfully activated + 用户已成功啟动 + + + + Unable to activate user. Username invalid + 不能啟动用户户口.用户名字无效 + + + + Unable to activate user. User already active + 不能啟动用户户口.用户已被啟动 + + + + Unable to activate user. Internal error + 不能啟动用户户口.内部故障 TabDeckEditor - - Search by card name (or search expressions) - Search by card name - 用卡的名找或者用表达找 - - - - Add to Deck - 添加至套牌 - - - - Add to Sideboard - 添加至备牌 - - - - Show Related cards - 显示关联牌 - - - - &Clear all filters - 清除所有筛选项目 - - - - Delete selected - 删除所选项 - - - - Deck &name: - 套牌名称: - - - - &Comments: - 评论: - - - - Hash: - 哈希值: - - - - &New deck - 创建新套牌 - - - - &Load deck... - 读取套牌... - - - - &Save deck - 保存套牌 - - - - Save deck &as... - 另存为套牌... - - - - Load deck from cl&ipboard... - 从剪贴板读取套牌... - - - - Save deck to clipboard - 保存卡组到剪切板 - - - - Annotated - 注释 - - - - Not Annotated - 没有注释 - - - - &Print deck... - 打印套牌... - - - - &Send deck to online service - 发送卡组到在线服务器 - - - - Create decklist (decklist.org) - 创建卡组(decklist.org) - - - - Analyze deck (deckstats.net) - 分析卡组(deckstats.net) - - - - Analyze deck (tappedout.net) - 分析卡组(tappedout.net) - - - - &Close - 关闭 - - - - Add card to &maindeck - 添加卡牌到主要套牌 - - - - Add card to &sideboard - 添加卡牌到备牌 - - - - &Remove row - 移除卡牌 - - - - &Increment number - 添加卡牌张数 - - - - &Decrement number - 减少卡牌张数 - - - - &Deck Editor - 套牌编辑 - - - - + Card Info - 卡牌信息 + 卡牌资讯 - - + Deck - 套牌 + 牌库 - - + Filters - 过滤器 + 筛选项目 - + &View - 视图 + &视图 - - - + + Printing + 印刷版本 + + + + + + Visible 可见 - - - + + + + Floating - 移动窗口 + 浮动 - + Reset layout - 重置界面 + 重置布局 - + Deck: %1 - 套牌: %1 - - - - Are you sure? - 你确定吗? - - - - The decklist has been modified. -Do you want to save the changes? - 套牌列表已被修改。 -你想要保存更改吗? - - - - Load deck - 读取套牌 - - - - - - - - - Error - 错误 - - - - The deck could not be saved. - 未能保存套牌。 - - - - Could not save remote deck - - - - - - The deck could not be saved. -Please check that the directory is writable and try again. - 套牌未能保存. -请检查目录是否可用后再重试. - - - - Save deck - 保存套牌 - - - - There are no cards in your deck to be exported - 你的卡组中没有卡可以导出 - - - - No deck was selected to be saved. - 没有选择卡组用于保存 + 牌库:%1 TabDeckStorage - - Deck storage - 套牌仓库 - - - + Local file system - 本地文件系统 + 本地档案系统 - + Server deck storage - 服务器上的套牌仓库 + 伺服器牌库储藏 - - + + Open in deck editor - 打开套牌编辑 + 在套牌编辑打开 - + + Rename deck or folder + + + + Upload deck - 上传套牌 + 上载牌库 - + Download deck 下载套牌 - - + + + + New folder - 新建文件夹 + 新文件夹 - - + + Delete 删除 - - + + Open decks folder + 打开牌库文件夹 + + + + Rename local folder + + + + + Rename local file + + + + + New name: + + + + + + + + Error + 出现错误 + + + + Rename failed + + + + + + Invalid deck file + 无效牌库档案 + + + + Enter deck name + 输入牌库名称 + + + + This decklist does not have a name. +Please enter a name: + 本牌库列表未有名称.请输入名称: + + + + Unnamed deck + 未命名牌库 + + + + Failed to upload deck to server + 牌库上载於伺服器时失败 + + + + Delete local file + 删除本地档案 + + + + Are you sure you want to delete the selected files? + 你确定要删除被选档案(眾)吗 ? + + + + Delete remote decks + 删除伺服器上的牌库 + + + + Are you sure you want to delete the selected decks? + 你确定要剷除所有被选的牌库吗? + + + + + Name of new folder: + 新建文件夹名称: + + + + Deck Storage + + + + + TabDeckStorageVisual + + + Visual Deck Storage + + + + Error - - Invalid deck file + + Could not open deck at %1 + + + TabEdhRec - - Enter deck name - 输入套牌名称 - - - - This decklist does not have a name. -Please enter a name: - 这个套牌没有名称. -请输入一个名称: - - - - Unnamed deck - 未命名套牌 - - - - Failed to upload deck to server + + EDHREC: - - - Delete local file - 删除本地文件 - - - - - - Are you sure you want to delete "%1"? - 你确定要删除"%1" 吗? - - - - Name of new folder: - 新建文件夹的名称: - - - - Delete remote folder - 删除服务器文件夹 - - - - Delete remote deck - 删除服务器上的套牌 - TabGame - - - + + + Replay - 录像 + 游戏录像重温 - - + + Game 游戏 - - + + Player List 玩家列表 - - + + Card Info - 卡牌信息 + 卡牌资讯 - - + + Messages - 消息 + 信息 - - + + Replay Timeline 录像时间线 - + &Phases 阶段 - + &Game 游戏 - + Next &phase - 下个阶段 + 下个&阶段 - + Next phase with &action 下个阶段&行动 - + Next &turn - 下个回合 + 下个&回合 - + Reverse turn order - 反转回合顺序 + 逆转回合次序 - + &Remove all local arrows - 重置所有箭头 + &移除所有自设箭头 - + Rotate View Cl&ockwise - 顺时针旋转视角 + &顺时针旋转视角 - + Rotate View Co&unterclockwise - 逆时针旋转视角 + &逆时针旋转视角 - + Game &information - 游戏信息 + 游戏&资讯 - + + Un&concede + 撤销&投降 + + + &Concede - 放弃游戏 + &投降 - + &Leave game - 离开游戏 + &离开游戏 - + C&lose replay - 关闭游戏录像 + &关闭游戏录像 - + &Focus Chat - 聚焦聊天室 + &聚焦聊天 - + &Say: - 说: + &説: - + &View - 查看 + &视图 - - - - + + + + Visible 可见 - - - - + + + + Floating - 移动窗口 + 浮动 - + Reset layout - 重置界面 + 重置布局 - + Concede - 放弃游戏 + 投降 - + Are you sure you want to concede this game? - 你确定放弃这个游戏? + 你确定要宣佈投降吗? - + Unconcede - 取消投降 + 撤销投降 - + You have already conceded. Do you want to return to this game? - 你已经投降,你想回到这场游戏吗? + 你已经宣佈投降,你想回到这场游戏吗? - + Leave game 离开游戏 - + Are you sure you want to leave this game? - 你确定离开这这个游戏? + 你确定要离开这个游戏吗? - + You are flooding the game. Please wait a couple of seconds. - 您正在刷屏。请稍等几秒钟。 + 你正在刷屏。请稍等几秒鐘。 - + A player has joined game #%1 - + 有玩家加入游戏 #%1 - + %1 has joined the game - + %1 已经加入游戏。 - + kicked by game host or moderator - 被游戏主机或管理器踢出 + 被游戏主机或版主踢出 - + player left the game - 玩家离开游戏 + 玩家已经离开游戏 - + player disconnected from server - 玩家与服务器断开连接 + 玩家与伺服器断线 - + reason unknown 原因不明 - + You have been kicked out of the game. 你已被踢出游戏。 @@ -5794,155 +6723,155 @@ Please enter a name: 记录 - - - + + + Time;SenderName;SenderIP;Message;TargetID;TargetName - 时间;发送者姓名;发送者IP;消息;目标ID;目标姓名 + 时间;发送者姓名;发送者IP;信息;目标ID;目标姓名 - + Room Logs 房间记录 - + Game Logs 游戏记录 - + Chat Logs 聊天记录 - - + + Error - 错误 + 出现错误 - + You must select at least one filter. - 您必须至少选择一个过滤项 + 您必须选择至少一个过滤项. - + You have to select a valid number of days to locate. - 您必须选择一个可用的天数用来定位 + 您必须选择一个可用的日数来作为定位. - + Username: - 用户名: + 用户名称: - + IP Address: - IP地址: + IP地址 - + Game Name: - 游戏名: + 游戏名称: - + GameID: 游戏ID: - + Message: - 消息: + 信息: + + + + Main Room + 主房 - Main Room - 主房间 - - - Game Room 游戏房间 - + Private Chat 私人聊天 - + Past X Days: 过去 X 天: - + Today 今天 - + Last Hour 1小时前 - + Maximum Results: - 最大结果: + 最多结果: - + At least one filter is required. The more information you put in, the more specific your results will be. 至少需要1个筛选项。 -您输入的信息越多,找到结果越准确。 +您输入的信息越多,找到结果越準确。 - + Get User Logs 获取用户记录 - + Clear Filters - 清楚筛选项目 + 清除筛选项目 - + Filters - 过滤器 + 筛选项目 - + Log Locations 记录位置 - + Date Range 日期范围 - + Maximum Results - 最大结果 + 最多结果 + + + + + Message History + 信息历史 - - Message History - 消息历史 - - - Failed to collect message history information. - 获取消息历史信息失败。 + 不能获取信息历史消息 - + There are no messages for the selected filters. - 没有符合筛选条件的消息。 + 没有符合筛选条件的信息。 @@ -5950,12 +6879,12 @@ The more information you put in, the more specific your results will be. Private &chat - 私人聊天 + 私人&聊天 &Leave - 离开 + &离开 @@ -5963,142 +6892,178 @@ The more information you put in, the more specific your results will be.%1 - 私人聊天 - + This user is ignoring you, they cannot see your messages in main chat and you cannot join their games. - 该用户已屏蔽你,他无法在主聊天中看见你的消息,你也不能加入他的游戏。 + 你已被这用户屏蔽, 它们不能再主聊天室看见你的信息,你亦不能参加它们的游戏. - + Private message from - 私人信息来自 + 私人信息信到, 来自 - + %1 has left the server. - %1 已经离开了服务器. + %1 已离开伺服器. - + %1 has joined the server. - %1 已经加入了服务器. + %1 已加入伺服器. TabReplays - - Game replays - 游戏录像 - - - + Local file system - 本地文件系统 + 本地档案系统 - + Server replay storage - 服务器录像仓库 + 伺服器录像仓库 - - + + Watch replay - 观看录像 + 观看游戏录像 - - + + Rename + 改名 + + + + + New folder + 新文件夹 + + + + Delete 删除 - + + Open replays folder + 打开录像文件夹 + + + Download replay 下载游戏录像 - + Toggle expiration lock 过期锁定 - + + Rename local folder + 本地档案夹改名 + + + + Rename local file + 本地档案改名 + + + + New name: + 新名称 + + + + Error + 出错 + + + + Rename failed + 改名不成功 + + + + Name of new folder: + 新建文件夹名称: + + + Delete local file - 删除本地文件 + 删除本地档案 - - Are you sure you want to delete "%1"? - 你确定想要删除"%1"? + + Are you sure you want to delete the selected files? + 你确定要剷除所有被选的档案吗? - - Downloading Replays - 正在下载游戏录像 + + Are you sure you want to delete the selected replays? + 你确定要删除被选录像档案吗 ? - - Folder download is not yet supported. Please download replays individually. - - - - + Delete remote replay - 删除服务器上的录像 + 删除伺服器录像 - - Are you sure you want to delete the replay of game %1? - 你确定要删除这这个游戏的录像吗 %1? + + Game Replays + TabRoom - + &Say: - 说: + &説: - + Chat 聊天 - + &Room - 房间 + &房间 - + &Leave room - 离开房间 + &离开房间 - + &Clear chat - 清除聊天记录 + &清除聊天记录 - + Chat Settings... - 聊天设置 + 聊天设置... - + mentioned you. 提到了你。 - + Click to view 点击查看 - + You are flooding the chat. Please wait a couple of seconds. - 你正在灌水,请等待几秒再发言. + 你正在刷屏。请稍等几秒鐘。 @@ -6106,131 +7071,151 @@ The more information you put in, the more specific your results will be. Server - 服务器 + 伺服器 + + + + + + + Error + 出现错误 - - - - Error - 错误 - - - Failed to join the server room: it doesn't exist on the server. - + 加入伺服器房间失败:在本伺服器上这房间并不存在。 - + The server thinks you are in the server room but your client is unable to display it. Try restarting your client. - + 伺服器认为你在房间内, 但Cockatrice无法显示。请重新启动Cockatrice。 - + You do not have the required permission to join this server room. - + 你未被允许进入该房间。 - + Failed to join the server room due to an unknown error: %1. - + 因为以下不知名的错误,所以不能参入伺服器房间: %1. TabSupervisor - + + Deck Editor + + + + + &Visual Deck Storage + + + + + Server + + + + + Account + + + + + Deck Storage + + + + + Game Replays + + + + + Administration + + + + + Logs + + + + Are you sure? - 你确定吗? + 你确定吗? - + There are still open games. Are you sure you want to quit? - 游戏还在继续, 你确定要退出吗? + 游戏还在继续, 你确定要退出吗? - + Click to view 点击查看 - + Your buddy %1 has signed on! 你的好友%1已登录! - + Unknown Event - 未知事件 + 不知名事件 - + The server has sent you a message that your client does not understand. This message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. To update your client, go to Help -> Check for Updates. - 服务器发送了一条你的客户端无法识别的消息。 -这条消息可能是表明当前有新版本的鸡蛇软件,或是服务器上运行的是自定义/预览版本。 + 伺服器发送了一条你的客户端无法识别的消息。 +这条消息可能是表明当前有新版本的Cockatrice软件,或是伺服器上运行的是自定/预览版本。 要升级你的客户端,请点击“帮助”->“检查更新” - + Idle Timeout - 闲置超时 + 閒置超时 - + You are about to be logged out due to inactivity. - 你即将因为长时间未操作而被退出。 + 你即将因为长时间没有活动而被退出。 - + Promotion - 晋升 + 升级 - + You have been promoted. Please log out and back in for changes to take effect. - 您已被提升为版主。请退出再登陆以使修改生效。 + 你已被升级.请登出然后再登入让本更改生效. - + Warned 被警告 - + You have received a warning due to %1. Please refrain from engaging in this activity or further actions may be taken against you. If you have any questions, please private message a moderator. - 你因为%1被警告。 -请杜绝此类行为否则会被采取进一步行动。如果有任何疑问,请私信版主。 + 你由於$1 原因,现在接受警告. 请避免继续本行为, 否则你将有可能会接受进一步的警诫或惩罚. 若有任何疑问, 请私信我们门下的版主. - + You have received the following message from the server. (custom messages like these could be untranslated) - 你收到来自服务器的如下消息。 -(消息可能未经翻译) - - - - TabUserLists - - - Account - 账户 - - - - Add to Buddy List - 加入到好友列表 - - - - Add to Ignore List - 加入到屏蔽列表 + 你收到松本伺服器发出的以下讯息(类似本信息的自定讯息可能未经翻译) @@ -6239,12 +7224,12 @@ Please refrain from engaging in this activity or further actions may be taken ag Error - 错误 + 出现错误 Unable to analyze the deck. - 无法分析套牌。 + 不能分析本牌库. @@ -6253,14 +7238,14 @@ Please refrain from engaging in this activity or further actions may be taken ag File does not exist. - 文件不存在 + 档案并不存在. Failed to open file. - 打开文件失败。 + 不能开啟档案. @@ -6317,633 +7302,821 @@ Please refrain from engaging in this activity or further actions may be taken ag UserContextMenu - - - User &details - 用户详细信息 - - - - Private &chat - 私人聊天 - - - - Show this user's &games - 显示这个用户的游戏 - - - - Add to &buddy list - 加入到好友列表 - - - - Remove from &buddy list - 从好友列表移除 - - Add to &ignore list - 加入到屏蔽列表 + User &details + 用户&详细信息 - Remove from &ignore list - 从屏蔽列表移除 + Private &chat + 私人&聊天 - Kick from &game - 从游戏中踢出 + Show this user's &games + 显示这个用户的&游戏 + Add to &buddy list + 加入到&好友列表 + + + + Remove from &buddy list + 从&好友列表移除 + + + + Add to &ignore list + 加入到&屏蔽列表 + + + + Remove from &ignore list + 从&屏蔽列表移除 + + + + Kick from &game + 从&游戏中踢出 + + + Warn user 警告用户 - + View user's war&n history 查看用户警告记录 - - - Ban from &server - 在服务器中禁止 - - - - View user's &ban history - 查看用户禁止记录 - - - - &Promote user to moderator - 将用户提升为版主 - - - - Dem&ote user from moderator - 撤销用户版主职位 - - - - Promote user to &judge - - + Ban from &server + 把用户从&伺服器禁用 + + + + View user's &ban history + 查看用户&禁止记录 + + + + &Promote user to moderator + 升$用户作为版主 + + + + Dem&ote user from moderator + 把&用户从版主降级 + + + + Promote user to &judge + 把&用户升级成裁判 + + + Demote user from judge - 撤销用户的裁判身份 + 把&用户从裁判降级 - + + View admin notes + 阅读管理笔记 + + + %1's games - %1的游戏 - - - - - - Ban History - 禁止历史 + %1 的游戏(眾) + + + Ban History + 禁用历史 + + + Ban Time;Moderator;Ban Length;Ban Reason;Visible Reason 禁止时间;版主;禁止时长;禁止理由;可见理由 - + User has never been banned. - 用户从未被禁止 + 用户从未被禁 - + Failed to collect ban information. - 获取禁止信息失败。 + 不能获取禁用讯息 - - - + + + Warning History 警告历史 - + Warning Time;Moderator;User Name;Reason - 警告时间;版主;用户名;理由 + 警告时间;版主;用户名称;理由 - + User has never been warned. - 用户从未被警告 + 用户从未接收警告 - + Failed to collect warning information. - 获取警告信息失败。 + 不能获取警告信息 - - + + Failed to get admin notes. + 不能获取管理笔记 + + + + Success 成功 - + Successfully promoted user. - 提升用户成功。 + 用户升级成功 - + Successfully demoted user. - 撤销用户成功。 + 用户降级成功 - - + + + Failed 失败 - + Failed to promote user. - 提升用户失败。 + 用户升级失败 - + Failed to demote user. - 撤销用户失败。 + 用户降级失败 - + Copy hash to clipboard - 复制哈希到剪切板 + 把哈希覆印到用户剪贴 - + Remove this user's messages - 除这个用户打的信 + 移除这用户讯息 UserInfoBox - + Location: - 位置: + 位置: - + Account Age: - 账号年龄: - - - - Edit - 编辑 + 帐户年龄 - Change password - 修改密码 + Edit + 更改 - Change avatar - 修改头像 + Change password + 更改密码 - + + Change avatar + 更改头像 + + + Administrator 管理员 - + Moderator 版主 - + Registered user - 注册用户 + 登记用户 - - + + Unregistered user - 未注册用户 + 未登记用户 - + Judge 裁判 - + Unknown - 未知的 + 不知名 - + The entered password does not match your account. - + 输入密码跟你户口不匹配 - - - + + + Information - 信息 + 资讯 - + User information updated. - 用户信息已更新。 + 用户资讯已更新。 - - - - - - - - - - + + + + + + + + + + Error - 错误 + 出现错误 - + User Information - + 用户资讯 - + Real Name: - + 实名: - + User Level: - 用户的等级: + 用户等级: - + %n Year(s), amount of years (only shown if more than 0) - + %n年(眾) , - + %10%n Day(s) %20 amount of years (if more than 0), amount of days, date in local short format - + %10%n日(眾) %20 - + Enter Password - + 输入密码 - + Password verification is required in order to change your email address - - - - - - - An error occurred while trying to update your user information. - - - - - This server does not permit you to update your user informations. - 该服务器不允许你更新你的用户信息。 - - - - Password changed. - 密码已修改。 - - - - This server does not permit you to change your password. - 该服务器不允许你修改你的密码。 + 要核实密码才能更改你的电邮地址 + - The new password is too short. - 新密码太短。 + + An error occurred while trying to update your user information. + 在尝试更新你的用户信息时出现错误. - - The old password is incorrect. - 原密码不正确。 + + This server does not permit you to update your user informations. + 本伺服器不容许你更新你的用户资讯. + + + + Password changed. + 密码已更改 + + + + This server does not permit you to change your password. + 本伺服器不容许你更改密码 + + + + The new password is too short. + 新的密码太短 + The old password is incorrect. + 旧的密码不正确 + + + Avatar updated. 头像已更新。 - + This server does not permit you to update your avatar. - 该服务器不允许你更新你的头像。 + 本伺服器不容许你更新你的头像. - + An error occured while trying to updater your avatar. - 尝试更新你的头像时出错。 + 在更新你头像时出现错误 UserInterfaceSettingsPage - + General interface settings - 通用接口设置 + 一般通用接口设置 - + &Double-click cards to play them (instead of single-click) - 双击卡牌开始 (而不是单击开始) + 双点击使出卡牌 (而不是单点击使出) - + + &Clicking plays all selected cards (instead of just the clicked card) + &单一点击把所有选择的卡牌使出(不只是被点击的单张卡牌) + + + &Play all nonlands onto the stack (not the battlefield) by default - 默认将所有非地牌加入堆叠(不是战场) + &把所有使出的非地牌放入堆迭区(不是战场) 作为常用设置 - + + Close card view window when last card is removed + + + + Annotate card text on tokens - 用卡牌信息给衍生物标注 + 在令牌上标註牌中文字 - + Use tear-off menus, allowing right click menus to persist on screen - 使用浮动菜单,允许右键单击菜单保留在屏幕上 + 使用浮动菜单,允许右键单击, 把菜单保留在屏幕上 - + Notifications settings - 通知设置 + 通告设置 - + Enable notifications in taskbar - 开启任务栏提醒 + 开启任务栏通告 - + Notify in the taskbar for game events while you are spectating 观看时在任务栏提示游戏信息 - + Notify in the taskbar when users in your buddy list connect - 在任务栏中提示好友连接到服务器 + 在任务栏中提示好友连到伺服器 - + Animation settings - 动画设置 + 动画设定 - + &Tap/untap animation - 横置/重置 动画 + &横置/重置 动画 + + + + Deck editor/storage settings + + + + + Open deck in new tab by default + 把用新页开啟牌库作为常用设置 + + + + Prompt before converting .txt decks to .cod format + + + + + Always convert if not prompted + + + + + Use visual deck storage in game lobby + + + + + Replay settings + 重播设置 + + + + Buffer time for backwards skip via shortcut: + 用速键啟动向后跳的缓衝时间: - UserList + UserListWidget - + Users connected to server: %1 - 用户连接到服务器:%1 + - + Users in this room: %1 - 房间人数: %1 + - + Buddies online: %1 / %2 - 好友在线: %1 / %2 + - + Ignored users online: %1 / %2 - 屏蔽用户在线: %1 / %2 + + + + + VisualDeckStorageFolderDisplayWidget + + + Deck Storage + + + + + VisualDeckStorageSearchWidget + + + Search by filename + 按档案名称搜寻 + + + + VisualDeckStorageSortWidget + + + Sort Alphabetically (Deck Name) + + + + + Sort Alphabetically (Filename) + + + + + Sort by Last Modified + + + + + Sort by Last Loaded + + + + + VisualDeckStorageWidget + + + Loading database ... + + + + + Show Folders + + + + + Show Tag Filter + + + + + Show Tags On Deck Previews + + + + + Show Banner Card Selection Option + + + + + Include Folder Names in Search + + + + + Draw unused Color Identities + + + + + Unused Color Identities Opacity + WarningDialog - + Which warning would you like to send? - 您想发出那种警告? + 你想发出那种警告? - + Redact all messages from this user in all rooms - + 把这用户在所有房间留下的讯息删除 - + &OK - 确认 + &确认 - + &Cancel - 取消 + &取消 - + Warn user for misconduct - 警告用户的不当行为 + 对行为不当的用户发出警告 - - + + Error - 错误 + 出现错误 - + User name to send a warning to can not be blank, please specify a user to warn. - 发送警告的用户名不能为空,请提供用户名以供警告。 + 接收警告的用户名称不能空白.请定好要接收警告的用户名字. - + Warning to use can not be blank, please select a valid warning to send. - 警告不能为空,请选择警告以供发送。 + 警告名称不能留空. 请选择有效的警告. WndSets - + Move selected set to the top - 将选择的系列移到顶端 + 将选择的牌组送到最顶 - + Move selected set up - 将选择的系列上移 + 将选择的牌组往上一格 - + Move selected set down - 将选择的系列下移 + 将选择的牌组往下一格 - + Move selected set to the bottom - 将选择的系列移到底部 + 将选择的牌组送到最底 - + Search by set name, code, or type - 通过系列名、代码或类型搜索 + 用排组名称,代码,或类型进行搜寻 - + Default order - 默认顺序 + 常用次序 - + Restore original art priority order - 恢复初始优先顺序 - - - - Enable all sets - 启用所有系列 - - - - Disable all sets - 禁用所有系列 - - - - Enable selected set(s) - 启用所选系列 + 恢復常用艺术先后次序 + Enable all sets + 啟用所有牌组 + + + + Disable all sets + 禁用所有卡组 + + + + Enable selected set(s) + 啟用选择的牌组(眾)  + + + Disable selected set(s) - 禁用所选系列 - - - - Deck Editor - 套牌编辑器 - - - - Use CTRL+A to select all sets in the view. - - - - - Only cards in enabled sets will appear in the card list of the deck editor. - - - - - Image priority is decided in the following order: - + 停用选择的牌组(眾) + Deck Editor + 牌库编辑 + + + + Use CTRL+A to select all sets in the view. + 请按 CTRL + A, 以此选择所有看到的牌组 + + + + Only cards in enabled sets will appear in the card list of the deck editor. + 只有已经被啟用的卡牌组中的卡牌,才会在牌库编辑中的卡牌清单出现 + + + + Image priority is decided in the following order: + 卡牌图片先后次序由以下次序决定: + + + first the CUSTOM Folder (%1), then the Enabled Sets in this dialog (Top to Bottom) %1 is a link to the wiki - + 自订档案夹(%`1)优先, 此对话窗中已被啟用的牌组为后(由顶到底) - + Card Art - 牌张风格 + 卡牌艺术 - + How to use custom card art - 如何使用自定义卡牌图片 + 如何使用自定卡牌图片 - + Hints 提示 - + Note - + 笔记 - + Sorting by column allows you to find a set while not changing set priority. - 按列排序允许您在不更改设置优先级的情况下查找系列。 + 按列排序允许您在不更改系列优先级别的情况下查找牌组。 - + To enable ordering again, click the column header until this message disappears. 要再次启用排序,请单击列标题,直到此消息消失。 - + Use the current sorting as the set priority instead - 使用当前设置的优先级排序 + 用现在排序代替牌组先后 - + Sorts the set priority using the same column - 使用同一列设置的优先级排序 + 用同一列分牌组先后 - + + Include online-only (Arena) cards [requires restart] + + + + Manage sets - 系列管理 + 管理牌组 ZoneViewWidget - - sort by name - 按名称排序 + + Ungrouped + 未分组 - - sort by type + + Group by Type + 按类型分组 + + + + Group by Mana Value + 按法力数值分组 + + + + Group by Color + 按顏色排序 + + + + Unsorted + 未排序 + + + + Sort by Name + 按名称排次序 + + + + Sort by Type 按类型排序 - - shuffle when closing - 当关闭界面时洗牌 + + Sort by Mana Cost + 按法力费用排序 - + + Sort by Colors + 按顏色(眾) 排序 + + + + Sort by P/T + 按力量/防御力排序 + + + + Sort by Set + 按排组排序 + + + + shuffle when closing + 关闭界面时洗牌 + + + pile view - 柱形图 + 栋迭观看 i18n - + English 简体中文 (Chinese Simplified) @@ -6951,798 +8124,920 @@ Please refrain from engaging in this activity or further actions may be taken ag main - + Connect on startup - 启动连接 + 啟动时自动连接 - + Debug to file - 调试至文件 + 侦错纪录写进档案 shortcutsTab - - - Main Window - 主窗口 - - - Deck Editor - 套牌编辑 + Main Window + 主视窗 - Game Lobby - 游戏大厅 + + Deck Editor + 牌库编辑 + Game Lobby + 游戏厅堂 + + + Card Counters 牌指示物 - + Player Counters - 牌手指示物 + 玩家指示物 - + Power and Toughness 力量和防御力 - + Game Phases 游戏阶段 - + Playing Area 游戏区域 - + Move Selected Card - 移动所选的牌张 + 移动所选牌张 - + View 查看 - - - Move Top Card - 移动上方的牌张 - - Move Bottom Card - 移动底部的牌张 + Move Top Card + 移动顶牌 + Move Bottom Card + 移动底牌 + + + Gameplay 游戏 - + Drawing - 绘制 + 抽牌 - + Chat Room 聊天室 - - - Game Window - 游戏窗口 - - Load Deck from Clipboard - 从剪贴板载入套牌 + Game Window + 游戏视窗 + Load Deck from Clipboard + 从剪贴板载入牌库 + + + + Replays + 游戏录像 + + + + Tabs - + Check for Card Updates... 检查卡牌更新 - + Connect... - 连接 - - - - Disconnect - 断开连接 + 连线 + Disconnect + 断开连线 + + + Exit 退出 - + Full screen 全屏 - + Register... - 注册 + 註册... - + Settings... - 设置 + 设定... - + Start a Local Game... - 开始本地游戏 + 开始离线游戏 - + Watch Replay... 观看录像 - - Analyze Deck - 分析套牌 + + Analyze Deck (deckstats.net) + Analyze Deck + - + + Analyze Deck (tappedout.net) + + + + Clear All Filters 清除所有筛选项目 - + Clear Selected Filter 清除所选的筛选项目 - + Close 关闭 - + Remove Card 移除卡牌 - + Manage Sets... - 系列管理 + 管理牌组 - + Edit Custom Tokens... - 编辑自定义衍生物 + 编辑自定眾令牌... - - Export Deck - 导出套牌 + + Export Deck (decklist.org) + - + + Export Deck (decklist.xyz) + + + + Add Card 添加卡牌 - + Load Deck... - 读取套牌 - - - - Load Deck from Clipboard... - 从剪贴板载入套牌 - - - - New Deck - 创建新套牌 - - - - Open Custom Pictures Folder - 打开自定义图片文件夹 - - - - Print Deck... - 打印套牌 + 载入牌库... + Load Deck from Clipboard... + 从剪贴板载入牌库 + + + + Edit Deck in Clipboard, Annotated + + + + + Edit Deck in Clipboard + + + + + New Deck + 创建新牌库 + + + + Open Custom Pictures Folder + 打开自定图片文件夹 + + + + Print Deck... + 印刷牌库 + + + Delete Card 删除卡牌 - - + + Reset Layout - 重置布局 + 重置佈局 - + Save Deck - 保存套牌 - - - - Save Deck as... - 套牌另存为 - - - - Save Deck to Clipboard, Annotated - 保存套牌到剪切板,带注释 - - - - Save Deck to Clipboard - 保存套牌到剪切板 - - - - Load Local Deck... - 从本地载入套牌 - - - - Load Remote Deck... - 载入远程套牌 - - - - Set Ready to Start - 系列准备好开始 + 保存牌库 - Toggle Sideboard Lock - 切换备牌锁 + Save Deck as... + 牌库另存为 - - + + Save Deck to Clipboard, Annotated + 从剪贴板储藏牌库, 带注释 + + + + Save Deck to Clipboard, Annotated (No Set Info) + + + + + Save Deck to Clipboard + 从剪贴板储藏牌库 + + + + Save Deck to Clipboard (No Set Info) + + + + + Load Local Deck... + 从自身档案载入牌库 + + + + Load Remote Deck... + 载入伺服器上的牌库... + + + + Set Ready to Start + 设定準备开始 + + + + Toggle Sideboard Lock + 切换副牌库锁 + + + + Add Green Counter 增加绿色指示物 - - + + Remove Green Counter 移除绿色指示物 - - + + Set Green Counters... - 设置绿色指示物 + 设定绿色指示物 - + Add Yellow Counter 增加黄色指示物 - + Remove Yellow Counter 移除黄色指示物 - + Set Yellow Counters... - 设置黄色指示物 + 设定黄色指示物 - - + + Add Red Counter 增加红色指示物 - - + + Remove Red Counter 移除红色指示物 - - + + Set Red Counters... - 设置红色指示物 + 设定红色指示物 - + Add Life Counter 增加生命指示物 - + Remove Life Counter 移除生命指示物 - + Set Life Counters... - 设置生命指示物 + 设定生命指示物 - + Add White Counter 增加白色指示物 - + Remove White Counter 移除白色指示物 - + Set White Counters... - 设置白色指示物 + 设定白色指示物 - + Add Blue Counter 增加蓝色指示物 - + Remove Blue Counter 移除蓝色指示物 - + Set Blue Counters... - 设置蓝色指示物 + 设定蓝色指示物 - + Add Black Counter 增加黑色指示物 - + Remove Black Counter 移除黑色指示物 - + Set Black Counters... - 设置黑色指示物 + 设定黑色指示物 - + Add Colorless Counter 增加无色指示物 - + Remove Colorless Counter 移除无色指示物 - + Set Colorless Counters... - 设置无色指示物 + 设定无色指示物 - + Add Other Counter - + 增加其他指示物 - + Remove Other Counter - + 移除其他指示物 - + Set Other Counters... - + 设定其他指示物 - + Add Power (+1/+0) 增加力量(+1/+0) - + Remove Power (-1/-0) - 减少力量(-1/-0) + 削减力量(-1/-0) - + Move Toughness to Power (+1/-1) - 移动防御到力量(+1/-1) + 防御移到力量(+1/-1) - + Add Toughness (+0/+1) 增加防御(+0/+1) - + Remove Toughness (-0/-1) - 减少防御(-0/-1) + 削减防御(-0/-1) - + Move Power to Toughness (-1/+1) - 移动力量到防御(-1/+1) + 力量移到防御(-1/+1) - + Add Power and Toughness (+1/+1) 增加力量和防御(+1/+1) - + Remove Power and Toughness (-1/-1) - 减少力量和防御(-1/-1) + 削减力量和防御(-1/-1) - + Set Power and Toughness... 设置力量和防御 - + Reset Power and Toughness 重置力量和防御 - + Untap 重置 - + Upkeep 维持 - + Draw - + 抽牌 - + First Main Phase - 主要阶段1 + 第一主阶段 - + Start Combat 开始战斗 - + Attack 攻击 - + Block - 阻挡 + 挡格 - + Damage - 伤害 + 计算损害 - + End Combat - 结束战斗 + 战斗结束 - + Second Main Phase - 主要阶段2 + 第二主阶段 - + End - 回合结束 + 完结 - + Next Phase 下个阶段 - + Next Phase Action 下个行动阶段 - + Next Turn - 下个回合 + 下一回合 - + + Hide Card in Reveal Window + 从揭露视窗中隐藏这牌 + + + Tap / Untap Card - 横置/重置卡牌 + 横置/重置这牌 - + Untap All - 重置所有 + 重置全牌 - + Toggle Untap 锁定重置状态 - + Turn Card Over - 将卡翻面 + 反转本牌 - + Peek Card 查看卡牌 - + Play Card - 使用卡牌 + 使出本牌 - + Attach Card... - 结附卡牌 + 结附牌 - + Unattach Card - 取消结附 + 解除牌结附 - + Clone Card - 复制卡牌 + 复製卡牌 - + Create Token... - 派出一个衍生物 + 派出令牌 - + Create All Related Tokens - 派出所有相关的衍生物 + 派出所有相关令牌 - + Create Another Token - 派出另一个衍生物 + 再做另外令牌 - + Set Annotation... - 设置注释... + 记下註解 - - + + Select All Cards in Zone + 选择所有在区域的牌 + + + + Select All Cards in Row + 选择所有横行牌 + + + + Select All Cards in Column + 选择所有直行牌 + + + + Bottom of Library 牌库底 - - - - + + + + Exile - 放逐区 + 放逐 - - - - + + + + Graveyard - 坟墓场 + 坟场 - - + + Hand 手牌 - - + + Top of Library 牌库顶 - - - + + + Battlefield, Face Down 战场,面朝下 - + Battlefield 战场 - + Library 牌库 - + Sideboard - 备牌 + 副牌库 - + Top Cards of Library - 牌库顶牌 + 排副多张顶牌 - + + Bottom Cards of Library + 排副多张底牌 + + + Close Recent View - 关闭最近的查看 + 关闭最近查看 - - + + Stack - 堆叠 + 迭区 - - + + Graveyard (Multiple) - 坟场(多个) + 多张送坟场 - - + + Exile (Multiple) - 放逐区(多个) + 多张放逐 - + Stack Until Found - + 找到以下牌前全送迭区 - + Draw Bottom Card - + 从牌库底抽一张牌 - + Draw Multiple Cards from Bottom... - + 从牌库底抽超过一张牌 - + Draw Arrow... - 划箭头... + 画箭咀 - + Remove Local Arrows - 移除本地箭头 + 清除自设箭咀 - + Leave Game 离开游戏 - + Concede - 放弃游戏 + 投降 - + Roll Dice... - 抛骰子 + 掷骰 - + Shuffle Library - 切洗套牌 + 洗牌 - - Mulligan - 起手牌 - - - - Draw a Card - 抓一张牌 - - - - Draw Multiple Cards... - 抓多张牌 - - - - Undo Draw - 撤销抓牌 - - - - Always Reveal Top Card - 一直展示牌库顶牌 - - - - Always Look At Top Card + + Shuffle Top Cards of Library - + + Shuffle Bottom Cards of Library + + + + + Mulligan + 再调度 + + + + Draw a Card + 抽一张牌 + + + + Draw Multiple Cards... + 抽超过一张牌 + + + + Undo Draw + 撤销抽牌 + + + + Always Reveal Top Card + 永远显示顶牌 + + + + Always Look At Top Card + 永远看到顶牌 + + + Rotate View Clockwise - 顺时针旋转视角 + 视角顺时针转 - + Rotate View Counterclockwise - 逆时针旋转视角 + 视角逆时针转 - + Unfocus Text Box 取消聚焦文本框 - + Focus Chat - 聚焦聊天室 + 聚焦聊天 - + Clear Chat - 清除聊天记录 + 扫清聊天 - + Refresh 刷新 - + + Skip Forward + 跳前 + + + + Skip Backward + 跳后 + + + + Skip Forward by a lot + 向前大跳 + + + + Skip Backward by a lot + 向后大跳 + + + Play/Pause + 播放/暂停 + + + + Toggle Fast Forward + 切换快进 + + + + Visual Deck Storage - - Toggle Fast Forward + + Deck Storage + + + + + Server + + + + + Account + + + + + Administration + + + + + Logs diff --git a/common/decklist.cpp b/common/decklist.cpp index 011c9e0f3..789f57910 100644 --- a/common/decklist.cpp +++ b/common/decklist.cpp @@ -156,12 +156,17 @@ AbstractDecklistNode *InnerDecklistNode::findChild(const QString &_name) return nullptr; } -AbstractDecklistNode *InnerDecklistNode::findCardChildByNameAndProviderId(const QString &_name, - const QString &_providerId) +AbstractDecklistNode *InnerDecklistNode::findCardChildByNameProviderIdAndNumber(const QString &_name, + const QString &_providerId, + const QString &_cardNumber) { - for (int i = 0; i < size(); i++) { - if (at(i) != nullptr && at(i)->getName() == _name && at(i)->getCardProviderId() == _providerId) { - return at(i); + for (const auto &i : *this) { + if (i != nullptr && i->getName() == _name) { + if (i->getCardCollectorNumber() == _cardNumber) { + if (i->getCardProviderId() == _providerId) { + return i; + } + } } } return nullptr; @@ -319,13 +324,13 @@ void AbstractDecklistCardNode::writeElement(QXmlStreamWriter *xml) xml->writeAttribute("number", QString::number(getNumber())); xml->writeAttribute("name", getName()); - if (getCardSetShortName().isEmpty()) { + if (!getCardSetShortName().isEmpty()) { xml->writeAttribute("setShortName", getCardSetShortName()); } - if (getCardCollectorNumber().isEmpty()) { + if (!getCardCollectorNumber().isEmpty()) { xml->writeAttribute("collectorNumber", getCardCollectorNumber()); } - if (getCardProviderId().isEmpty()) { + if (!getCardProviderId().isEmpty()) { xml->writeAttribute("uuid", getCardProviderId()); } } @@ -363,7 +368,8 @@ DeckList::DeckList() // TODO: https://qt-project.org/doc/qt-4.8/qobject.html#no-copy-constructor-or-assignment-operator DeckList::DeckList(const DeckList &other) - : QObject(), name(other.name), comments(other.comments), deckHash(other.deckHash) + : QObject(), name(other.name), comments(other.comments), bannerCard(other.bannerCard), + lastLoadedTimestamp(other.lastLoadedTimestamp), tags(other.tags), cachedDeckHash(other.cachedDeckHash) { root = new InnerDecklistNode(other.getRoot()); @@ -372,7 +378,6 @@ DeckList::DeckList(const DeckList &other) spIterator.next(); sideboardPlans.insert(spIterator.key(), new SideboardPlan(spIterator.key(), spIterator.value()->getMoveList())); } - updateDeckHash(); } DeckList::DeckList(const QString &nativeString) @@ -414,22 +419,37 @@ bool DeckList::readElement(QXmlStreamReader *xml) { const QString childName = xml->name().toString(); if (xml->isStartElement()) { - if (childName == "deckname") + if (childName == "lastLoadedTimestamp") { + lastLoadedTimestamp = xml->readElementText(); + } else if (childName == "deckname") { name = xml->readElementText(); - else if (childName == "comments") + } else if (childName == "comments") { comments = xml->readElementText(); - else if (childName == "zone") { + } else if (childName == "bannerCard") { + QString providerId = xml->attributes().value("providerId").toString(); + QString cardName = xml->readElementText(); + bannerCard = QPair(cardName, providerId); + } else if (childName == "tags") { + tags.clear(); // Clear existing tags + while (xml->readNextStartElement()) { + if (xml->name().toString() == "tag") { + tags.append(xml->readElementText()); + } + } + } else if (childName == "zone") { InnerDecklistNode *newZone = getZoneObjFromName(xml->attributes().value("name").toString()); newZone->readElement(xml); } else if (childName == "sideboard_plan") { SideboardPlan *newSideboardPlan = new SideboardPlan; - if (newSideboardPlan->readElement(xml)) + if (newSideboardPlan->readElement(xml)) { sideboardPlans.insert(newSideboardPlan->getName(), newSideboardPlan); - else + } else { delete newSideboardPlan; + } } - } else if (xml->isEndElement() && (childName == "cockatrice_deck")) + } else if (xml->isEndElement() && (childName == "cockatrice_deck")) { return false; + } return true; } @@ -437,16 +457,33 @@ void DeckList::write(QXmlStreamWriter *xml) { xml->writeStartElement("cockatrice_deck"); xml->writeAttribute("version", "1"); + xml->writeTextElement("lastLoadedTimestamp", lastLoadedTimestamp); xml->writeTextElement("deckname", name); + xml->writeStartElement("bannerCard"); + xml->writeAttribute("providerId", bannerCard.second); + xml->writeCharacters(bannerCard.first); + xml->writeEndElement(); xml->writeTextElement("comments", comments); - for (int i = 0; i < root->size(); i++) - root->at(i)->writeElement(xml); - - QMapIterator i(sideboardPlans); - while (i.hasNext()) - i.next().value()->write(xml); + // Write tags + xml->writeStartElement("tags"); + for (const QString &tag : tags) { + xml->writeTextElement("tag", tag); + } xml->writeEndElement(); + + // Write zones + for (int i = 0; i < root->size(); i++) { + root->at(i)->writeElement(xml); + } + + // Write sideboard plans + QMapIterator i(sideboardPlans); + while (i.hasNext()) { + i.next().value()->write(xml); + } + + xml->writeEndElement(); // Close "cockatrice_deck" } bool DeckList::loadFromXml(QXmlStreamReader *xml) @@ -469,7 +506,7 @@ bool DeckList::loadFromXml(QXmlStreamReader *xml) } } } - updateDeckHash(); + refreshDeckHash(); if (xml->error()) { qDebug() << "Error loading deck from xml: " << xml->errorString(); return false; @@ -511,7 +548,14 @@ bool DeckList::saveToFile_Native(QIODevice *device) return true; } -bool DeckList::loadFromStream_Plain(QTextStream &in) +/** + * Clears the decklist and loads in a new deck from text + * + * @param in The text to load + * @param preserveMetadata If true, don't clear the existing metadata + * @return False if the input was empty, true otherwise. + */ +bool DeckList::loadFromStream_Plain(QTextStream &in, bool preserveMetadata) { const QRegularExpression reCardLine(R"(^\s*[\w\[\(\{].*$)", QRegularExpression::UseUnicodePropertiesOption); const QRegularExpression reEmpty("^\\s*$"); @@ -521,28 +565,35 @@ bool DeckList::loadFromStream_Plain(QTextStream &in) const QRegularExpression reDeckComment("^((main)?deck(list)?|mainboard)\\b", QRegularExpression::CaseInsensitiveOption); - // simplified matches + // Regex for advanced card parsing const QRegularExpression reMultiplier(R"(^[xX\(\[]*(\d+)[xX\*\)\]]* ?(.+))"); + const QRegularExpression reSplitCard(R"( ?\/\/ ?)"); const QRegularExpression reBrace(R"( ?[\[\{][^\]\}]*[\]\}] ?)"); // not nested const QRegularExpression reRoundBrace(R"(^\([^\)]*\) ?)"); // () are only matched at start of string const QRegularExpression reDigitBrace(R"( ?\(\d*\) ?)"); // () are matched if containing digits - // () are matched if containing setcode then a number - const QRegularExpression reBraceDigit(R"( ?\([\dA-Z]+\) *\d+$)"); + const QRegularExpression reBraceDigit( + R"( ?\([\dA-Z]+\) *\d+$)"); // () are matched if containing setcode then a number + const QRegularExpression reDoubleFacedMarker(R"( ?\(Transform\) ?)"); + + // Regex for extracting set code and collector number with attached symbols + const QRegularExpression reHyphenFormat(R"(\((\w{3,})\)\s+(\w{3,})-(\d+[^\w\s]*))"); + const QRegularExpression reRegularFormat(R"(\((\w{3,})\)\s+(\d+[^\w\s]*))"); + const QHash differences{{QRegularExpression("’"), QString("'")}, {QRegularExpression("Æ"), QString("Ae")}, {QRegularExpression("æ"), QString("ae")}, {QRegularExpression(" ?[|/]+ ?"), QString(" // ")}}; - cleanList(); + cleanList(preserveMetadata); auto inputs = in.readAll().trimmed().split('\n'); auto max_line = inputs.size(); - // start at the first empty line before the first cardline + // Start at the first empty line before the first card line auto deckStart = inputs.indexOf(reCardLine); - if (deckStart == -1) { // there are no cards? + if (deckStart == -1) { if (inputs.indexOf(reComment) == -1) { - return false; // input is empty + return false; // Input is empty } deckStart = max_line; } else { @@ -563,7 +614,7 @@ bool DeckList::loadFromStream_Plain(QTextStream &in) } auto nextCard = inputs.indexOf(reCardLine, sBStart + 1); if (inputs.indexOf(reEmpty, nextCard + 1) != -1) { - sBStart = max_line; // if there is another empty line all cards are mainboard + sBStart = max_line; } } } @@ -571,7 +622,7 @@ bool DeckList::loadFromStream_Plain(QTextStream &in) int index = 0; QRegularExpressionMatch match; - // parse name and comments + // Parse name and comments while (index < deckStart) { const auto ¤t = inputs.at(index++); if (!current.contains(reEmpty)) { @@ -587,29 +638,29 @@ bool DeckList::loadFromStream_Plain(QTextStream &in) comments += match.captured() + '\n'; } } - comments.chop(1); // remove last newline + comments.chop(1); - // discard empty lines + // Discard empty lines while (index < max_line && inputs.at(index).contains(reEmpty)) { ++index; } - // discard line if it starts with deck or mainboard, all cards until the sideboard starts are in the mainboard + // Discard line if it starts with deck or mainboard, all cards until the sideboard starts are in the mainboard if (inputs.at(index).contains(reDeckComment)) { ++index; } - // parse decklist + // Parse decklist for (; index < max_line; ++index) { - // check if line is a card match = reCardLine.match(inputs.at(index)); if (!match.hasMatch()) continue; - QString cardName = match.captured().simplified(); - // check if card should be sideboard + QString cardName = match.captured().simplified(); bool sideboard = false; + + // Sideboard detection if (sBStart < 0) { match = reSBMark.match(cardName); if (match.hasMatch()) { @@ -617,11 +668,39 @@ bool DeckList::loadFromStream_Plain(QTextStream &in) cardName = match.captured(1); } } else { - if (index == sBStart) // skip sideboard line itself + if (index == sBStart) continue; sideboard = index > sBStart; } + // Extract set code, collector number, and foil + QString setCode; + QString collectorNumber; + bool isFoil = false; + + // Check for foil status at the end of the card name + if (cardName.endsWith("*F*", Qt::CaseInsensitive)) { + isFoil = true; + cardName.chop(3); // Remove the "*F*" from the card name + } + Q_UNUSED(isFoil); + + // Attempt to match the hyphen-separated format (PLST-2094) + match = reHyphenFormat.match(cardName); + if (match.hasMatch()) { + setCode = match.captured(2).toUpper(); + collectorNumber = match.captured(3); + cardName = cardName.left(match.capturedStart()).trimmed(); + } else { + // Attempt to match the regular format (PLST) 2094 + match = reRegularFormat.match(cardName); + if (match.hasMatch()) { + setCode = match.captured(1).toUpper(); + collectorNumber = match.captured(2); + cardName = cardName.left(match.capturedStart()).trimmed(); + } + } + // check if a specific amount is mentioned int amount = 1; match = reMultiplier.match(cardName); @@ -630,28 +709,38 @@ bool DeckList::loadFromStream_Plain(QTextStream &in) cardName = match.captured(2); } - // remove stuff inbetween braces + // Handle advanced card types + if (cardName.contains(reSplitCard)) { + cardName = cardName.split(reSplitCard).join(" // "); + } + + if (cardName.contains(reDoubleFacedMarker)) { + QStringList faces = cardName.split(reDoubleFacedMarker); + cardName = faces.first().trimmed(); + } + + // Remove unnecessary characters cardName.remove(reBrace); cardName.remove(reRoundBrace); // I'll be entirely honest here, these are split to accommodate just three cards cardName.remove(reDigitBrace); // from un-sets that have a word in between round braces at the end cardName.remove(reBraceDigit); // very specific format with the set code in () and collectors number after - // replace common differences in cardnames + // Normalize names for (auto diff = differences.constBegin(); diff != differences.constEnd(); ++diff) { cardName.replace(diff.key(), diff.value()); } - // get cardname, this function does nothing if the name is not found + // Resolve complete card name, this function does nothing if the name is not found cardName = getCompleteCardName(cardName); - // get zone name based on if it's in sideboard + // Determine the zone (mainboard/sideboard) QString zoneName = getCardZoneFromName(cardName, sideboard ? DECK_ZONE_SIDE : DECK_ZONE_MAIN); // make new entry in decklist - new DecklistCardNode(cardName, amount, getZoneObjFromName(zoneName)); + new DecklistCardNode(cardName, amount, getZoneObjFromName(zoneName), setCode, collectorNumber); } - updateDeckHash(); + refreshDeckHash(); return true; } @@ -670,7 +759,7 @@ InnerDecklistNode *DeckList::getZoneObjFromName(const QString &zoneName) bool DeckList::loadFromFile_Plain(QIODevice *device) { QTextStream in(device); - return loadFromStream_Plain(in); + return loadFromStream_Plain(in, false); } struct WriteToStream @@ -719,13 +808,20 @@ QString DeckList::writeToString_Plain(bool prefixSideboardCards, bool slashTappe return result; } -void DeckList::cleanList() +/** + * Clears all cards and other data from the decklist + * + * @param preserveMetadata If true, only clear the cards + */ +void DeckList::cleanList(bool preserveMetadata) { root->clearTree(); - setName(); - setComments(); - deckHash = QString(); - emit deckHashChanged(); + if (!preserveMetadata) { + setName(); + setComments(); + setTags(); + } + refreshDeckHash(); } void DeckList::getCardListHelper(InnerDecklistNode *item, QSet &result) const @@ -741,6 +837,19 @@ void DeckList::getCardListHelper(InnerDecklistNode *item, QSet &result) } } +void DeckList::getCardListWithProviderIdHelper(InnerDecklistNode *item, QMap &result) const +{ + for (int i = 0; i < item->size(); ++i) { + auto *node = dynamic_cast(item->at(i)); + + if (node) { + result.insert(node->getName(), node->getCardProviderId()); + } else { + getCardListWithProviderIdHelper(dynamic_cast(item->at(i)), result); + } + } +} + QStringList DeckList::getCardList() const { QSet result; @@ -748,6 +857,13 @@ QStringList DeckList::getCardList() const return result.values(); } +QMap DeckList::getCardListWithProviderId() const +{ + QMap result; + getCardListWithProviderIdHelper(root, result); + return result; +} + int DeckList::getSideboardSize() const { int size = 0; @@ -777,7 +893,7 @@ DecklistCardNode *DeckList::addCard(const QString &cardName, } auto *node = new DecklistCardNode(cardName, 1, zoneNode, cardSetName, cardSetCollectorNumber, cardProviderId); - updateDeckHash(); + refreshDeckHash(); return node; } @@ -803,7 +919,7 @@ bool DeckList::deleteNode(AbstractDecklistNode *node, InnerDecklistNode *rootNod } if (updateHash) { - updateDeckHash(); + refreshDeckHash(); } return true; @@ -814,7 +930,7 @@ bool DeckList::deleteNode(AbstractDecklistNode *node, InnerDecklistNode *rootNod if (inner) { if (deleteNode(node, inner)) { if (updateHash) { - updateDeckHash(); + refreshDeckHash(); } return true; @@ -825,7 +941,7 @@ bool DeckList::deleteNode(AbstractDecklistNode *node, InnerDecklistNode *rootNod return false; } -void DeckList::updateDeckHash() +static QString computeDeckHash(const InnerDecklistNode *root) { QStringList cardList; QSet hashZones, optionalZones; @@ -851,7 +967,30 @@ void DeckList::updateDeckHash() (((quint64)(unsigned char)deckHashArray[1]) << 24) + (((quint64)(unsigned char)deckHashArray[2] << 16)) + (((quint64)(unsigned char)deckHashArray[3]) << 8) + (quint64)(unsigned char)deckHashArray[4]; - deckHash = QString::number(number, 32).rightJustified(8, '0'); + return QString::number(number, 32).rightJustified(8, '0'); +} +/** + * Gets the deck hash. + * The hash is computed on the first call to this method, and is cached until the decklist is modified. + * + * @return The deck hash + */ +QString DeckList::getDeckHash() const +{ + if (!cachedDeckHash.isEmpty()) { + return cachedDeckHash; + } + + cachedDeckHash = computeDeckHash(root); + return cachedDeckHash; +} + +/** + * Invalidates the cached deckHash and emits the deckHashChanged signal. + */ +void DeckList::refreshDeckHash() +{ + cachedDeckHash = QString(); emit deckHashChanged(); } diff --git a/common/decklist.h b/common/decklist.h index ad5f59b22..de839ad23 100644 --- a/common/decklist.h +++ b/common/decklist.h @@ -66,6 +66,7 @@ public: virtual QString getCardProviderId() const = 0; virtual QString getCardSetShortName() const = 0; virtual QString getCardCollectorNumber() const = 0; + [[nodiscard]] virtual bool isDeckHeader() const = 0; InnerDecklistNode *getParent() const { return parent; @@ -128,10 +129,16 @@ public: { cardCollectorNumber = _cardCollectorNumber; } + [[nodiscard]] bool isDeckHeader() const override + { + return true; + } void clearTree(); AbstractDecklistNode *findChild(const QString &_name); - AbstractDecklistNode *findCardChildByNameAndProviderId(const QString &_name, const QString &_providerId); + AbstractDecklistNode *findCardChildByNameProviderIdAndNumber(const QString &_name, + const QString &_providerId, + const QString &_cardNumber = ""); int height() const override; int recursiveCount(bool countTotalCards = false) const; bool compare(AbstractDecklistNode *other) const override; @@ -233,6 +240,10 @@ public: { cardSetNumber = _cardSetNumber; } + [[nodiscard]] bool isDeckHeader() const override + { + return false; + } }; class DeckList : public QObject @@ -240,12 +251,20 @@ class DeckList : public QObject Q_OBJECT private: QString name, comments; - QString deckHash; + QPair bannerCard; + QString lastLoadedTimestamp; + QStringList tags; QMap sideboardPlans; InnerDecklistNode *root; void getCardListHelper(InnerDecklistNode *node, QSet &result) const; + void getCardListWithProviderIdHelper(InnerDecklistNode *item, QMap &result) const; InnerDecklistNode *getZoneObjFromName(const QString &zoneName); + /** + * Empty string indicates invalidated cache. + */ + mutable QString cachedDeckHash; + protected: virtual QString getCardZoneFromName(const QString /*cardName*/, QString currentZoneName) { @@ -258,6 +277,7 @@ protected: signals: void deckHashChanged(); + void deckTagsChanged(); public slots: void setName(const QString &_name = QString()) @@ -268,6 +288,29 @@ public slots: { comments = _comments; } + void setTags(const QStringList &_tags = QStringList()) + { + tags = _tags; + emit deckTagsChanged(); + } + void addTag(const QString &_tag) + { + tags.append(_tag); + emit deckTagsChanged(); + } + void clearTags() + { + tags.clear(); + emit deckTagsChanged(); + } + void setBannerCard(const QPair &_bannerCard = QPair()) + { + bannerCard = _bannerCard; + } + void setLastLoadedTimestamp(const QString &_lastLoadedTimestamp = QString()) + { + lastLoadedTimestamp = _lastLoadedTimestamp; + } public: explicit DeckList(); @@ -282,6 +325,18 @@ public: { return comments; } + QStringList getTags() const + { + return tags; + } + QPair getBannerCard() const + { + return bannerCard; + } + QString getLastLoadedTimestamp() const + { + return lastLoadedTimestamp; + } QList getCurrentSideboardPlan(); void setCurrentSideboardPlan(const QList &plan); const QMap &getSideboardPlans() const @@ -296,27 +351,22 @@ public: QString writeToString_Native(); bool loadFromFile_Native(QIODevice *device); bool saveToFile_Native(QIODevice *device); - bool loadFromStream_Plain(QTextStream &stream); + bool loadFromStream_Plain(QTextStream &stream, bool preserveMetadata); bool loadFromFile_Plain(QIODevice *device); bool saveToStream_Plain(QTextStream &stream, bool prefixSideboardCards, bool slashTappedOutSplitCards); bool saveToFile_Plain(QIODevice *device, bool prefixSideboardCards = true, bool slashTappedOutSplitCards = false); QString writeToString_Plain(bool prefixSideboardCards = true, bool slashTappedOutSplitCards = false); - void cleanList(); + void cleanList(bool preserveMetadata = false); bool isEmpty() const { return root->isEmpty() && name.isEmpty() && comments.isEmpty() && sideboardPlans.isEmpty(); } QStringList getCardList() const; + QMap getCardListWithProviderId() const; int getSideboardSize() const; - QString getDeckHash() const - { - return deckHash; - } - void updateDeckHash(); - InnerDecklistNode *getRoot() const { return root; @@ -328,19 +378,22 @@ public: const QString &cardProviderId = QString()); bool deleteNode(AbstractDecklistNode *node, InnerDecklistNode *rootNode = nullptr); + QString getDeckHash() const; + void refreshDeckHash(); + /** * Calls a given function object for each card in the deck. It must * take a InnerDecklistNode* as its first argument and a * DecklistCardNode* as its second. */ - template void forEachCard(Callback &callback) const + template void forEachCard(Callback &callback) { // Support for this is only possible if the internal structure // doesn't get more complicated. for (int i = 0; i < root->size(); i++) { - const InnerDecklistNode *node = dynamic_cast(root->at(i)); + InnerDecklistNode *node = dynamic_cast(root->at(i)); for (int j = 0; j < node->size(); j++) { - const DecklistCardNode *card = dynamic_cast(node->at(j)); + DecklistCardNode *card = dynamic_cast(node->at(j)); callback(node, card); } } diff --git a/common/expression.cpp b/common/expression.cpp index cc3a393b9..9cb40ea3c 100644 --- a/common/expression.cpp +++ b/common/expression.cpp @@ -51,9 +51,9 @@ double Expression::eval(const peg::Ast &ast) { const auto &nodes = ast.nodes; if (ast.name == "NUMBER") { - return stod(ast.token); + return stod(std::string(ast.token)); } else if (ast.name == "FUNCTION") { - QString name = QString::fromStdString(nodes[0]->token); + QString name = QString::fromStdString(std::string(nodes[0]->token)); if (!fns.contains(name)) return 0; return fns[name](eval(*nodes[1])); diff --git a/common/lib/peglib.h b/common/lib/peglib.h index 5c5e4c011..459fa5a27 100644 --- a/common/lib/peglib.h +++ b/common/lib/peglib.h @@ -1,15 +1,27 @@ // // peglib.h // -// Copyright (c) 2015-18 Yuji Hirose. All rights reserved. +// Copyright (c) 2022 Yuji Hirose. All rights reserved. // MIT License // -#ifndef CPPPEGLIB_PEGLIB_H -#define CPPPEGLIB_PEGLIB_H +#pragma once + +/* + * Configuration + */ + +#ifndef CPPPEGLIB_HEURISTIC_ERROR_TOKEN_MAX_CHAR_COUNT +#define CPPPEGLIB_HEURISTIC_ERROR_TOKEN_MAX_CHAR_COUNT 32 +#endif #include +#include #include +#include +#if __has_include() +#include +#endif #include #include #include @@ -19,312 +31,210 @@ #include #include #include +#include #include #include +#include #include -// guard for older versions of VC++ -#ifdef _MSC_VER -// VS2013 has no constexpr -#if (_MSC_VER == 1800) -#define PEGLIB_NO_CONSTEXPR_SUPPORT -#elif (_MSC_VER >= 1800) -// good to go -#else (_MSC_VER < 1800) -#error "Requires C+11 support" +#if !defined(__cplusplus) || __cplusplus < 201703L +#error "Requires complete C++17 support" #endif -#endif - -// define if the compiler doesn't support unicode characters reliably in the -// source code -//#define PEGLIB_NO_UNICODE_CHARS namespace peg { -/*----------------------------------------------------------------------------- - * any - *---------------------------------------------------------------------------*/ - -class any -{ -public: - any() : content_(nullptr) {} - - any(const any& rhs) : content_(rhs.clone()) {} - - any(any&& rhs) : content_(rhs.content_) { - rhs.content_ = nullptr; - } - - template - any(const T& value) : content_(new holder(value)) {} - - any& operator=(const any& rhs) { - if (this != &rhs) { - if (content_) { - delete content_; - } - content_ = rhs.clone(); - } - return *this; - } - - any& operator=(any&& rhs) { - if (this != &rhs) { - if (content_) { - delete content_; - } - content_ = rhs.content_; - rhs.content_ = nullptr; - } - return *this; - } - - ~any() { - delete content_; - } - - bool is_undefined() const { - return content_ == nullptr; - } - - template < - typename T, - typename std::enable_if::value, std::nullptr_t>::type = nullptr - > - T& get() { - if (!content_) { - throw std::bad_cast(); - } - auto p = dynamic_cast*>(content_); - assert(p); - if (!p) { - throw std::bad_cast(); - } - return p->value_; - } - - template < - typename T, - typename std::enable_if::value, std::nullptr_t>::type = nullptr - > - T& get() { - return *this; - } - - template < - typename T, - typename std::enable_if::value, std::nullptr_t>::type = nullptr - > - const T& get() const { - assert(content_); - auto p = dynamic_cast*>(content_); - assert(p); - if (!p) { - throw std::bad_cast(); - } - return p->value_; - } - - template < - typename T, - typename std::enable_if::value, std::nullptr_t>::type = nullptr - > - const any& get() const { - return *this; - } - -private: - struct placeholder { - virtual ~placeholder() {} - virtual placeholder* clone() const = 0; - }; - - template - struct holder : placeholder { - holder(const T& value) : value_(value) {} - placeholder* clone() const override { - return new holder(value_); - } - T value_; - }; - - placeholder* clone() const { - return content_ ? content_->clone() : nullptr; - } - - placeholder* content_; -}; - /*----------------------------------------------------------------------------- * scope_exit *---------------------------------------------------------------------------*/ -// This is based on "http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4189". +// This is based on +// "http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4189". -template -struct scope_exit -{ - explicit scope_exit(EF&& f) - : exit_function(std::move(f)) - , execute_on_destruction{true} {} +template struct scope_exit { + explicit scope_exit(EF &&f) + : exit_function(std::move(f)), execute_on_destruction{true} {} - scope_exit(scope_exit&& rhs) - : exit_function(std::move(rhs.exit_function)) - , execute_on_destruction{rhs.execute_on_destruction} { - rhs.release(); + scope_exit(scope_exit &&rhs) + : exit_function(std::move(rhs.exit_function)), + execute_on_destruction{rhs.execute_on_destruction} { + rhs.release(); } ~scope_exit() { - if (execute_on_destruction) { - this->exit_function(); - } + if (execute_on_destruction) { this->exit_function(); } } - void release() { - this->execute_on_destruction = false; - } + void release() { this->execute_on_destruction = false; } private: - scope_exit(const scope_exit&) = delete; - void operator=(const scope_exit&) = delete; - scope_exit& operator=(scope_exit&&) = delete; + scope_exit(const scope_exit &) = delete; + void operator=(const scope_exit &) = delete; + scope_exit &operator=(scope_exit &&) = delete; - EF exit_function; + EF exit_function; bool execute_on_destruction; }; -template -auto make_scope_exit(EF&& exit_function) -> scope_exit { - return scope_exit::type>(std::forward(exit_function)); -} - /*----------------------------------------------------------------------------- * UTF8 functions *---------------------------------------------------------------------------*/ inline size_t codepoint_length(const char *s8, size_t l) { - if (l) { - auto b = static_cast(s8[0]); - if ((b & 0x80) == 0) { - return 1; - } else if ((b & 0xE0) == 0xC0) { - return 2; - } else if ((b & 0xF0) == 0xE0) { - return 3; - } else if ((b & 0xF8) == 0xF0) { - return 4; + if (l) { + auto b = static_cast(s8[0]); + if ((b & 0x80) == 0) { + return 1; + } else if ((b & 0xE0) == 0xC0 && l >= 2) { + return 2; + } else if ((b & 0xF0) == 0xE0 && l >= 3) { + return 3; + } else if ((b & 0xF8) == 0xF0 && l >= 4) { + return 4; + } } - } - return 0; + return 0; +} + +inline size_t codepoint_count(const char *s8, size_t l) { + size_t count = 0; + for (size_t i = 0; i < l; i += codepoint_length(s8 + i, l - i)) { + count++; + } + return count; } inline size_t encode_codepoint(char32_t cp, char *buff) { - if (cp < 0x0080) { - buff[0] = static_cast(cp & 0x7F); - return 1; - } else if (cp < 0x0800) { - buff[0] = static_cast(0xC0 | ((cp >> 6) & 0x1F)); - buff[1] = static_cast(0x80 | (cp & 0x3F)); - return 2; - } else if (cp < 0xD800) { - buff[0] = static_cast(0xE0 | ((cp >> 12) & 0xF)); - buff[1] = static_cast(0x80 | ((cp >> 6) & 0x3F)); - buff[2] = static_cast(0x80 | (cp & 0x3F)); - return 3; - } else if (cp < 0xE000) { - // D800 - DFFF is invalid... + if (cp < 0x0080) { + buff[0] = static_cast(cp & 0x7F); + return 1; + } else if (cp < 0x0800) { + buff[0] = static_cast(0xC0 | ((cp >> 6) & 0x1F)); + buff[1] = static_cast(0x80 | (cp & 0x3F)); + return 2; + } else if (cp < 0xD800) { + buff[0] = static_cast(0xE0 | ((cp >> 12) & 0xF)); + buff[1] = static_cast(0x80 | ((cp >> 6) & 0x3F)); + buff[2] = static_cast(0x80 | (cp & 0x3F)); + return 3; + } else if (cp < 0xE000) { + // D800 - DFFF is invalid... + return 0; + } else if (cp < 0x10000) { + buff[0] = static_cast(0xE0 | ((cp >> 12) & 0xF)); + buff[1] = static_cast(0x80 | ((cp >> 6) & 0x3F)); + buff[2] = static_cast(0x80 | (cp & 0x3F)); + return 3; + } else if (cp < 0x110000) { + buff[0] = static_cast(0xF0 | ((cp >> 18) & 0x7)); + buff[1] = static_cast(0x80 | ((cp >> 12) & 0x3F)); + buff[2] = static_cast(0x80 | ((cp >> 6) & 0x3F)); + buff[3] = static_cast(0x80 | (cp & 0x3F)); + return 4; + } return 0; - } else if (cp < 0x10000) { - buff[0] = static_cast(0xE0 | ((cp >> 12) & 0xF)); - buff[1] = static_cast(0x80 | ((cp >> 6) & 0x3F)); - buff[2] = static_cast(0x80 | (cp & 0x3F)); - return 3; - } else if (cp < 0x110000) { - buff[0] = static_cast(0xF0 | ((cp >> 18) & 0x7)); - buff[1] = static_cast(0x80 | ((cp >> 12) & 0x3F)); - buff[2] = static_cast(0x80 | ((cp >> 6) & 0x3F)); - buff[3] = static_cast(0x80 | (cp & 0x3F)); - return 4; - } - return 0; } inline std::string encode_codepoint(char32_t cp) { - char buff[4]; - auto l = encode_codepoint(cp, buff); - return std::string(buff, l); + char buff[4]; + auto l = encode_codepoint(cp, buff); + return std::string(buff, l); } inline bool decode_codepoint(const char *s8, size_t l, size_t &bytes, char32_t &cp) { - if (l) { - auto b = static_cast(s8[0]); - if ((b & 0x80) == 0) { - bytes = 1; - cp = b; - return true; - } else if ((b & 0xE0) == 0xC0) { - if (l >= 2) { - bytes = 2; - cp = ((static_cast(s8[0] & 0x1F)) << 6) | - (static_cast(s8[1] & 0x3F)); - return true; - } - } else if ((b & 0xF0) == 0xE0) { - if (l >= 3) { - bytes = 3; - cp = ((static_cast(s8[0] & 0x0F)) << 12) | - ((static_cast(s8[1] & 0x3F)) << 6) | - (static_cast(s8[2] & 0x3F)); - return true; - } - } else if ((b & 0xF8) == 0xF0) { - if (l >= 4) { - bytes = 4; - cp = ((static_cast(s8[0] & 0x07)) << 18) | - ((static_cast(s8[1] & 0x3F)) << 12) | - ((static_cast(s8[2] & 0x3F)) << 6) | - (static_cast(s8[3] & 0x3F)); - return true; - } + if (l) { + auto b = static_cast(s8[0]); + if ((b & 0x80) == 0) { + bytes = 1; + cp = b; + return true; + } else if ((b & 0xE0) == 0xC0) { + if (l >= 2) { + bytes = 2; + cp = ((static_cast(s8[0] & 0x1F)) << 6) | + (static_cast(s8[1] & 0x3F)); + return true; + } + } else if ((b & 0xF0) == 0xE0) { + if (l >= 3) { + bytes = 3; + cp = ((static_cast(s8[0] & 0x0F)) << 12) | + ((static_cast(s8[1] & 0x3F)) << 6) | + (static_cast(s8[2] & 0x3F)); + return true; + } + } else if ((b & 0xF8) == 0xF0) { + if (l >= 4) { + bytes = 4; + cp = ((static_cast(s8[0] & 0x07)) << 18) | + ((static_cast(s8[1] & 0x3F)) << 12) | + ((static_cast(s8[2] & 0x3F)) << 6) | + (static_cast(s8[3] & 0x3F)); + return true; + } + } } - } - return false; + return false; } -inline size_t decode_codepoint(const char *s8, size_t l, char32_t &out) { - size_t bytes; - if (decode_codepoint(s8, l, bytes, out)) { - return bytes; - } - return 0; +inline size_t decode_codepoint(const char *s8, size_t l, char32_t &cp) { + size_t bytes; + if (decode_codepoint(s8, l, bytes, cp)) { return bytes; } + return 0; } inline char32_t decode_codepoint(const char *s8, size_t l) { - char32_t out = 0; - decode_codepoint(s8, l, out); - return out; + char32_t cp = 0; + decode_codepoint(s8, l, cp); + return cp; } inline std::u32string decode(const char *s8, size_t l) { - std::u32string out; - size_t i = 0; - while (i < l) { - auto beg = i++; - while (i < l && (s8[i] & 0xc0) == 0x80) { - i++; + std::u32string out; + size_t i = 0; + while (i < l) { + auto beg = i++; + while (i < l && (s8[i] & 0xc0) == 0x80) { + i++; + } + out += decode_codepoint(&s8[beg], (i - beg)); } - out += decode_codepoint(&s8[beg], (i - beg)); - } - return out; + return out; +} + +template const char *u8(const T *s) { + return reinterpret_cast(s); +} + +/*----------------------------------------------------------------------------- + * escape_characters + *---------------------------------------------------------------------------*/ + +inline std::string escape_characters(const char *s, size_t n) { + std::string str; + for (size_t i = 0; i < n; i++) { + auto c = s[i]; + switch (c) { + case '\f': str += "\\f"; break; + case '\n': str += "\\n"; break; + case '\r': str += "\\r"; break; + case '\t': str += "\\t"; break; + case '\v': str += "\\v"; break; + default: str += c; break; + } + } + return str; +} + +inline std::string escape_characters(std::string_view sv) { + return escape_characters(sv.data(), sv.size()); } /*----------------------------------------------------------------------------- * resolve_escape_sequence *---------------------------------------------------------------------------*/ -inline bool is_hex(char c, int& v) { +inline bool is_hex(char c, int &v) { if ('0' <= c && c <= '9') { v = c - '0'; return true; @@ -338,7 +248,7 @@ inline bool is_hex(char c, int& v) { return false; } -inline bool is_digit(char c, int& v) { +inline bool is_digit(char c, int &v) { if ('0' <= c && c <= '9') { v = c - '0'; return true; @@ -346,27 +256,29 @@ inline bool is_digit(char c, int& v) { return false; } -inline std::pair parse_hex_number(const char* s, size_t n, size_t i) { +inline std::pair parse_hex_number(const char *s, size_t n, + size_t i) { int ret = 0; int val; while (i < n && is_hex(s[i], val)) { ret = static_cast(ret * 16 + val); i++; } - return std::make_pair(ret, i); + return std::pair(ret, i); } -inline std::pair parse_octal_number(const char* s, size_t n, size_t i) { +inline std::pair parse_octal_number(const char *s, size_t n, + size_t i) { int ret = 0; int val; while (i < n && is_digit(s[i], val)) { ret = static_cast(ret * 8 + val); i++; } - return std::make_pair(ret, i); + return std::pair(ret, i); } -inline std::string resolve_escape_sequence(const char* s, size_t n) { +inline std::string resolve_escape_sequence(const char *s, size_t n) { std::string r; r.reserve(n); @@ -375,15 +287,48 @@ inline std::string resolve_escape_sequence(const char* s, size_t n) { auto ch = s[i]; if (ch == '\\') { i++; + if (i == n) { throw std::runtime_error("Invalid escape sequence..."); } switch (s[i]) { - case 'n': r += '\n'; i++; break; - case 'r': r += '\r'; i++; break; - case 't': r += '\t'; i++; break; - case '\'': r += '\''; i++; break; - case '"': r += '"'; i++; break; - case '[': r += '['; i++; break; - case ']': r += ']'; i++; break; - case '\\': r += '\\'; i++; break; + case 'f': + r += '\f'; + i++; + break; + case 'n': + r += '\n'; + i++; + break; + case 'r': + r += '\r'; + i++; + break; + case 't': + r += '\t'; + i++; + break; + case 'v': + r += '\v'; + i++; + break; + case '\'': + r += '\''; + i++; + break; + case '"': + r += '"'; + i++; + break; + case '[': + r += '['; + i++; + break; + case ']': + r += ']'; + i++; + break; + case '\\': + r += '\\'; + i++; + break; case 'x': case 'u': { char32_t cp; @@ -406,14 +351,111 @@ inline std::string resolve_escape_sequence(const char* s, size_t n) { return r; } +/*----------------------------------------------------------------------------- + * token_to_number_ - This function should be removed eventually + *---------------------------------------------------------------------------*/ + +template T token_to_number_(std::string_view sv) { + T n = 0; +#if __has_include() + if constexpr (!std::is_floating_point::value) { + std::from_chars(sv.data(), sv.data() + sv.size(), n); +#else + if constexpr (false) { +#endif + } else { + auto s = std::string(sv); + std::istringstream ss(s); + ss >> n; + } + return n; +} + +/*----------------------------------------------------------------------------- + * Trie + *---------------------------------------------------------------------------*/ + +class Trie { +public: + Trie(const std::vector &items, bool ignore_case) + : ignore_case_(ignore_case) { + size_t id = 0; + for (const auto &item : items) { + const auto &s = ignore_case ? to_lower(item) : item; + for (size_t len = 1; len <= item.size(); len++) { + auto last = len == item.size(); + std::string_view sv(s.data(), len); + auto it = dic_.find(sv); + if (it == dic_.end()) { + dic_.emplace(sv, Info{last, last, id}); + } else if (last) { + it->second.match = true; + } else { + it->second.done = false; + } + } + id++; + } + } + + size_t match(const char *text, size_t text_len, size_t &id) const { + std::string lower_text; + if (ignore_case_) { + lower_text = to_lower(text); + text = lower_text.data(); + } + + size_t match_len = 0; + auto done = false; + size_t len = 1; + while (!done && len <= text_len) { + std::string_view sv(text, len); + auto it = dic_.find(sv); + if (it == dic_.end()) { + done = true; + } else { + if (it->second.match) { + match_len = len; + id = it->second.id; + } + if (it->second.done) { done = true; } + } + len += 1; + } + return match_len; + } + + size_t size() const { return dic_.size(); } + +private: + std::string to_lower(std::string s) const { + for (char &c : s) { + c = std::tolower(c); + } + return s; + } + + struct Info { + bool done; + bool match; + size_t id; + }; + + // TODO: Use unordered_map when heterogeneous lookup is supported in C++20 + // std::unordered_map dic_; + std::map> dic_; + + bool ignore_case_; +}; + /*----------------------------------------------------------------------------- * PEG *---------------------------------------------------------------------------*/ /* -* Line information utility function -*/ -inline std::pair line_info(const char* start, const char* cur) { + * Line information utility function + */ +inline std::pair line_info(const char *start, const char *cur) { auto p = start; auto col_ptr = p; auto no = 1; @@ -426,398 +468,371 @@ inline std::pair line_info(const char* start, const char* cur) { p++; } - auto col = p - col_ptr + 1; + auto col = codepoint_count(col_ptr, p - col_ptr) + 1; - return std::make_pair(no, col); + return std::pair(no, col); } /* -* Semantic values -*/ -struct SemanticValues : protected std::vector -{ + * String tag + */ +inline constexpr unsigned int str2tag_core(const char *s, size_t l, + unsigned int h) { + return (l == 0) ? h + : str2tag_core(s + 1, l - 1, + (h * 33) ^ static_cast(*s)); +} + +inline constexpr unsigned int str2tag(std::string_view sv) { + return str2tag_core(sv.data(), sv.size(), 0); +} + +namespace udl { + +inline constexpr unsigned int operator"" _(const char *s, size_t l) { + return str2tag_core(s, l, 0); +} + +} // namespace udl + +/* + * Semantic values + */ +class Context; + +struct SemanticValues : protected std::vector { + SemanticValues() = default; + SemanticValues(Context *c) : c_(c) {} + // Input text - const char* path; - const char* ss; + const char *path = nullptr; + const char *ss = nullptr; // Matched string - const char* c_str() const { return s_; } - size_t length() const { return n_; } + std::string_view sv() const { return sv_; } - std::string str() const { - return std::string(s_, n_); - } + // Definition name + const std::string &name() const { return name_; } + + std::vector tags; // Line number and column at which the matched string is - std::pair line_info() const { - return peg::line_info(ss, s_); - } + std::pair line_info() const; // Choice count - size_t choice_count() const { return choice_count_; } + size_t choice_count() const { return choice_count_; } // Choice number (0 based index) - size_t choice() const { return choice_; } + size_t choice() const { return choice_; } // Tokens - std::vector> tokens; + std::vector tokens; - std::string token(size_t id = 0) const { - if (!tokens.empty()) { - assert(id < tokens.size()); - const auto& tok = tokens[id]; - return std::string(tok.first, tok.second); - } - return std::string(s_, n_); + std::string_view token(size_t id = 0) const { + if (tokens.empty()) { return sv_; } + assert(id < tokens.size()); + return tokens[id]; + } + + // Token conversion + std::string token_to_string(size_t id = 0) const { + return std::string(token(id)); + } + + template T token_to_number() const { + return token_to_number_(token()); } // Transform the semantic value vector to another vector template - auto transform(size_t beg = 0, size_t end = static_cast(-1)) const -> vector { - return this->transform(beg, end, [](const any& v) { return v.get(); }); + std::vector transform(size_t beg = 0, + size_t end = static_cast(-1)) const { + std::vector r; + end = (std::min)(end, size()); + for (size_t i = beg; i < end; i++) { + r.emplace_back(std::any_cast((*this)[i])); + } + return r; } - SemanticValues() : s_(nullptr), n_(0), choice_count_(0), choice_(0) {} + void append(SemanticValues &chvs) { + sv_ = chvs.sv_; + for (auto &v : chvs) { + emplace_back(std::move(v)); + } + for (auto &tag : chvs.tags) { + tags.emplace_back(std::move(tag)); + } + for (auto &tok : chvs.tokens) { + tokens.emplace_back(std::move(tok)); + } + } - using std::vector::iterator; - using std::vector::const_iterator; - using std::vector::size; - using std::vector::empty; - using std::vector::assign; - using std::vector::begin; - using std::vector::end; - using std::vector::rbegin; - using std::vector::rend; - using std::vector::operator[]; - using std::vector::at; - using std::vector::resize; - using std::vector::front; - using std::vector::back; - using std::vector::push_back; - using std::vector::pop_back; - using std::vector::insert; - using std::vector::erase; - using std::vector::clear; - using std::vector::swap; - using std::vector::emplace; - using std::vector::emplace_back; + using std::vector::iterator; + using std::vector::const_iterator; + using std::vector::size; + using std::vector::empty; + using std::vector::assign; + using std::vector::begin; + using std::vector::end; + using std::vector::rbegin; + using std::vector::rend; + using std::vector::operator[]; + using std::vector::at; + using std::vector::resize; + using std::vector::front; + using std::vector::back; + using std::vector::push_back; + using std::vector::pop_back; + using std::vector::insert; + using std::vector::erase; + using std::vector::clear; + using std::vector::swap; + using std::vector::emplace; + using std::vector::emplace_back; private: friend class Context; + friend class Dictionary; friend class Sequence; friend class PrioritizedChoice; + friend class Repetition; friend class Holder; + friend class PrecedenceClimbing; - const char* s_; - size_t n_; - size_t choice_count_; - size_t choice_; - - template - auto transform(F f) const -> vector::type> { - vector::type> r; - for (const auto& v: *this) { - r.emplace_back(f(v)); - } - return r; - } - - template - auto transform(size_t beg, size_t end, F f) const -> vector::type> { - vector::type> r; - end = (std::min)(end, size()); - for (size_t i = beg; i < end; i++) { - r.emplace_back(f((*this)[i])); - } - return r; - } - - void reset() { - path = nullptr; - ss = nullptr; - tokens.clear(); - - s_ = nullptr; - n_ = 0; - choice_count_ = 0; - choice_ = 0; - } + Context *c_ = nullptr; + std::string_view sv_; + size_t choice_count_ = 0; + size_t choice_ = 0; + std::string name_; }; /* * Semantic action */ -template < - typename R, typename F, - typename std::enable_if::value, std::nullptr_t>::type = nullptr, - typename... Args> -any call(F fn, Args&&... args) { - fn(std::forward(args)...); - return any(); +template std::any call(F fn, Args &&...args) { + using R = decltype(fn(std::forward(args)...)); + if constexpr (std::is_void::value) { + fn(std::forward(args)...); + return std::any(); + } else if constexpr (std::is_same::type, + std::any>::value) { + return fn(std::forward(args)...); + } else { + return std::any(fn(std::forward(args)...)); + } } -template < - typename R, typename F, - typename std::enable_if::type, any>::value, std::nullptr_t>::type = nullptr, - typename... Args> -any call(F fn, Args&&... args) { - return fn(std::forward(args)...); -} +template +struct argument_count : argument_count {}; +template +struct argument_count + : std::integral_constant {}; +template +struct argument_count + : std::integral_constant {}; +template +struct argument_count + : std::integral_constant {}; -template < - typename R, typename F, - typename std::enable_if< - !std::is_void::value && - !std::is_same::type, any>::value, std::nullptr_t>::type = nullptr, - typename... Args> -any call(F fn, Args&&... args) { - return any(fn(std::forward(args)...)); -} - -class Action -{ +class Action { public: Action() = default; + Action(Action &&rhs) = default; + template Action(F fn) : fn_(make_adaptor(fn)) {} + template void operator=(F fn) { fn_ = make_adaptor(fn); } + Action &operator=(const Action &rhs) = default; - Action(const Action& rhs) : fn_(rhs.fn_) {} + operator bool() const { return bool(fn_); } - template ::value && !std::is_same::value, std::nullptr_t>::type = nullptr> - Action(F fn) : fn_(make_adaptor(fn, &F::operator())) {} - - template ::value, std::nullptr_t>::type = nullptr> - Action(F fn) : fn_(make_adaptor(fn, fn)) {} - - template ::value, std::nullptr_t>::type = nullptr> - Action(F /*fn*/) {} - - template ::value && !std::is_same::value, std::nullptr_t>::type = nullptr> - void operator=(F fn) { - fn_ = make_adaptor(fn, &F::operator()); - } - - template ::value, std::nullptr_t>::type = nullptr> - void operator=(F fn) { - fn_ = make_adaptor(fn, fn); - } - - template ::value, std::nullptr_t>::type = nullptr> - void operator=(F /*fn*/) {} - - Action& operator=(const Action& rhs) = default; - - operator bool() const { - return bool(fn_); - } - - any operator()(SemanticValues& sv, any& dt) const { - return fn_(sv, dt); + std::any operator()(SemanticValues &vs, std::any &dt) const { + return fn_(vs, dt); } private: - template - struct TypeAdaptor_sv { - TypeAdaptor_sv(std::function fn) - : fn_(fn) {} - any operator()(SemanticValues& sv, any& /*dt*/) { - return call(fn_, sv); + using Fty = std::function; + + template Fty make_adaptor(F fn) { + if constexpr (argument_count::value == 1) { + return [fn](auto &vs, auto & /*dt*/) { return call(fn, vs); }; + } else { + return [fn](auto &vs, auto &dt) { return call(fn, vs, dt); }; } - std::function fn_; - }; - - template - struct TypeAdaptor_csv { - TypeAdaptor_csv(std::function fn) - : fn_(fn) {} - any operator()(SemanticValues& sv, any& /*dt*/) { - return call(fn_, sv); - } - std::function fn_; - }; - - template - struct TypeAdaptor_sv_dt { - TypeAdaptor_sv_dt(std::function fn) - : fn_(fn) {} - any operator()(SemanticValues& sv, any& dt) { - return call(fn_, sv, dt); - } - std::function fn_; - }; - - template - struct TypeAdaptor_csv_dt { - TypeAdaptor_csv_dt(std::function fn) - : fn_(fn) {} - any operator()(SemanticValues& sv, any& dt) { - return call(fn_, sv, dt); - } - std::function fn_; - }; - - typedef std::function Fty; - - template - Fty make_adaptor(F fn, R (F::* /*mf*/)(SemanticValues& sv) const) { - return TypeAdaptor_sv(fn); - } - - template - Fty make_adaptor(F fn, R (F::* /*mf*/)(const SemanticValues& sv) const) { - return TypeAdaptor_csv(fn); - } - - template - Fty make_adaptor(F fn, R (F::* /*mf*/)(SemanticValues& sv)) { - return TypeAdaptor_sv(fn); - } - - template - Fty make_adaptor(F fn, R (F::* /*mf*/)(const SemanticValues& sv)) { - return TypeAdaptor_csv(fn); - } - - template - Fty make_adaptor(F fn, R (* /*mf*/)(SemanticValues& sv)) { - return TypeAdaptor_sv(fn); - } - - template - Fty make_adaptor(F fn, R (* /*mf*/)(const SemanticValues& sv)) { - return TypeAdaptor_csv(fn); - } - - template - Fty make_adaptor(F fn, R (F::* /*mf*/)(SemanticValues& sv, any& dt) const) { - return TypeAdaptor_sv_dt(fn); - } - - template - Fty make_adaptor(F fn, R (F::* /*mf*/)(const SemanticValues& sv, any& dt) const) { - return TypeAdaptor_csv_dt(fn); - } - - template - Fty make_adaptor(F fn, R (F::* /*mf*/)(SemanticValues& sv, any& dt)) { - return TypeAdaptor_sv_dt(fn); - } - - template - Fty make_adaptor(F fn, R (F::* /*mf*/)(const SemanticValues& sv, any& dt)) { - return TypeAdaptor_csv_dt(fn); - } - - template - Fty make_adaptor(F fn, R(* /*mf*/)(SemanticValues& sv, any& dt)) { - return TypeAdaptor_sv_dt(fn); - } - - template - Fty make_adaptor(F fn, R(* /*mf*/)(const SemanticValues& sv, any& dt)) { - return TypeAdaptor_csv_dt(fn); } Fty fn_; }; /* - * Semantic predicate + * Parse result helper */ -// Note: 'parse_error' exception class should be be used in sematic action handlers to reject the rule. -struct parse_error { - parse_error() = default; - parse_error(const char* s) : s_(s) {} - const char* what() const { return s_.empty() ? nullptr : s_.c_str(); } -private: - std::string s_; -}; +inline bool success(size_t len) { return len != static_cast(-1); } + +inline bool fail(size_t len) { return len == static_cast(-1); } /* - * Result + * Log */ -inline bool success(size_t len) { - return len != static_cast(-1); -} +using Log = std::function; -inline bool fail(size_t len) { - return len == static_cast(-1); -} +/* + * ErrorInfo + */ +class Definition; + +struct ErrorInfo { + const char *error_pos = nullptr; + std::vector> expected_tokens; + const char *message_pos = nullptr; + std::string message; + std::string label; + const char *last_output_pos = nullptr; + bool keep_previous_token = false; + + void clear() { + error_pos = nullptr; + expected_tokens.clear(); + message_pos = nullptr; + message.clear(); + } + + void add(const char *error_literal, const Definition *error_rule) { + for (const auto &[t, r] : expected_tokens) { + if (t == error_literal && r == error_rule) { return; } + } + expected_tokens.emplace_back(error_literal, error_rule); + } + + void output_log(const Log &log, const char *s, size_t n); + +private: + int cast_char(char c) const { return static_cast(c); } + + std::string heuristic_error_token(const char *s, size_t n, + const char *pos) const { + auto len = n - std::distance(s, pos); + if (len) { + size_t i = 0; + auto c = cast_char(pos[i++]); + if (!std::ispunct(c) && !std::isspace(c)) { + while (i < len && !std::ispunct(cast_char(pos[i])) && + !std::isspace(cast_char(pos[i]))) { + i++; + } + } + + size_t count = CPPPEGLIB_HEURISTIC_ERROR_TOKEN_MAX_CHAR_COUNT; + size_t j = 0; + while (count > 0 && j < i) { + j += codepoint_length(&pos[j], i - j); + count--; + } + + return escape_characters(pos, j); + } + return std::string(); + } + + std::string replace_all(std::string str, const std::string &from, + const std::string &to) const { + size_t pos = 0; + while ((pos = str.find(from, pos)) != std::string::npos) { + str.replace(pos, from.length(), to); + pos += to.length(); + } + return str; + } +}; /* * Context */ -class Context; class Ope; -class Definition; -typedef std::function Tracer; +using TracerEnter = std::function; -class Context -{ +using TracerLeave = std::function; + +using TracerStartOrEnd = std::function; + +class Context { public: - const char* path; - const char* s; - const size_t l; + const char *path; + const char *s; + const size_t l; - const char* error_pos; - const char* message_pos; - std::string message; // TODO: should be `int`. + ErrorInfo error_info; + bool recovered = false; std::vector> value_stack; - size_t value_stack_size; + size_t value_stack_size = 0; + + std::vector rule_stack; std::vector>> args_stack; - size_t nest_level; + size_t in_token_boundary_count = 0; - bool in_token; + std::shared_ptr whitespaceOpe; + bool in_whitespace = false; - std::shared_ptr whitespaceOpe; - bool in_whitespace; + std::shared_ptr wordOpe; - std::shared_ptr wordOpe; + std::vector> capture_scope_stack; + size_t capture_scope_stack_size = 0; - std::vector> capture_scope_stack; + std::vector cut_stack; - const size_t def_count; - const bool enablePackratParsing; - std::vector cache_registered; - std::vector cache_success; + const size_t def_count; + const bool enablePackratParsing; + std::vector cache_registered; + std::vector cache_success; - std::map, std::tuple> cache_values; + std::map, std::tuple> + cache_values; - std::function tracer; + TracerEnter tracer_enter; + TracerLeave tracer_leave; + std::any trace_data; + const bool verbose_trace; - Context( - const char* a_path, - const char* a_s, - size_t a_l, - size_t a_def_count, - std::shared_ptr a_whitespaceOpe, - std::shared_ptr a_wordOpe, - bool a_enablePackratParsing, - Tracer a_tracer) - : path(a_path) - , s(a_s) - , l(a_l) - , error_pos(nullptr) - , message_pos(nullptr) - , value_stack_size(0) - , nest_level(0) - , in_token(false) - , whitespaceOpe(a_whitespaceOpe) - , in_whitespace(false) - , wordOpe(a_wordOpe) - , def_count(a_def_count) - , enablePackratParsing(a_enablePackratParsing) - , cache_registered(enablePackratParsing ? def_count * (l + 1) : 0) - , cache_success(enablePackratParsing ? def_count * (l + 1) : 0) - , tracer(a_tracer) - { - args_stack.resize(1); - capture_scope_stack.resize(1); + Log log; + + Context(const char *path, const char *s, size_t l, size_t def_count, + std::shared_ptr whitespaceOpe, std::shared_ptr wordOpe, + bool enablePackratParsing, TracerEnter tracer_enter, + TracerLeave tracer_leave, std::any trace_data, bool verbose_trace, + Log log) + : path(path), s(s), l(l), whitespaceOpe(whitespaceOpe), wordOpe(wordOpe), + def_count(def_count), enablePackratParsing(enablePackratParsing), + cache_registered(enablePackratParsing ? def_count * (l + 1) : 0), + cache_success(enablePackratParsing ? def_count * (l + 1) : 0), + tracer_enter(tracer_enter), tracer_leave(tracer_leave), + trace_data(trace_data), verbose_trace(verbose_trace), log(log) { + + push_args({}); + push_capture_scope(); } + ~Context() { + pop_capture_scope(); + + assert(!value_stack_size); + assert(!capture_scope_stack_size); + assert(cut_stack.empty()); + } + + Context(const Context &) = delete; + Context(Context &&) = delete; + Context operator=(const Context &) = delete; + template - void packrat(const char* a_s, size_t def_id, size_t& len, any& val, T fn) { + void packrat(const char *a_s, size_t def_id, size_t &len, std::any &val, + T fn) { if (!enablePackratParsing) { fn(val); return; @@ -828,7 +843,7 @@ public: if (cache_registered[idx]) { if (cache_success[idx]) { - auto key = std::make_pair(col, def_id); + auto key = std::pair(col, def_id); std::tie(len, val) = cache_values[key]; return; } else { @@ -840,469 +855,445 @@ public: cache_registered[idx] = true; cache_success[idx] = success(len); if (success(len)) { - auto key = std::make_pair(col, def_id); - cache_values[key] = std::make_pair(len, val); + auto key = std::pair(col, def_id); + cache_values[key] = std::pair(len, val); } return; } } - SemanticValues& push() { - assert(value_stack_size <= value_stack.size()); - if (value_stack_size == value_stack.size()) { - value_stack.emplace_back(std::make_shared()); - } - auto& sv = *value_stack[value_stack_size++]; - if (!sv.empty()) { - sv.clear(); - } - sv.reset(); - sv.path = path; - sv.ss = s; - return sv; + SemanticValues &push() { + push_capture_scope(); + return push_semantic_values_scope(); } void pop() { - value_stack_size--; + pop_capture_scope(); + pop_semantic_values_scope(); } - void push_args(const std::vector>& args) { - args_stack.push_back(args); + // Semantic values + SemanticValues &push_semantic_values_scope() { + assert(value_stack_size <= value_stack.size()); + if (value_stack_size == value_stack.size()) { + value_stack.emplace_back(std::make_shared(this)); + } else { + auto &vs = *value_stack[value_stack_size]; + if (!vs.empty()) { + vs.clear(); + if (!vs.tags.empty()) { vs.tags.clear(); } + } + vs.sv_ = std::string_view(); + vs.choice_count_ = 0; + vs.choice_ = 0; + if (!vs.tokens.empty()) { vs.tokens.clear(); } + } + + auto &vs = *value_stack[value_stack_size++]; + vs.path = path; + vs.ss = s; + return vs; } - void pop_args() { - args_stack.pop_back(); + void pop_semantic_values_scope() { value_stack_size--; } + + // Arguments + void push_args(std::vector> &&args) { + args_stack.emplace_back(args); } - const std::vector>& top_args() const { + void pop_args() { args_stack.pop_back(); } + + const std::vector> &top_args() const { return args_stack[args_stack.size() - 1]; } + // Capture scope void push_capture_scope() { - capture_scope_stack.resize(capture_scope_stack.size() + 1); + assert(capture_scope_stack_size <= capture_scope_stack.size()); + if (capture_scope_stack_size == capture_scope_stack.size()) { + capture_scope_stack.emplace_back( + std::map()); + } else { + auto &cs = capture_scope_stack[capture_scope_stack_size]; + if (!cs.empty()) { cs.clear(); } + } + capture_scope_stack_size++; } - void pop_capture_scope() { - capture_scope_stack.pop_back(); - } + void pop_capture_scope() { capture_scope_stack_size--; } void shift_capture_values() { - assert(capture_scope_stack.size() >= 2); - auto it = capture_scope_stack.rbegin(); - auto it_prev = it + 1; - for (const auto& kv: *it) { - (*it_prev)[kv.first] = kv.second; + assert(capture_scope_stack_size >= 2); + auto curr = &capture_scope_stack[capture_scope_stack_size - 1]; + auto prev = curr - 1; + for (const auto &[k, v] : *curr) { + (*prev)[k] = v; } } - void set_error_pos(const char* a_s) { - if (error_pos < a_s) error_pos = a_s; + // Error + void set_error_pos(const char *a_s, const char *literal = nullptr); + + // Trace + void trace_enter(const Ope &ope, const char *a_s, size_t n, + const SemanticValues &vs, std::any &dt); + void trace_leave(const Ope &ope, const char *a_s, size_t n, + const SemanticValues &vs, std::any &dt, size_t len); + bool is_traceable(const Ope &ope) const; + + // Line info + std::pair line_info(const char *cur) const { + std::call_once(source_line_index_init_, [this]() { + for (size_t pos = 0; pos < l; pos++) { + if (s[pos] == '\n') { source_line_index.push_back(pos); } + } + source_line_index.push_back(l); + }); + + auto pos = static_cast(std::distance(s, cur)); + + auto it = std::lower_bound( + source_line_index.begin(), source_line_index.end(), pos, + [](size_t element, size_t value) { return element < value; }); + + auto id = static_cast(std::distance(source_line_index.begin(), it)); + auto off = pos - (id == 0 ? 0 : source_line_index[id - 1] + 1); + return std::pair(id + 1, off + 1); } - void trace(const char* name, const char* a_s, size_t n, SemanticValues& sv, any& dt) const { - if (tracer) tracer(name, a_s, n, sv, *this, dt); - } + size_t next_trace_id = 0; + std::vector trace_ids; + bool ignore_trace_state = false; + mutable std::once_flag source_line_index_init_; + mutable std::vector source_line_index; }; /* * Parser operators */ -class Ope -{ +class Ope { public: struct Visitor; - virtual ~Ope() {} - virtual size_t parse(const char* s, size_t n, SemanticValues& sv, Context& c, any& dt) const = 0; - virtual void accept(Visitor& v) = 0; + virtual ~Ope() = default; + size_t parse(const char *s, size_t n, SemanticValues &vs, Context &c, + std::any &dt) const; + virtual size_t parse_core(const char *s, size_t n, SemanticValues &vs, + Context &c, std::any &dt) const = 0; + virtual void accept(Visitor &v) = 0; }; -class Sequence : public Ope -{ +class Sequence : public Ope { public: - Sequence(const Sequence& rhs) : opes_(rhs.opes_) {} - -#if defined(_MSC_VER) && _MSC_VER < 1900 // Less than Visual Studio 2015 - // NOTE: Compiler Error C2797 on Visual Studio 2013 - // "The C++ compiler in Visual Studio does not implement list - // initialization inside either a member initializer list or a non-static - // data member initializer. Before Visual Studio 2013 Update 3, this was - // silently converted to a function call, which could lead to bad code - // generation. Visual Studio 2013 Update 3 reports this as an error." template - Sequence(const Args& ...args) { - opes_ = std::vector>{ static_cast>(args)... }; - } -#else - template - Sequence(const Args& ...args) : opes_{ static_cast>(args)... } {} -#endif + Sequence(const Args &...args) + : opes_{static_cast>(args)...} {} + Sequence(const std::vector> &opes) : opes_(opes) {} + Sequence(std::vector> &&opes) : opes_(opes) {} - Sequence(const std::vector>& opes) : opes_(opes) {} - Sequence(std::vector>&& opes) : opes_(opes) {} - - size_t parse(const char* s, size_t n, SemanticValues& sv, Context& c, any& dt) const override { - c.trace("Sequence", s, n, sv, dt); - auto& chldsv = c.push(); + size_t parse_core(const char *s, size_t n, SemanticValues &vs, Context &c, + std::any &dt) const override { + auto &chvs = c.push_semantic_values_scope(); + auto se = scope_exit([&]() { c.pop_semantic_values_scope(); }); size_t i = 0; - for (const auto& ope : opes_) { - c.nest_level++; - auto se = make_scope_exit([&]() { c.nest_level--; }); - const auto& rule = *ope; - auto len = rule.parse(s + i, n - i, chldsv, c, dt); - if (fail(len)) { - return static_cast(-1); - } + for (const auto &ope : opes_) { + auto len = ope->parse(s + i, n - i, chvs, c, dt); + if (fail(len)) { return len; } i += len; } - sv.insert(sv.end(), chldsv.begin(), chldsv.end()); - sv.s_ = chldsv.c_str(); - sv.n_ = chldsv.length(); - sv.tokens.insert(sv.tokens.end(), chldsv.tokens.begin(), chldsv.tokens.end()); + vs.append(chvs); return i; } - void accept(Visitor& v) override; + void accept(Visitor &v) override; std::vector> opes_; }; -class PrioritizedChoice : public Ope -{ +class PrioritizedChoice : public Ope { public: -#if defined(_MSC_VER) && _MSC_VER < 1900 // Less than Visual Studio 2015 - // NOTE: Compiler Error C2797 on Visual Studio 2013 - // "The C++ compiler in Visual Studio does not implement list - // initialization inside either a member initializer list or a non-static - // data member initializer. Before Visual Studio 2013 Update 3, this was - // silently converted to a function call, which could lead to bad code - // generation. Visual Studio 2013 Update 3 reports this as an error." template - PrioritizedChoice(const Args& ...args) { - opes_ = std::vector>{ static_cast>(args)... }; - } -#else - template - PrioritizedChoice(const Args& ...args) : opes_{ static_cast>(args)... } {} -#endif + PrioritizedChoice(bool for_label, const Args &...args) + : opes_{static_cast>(args)...}, + for_label_(for_label) {} + PrioritizedChoice(const std::vector> &opes) + : opes_(opes) {} + PrioritizedChoice(std::vector> &&opes) : opes_(opes) {} - PrioritizedChoice(const std::vector>& opes) : opes_(opes) {} - PrioritizedChoice(std::vector>&& opes) : opes_(opes) {} + size_t parse_core(const char *s, size_t n, SemanticValues &vs, Context &c, + std::any &dt) const override { + size_t len = static_cast(-1); + + if (!for_label_) { c.cut_stack.push_back(false); } + auto se1 = scope_exit([&]() { + if (!for_label_) { c.cut_stack.pop_back(); } + }); - size_t parse(const char* s, size_t n, SemanticValues& sv, Context& c, any& dt) const override { - c.trace("PrioritizedChoice", s, n, sv, dt); size_t id = 0; - for (const auto& ope : opes_) { - c.nest_level++; - auto& chldsv = c.push(); - c.push_capture_scope(); - auto se = make_scope_exit([&]() { - c.nest_level--; - c.pop(); - c.pop_capture_scope(); - }); - const auto& rule = *ope; - auto len = rule.parse(s, n, chldsv, c, dt); - if (success(len)) { - sv.insert(sv.end(), chldsv.begin(), chldsv.end()); - sv.s_ = chldsv.c_str(); - sv.n_ = chldsv.length(); - sv.choice_count_ = opes_.size(); - sv.choice_ = id; - sv.tokens.insert(sv.tokens.end(), chldsv.tokens.begin(), chldsv.tokens.end()); + for (const auto &ope : opes_) { + if (!c.cut_stack.empty()) { c.cut_stack.back() = false; } + auto &chvs = c.push(); + c.error_info.keep_previous_token = id > 0; + auto se2 = scope_exit([&]() { + c.pop(); + c.error_info.keep_previous_token = false; + }); + + len = ope->parse(s, n, chvs, c, dt); + + if (success(len)) { + vs.append(chvs); + vs.choice_count_ = opes_.size(); + vs.choice_ = id; c.shift_capture_values(); - return len; + break; + } else if (!c.cut_stack.empty() && c.cut_stack.back()) { + break; } + id++; } - return static_cast(-1); + + return len; } - void accept(Visitor& v) override; + void accept(Visitor &v) override; - size_t size() const { return opes_.size(); } + size_t size() const { return opes_.size(); } std::vector> opes_; + bool for_label_ = false; }; -class ZeroOrMore : public Ope -{ +class Repetition : public Ope { public: - ZeroOrMore(const std::shared_ptr& ope) : ope_(ope) {} + Repetition(const std::shared_ptr &ope, size_t min, size_t max) + : ope_(ope), min_(min), max_(max) {} - size_t parse(const char* s, size_t n, SemanticValues& sv, Context& c, any& dt) const override { - c.trace("ZeroOrMore", s, n, sv, dt); - auto save_error_pos = c.error_pos; + size_t parse_core(const char *s, size_t n, SemanticValues &vs, Context &c, + std::any &dt) const override { + size_t count = 0; size_t i = 0; - while (n - i > 0) { - c.nest_level++; - c.push_capture_scope(); - auto se = make_scope_exit([&]() { - c.nest_level--; - c.pop_capture_scope(); - }); - auto save_sv_size = sv.size(); - auto save_tok_size = sv.tokens.size(); - const auto& rule = *ope_; - auto len = rule.parse(s + i, n - i, sv, c, dt); + while (count < min_) { + auto &chvs = c.push(); + auto se = scope_exit([&]() { c.pop(); }); + + auto len = ope_->parse(s + i, n - i, chvs, c, dt); + if (success(len)) { + vs.append(chvs); + c.shift_capture_values(); + } else { + return len; + } + i += len; + count++; + } + + while (count < max_) { + auto &chvs = c.push(); + auto se = scope_exit([&]() { c.pop(); }); + + auto len = ope_->parse(s + i, n - i, chvs, c, dt); + + if (success(len)) { + vs.append(chvs); c.shift_capture_values(); } else { - if (sv.size() != save_sv_size) { - sv.erase(sv.begin() + static_cast(save_sv_size)); - } - if (sv.tokens.size() != save_tok_size) { - sv.tokens.erase(sv.tokens.begin() + static_cast(save_tok_size)); - } - c.error_pos = save_error_pos; break; } i += len; + count++; } return i; } - void accept(Visitor& v) override; + void accept(Visitor &v) override; - std::shared_ptr ope_; -}; - -class OneOrMore : public Ope -{ -public: - OneOrMore(const std::shared_ptr& ope) : ope_(ope) {} - - size_t parse(const char* s, size_t n, SemanticValues& sv, Context& c, any& dt) const override { - c.trace("OneOrMore", s, n, sv, dt); - size_t len = 0; - { - c.nest_level++; - c.push_capture_scope(); - auto se = make_scope_exit([&]() { - c.nest_level--; - c.pop_capture_scope(); - }); - const auto& rule = *ope_; - len = rule.parse(s, n, sv, c, dt); - if (success(len)) { - c.shift_capture_values(); - } else { - return static_cast(-1); - } - } - auto save_error_pos = c.error_pos; - auto i = len; - while (n - i > 0) { - c.nest_level++; - c.push_capture_scope(); - auto se = make_scope_exit([&]() { - c.nest_level--; - c.pop_capture_scope(); - }); - auto save_sv_size = sv.size(); - auto save_tok_size = sv.tokens.size(); - const auto& rule = *ope_; - len = rule.parse(s + i, n - i, sv, c, dt); - if (success(len)) { - c.shift_capture_values(); - } else { - if (sv.size() != save_sv_size) { - sv.erase(sv.begin() + static_cast(save_sv_size)); - } - if (sv.tokens.size() != save_tok_size) { - sv.tokens.erase(sv.tokens.begin() + static_cast(save_tok_size)); - } - c.error_pos = save_error_pos; - break; - } - i += len; - } - return i; + bool is_zom() const { + return min_ == 0 && max_ == std::numeric_limits::max(); } - void accept(Visitor& v) override; + static std::shared_ptr zom(const std::shared_ptr &ope) { + return std::make_shared(ope, 0, + std::numeric_limits::max()); + } + + static std::shared_ptr oom(const std::shared_ptr &ope) { + return std::make_shared(ope, 1, + std::numeric_limits::max()); + } + + static std::shared_ptr opt(const std::shared_ptr &ope) { + return std::make_shared(ope, 0, 1); + } std::shared_ptr ope_; + size_t min_; + size_t max_; }; -class Option : public Ope -{ +class AndPredicate : public Ope { public: - Option(const std::shared_ptr& ope) : ope_(ope) {} + AndPredicate(const std::shared_ptr &ope) : ope_(ope) {} + + size_t parse_core(const char *s, size_t n, SemanticValues & /*vs*/, + Context &c, std::any &dt) const override { + auto &chvs = c.push(); + auto se = scope_exit([&]() { c.pop(); }); + + auto len = ope_->parse(s, n, chvs, c, dt); - size_t parse(const char* s, size_t n, SemanticValues& sv, Context& c, any& dt) const override { - c.trace("Option", s, n, sv, dt); - auto save_error_pos = c.error_pos; - c.nest_level++; - auto save_sv_size = sv.size(); - auto save_tok_size = sv.tokens.size(); - c.push_capture_scope(); - auto se = make_scope_exit([&]() { - c.nest_level--; - c.pop_capture_scope(); - }); - const auto& rule = *ope_; - auto len = rule.parse(s, n, sv, c, dt); if (success(len)) { - c.shift_capture_values(); + return 0; + } else { return len; - } else { - if (sv.size() != save_sv_size) { - sv.erase(sv.begin() + static_cast(save_sv_size)); - } - if (sv.tokens.size() != save_tok_size) { - sv.tokens.erase(sv.tokens.begin() + static_cast(save_tok_size)); - } - c.error_pos = save_error_pos; - return 0; } } - void accept(Visitor& v) override; + void accept(Visitor &v) override; std::shared_ptr ope_; }; -class AndPredicate : public Ope -{ +class NotPredicate : public Ope { public: - AndPredicate(const std::shared_ptr& ope) : ope_(ope) {} + NotPredicate(const std::shared_ptr &ope) : ope_(ope) {} - size_t parse(const char* s, size_t n, SemanticValues& sv, Context& c, any& dt) const override { - c.trace("AndPredicate", s, n, sv, dt); - c.nest_level++; - auto& chldsv = c.push(); - c.push_capture_scope(); - auto se = make_scope_exit([&]() { - c.nest_level--; - c.pop(); - c.pop_capture_scope(); - }); - const auto& rule = *ope_; - auto len = rule.parse(s, n, chldsv, c, dt); - if (success(len)) { - return 0; - } else { - return static_cast(-1); - } - } - - void accept(Visitor& v) override; - - std::shared_ptr ope_; -}; - -class NotPredicate : public Ope -{ -public: - NotPredicate(const std::shared_ptr& ope) : ope_(ope) {} - - size_t parse(const char* s, size_t n, SemanticValues& sv, Context& c, any& dt) const override { - c.trace("NotPredicate", s, n, sv, dt); - auto save_error_pos = c.error_pos; - c.nest_level++; - auto& chldsv = c.push(); - c.push_capture_scope(); - auto se = make_scope_exit([&]() { - c.nest_level--; - c.pop(); - c.pop_capture_scope(); - }); - const auto& rule = *ope_; - auto len = rule.parse(s, n, chldsv, c, dt); + size_t parse_core(const char *s, size_t n, SemanticValues & /*vs*/, + Context &c, std::any &dt) const override { + auto &chvs = c.push(); + auto se = scope_exit([&]() { c.pop(); }); + auto len = ope_->parse(s, n, chvs, c, dt); if (success(len)) { c.set_error_pos(s); return static_cast(-1); } else { - c.error_pos = save_error_pos; return 0; } } - void accept(Visitor& v) override; + void accept(Visitor &v) override; std::shared_ptr ope_; }; -class LiteralString : public Ope - , public std::enable_shared_from_this -{ +class Dictionary : public Ope, public std::enable_shared_from_this { public: - LiteralString(const std::string& s) - : lit_(s) - , init_is_word_(false) - , is_word_(false) - {} + Dictionary(const std::vector &v, bool ignore_case) + : trie_(v, ignore_case) {} - size_t parse(const char* s, size_t n, SemanticValues& sv, Context& c, any& dt) const override; + size_t parse_core(const char *s, size_t n, SemanticValues &vs, Context &c, + std::any &dt) const override; - void accept(Visitor& v) override; + void accept(Visitor &v) override; + + Trie trie_; +}; + +class LiteralString : public Ope, + public std::enable_shared_from_this { +public: + LiteralString(std::string &&s, bool ignore_case) + : lit_(s), ignore_case_(ignore_case), is_word_(false) {} + + LiteralString(const std::string &s, bool ignore_case) + : lit_(s), ignore_case_(ignore_case), is_word_(false) {} + + size_t parse_core(const char *s, size_t n, SemanticValues &vs, Context &c, + std::any &dt) const override; + + void accept(Visitor &v) override; std::string lit_; - mutable bool init_is_word_; + bool ignore_case_; + mutable std::once_flag init_is_word_; mutable bool is_word_; }; -class CharacterClass : public Ope - , public std::enable_shared_from_this -{ +class CharacterClass : public Ope, + public std::enable_shared_from_this { public: - CharacterClass(const std::string& s) { - auto chars = decode(s.c_str(), s.length()); + CharacterClass(const std::string &s, bool negated, bool ignore_case) + : negated_(negated), ignore_case_(ignore_case) { + auto chars = decode(s.data(), s.length()); auto i = 0u; while (i < chars.size()) { if (i + 2 < chars.size() && chars[i + 1] == '-') { auto cp1 = chars[i]; auto cp2 = chars[i + 2]; - ranges_.emplace_back(std::make_pair(cp1, cp2)); + ranges_.emplace_back(std::pair(cp1, cp2)); i += 3; } else { auto cp = chars[i]; - ranges_.emplace_back(std::make_pair(cp, cp)); + ranges_.emplace_back(std::pair(cp, cp)); i += 1; } } + assert(!ranges_.empty()); } - CharacterClass(const std::vector>& ranges) : ranges_(ranges) {} - - size_t parse(const char* s, size_t n, SemanticValues& sv, Context& c, any& dt) const override { - c.trace("CharacterClass", s, n, sv, dt); + CharacterClass(const std::vector> &ranges, + bool negated, bool ignore_case) + : ranges_(ranges), negated_(negated), ignore_case_(ignore_case) { + assert(!ranges_.empty()); + } + size_t parse_core(const char *s, size_t n, SemanticValues & /*vs*/, + Context &c, std::any & /*dt*/) const override { if (n < 1) { c.set_error_pos(s); return static_cast(-1); } - char32_t cp; + char32_t cp = 0; auto len = decode_codepoint(s, n, cp); - if (!ranges_.empty()) { - for (const auto& range: ranges_) { - if (range.first <= cp && cp <= range.second) { + for (const auto &range : ranges_) { + if (in_range(range, cp)) { + if (negated_) { + c.set_error_pos(s); + return static_cast(-1); + } else { return len; } } } - c.set_error_pos(s); - return static_cast(-1); + if (negated_) { + return len; + } else { + c.set_error_pos(s); + return static_cast(-1); + } } - void accept(Visitor& v) override; + void accept(Visitor &v) override; + +private: + bool in_range(const std::pair &range, char32_t cp) const { + if (ignore_case_) { + auto cpl = std::tolower(cp); + return std::tolower(range.first) <= cpl && + cpl <= std::tolower(range.second); + } else { + return range.first <= cp && cp <= range.second; + } + } std::vector> ranges_; + bool negated_; + bool ignore_case_; }; -class Character : public Ope - , public std::enable_shared_from_this -{ +class Character : public Ope, public std::enable_shared_from_this { public: Character(char ch) : ch_(ch) {} - size_t parse(const char* s, size_t n, SemanticValues& sv, Context& c, any& dt) const override { - c.trace("Character", s, n, sv, dt); + size_t parse_core(const char *s, size_t n, SemanticValues & /*vs*/, + Context &c, std::any & /*dt*/) const override { if (n < 1 || s[0] != ch_) { c.set_error_pos(s); return static_cast(-1); @@ -1310,17 +1301,16 @@ public: return 1; } - void accept(Visitor& v) override; + void accept(Visitor &v) override; char ch_; }; -class AnyCharacter : public Ope - , public std::enable_shared_from_this -{ +class AnyCharacter : public Ope, + public std::enable_shared_from_this { public: - size_t parse(const char* s, size_t n, SemanticValues& sv, Context& c, any& dt) const override { - c.trace("AnyCharacter", s, n, sv, dt); + size_t parse_core(const char *s, size_t n, SemanticValues & /*vs*/, + Context &c, std::any & /*dt*/) const override { auto len = codepoint_length(s, n); if (len < 1) { c.set_error_pos(s); @@ -1329,393 +1319,553 @@ public: return len; } - void accept(Visitor& v) override; + void accept(Visitor &v) override; }; -class CaptureScope : public Ope -{ +class CaptureScope : public Ope { public: - CaptureScope(const std::shared_ptr& ope) - : ope_(ope) {} + CaptureScope(const std::shared_ptr &ope) : ope_(ope) {} - size_t parse(const char* s, size_t n, SemanticValues& sv, Context& c, any& dt) const override { + size_t parse_core(const char *s, size_t n, SemanticValues &vs, Context &c, + std::any &dt) const override { c.push_capture_scope(); - auto se = make_scope_exit([&]() { - c.pop_capture_scope(); - }); - const auto& rule = *ope_; - auto len = rule.parse(s, n, sv, c, dt); - return len; + auto se = scope_exit([&]() { c.pop_capture_scope(); }); + return ope_->parse(s, n, vs, c, dt); } - void accept(Visitor& v) override; + void accept(Visitor &v) override; std::shared_ptr ope_; }; -class Capture : public Ope -{ +class Capture : public Ope { public: - typedef std::function MatchAction; + using MatchAction = std::function; - Capture(const std::shared_ptr& ope, MatchAction ma) + Capture(const std::shared_ptr &ope, MatchAction ma) : ope_(ope), match_action_(ma) {} - size_t parse(const char* s, size_t n, SemanticValues& sv, Context& c, any& dt) const override { - const auto& rule = *ope_; - auto len = rule.parse(s, n, sv, c, dt); - if (success(len) && match_action_) { - match_action_(s, len, c); - } + size_t parse_core(const char *s, size_t n, SemanticValues &vs, Context &c, + std::any &dt) const override { + auto len = ope_->parse(s, n, vs, c, dt); + if (success(len) && match_action_) { match_action_(s, len, c); } return len; } - void accept(Visitor& v) override; + void accept(Visitor &v) override; std::shared_ptr ope_; - MatchAction match_action_; + MatchAction match_action_; }; -class TokenBoundary : public Ope -{ +class TokenBoundary : public Ope { public: - TokenBoundary(const std::shared_ptr& ope) : ope_(ope) {} + TokenBoundary(const std::shared_ptr &ope) : ope_(ope) {} - size_t parse(const char* s, size_t n, SemanticValues& sv, Context& c, any& dt) const override; + size_t parse_core(const char *s, size_t n, SemanticValues &vs, Context &c, + std::any &dt) const override; - void accept(Visitor& v) override; + void accept(Visitor &v) override; std::shared_ptr ope_; }; -class Ignore : public Ope -{ +class Ignore : public Ope { public: - Ignore(const std::shared_ptr& ope) : ope_(ope) {} + Ignore(const std::shared_ptr &ope) : ope_(ope) {} - size_t parse(const char* s, size_t n, SemanticValues& /*sv*/, Context& c, any& dt) const override { - const auto& rule = *ope_; - auto& chldsv = c.push(); - auto se = make_scope_exit([&]() { - c.pop(); - }); - return rule.parse(s, n, chldsv, c, dt); + size_t parse_core(const char *s, size_t n, SemanticValues & /*vs*/, + Context &c, std::any &dt) const override { + auto &chvs = c.push_semantic_values_scope(); + auto se = scope_exit([&]() { c.pop_semantic_values_scope(); }); + return ope_->parse(s, n, chvs, c, dt); } - void accept(Visitor& v) override; + void accept(Visitor &v) override; std::shared_ptr ope_; }; -typedef std::function Parser; +using Parser = std::function; -class User : public Ope -{ +class User : public Ope { public: User(Parser fn) : fn_(fn) {} - size_t parse(const char* s, size_t n, SemanticValues& sv, Context& c, any& dt) const override { - c.trace("User", s, n, sv, dt); + size_t parse_core(const char *s, size_t n, SemanticValues &vs, + Context & /*c*/, std::any &dt) const override { assert(fn_); - return fn_(s, n, sv, dt); + return fn_(s, n, vs, dt); } - void accept(Visitor& v) override; - std::function fn_; + void accept(Visitor &v) override; + std::function + fn_; }; -class WeakHolder : public Ope -{ +class WeakHolder : public Ope { public: - WeakHolder(const std::shared_ptr& ope) : weak_(ope) {} + WeakHolder(const std::shared_ptr &ope) : weak_(ope) {} - size_t parse(const char* s, size_t n, SemanticValues& sv, Context& c, any& dt) const override { + size_t parse_core(const char *s, size_t n, SemanticValues &vs, Context &c, + std::any &dt) const override { auto ope = weak_.lock(); assert(ope); - const auto& rule = *ope; - return rule.parse(s, n, sv, c, dt); + return ope->parse(s, n, vs, c, dt); } - void accept(Visitor& v) override; + void accept(Visitor &v) override; std::weak_ptr weak_; }; -class Holder : public Ope -{ +class Holder : public Ope { public: - Holder(Definition* outer) - : outer_(outer) {} + Holder(Definition *outer) : outer_(outer) {} - size_t parse(const char* s, size_t n, SemanticValues& sv, Context& c, any& dt) const override; + size_t parse_core(const char *s, size_t n, SemanticValues &vs, Context &c, + std::any &dt) const override; - void accept(Visitor& v) override; + void accept(Visitor &v) override; - any reduce(SemanticValues& sv, any& dt) const; + std::any reduce(SemanticValues &vs, std::any &dt) const; + + const std::string &name() const; + const std::string &trace_name() const; std::shared_ptr ope_; - Definition* outer_; + Definition *outer_; + mutable std::once_flag trace_name_init_; + mutable std::string trace_name_; friend class Definition; }; -typedef std::unordered_map Grammar; +using Grammar = std::unordered_map; -class Reference : public Ope - , public std::enable_shared_from_this -{ +class Reference : public Ope, public std::enable_shared_from_this { public: - Reference( - const Grammar& grammar, - const std::string& name, - const char* s, - bool is_macro, - const std::vector>& args) - : grammar_(grammar) - , name_(name) - , s_(s) - , is_macro_(is_macro) - , args_(args) - , rule_(nullptr) - , iarg_(0) - {} + Reference(const Grammar &grammar, const std::string &name, const char *s, + bool is_macro, const std::vector> &args) + : grammar_(grammar), name_(name), s_(s), is_macro_(is_macro), args_(args), + rule_(nullptr), iarg_(0) {} - size_t parse(const char* s, size_t n, SemanticValues& sv, Context& c, any& dt) const override; + size_t parse_core(const char *s, size_t n, SemanticValues &vs, Context &c, + std::any &dt) const override; - void accept(Visitor& v) override; + void accept(Visitor &v) override; std::shared_ptr get_core_operator() const; - const Grammar& grammar_; + const Grammar &grammar_; const std::string name_; - const char* s_; + const char *s_; const bool is_macro_; const std::vector> args_; - Definition* rule_; + Definition *rule_; size_t iarg_; }; -class Whitespace : public Ope -{ +class Whitespace : public Ope { public: - Whitespace(const std::shared_ptr& ope) : ope_(ope) {} + Whitespace(const std::shared_ptr &ope) : ope_(ope) {} - size_t parse(const char* s, size_t n, SemanticValues& sv, Context& c, any& dt) const override { - if (c.in_whitespace) { - return 0; - } + size_t parse_core(const char *s, size_t n, SemanticValues &vs, Context &c, + std::any &dt) const override { + if (c.in_whitespace) { return 0; } c.in_whitespace = true; - auto se = make_scope_exit([&]() { c.in_whitespace = false; }); - const auto& rule = *ope_; - return rule.parse(s, n, sv, c, dt); + auto se = scope_exit([&]() { c.in_whitespace = false; }); + return ope_->parse(s, n, vs, c, dt); } - void accept(Visitor& v) override; + void accept(Visitor &v) override; std::shared_ptr ope_; }; -class BackReference : public Ope -{ +class BackReference : public Ope { public: - BackReference(const std::string& name) : name_(name) {} + BackReference(std::string &&name) : name_(name) {} - size_t parse(const char* s, size_t n, SemanticValues& sv, Context& c, any& dt) const override; + BackReference(const std::string &name) : name_(name) {} - void accept(Visitor& v) override; + size_t parse_core(const char *s, size_t n, SemanticValues &vs, Context &c, + std::any &dt) const override; + + void accept(Visitor &v) override; std::string name_; }; +class PrecedenceClimbing : public Ope { +public: + using BinOpeInfo = std::map>; + + PrecedenceClimbing(const std::shared_ptr &atom, + const std::shared_ptr &binop, const BinOpeInfo &info, + const Definition &rule) + : atom_(atom), binop_(binop), info_(info), rule_(rule) {} + + size_t parse_core(const char *s, size_t n, SemanticValues &vs, Context &c, + std::any &dt) const override { + return parse_expression(s, n, vs, c, dt, 0); + } + + void accept(Visitor &v) override; + + std::shared_ptr atom_; + std::shared_ptr binop_; + BinOpeInfo info_; + const Definition &rule_; + +private: + size_t parse_expression(const char *s, size_t n, SemanticValues &vs, + Context &c, std::any &dt, size_t min_prec) const; + + Definition &get_reference_for_binop(Context &c) const; +}; + +class Recovery : public Ope { +public: + Recovery(const std::shared_ptr &ope) : ope_(ope) {} + + size_t parse_core(const char *s, size_t n, SemanticValues &vs, Context &c, + std::any &dt) const override; + + void accept(Visitor &v) override; + + std::shared_ptr ope_; +}; + +class Cut : public Ope, public std::enable_shared_from_this { +public: + size_t parse_core(const char * /*s*/, size_t /*n*/, SemanticValues & /*vs*/, + Context &c, std::any & /*dt*/) const override { + if (!c.cut_stack.empty()) { c.cut_stack.back() = true; } + return 0; + } + + void accept(Visitor &v) override; +}; + /* * Factories */ -template -std::shared_ptr seq(Args&& ...args) { +template std::shared_ptr seq(Args &&...args) { return std::make_shared(static_cast>(args)...); } -template -std::shared_ptr cho(Args&& ...args) { - return std::make_shared(static_cast>(args)...); +template std::shared_ptr cho(Args &&...args) { + return std::make_shared( + false, static_cast>(args)...); } -inline std::shared_ptr zom(const std::shared_ptr& ope) { - return std::make_shared(ope); +template std::shared_ptr cho4label_(Args &&...args) { + return std::make_shared( + true, static_cast>(args)...); } -inline std::shared_ptr oom(const std::shared_ptr& ope) { - return std::make_shared(ope); +inline std::shared_ptr zom(const std::shared_ptr &ope) { + return Repetition::zom(ope); } -inline std::shared_ptr opt(const std::shared_ptr& ope) { - return std::make_shared