diff --git a/.travis.yml b/.travis.yml
index 11cff198..b8a00074 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,13 +1,27 @@
language: cpp
-env:
- - QT4=1
- - QT4=0
-os:
- - linux
- - osx
-compiler:
- - gcc
- - clang
+matrix:
+ fast_finish: true
+ include:
+ # linux precise debug build, precise + gcc + qt4
+ - os: linux
+ env: QT4=1 BUILDTYPE=Debug DIST=precise
+ # linux debug build, trusty + gcc + qt5
+ - os: linux
+ dist: trusty
+ env: QT4=0 BUILDTYPE=Debug DIST=trusty
+ # osx debug build, osx + clang + qt5
+ - os: osx
+ env: QT4=0 BUILDTYPE=Debug
+ # linux precise release build, precise + gcc + qt4
+ - os: linux
+ env: QT4=1 BUILDTYPE=Release DIST=precise
+ # linux trusty release build, precise + gcc + qt5
+ - os: linux
+ dist: trusty
+ env: QT4=0 BUILDTYPE=Release DIST=trusty
+ # osx release build, osx + gcc + qt5
+ - os: osx
+ env: QT4=0 BUILDTYPE=Release
script: ./travis-compile.sh
install: ./travis-dependencies.sh
cache: apt
@@ -18,6 +32,12 @@ notifications:
- https://webhooks.gitter.im/e/d94969c3b01b22cbdcb7
on_success: change
on_failure: change
- on_start: false
-matrix:
- fast_finish: true
+ on_start: never
+deploy:
+ provider: bintray
+ file: "build/bintray_deploy.json"
+ user: "ctrlaltca"
+ key:
+ secure: DtVeeLoi5fZG/RvLTecRnRQGW9fVNS4Oa5iut2vJa14PdKBAJiXACQ0EzcRJFsbtby7MyMc2IVtT5skpvsaSqkpaxoBaL1YtKwJ4CTkYcm2MDWHS7UlijuxxTjI6BnaL3lcCCIeG+NHBZa3dV2YNJ1sWv6Xmiiix1ujPPW8VtnM=
+ on:
+ condition: $BUILDTYPE = Release
\ No newline at end of file
diff --git a/CMakeLists.txt b/CMakeLists.txt
index c4d239fe..1bbb7b84 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -19,6 +19,10 @@ if(POLICY CMP0048)
cmake_policy(SET CMP0048 OLD)
endif()
+if(POLICY CMP0064)
+ cmake_policy(SET CMP0064 OLD)
+endif()
+
set(PROJECT_NAME "Cockatrice")
# Default to "Release" build type
@@ -71,7 +75,7 @@ endif()
IF(MSVC)
# Visual Studio:
# Maximum optimization
- set(CMAKE_CXX_FLAGS_RELEASE "/Ox")
+ set(CMAKE_CXX_FLAGS_RELEASE "/Ox /MD")
# Generate complete debugging information
#set(CMAKE_CXX_FLAGS_DEBUG "/Zi")
ELSEIF (CMAKE_COMPILER_IS_GNUCXX)
@@ -104,7 +108,7 @@ ENDIF()
# Default is Qt5 unless WITH_QT4 option is enabled
option(WITH_QT4 "Force the use of Qt4 libraries" OFF)
OPTION(UPDATE_TRANSLATIONS "Update translations on compile" OFF)
-MESSAGE("UPDATE TRANSLATIONS: ${UPDATE_TRANSLATIONS}")
+MESSAGE(STATUS "UPDATE TRANSLATIONS: ${UPDATE_TRANSLATIONS}")
IF(NOT WITH_QT4)
# First known not-broken Qt5 version (5.0.2 available on old ubuntus is buggy).
@@ -174,11 +178,6 @@ set(CPACK_PACKAGE_VERSION_MAJOR "${PROJECT_VERSION_MAJOR}")
set(CPACK_PACKAGE_VERSION_MINOR "${PROJECT_VERSION_MINOR}")
set(CPACK_PACKAGE_VERSION_PATCH "${PROJECT_VERSION_PATCH}")
-# Create a version string suitable for file names
-string(REPLACE " " "_" PROJECT_VERSION_SAFE "${PROJECT_VERSION}")
-string(REPLACE "(" "" PROJECT_VERSION_SAFE "${PROJECT_VERSION_SAFE}")
-string(REPLACE ")" "" PROJECT_VERSION_SAFE "${PROJECT_VERSION_SAFE}")
-
if(UNIX)
if(APPLE)
set(CPACK_GENERATOR DragNDrop ${CPACK_GENERATOR})
@@ -186,18 +185,22 @@ if(UNIX)
set(CPACK_DMG_FORMAT "UDBZ")
set(CPACK_DMG_VOLUME_NAME "${PROJECT_NAME}")
set(CPACK_SYSTEM_NAME "OSX")
- set(CPACK_PACKAGE_FILE_NAME "${PROJECT_NAME}-osx_git-${PROJECT_VERSION_SAFE}")
+ set(CPACK_PACKAGE_FILE_NAME "${PROJECT_NAME}-${PROJECT_VERSION}")
set(CPACK_PACKAGE_ICON "${CMAKE_CURRENT_SOURCE_DIR}/cockatrice/resources/appicon.icns")
else()
# linux
set(CPACK_GENERATOR DEB ${CPACK_GENERATOR})
- set(CPACK_PACKAGE_INSTALL_DIRECTORY "${PROJECT_NAME}/${PROJECT_VERSION_SAFE}")
- set(CPACK_STRIP_FILES "bin/${PROJECT_NAME}")
- set(CPACK_SOURCE_STRIP_FILES "")
+ set(CPACK_PACKAGE_FILE_NAME "${PROJECT_NAME}-${PROJECT_VERSION}")
+ set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS ON)
+ set(CPACK_DEBIAN_PACKAGE_SECTION "games")
+ set(CPACK_DEBIAN_PACKAGE_HOMEPAGE "http://github.com/Cockatrice/Cockatrice")
+ IF(Qt5Widgets_FOUND)
+ set(CPACK_DEBIAN_PACKAGE_DEPENDS "libqt5multimedia5-plugins")
+ ENDIF()
endif()
elseif(WIN32)
set(CPACK_GENERATOR NSIS ${CPACK_GENERATOR})
- set(CPACK_PACKAGE_FILE_NAME "${PROJECT_NAME}_win32_git-${PROJECT_VERSION_SAFE}")
+ set(CPACK_PACKAGE_FILE_NAME "${PROJECT_NAME}-${PROJECT_VERSION}")
# Configure file with custom definitions for NSIS.
configure_file(
@@ -206,6 +209,12 @@ elseif(WIN32)
)
endif()
+# Configure file with build deployment data for travis
+configure_file(
+ ${CMAKE_MODULE_PATH}/bintray_deploy.json.in
+ ${PROJECT_BINARY_DIR}/bintray_deploy.json
+)
+
include(CPack)
# Compile servatrice (default off)
@@ -231,3 +240,10 @@ if(WITH_ORACLE)
add_subdirectory(oracle)
SET(CPACK_INSTALL_CMAKE_PROJECTS "release/oracle.app;oracle;ALL;/" ${CPACK_INSTALL_CMAKE_PROJECTS})
endif()
+
+# Compile tests (default off)
+option(TEST "build tests" OFF)
+if(TEST)
+ include(CTest)
+ add_subdirectory(tests)
+endif()
diff --git a/README.md b/README.md
index 551f80e0..f889fe4b 100644
--- a/README.md
+++ b/README.md
@@ -2,7 +2,7 @@
---
-**Table of Contents** [Cockatrice](#cockatrice) | [Get Involved] (#get-involved-) | [Community](#community-resources) | [Translation](#translation-status-) | [Building](#building-) | [Running](#running) | [License](#license-)
+**Table of Contents** [Cockatrice](#cockatrice) | [Get Involved] (#get-involved-) | [Community](#community-resources) | [Translation](#translation-status-) | [Building](#building--) | [Running](#running) | [License](#license-)
---
@@ -13,6 +13,12 @@ such as Magic: The Gathering, over a network. It is fully client-server based
to prevent any kind of cheating, though it supports single-player games without
a network interface as well. Both client and server are written in Qt, supporting both Qt4 and Qt5.
+# Downloads
+We offer a download for both the last stable version (recommended for users) and the last development version. The development version contains the last implemented features, but can be unstable and unsuitable for gaming.
+Downloads are hosted on [BinTray](https://bintray.com/).
+
+- Latest stable version download: [  ](https://bintray.com/cockatrice/Cockatrice/Cockatrice/_latestVersion)
+- Latest development (unstable) version download: [  ](https://bintray.com/cockatrice/Cockatrice/Cockatrice-git/_latestVersion)
# Get Involved [](https://gitter.im/Cockatrice/Cockatrice)
@@ -37,7 +43,7 @@ Language statistics for `Cockatrice` *(on the left)* and `Oracle` *(on the right
Check out our [Translator FAQ](https://github.com/Cockatrice/Cockatrice/wiki/Translation-FAQ) for more information!
-# Building [](https://travis-ci.org/Cockatrice/Cockatrice)
+# Building [](https://travis-ci.org/Cockatrice/Cockatrice) [](https://ci.appveyor.com/project/Daenyth/cockatrice/branch/master)
**Detailed compiling instructions are on the Cockatrice wiki under [Compiling Cockatrice](https://github.com/Cockatrice/Cockatrice/wiki/Compiling-Cockatrice)**
@@ -59,17 +65,28 @@ To compile:
cd build
cmake ..
make
+
+You can then run
+
make install
+to get a cockatrice installation inside the `release` folder, or:
+
+ make package
+
+to create a system-specific installation package.
+
The following flags can be passed to `cmake`:
-- `-DWITH_SERVER=1` Build the server.
-- `-DWITH_CLIENT=0` Do not build the client.
-- `-DWITH_ORACLE=0` Do not build oracle.
-- `-DPORTABLE=1` Build portable versions of client & oracle.
-- `-DWITH_QT4=1` Force compilation to use Qt4 instead of Qt5.
-- `-DCMAKE_BUILD_TYPE=Debug` Compile in debug mode. Enables extra logging output, debug symbols, and much more verbose compiler warnings.
-- `-DUPDATE_TRANSLATIONS=1` Configure `make` to update the translation .ts files for new strings in the source code. Note: Running `make clean` will remove the .ts files.
+- `-DWITH_SERVER=1` Whether to build the server (default 0 = no).
+- `-DWITH_CLIENT=0` Whether to build the client (default 1 = yes).
+- `-DWITH_ORACLE=0` Whether to build oracle (default 1 = yes).
+- `-DPORTABLE=1` Build portable versions of client & oracle (default 0 = no).
+- `-DWITH_QT4=1` Force compilation to use Qt4 instead of Qt5 (default 0 = no).
+- `-DCMAKE_BUILD_TYPE=Debug` Compile in debug mode. Enables extra logging output, debug symbols, and much more verbose compiler warnings (default `Release`).
+- `-DUPDATE_TRANSLATIONS=1` Configure `make` to update the translation .ts files for new strings in the source code. Note: Running `make clean` will remove the .ts files (default 0 = no).
+- `-DTEST=1` Enable regression tests (default 0 = no). Note: needs googletest, will be downloaded on the fly if unavailable. To run tests: ```make test```.
+
#### Building servatrice Docker container
`docker build -t servatrice .`
diff --git a/appveyor.yml b/appveyor.yml
new file mode 100644
index 00000000..c057f4c1
--- /dev/null
+++ b/appveyor.yml
@@ -0,0 +1,92 @@
+version: 0.0.1-branch-{branch}-build-{build}
+cache:
+ - c:\protobuf
+ - c:\protoc
+ - c:\zlib
+environment:
+ matrix:
+ - vc_arch: amd64
+ choco_arch:
+ nuget_arch: x64
+ target_arch: x86_64
+ qt_ver: 5.5\msvc2013_64
+ bintray_path: Win64
+ - vc_arch: amd64_x86 # cross-compile from amd64 to x86
+ choco_arch: --x86
+ nuget_arch: Win32
+ target_arch: x86
+ qt_ver: 5.5\msvc2013
+ bintray_path: Win32
+install:
+ - systeminfo
+ # upgrade cmake in order to have c++11 support
+ - choco install cmake -y
+ - choco install nsis -y
+ - ps: |
+ if (Test-Path c:\protoc) {
+ echo "using protoc from cache"
+ } else {
+ Invoke-WebRequest "https://github.com/google/protobuf/releases/download/v2.6.1/protoc-2.6.1-win32.zip" -OutFile c:\protoc-2.6.1-win32.zip
+ c:\cygwin\bin\bash -lc "cd /cygdrive/c; 7z x -y protoc-2.6.1-win32.zip -oc:\protoc"
+ }
+ - ps: |
+ if (Test-Path c:\protobuf) {
+ echo "using protobuf from cache"
+ } else {
+ nuget install protobuf-v120 -OutputDirectory c:\protobuf
+ }
+ - ps: |
+ if (Test-Path c:\zlib) {
+ echo "using zlib from cache"
+ } else {
+ nuget install zlib -OutputDirectory c:\zlib
+ }
+build_script:
+ - mkdir build
+ - cd build
+ - '"c:/Program Files (x86)/Microsoft Visual Studio 12.0/VC/vcvarsall" %vc_arch%'
+ - path
+ - ps: |
+ $zlibinc = c:\cygwin\bin\find /cygdrive/c/zlib/ -path '*v120*/zlib.h'
+ $zlibinc = c:\cygwin\bin\dirname $zlibinc
+ $zlibinc = c:\cygwin\bin\cygpath -m $zlibinc
+ $zliblib = c:\cygwin\bin\find /cygdrive/c/zlib/ -path "*v120*/$env:nuget_arch/Release/zlib.lib"
+ $zliblib = c:\cygwin\bin\cygpath -m $zliblib
+ $protoinc = c:\cygwin\bin\find /cygdrive/c/protobuf/ -name 'google'
+ $protoinc = c:\cygwin\bin\dirname $protoinc
+ $protoinc = c:\cygwin\bin\cygpath -m $protoinc
+ $protolib = c:\cygwin\bin\find /cygdrive/c/protobuf/ -path "*/lib/$env:nuget_arch/v120/Release/libprotobuf.lib"
+ $protolib = c:\cygwin\bin\cygpath -m $protolib
+ $protoc = c:\cygwin\bin\find /cygdrive/c/protoc/ -name "protoc.exe"
+ $protoc = c:\cygwin\bin\cygpath -m $protoc
+ Write-Output "ZLIBINC = $zlibinc"
+ Write-Output "ZLIBLIB = $zliblib"
+ Write-Output "PROTOINC = $protoinc"
+ Write-Output "PROTOLIB = $protolib"
+ Write-Output "PROTOC = $protoc"
+ cmake .. "-GNMake Makefiles" "-DCMAKE_BUILD_TYPE=Release" "-DCMAKE_PREFIX_PATH=c:/Qt/$env:qt_ver" "-DWITH_SERVER=1" "-DZLIB_INCLUDE_DIR=$zlibinc" "-DZLIB_LIBRARY=$zliblib" "-DPROTOBUF_INCLUDE_DIR=$protoinc" "-DPROTOBUF_LIBRARIES=$protolib" "-DPROTOBUF_LIBRARIES=$protolib" "-DPROTOBUF_LIBRARY=$protolib" "-DPROTOBUF_PROTOC_EXECUTABLE=$protoc"
+ - nmake package
+ - c:\cygwin\bin\ls -l
+ - ps: |
+ $exe = dir -name *.exe
+ $new_name = $exe.Replace(".exe", "-${env:target_arch}_qt5.exe")
+ Push-AppveyorArtifact $exe -FileName $new_name
+ $cmake_name = $exe.Replace(".exe", "-${env:target_arch}_qt5.cmake.txt")
+ Push-AppveyorArtifact CMakeCache.txt -FileName $cmake_name
+ $json = New-Object PSObject
+ (New-Object PSObject | Add-Member -PassThru NoteProperty bin $new_name | Add-Member -PassThru NoteProperty cmake $cmake_name | Add-Member -PassThru NoteProperty commit $env:APPVEYOR_REPO_COMMIT) | ConvertTo-JSON | Out-File -FilePath "latest-$env:target_arch" -Encoding ASCII
+ Push-AppveyorArtifact "latest-$env:target_arch"
+ $bt_password = ConvertTo-SecureString $Env:BINTRAY_APIKEY -AsPlainText -Force
+ $bt_credentials = New-Object System.Management.Automation.PSCredential ($Env:BINTRAY_USER, $bt_password)
+ $exe -match "Cockatrice-(?.*)\.exe"
+ $version = $matches['content']
+ $bt_headers = @{
+ "X-Bintray-Package" = "Cockatrice-git";
+ "X-Bintray-Version" = $version;
+ "X-Bintray-Publish" = 1;
+ "X-Bintray-Override" = 1;
+ }
+ $url = "https://api.bintray.com/content/cockatrice/Cockatrice/$env:bintray_path/$new_name"
+ $result = Invoke-WebRequest -Uri $url -Credential $bt_credentials -Method PUT -Headers $bt_headers -InFile "$exe"
+ Write-Host $result
+test: off
diff --git a/cmake/bintray_deploy.json.in b/cmake/bintray_deploy.json.in
new file mode 100644
index 00000000..37306db7
--- /dev/null
+++ b/cmake/bintray_deploy.json.in
@@ -0,0 +1,28 @@
+{
+ "package": {
+ "name": "Cockatrice-git",
+ "repo": "Cockatrice",
+ "subject": "cockatrice",
+ "desc": "Cockatrice master branch automated builds",
+ "website_url": "https://github.com/Cockatrice/Cockatrice",
+ "issue_tracker_url": "https://github.com/Cockatrice/Cockatrice/issues",
+ "vcs_url": "https://github.com/Cockatrice/Cockatrice.git",
+ "github_use_tag_release_notes": true,
+ "github_release_notes_file": "RELEASE.txt",
+ "licenses": ["GPL-2.0"],
+ "labels": ["card", "tabletop", "game"],
+ "public_download_numbers": false,
+ "public_stats": true
+ },
+ "version": {
+ "name": "@PROJECT_VERSION@",
+ "desc": "Unstable builds from master",
+ "vcs_tag": "@GIT_COMMIT_ID@",
+ "gpgSign": false
+ },
+ "files": [
+ { "includePattern": "build/(Cockatrice.*\\.deb)", "uploadPattern": "Ubuntu/$ENV{DIST}/$1" },
+ { "includePattern": "build/(Cockatrice.*\\.dmg)", "uploadPattern": "MacOSX/$1" }
+ ],
+ "publish": true
+}
\ No newline at end of file
diff --git a/cmake/createversionfile.cmake b/cmake/createversionfile.cmake
index b71aa8ad..ca5d0d76 100644
--- a/cmake/createversionfile.cmake
+++ b/cmake/createversionfile.cmake
@@ -3,7 +3,7 @@ set(VERSION_STRING_H "${PROJECT_BINARY_DIR}/version_string.h")
INCLUDE_DIRECTORIES(${PROJECT_BINARY_DIR})
set( hstring "extern const char *VERSION_STRING\;\n" )
-set( cppstring "const char * VERSION_STRING = \"${PROJECT_VERSION}\"\;\n")
+set( cppstring "const char * VERSION_STRING = \"${PROJECT_VERSION_FRIENDLY}\"\;\n")
file(WRITE ${PROJECT_BINARY_DIR}/version_string.cpp.txt ${cppstring} )
file(WRITE ${PROJECT_BINARY_DIR}/version_string.h.txt ${hstring} )
diff --git a/cmake/getversion.cmake b/cmake/getversion.cmake
index 7397a4fc..39859ee7 100644
--- a/cmake/getversion.cmake
+++ b/cmake/getversion.cmake
@@ -1,20 +1,40 @@
find_package(Git)
if(GIT_FOUND)
execute_process(
- COMMAND ${GIT_EXECUTABLE} log -1 --date=short "--pretty=%h (%cd)"
+ COMMAND ${GIT_EXECUTABLE} log -1 --date=short "--pretty=%h"
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
RESULT_VARIABLE res_var
OUTPUT_VARIABLE GIT_COM_ID
)
if( NOT ${res_var} EQUAL 0 )
- set( GIT_COMMIT_ID "git commit id unknown")
+ set( GIT_COMMIT_ID "unknown")
message( WARNING "Git failed (not a repo, or no tags). Build will not contain git revision info." )
endif()
string( REPLACE "\n" "" GIT_COMMIT_ID "${GIT_COM_ID}" )
+
+ execute_process(
+ COMMAND ${GIT_EXECUTABLE} log -1 --date=short "--pretty=%cd"
+ WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
+ RESULT_VARIABLE res_var
+ OUTPUT_VARIABLE GIT_COM_DATE
+ )
+ if( NOT ${res_var} EQUAL 0 )
+ set( GIT_COMMIT_DATE "unknown")
+ set( GIT_COMMIT_DATE_FRIENDLY "unknown")
+ message( WARNING "Git failed (not a repo, or no tags). Build will not contain git revision info." )
+ endif()
+ string( REPLACE "\n" "" GIT_COMMIT_DATE_FRIENDLY "${GIT_COM_DATE}" )
+ string( REPLACE "-" "" GIT_COMMIT_DATE "${GIT_COMMIT_DATE_FRIENDLY}" )
else()
- set( GIT_COMMIT_ID "unknown (git not found!)")
+ set( GIT_COMMIT_ID "unknown")
+ set( GIT_COMMIT_DATE "unknown")
+ set( GIT_COMMIT_DATE_FRIENDLY "unknown")
message( WARNING "Git not found. Build will not contain git revision info." )
endif()
-set(PROJECT_VERSION_MAJOR ${GIT_COMMIT_ID})
-set(PROJECT_VERSION ${GIT_COMMIT_ID} )
\ No newline at end of file
+set(PROJECT_VERSION_MAJOR "0")
+set(PROJECT_VERSION_MINOR "0")
+set(PROJECT_VERSION_PATCH "1~git${GIT_COMMIT_DATE}.${GIT_COMMIT_ID}")
+set(PROJECT_VERSION "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}")
+set(PROJECT_VERSION_FRIENDLY "${GIT_COMMIT_ID} (${GIT_COMMIT_DATE_FRIENDLY})")
+
diff --git a/cmake/gtest-CMakeLists.txt.in b/cmake/gtest-CMakeLists.txt.in
new file mode 100644
index 00000000..f5e07d30
--- /dev/null
+++ b/cmake/gtest-CMakeLists.txt.in
@@ -0,0 +1,15 @@
+cmake_minimum_required(VERSION 3.2)
+
+project(gtest-download LANGUAGES NONE)
+
+include(ExternalProject)
+ExternalProject_Add(googletest
+ URL https://googletest.googlecode.com/files/gtest-1.7.0.zip
+ URL_HASH SHA1=f85f6d2481e2c6c4a18539e391aa4ea8ab0394af
+ SOURCE_DIR "${CMAKE_BINARY_DIR}/gtest-src"
+ BINARY_DIR "${CMAKE_BINARY_DIR}/gtest-build"
+ CONFIGURE_COMMAND ""
+ BUILD_COMMAND ""
+ INSTALL_COMMAND ""
+ TEST_COMMAND ""
+)
\ No newline at end of file
diff --git a/cockatrice/CMakeLists.txt b/cockatrice/CMakeLists.txt
index f9657ced..9b951d62 100644
--- a/cockatrice/CMakeLists.txt
+++ b/cockatrice/CMakeLists.txt
@@ -96,6 +96,7 @@ SET(cockatrice_SOURCES
src/qt-json/json.cpp
src/soundengine.cpp
src/pending_command.cpp
+ src/pictureloader.cpp
src/shortcutssettings.cpp
src/sequenceEdit/sequenceedit.cpp
src/sequenceEdit/shortcutstab.cpp
@@ -109,10 +110,6 @@ SET(cockatrice_SOURCES
${VERSION_STRING_CPP}
)
-if (UNIX AND NOT APPLE)
- set_source_files_properties(src/main.cpp PROPERTIES COMPILE_FLAGS -DTRANSLATION_PATH=\\"${CMAKE_INSTALL_PREFIX}/share/cockatrice/translations\\")
-endif (UNIX AND NOT APPLE)
-
set(cockatrice_RESOURCES cockatrice.qrc)
IF(UPDATE_TRANSLATIONS)
@@ -281,6 +278,7 @@ if(APPLE)
# these needs to be relative to CMAKE_INSTALL_PREFIX
set(plugin_dest_dir cockatrice.app/Contents/Plugins)
set(qtconf_dest_dir cockatrice.app/Contents/Resources)
+ get_filename_component(QT_LIBRARY_DIR "${QT_LIBRARY_DIR}/.." ABSOLUTE)
# qt4: codecs, iconengines, imageformats
# qt5: audio, iconengines, imageformats, platforms, printsupport
@@ -339,4 +337,4 @@ endif()
option(PORTABLE "portable build" OFF)
IF(PORTABLE)
add_definitions(-DPORTABLE_BUILD)
-endif()
+endif()
\ No newline at end of file
diff --git a/cockatrice/src/abstractcarditem.cpp b/cockatrice/src/abstractcarditem.cpp
index dcd68fa1..7f9dfe23 100644
--- a/cockatrice/src/abstractcarditem.cpp
+++ b/cockatrice/src/abstractcarditem.cpp
@@ -9,6 +9,7 @@
#include "carddatabase.h"
#include "cardinfowidget.h"
#include "abstractcarditem.h"
+#include "pictureloader.h"
#include "settingscache.h"
#include "main.h"
#include "gamescene.h"
@@ -93,7 +94,7 @@ void AbstractCardItem::paintPicture(QPainter *painter, const QSizeF &translatedS
QPixmap translatedPixmap;
// don't even spend time trying to load the picture if our size is too small
if(translatedSize.width() > 10)
- imageSource->getPixmap(translatedSize.toSize(), translatedPixmap);
+ PictureLoader::getPixmap(translatedPixmap, imageSource, translatedSize.toSize());
painter->save();
QColor bgColor = Qt::transparent;
diff --git a/cockatrice/src/carddatabase.cpp b/cockatrice/src/carddatabase.cpp
index 3e5a1489..433f1d8b 100644
--- a/cockatrice/src/carddatabase.cpp
+++ b/cockatrice/src/carddatabase.cpp
@@ -1,20 +1,14 @@
#include "carddatabase.h"
+#include "pictureloader.h"
#include "settingscache.h"
#include "thememanager.h"
#include
+#include
#include
#include
#include
#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
#include
const int CardDatabase::versionNeeded = 3;
@@ -88,38 +82,6 @@ void SetList::sortByKey()
qSort(begin(), end(), KeyCompareFunctor());
}
-class SetList::EnabledAndKeyCompareFunctor {
-public:
- inline bool operator()(CardSet *a, CardSet *b) const
- {
- if(a->getEnabled())
- {
- if(b->getEnabled())
- {
- // both enabled: sort by key
- return a->getSortKey() < b->getSortKey();
- } else {
- // only a enabled
- return true;
- }
- } else {
- if(b->getEnabled())
- {
- // only b enabled
- return false;
- } else {
- // both disabled: sort by key
- return a->getSortKey() < b->getSortKey();
- }
- }
- }
-};
-
-void SetList::sortByEnabledAndKey()
-{
- qSort(begin(), end(), EnabledAndKeyCompareFunctor());
-}
-
int SetList::getEnabledSetsNum()
{
int num=0;
@@ -195,334 +157,6 @@ void SetList::guessSortKeys()
}
}
-PictureToLoad::PictureToLoad(CardInfo *_card, bool _hq)
- : card(_card), setIndex(0), hq(_hq)
-{
- if (card) {
- sortedSets = card->getSets();
- sortedSets.sortByEnabledAndKey();
- }
-}
-
-bool PictureToLoad::nextSet()
-{
- if (setIndex == sortedSets.size() - 1)
- return false;
- ++setIndex;
- return true;
-}
-
-QString PictureToLoad::getSetName() const
-{
- if (setIndex < sortedSets.size())
- return sortedSets[setIndex]->getCorrectedShortName();
- else
- return QString("");
-}
-
-CardSet *PictureToLoad::getCurrentSet() const
-{
- if (setIndex < sortedSets.size())
- return sortedSets[setIndex];
- else
- return 0;
-}
-
-QStringList PictureLoader::md5Blacklist = QStringList()
- << "db0c48db407a907c16ade38de048a441"; // card back returned by gatherer when card is not found
-
-PictureLoader::PictureLoader(const QString &__picsPath, bool _picDownload, bool _picDownloadHq, QObject *parent)
- : QObject(parent),
- _picsPath(__picsPath), picDownload(_picDownload), picDownloadHq(_picDownloadHq),
- downloadRunning(false), loadQueueRunning(false)
-{
- connect(this, SIGNAL(startLoadQueue()), this, SLOT(processLoadQueue()), Qt::QueuedConnection);
-
- networkManager = new QNetworkAccessManager(this);
- connect(networkManager, SIGNAL(finished(QNetworkReply *)), this, SLOT(picDownloadFinished(QNetworkReply *)));
-}
-
-PictureLoader::~PictureLoader()
-{
- // This does not work with the destroyed() signal as this destructor is called after the main event loop is done.
- thread()->quit();
-}
-
-void PictureLoader::processLoadQueue()
-{
- if (loadQueueRunning)
- return;
-
- loadQueueRunning = true;
- forever {
- mutex.lock();
- if (loadQueue.isEmpty()) {
- mutex.unlock();
- loadQueueRunning = false;
- return;
- }
- cardBeingLoaded = loadQueue.takeFirst();
- mutex.unlock();
-
- QString setName = cardBeingLoaded.getSetName();
- QString correctedCardname = cardBeingLoaded.getCard()->getCorrectedName();
- qDebug() << "Trying to load picture (set: " << setName << " card: " << correctedCardname << ")";
-
- //The list of paths to the folders in which to search for images
- QList picsPaths = QList() << _picsPath + "/CUSTOM/" + correctedCardname;
-
- if(!setName.isEmpty())
- {
- picsPaths << _picsPath + "/" + setName + "/" + correctedCardname
- << _picsPath + "/downloadedPics/" + setName + "/" + correctedCardname;
- }
-
- QImage image;
- QImageReader imgReader;
- imgReader.setDecideFormatFromContent(true);
- bool found = false;
-
- //Iterates through the list of paths, searching for images with the desired name with any QImageReader-supported extension
- for (int i = 0; i < picsPaths.length() && !found; i ++) {
- imgReader.setFileName(picsPaths.at(i));
- if (imgReader.read(&image)) {
- qDebug() << "Picture found on disk (set: " << setName << " card: " << correctedCardname << ")";
- emit imageLoaded(cardBeingLoaded.getCard(), image);
- found = true;
- break;
- }
- imgReader.setFileName(picsPaths.at(i) + ".full");
- if (imgReader.read(&image)) {
- qDebug() << "Picture.full found on disk (set: " << setName << " card: " << correctedCardname << ")";
- emit imageLoaded(cardBeingLoaded.getCard(), image);
- found = true;
- }
- }
-
- if (!found) {
- if (picDownload) {
- qDebug() << "Picture NOT found, trying to download (set: " << setName << " card: " << correctedCardname << ")";
- cardsToDownload.append(cardBeingLoaded);
- cardBeingLoaded=0;
- if (!downloadRunning)
- startNextPicDownload();
- } else {
- if (cardBeingLoaded.nextSet())
- {
- qDebug() << "Picture NOT found and download disabled, moving to next set (newset: " << setName << " card: " << correctedCardname << ")";
- mutex.lock();
- loadQueue.prepend(cardBeingLoaded);
- cardBeingLoaded=0;
- mutex.unlock();
- } else {
- qDebug() << "Picture NOT found, download disabled, no more sets to try: BAILING OUT (oldset: " << setName << " card: " << correctedCardname << ")";
- emit imageLoaded(cardBeingLoaded.getCard(), QImage());
- }
- }
- }
- }
-}
-
-QString PictureLoader::getPicUrl()
-{
- if (!picDownload) return QString("");
-
- CardInfo *card = cardBeingDownloaded.getCard();
- CardSet *set=cardBeingDownloaded.getCurrentSet();
- QString picUrl = QString("");
-
- // if sets have been defined for the card, they can contain custom picUrls
- if(set)
- {
- // first check if Hq is enabled and a custom Hq card url exists in cards.xml
- if(picDownloadHq)
- {
- picUrl = card->getCustomPicURLHq(set->getShortName());
- if (!picUrl.isEmpty())
- return picUrl;
- }
-
- // then, test for a custom, non-Hq card url in cards.xml
- picUrl = card->getCustomPicURL(set->getShortName());
- if (!picUrl.isEmpty())
- return picUrl;
- }
-
- // if a card has a muid, use the default url; if not, use the fallback
- int muid = set ? card->getMuId(set->getShortName()) : 0;
- if(muid)
- picUrl = picDownloadHq ? settingsCache->getPicUrlHq() : settingsCache->getPicUrl();
- else
- picUrl = picDownloadHq ? settingsCache->getPicUrlHqFallback() : settingsCache->getPicUrlFallback();
-
- picUrl.replace("!name!", QUrl::toPercentEncoding(card->getCorrectedName()));
- picUrl.replace("!name_lower!", QUrl::toPercentEncoding(card->getCorrectedName().toLower()));
- picUrl.replace("!cardid!", QUrl::toPercentEncoding(QString::number(muid)));
- if (set)
- {
- picUrl.replace("!setcode!", QUrl::toPercentEncoding(set->getShortName()));
- picUrl.replace("!setcode_lower!", QUrl::toPercentEncoding(set->getShortName().toLower()));
- picUrl.replace("!setname!", QUrl::toPercentEncoding(set->getLongName()));
- picUrl.replace("!setname_lower!", QUrl::toPercentEncoding(set->getLongName().toLower()));
- }
-
- if (
- picUrl.contains("!name!") ||
- picUrl.contains("!name_lower!") ||
- picUrl.contains("!setcode!") ||
- picUrl.contains("!setcode_lower!") ||
- picUrl.contains("!setname!") ||
- picUrl.contains("!setname_lower!") ||
- picUrl.contains("!cardid!")
- )
- {
- qDebug() << "Insufficient card data to download" << card->getName() << "Url:" << picUrl;
- return QString("");
- }
-
- return picUrl;
-}
-
-void PictureLoader::startNextPicDownload()
-{
- if (cardsToDownload.isEmpty()) {
- cardBeingDownloaded = 0;
- downloadRunning = false;
- return;
- }
-
- downloadRunning = true;
-
- cardBeingDownloaded = cardsToDownload.takeFirst();
-
- QString picUrl = getPicUrl();
- if (picUrl.isEmpty()) {
- downloadRunning = false;
- picDownloadFailed();
- } else {
- QUrl url(picUrl);
-
- QNetworkRequest req(url);
- qDebug() << "starting picture download:" << cardBeingDownloaded.getCard()->getName() << "Url:" << req.url();
- networkManager->get(req);
- }
-}
-
-void PictureLoader::picDownloadFailed()
-{
- if (cardBeingDownloaded.nextSet())
- {
- qDebug() << "Picture NOT found, download failed, moving to next set (newset: " << cardBeingDownloaded.getSetName() << " card: " << cardBeingDownloaded.getCard()->getCorrectedName() << ")";
- mutex.lock();
- loadQueue.prepend(cardBeingDownloaded);
- mutex.unlock();
- emit startLoadQueue();
- } else {
- qDebug() << "Picture NOT found, download failed, no more sets to try: BAILING OUT (oldset: " << cardBeingDownloaded.getSetName() << " card: " << cardBeingDownloaded.getCard()->getCorrectedName() << ")";
- cardBeingDownloaded = 0;
- emit imageLoaded(cardBeingDownloaded.getCard(), QImage());
- }
-}
-
-void PictureLoader::picDownloadFinished(QNetworkReply *reply)
-{
- QString picsPath = _picsPath;
- if (reply->error()) {
- qDebug() << "Download failed:" << reply->errorString();
- }
-
- int statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
- if (statusCode == 301 || statusCode == 302) {
- QUrl redirectUrl = reply->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl();
- QNetworkRequest req(redirectUrl);
- qDebug() << "following redirect:" << cardBeingDownloaded.getCard()->getName() << "Url:" << req.url();
- networkManager->get(req);
- return;
- }
-
- const QByteArray &picData = reply->peek(reply->size()); //peek is used to keep the data in the buffer for use by QImageReader
-
- // check if the image is blacklisted
- QString md5sum = QCryptographicHash::hash(picData, QCryptographicHash::Md5).toHex();
- if(md5Blacklist.contains(md5sum))
- {
- qDebug() << "Picture downloaded, but blacklisted (" << md5sum << "), will consider it as not found";
- picDownloadFailed();
- reply->deleteLater();
- startNextPicDownload();
- return;
- }
-
- QImage testImage;
-
- QImageReader imgReader;
- imgReader.setDecideFormatFromContent(true);
- imgReader.setDevice(reply);
- QString extension = "." + imgReader.format(); //the format is determined prior to reading the QImageReader data into a QImage object, as that wipes the QImageReader buffer
- if (extension == ".jpeg")
- extension = ".jpg";
-
- if (imgReader.read(&testImage)) {
- QString setName = cardBeingDownloaded.getSetName();
- if(!setName.isEmpty())
- {
- if (!QDir().mkpath(picsPath + "/downloadedPics/" + setName)) {
- qDebug() << picsPath + "/downloadedPics/" + setName + " could not be created.";
- return;
- }
-
- QFile newPic(picsPath + "/downloadedPics/" + setName + "/" + cardBeingDownloaded.getCard()->getCorrectedName() + extension);
- if (!newPic.open(QIODevice::WriteOnly))
- return;
- newPic.write(picData);
- newPic.close();
- }
-
- emit imageLoaded(cardBeingDownloaded.getCard(), testImage);
- } else {
- picDownloadFailed();
- }
-
- reply->deleteLater();
- startNextPicDownload();
-}
-
-void PictureLoader::loadImage(CardInfo *card)
-{
- QMutexLocker locker(&mutex);
-
- // avoid queueing the same card more than once
- if(card == cardBeingLoaded.getCard() || card == cardBeingDownloaded.getCard())
- return;
-
- foreach(PictureToLoad pic, loadQueue)
- {
- if(pic.getCard() == card)
- return;
- }
-
- loadQueue.append(PictureToLoad(card));
- emit startLoadQueue();
-}
-
-void PictureLoader::setPicsPath(const QString &path)
-{
- QMutexLocker locker(&mutex);
- _picsPath = path;
-}
-
-void PictureLoader::setPicDownload(bool _picDownload)
-{
- QMutexLocker locker(&mutex);
- picDownload = _picDownload;
-}
-
-void PictureLoader::setPicDownloadHq(bool _picDownloadHq)
-{
- QMutexLocker locker(&mutex);
- picDownloadHq = _picDownloadHq;
-}
-
CardInfo::CardInfo(CardDatabase *_db,
const QString &_name,
bool _isToken,
@@ -533,13 +167,13 @@ CardInfo::CardInfo(CardDatabase *_db,
const QString &_text,
const QStringList &_colors,
const QStringList &_relatedCards,
+ const QStringList &_reverseRelatedCards,
bool _upsideDownArt,
int _loyalty,
bool _cipt,
int _tableRow,
const SetList &_sets,
const QStringMap &_customPicURLs,
- const QStringMap &_customPicURLsHq,
MuidMap _muIds
)
: db(_db),
@@ -553,10 +187,10 @@ CardInfo::CardInfo(CardDatabase *_db,
text(_text),
colors(_colors),
relatedCards(_relatedCards),
+ reverseRelatedCards(_reverseRelatedCards),
upsideDownArt(_upsideDownArt),
loyalty(_loyalty),
customPicURLs(_customPicURLs),
- customPicURLsHq(_customPicURLsHq),
muIds(_muIds),
cipt(_cipt),
tableRow(_tableRow)
@@ -570,7 +204,7 @@ CardInfo::CardInfo(CardDatabase *_db,
CardInfo::~CardInfo()
{
- clearPixmapCache();
+ PictureLoader::clearPixmapCache(this);
}
QString CardInfo::getMainCardType() const
@@ -617,82 +251,6 @@ void CardInfo::addToSet(CardSet *set)
sets << set;
}
-void CardInfo::loadPixmap(QPixmap &pixmap)
-{
- if(QPixmapCache::find(pixmapCacheKey, &pixmap))
- return;
-
- pixmap = QPixmap();
-
- if (getName().isEmpty()) {
- pixmap = QPixmap("theme:cardback");
- return;
- }
-
- db->loadImage(this);
-}
-
-void CardInfo::imageLoaded(const QImage &image)
-{
- if (!image.isNull()) {
- if(upsideDownArt)
- {
- QImage mirrorImage = image.mirrored(true, true);
- QPixmapCache::insert(pixmapCacheKey, QPixmap::fromImage(mirrorImage));
- } else {
- QPixmapCache::insert(pixmapCacheKey, QPixmap::fromImage(image));
- }
- emit pixmapUpdated();
- }
-}
-
-void CardInfo::getPixmap(QSize size, QPixmap &pixmap)
-{
- QString key = QLatin1String("card_") + name + QLatin1Char('_') + QString::number(size.width()) + QString::number(size.height());
- if(QPixmapCache::find(key, &pixmap))
- return;
-
- QPixmap bigPixmap;
- loadPixmap(bigPixmap);
- if (bigPixmap.isNull()) {
- if (getName().isEmpty()) {
- pixmap = pixmap = QPixmap("theme:cardback");
- } else {
- pixmap = QPixmap(); // null
- return;
- }
- }
-
- pixmap = bigPixmap.scaled(size, Qt::KeepAspectRatio, Qt::SmoothTransformation);
- QPixmapCache::insert(key, pixmap);
-}
-
-void CardInfo::clearPixmapCache()
-{
- //qDebug() << "Deleting pixmap for" << name;
- QPixmapCache::remove(pixmapCacheKey);
-}
-
-void CardInfo::clearPixmapCacheMiss()
-{
- QPixmap pixmap;
- if(!QPixmapCache::find(pixmapCacheKey, &pixmap))
- return;
-
- if (pixmap.isNull())
- clearPixmapCache();
-}
-
-void CardInfo::updatePixmapCache()
-{
- qDebug() << "Updating pixmap cache for" << name;
- clearPixmapCache();
- QPixmap tmp;
- loadPixmap(tmp);
-
- emit pixmapUpdated();
-}
-
QString CardInfo::simplifyName(const QString &name) {
QString simpleName(name);
@@ -731,10 +289,6 @@ static QXmlStreamWriter &operator<<(QXmlStreamWriter &xml, const CardInfo *info)
if(!tmpString.isEmpty())
xml.writeAttribute("picURL", tmpString);
- tmpString = info->getCustomPicURLHq(tmpSet);
- if(!tmpString.isEmpty())
- xml.writeAttribute("picURLHq", tmpString);
-
xml.writeCharacters(tmpSet);
xml.writeEndElement();
}
@@ -746,6 +300,10 @@ static QXmlStreamWriter &operator<<(QXmlStreamWriter &xml, const CardInfo *info)
for (int i = 0; i < related.size(); i++)
xml.writeTextElement("related", related[i]);
+ const QStringList &reverseRelated = info->getReverseRelatedCards();
+ for (int i = 0; i < reverseRelated.size(); i++)
+ xml.writeTextElement("reverse-related", reverseRelated[i]);
+
xml.writeTextElement("manacost", info->getManaCost());
xml.writeTextElement("cmc", info->getCmc());
xml.writeTextElement("type", info->getCardType());
@@ -769,35 +327,19 @@ static QXmlStreamWriter &operator<<(QXmlStreamWriter &xml, const CardInfo *info)
CardDatabase::CardDatabase(QObject *parent)
: QObject(parent), noCard(0), loadStatus(NotLoaded)
{
- connect(settingsCache, SIGNAL(picsPathChanged()), this, SLOT(picsPathChanged()));
connect(settingsCache, SIGNAL(cardDatabasePathChanged()), this, SLOT(loadCardDatabase()));
connect(settingsCache, SIGNAL(tokenDatabasePathChanged()), this, SLOT(loadTokenDatabase()));
- connect(settingsCache, SIGNAL(picDownloadChanged()), this, SLOT(picDownloadChanged()));
- connect(settingsCache, SIGNAL(picDownloadHqChanged()), this, SLOT(picDownloadHqChanged()));
loadCardDatabase();
loadTokenDatabase();
- pictureLoaderThread = new QThread;
- pictureLoader = new PictureLoader(settingsCache->getPicsPath(), settingsCache->getPicDownload(), settingsCache->getPicDownloadHq());
- pictureLoader->moveToThread(pictureLoaderThread);
- connect(pictureLoader, SIGNAL(imageLoaded(CardInfo *, const QImage &)), this, SLOT(imageLoaded(CardInfo *, const QImage &)));
- pictureLoaderThread->start(QThread::LowPriority);
-
noCard = new CardInfo(this);
- QPixmap tmp;
- noCard->loadPixmap(tmp); // cache pixmap for card back
- connect(themeManager, SIGNAL(themeChanged()), noCard, SLOT(updatePixmapCache()));
}
CardDatabase::~CardDatabase()
{
clear();
delete noCard;
-
- pictureLoader->deleteLater();
- pictureLoaderThread->wait();
- delete pictureLoaderThread;
}
void CardDatabase::clear()
@@ -839,6 +381,15 @@ CardInfo *CardDatabase::getCard(const QString &cardName, bool createIfNotFound)
return getCardFromMap(cards, cardName, createIfNotFound);
}
+QList CardDatabase::getCards(const QStringList &cardNames)
+{
+ QList cardInfos;
+ foreach(QString cardName, cardNames)
+ cardInfos.append(getCardFromMap(cards, cardName, false));
+
+ return cardInfos;
+}
+
CardInfo *CardDatabase::getCardBySimpleName(const QString &cardName, bool createIfNotFound) {
QString simpleName = CardInfo::simplifyName(cardName);
return getCardFromMap(simpleNameCards, simpleName, createIfNotFound);
@@ -866,21 +417,6 @@ SetList CardDatabase::getSetList() const
return result;
}
-void CardDatabase::clearPixmapCache()
-{
- // This also clears the cards in simpleNameCards since they point to the
- // same object.
- QHashIterator i(cards);
- while (i.hasNext()) {
- i.next();
- i.value()->clearPixmapCache();
- }
- if (noCard)
- noCard->clearPixmapCache();
-
- QPixmapCache::clear();
-}
-
void CardDatabase::loadSetsFromXml(QXmlStreamReader &xml)
{
while (!xml.atEnd()) {
@@ -917,8 +453,8 @@ void CardDatabase::loadCardsFromXml(QXmlStreamReader &xml, bool tokens)
break;
if (xml.name() == "card") {
QString name, manacost, cmc, type, pt, text;
- QStringList colors, relatedCards;
- QStringMap customPicURLs, customPicURLsHq;
+ QStringList colors, relatedCards, reverseRelatedCards;
+ QStringMap customPicURLs;
MuidMap muids;
SetList sets;
int tableRow = 0;
@@ -951,13 +487,12 @@ void CardDatabase::loadCardsFromXml(QXmlStreamReader &xml, bool tokens)
if (attrs.hasAttribute("picURL")) {
customPicURLs[setName] = attrs.value("picURL").toString();
}
- if (attrs.hasAttribute("picURLHq")) {
- customPicURLsHq[setName] = attrs.value("picURLHq").toString();
- }
} else if (xml.name() == "color")
colors << xml.readElementText();
else if (xml.name() == "related")
relatedCards << xml.readElementText();
+ else if (xml.name() == "reverse-related")
+ reverseRelatedCards << xml.readElementText();
else if (xml.name() == "tablerow")
tableRow = xml.readElementText().toInt();
else if (xml.name() == "cipt")
@@ -971,24 +506,24 @@ void CardDatabase::loadCardsFromXml(QXmlStreamReader &xml, bool tokens)
}
if (isToken == tokens) {
- addCard(new CardInfo(this, name, isToken, manacost, cmc, type, pt, text, colors, relatedCards, upsideDown, loyalty, cipt, tableRow, sets, customPicURLs, customPicURLsHq, muids));
+ addCard(new CardInfo(this, name, isToken, manacost, cmc, type, pt, text, colors, relatedCards, reverseRelatedCards, upsideDown, loyalty, cipt, tableRow, sets, customPicURLs, muids));
}
}
}
}
CardInfo *CardDatabase::getCardFromMap(CardNameMap &cardMap, const QString &cardName, bool createIfNotFound) {
- if (cardName.isEmpty())
- return noCard;
- else if (cardMap.contains(cardName))
+ if (cardMap.contains(cardName))
return cardMap.value(cardName);
- else if (createIfNotFound) {
+
+ if (createIfNotFound) {
CardInfo *newCard = new CardInfo(this, cardName, true);
newCard->addToSet(getSet(CardDatabase::TOKENS_SETNAME));
cardMap.insert(cardName, newCard);
return newCard;
- } else
- return 0;
+ }
+
+ return noCard;
}
LoadStatus CardDatabase::loadFromFile(const QString &fileName, bool tokens)
@@ -1061,26 +596,6 @@ bool CardDatabase::saveToFile(const QString &fileName, bool tokens)
return true;
}
-void CardDatabase::picDownloadChanged()
-{
- pictureLoader->setPicDownload(settingsCache->getPicDownload());
- if (settingsCache->getPicDownload()) {
- QHashIterator cardIterator(cards);
- while (cardIterator.hasNext())
- cardIterator.next().value()->clearPixmapCacheMiss();
- }
-}
-
-void CardDatabase::picDownloadHqChanged()
-{
- pictureLoader->setPicDownloadHq(settingsCache->getPicDownloadHq());
- if (settingsCache->getPicDownloadHq()) {
- QHashIterator cardIterator(cards);
- while (cardIterator.hasNext())
- cardIterator.next().value()->clearPixmapCacheMiss();
- }
-}
-
void CardDatabase::emitCardListChanged()
{
emit cardListChanged();
@@ -1135,6 +650,32 @@ void CardDatabase::loadCustomCardDatabases(const QString &path)
}
}
+void CardDatabase::refreshCachedReverseRelatedCards()
+{
+ foreach(CardInfo * card, cards)
+ card->resetReverseRelatedCards2Me();
+
+ foreach(CardInfo * card, cards)
+ {
+ if(card->getReverseRelatedCards().isEmpty())
+ continue;
+
+ QString relatedCardName;
+ if (card->getPowTough().size() > 0)
+ relatedCardName = card->getPowTough() + " " + card->getName(); // "n/n name"
+ else
+ relatedCardName = card->getName(); // "name"
+
+ foreach(QString targetCard, card->getReverseRelatedCards())
+ {
+ if (!cards.contains(targetCard))
+ continue;
+
+ cards.value(targetCard)->addReverseRelatedCards2Me(relatedCardName);
+ }
+ }
+}
+
QStringList CardDatabase::getAllColors() const
{
QSet colors;
@@ -1159,31 +700,6 @@ QStringList CardDatabase::getAllMainCardTypes() const
return types.toList();
}
-void CardDatabase::cacheCardPixmaps(const QStringList &cardNames)
-{
- QPixmap tmp;
- // never cache more than 300 cards at once for a single deck
- int max = qMin(cardNames.size(), 300);
- for (int i = 0; i < max; ++i)
- getCard(cardNames[i])->loadPixmap(tmp);
-}
-
-void CardDatabase::loadImage(CardInfo *card)
-{
- pictureLoader->loadImage(card);
-}
-
-void CardDatabase::imageLoaded(CardInfo *card, QImage image)
-{
- card->imageLoaded(image);
-}
-
-void CardDatabase::picsPathChanged()
-{
- pictureLoader->setPicsPath(settingsCache->getPicsPath());
- clearPixmapCache();
-}
-
void CardDatabase::checkUnknownSets()
{
SetList sets = getSetList();
diff --git a/cockatrice/src/carddatabase.h b/cockatrice/src/carddatabase.h
index df3995da..1cb8544a 100644
--- a/cockatrice/src/carddatabase.h
+++ b/cockatrice/src/carddatabase.h
@@ -8,17 +8,9 @@
#include
#include
#include
-#include
-#include
-#include
-#include
-#include
class CardDatabase;
class CardInfo;
-class QNetworkAccessManager;
-class QNetworkReply;
-class QNetworkRequest;
typedef QMap QStringMap;
@@ -55,9 +47,7 @@ public:
class SetList : public QList {
private:
class KeyCompareFunctor;
- class EnabledAndKeyCompareFunctor;
public:
- void sortByEnabledAndKey();
void sortByKey();
void guessSortKeys();
void enableAllUnknown();
@@ -67,53 +57,6 @@ public:
int getUnknownSetsNum();
};
-class PictureToLoad {
-private:
- CardInfo *card;
- SetList sortedSets;
- int setIndex;
- bool hq;
-public:
- PictureToLoad(CardInfo *_card = 0, bool _hq = true);
- CardInfo *getCard() const { return card; }
- CardSet *getCurrentSet() const;
- QString getSetName() const;
- bool nextSet();
- bool getHq() const { return hq; }
- void setHq(bool _hq) { hq = _hq; }
-};
-
-class PictureLoader : public QObject {
- Q_OBJECT
-private:
- QString _picsPath;
- QList loadQueue;
- QMutex mutex;
- QNetworkAccessManager *networkManager;
- QList cardsToDownload;
- PictureToLoad cardBeingLoaded;
- PictureToLoad cardBeingDownloaded;
- bool picDownload, picDownloadHq, downloadRunning, loadQueueRunning;
- void startNextPicDownload();
- QString getPicUrl();
- static QStringList md5Blacklist;
-public:
- PictureLoader(const QString &__picsPath, bool _picDownload, bool _picDownloadHq, QObject *parent = 0);
- ~PictureLoader();
- void setPicsPath(const QString &path);
- void setPicDownload(bool _picDownload);
- void setPicDownloadHq(bool _picDownloadHq);
- void loadImage(CardInfo *card);
-private slots:
- void picDownloadFinished(QNetworkReply *reply);
- void picDownloadFailed();
-public slots:
- void processLoadQueue();
-signals:
- void startLoadQueue();
- void imageLoaded(CardInfo *card, const QImage &image);
-};
-
class CardInfo : public QObject {
Q_OBJECT
private:
@@ -135,14 +78,20 @@ private:
QString powtough;
QString text;
QStringList colors;
+ // the cards i'm related to
QStringList relatedCards;
+ // the card i'm reverse-related to
+ QStringList reverseRelatedCards;
+ // the cards thare are reverse-related to me
+ QStringList reverseRelatedCardsToMe;
bool upsideDownArt;
int loyalty;
- QStringMap customPicURLs, customPicURLsHq;
+ QStringMap customPicURLs;
MuidMap muIds;
bool cipt;
int tableRow;
QString pixmapCacheKey;
+
public:
CardInfo(CardDatabase *_db,
const QString &_name = QString(),
@@ -154,13 +103,13 @@ public:
const QString &_text = QString(),
const QStringList &_colors = QStringList(),
const QStringList &_relatedCards = QStringList(),
+ const QStringList &_reverseRelatedCards = QStringList(),
bool _upsideDownArt = false,
int _loyalty = 0,
bool _cipt = false,
int _tableRow = 0,
const SetList &_sets = SetList(),
const QStringMap &_customPicURLs = QStringMap(),
- const QStringMap &_customPicURLsHq = QStringMap(),
MuidMap muids = MuidMap()
);
~CardInfo();
@@ -173,6 +122,7 @@ public:
const QString &getCardType() const { return cardtype; }
const QString &getPowTough() const { return powtough; }
const QString &getText() const { return text; }
+ const QString &getPixmapCacheKey() const { return pixmapCacheKey; }
const int &getLoyalty() const { return loyalty; }
bool getCipt() const { return cipt; }
void setManaCost(const QString &_manaCost) { manacost = _manaCost; emit cardInfoChanged(this); }
@@ -183,9 +133,12 @@ public:
void setColors(const QStringList &_colors) { colors = _colors; emit cardInfoChanged(this); }
const QStringList &getColors() const { return colors; }
const QStringList &getRelatedCards() const { return relatedCards; }
+ const QStringList &getReverseRelatedCards() const { return reverseRelatedCards; }
+ const QStringList &getReverseRelatedCards2Me() const { return reverseRelatedCardsToMe; }
+ void resetReverseRelatedCards2Me() { reverseRelatedCardsToMe = QStringList(); }
+ void addReverseRelatedCards2Me(QString & cardName) { reverseRelatedCardsToMe.append(cardName); }
bool getUpsideDownArt() const { return upsideDownArt; }
QString getCustomPicURL(const QString &set) const { return customPicURLs.value(set); }
- QString getCustomPicURLHq(const QString &set) const { return customPicURLsHq.value(set); }
int getMuId(const QString &set) const { return muIds.value(set); }
QString getMainCardType() const;
QString getCorrectedName() const;
@@ -193,22 +146,15 @@ public:
void setTableRow(int _tableRow) { tableRow = _tableRow; }
void setLoyalty(int _loyalty) { loyalty = _loyalty; emit cardInfoChanged(this); }
void setCustomPicURL(const QString &_set, const QString &_customPicURL) { customPicURLs.insert(_set, _customPicURL); }
- void setCustomPicURLHq(const QString &_set, const QString &_customPicURL) { customPicURLsHq.insert(_set, _customPicURL); }
void setMuId(const QString &_set, const int &_muId) { muIds.insert(_set, _muId); }
void addToSet(CardSet *set);
- void loadPixmap(QPixmap &pixmap);
- void getPixmap(QSize size, QPixmap &pixmap);
- void clearPixmapCache();
- void clearPixmapCacheMiss();
- void imageLoaded(const QImage &image);
+ void emitPixmapUpdated() { emit pixmapUpdated(); }
/**
* Simplify a name to have no punctuation and lowercase all letters, for
* less strict name-matching.
*/
static QString simplifyName(const QString &name);
-public slots:
- void updatePixmapCache();
signals:
void pixmapUpdated();
void cardInfoChanged(CardInfo *card);
@@ -237,10 +183,11 @@ protected:
*/
SetNameMap sets;
+ /*
+ * A dummy card returned by getCard() ad a fallback
+ */
CardInfo *noCard;
- QThread *pictureLoaderThread;
- PictureLoader *pictureLoader;
LoadStatus loadStatus;
bool detectedFirstRun;
private:
@@ -258,13 +205,17 @@ public:
void clear();
void addCard(CardInfo *card);
void removeCard(CardInfo *card);
- CardInfo *getCard(const QString &cardName = QString(), bool createIfNotFound = true);
+ /*
+ * Get card object by name. Ensured to return a valid CardInfo * object; check noCard
+ */
+ CardInfo *getCard(const QString &cardName = QString(), bool createIfNotFound = false);
+ QList getCards(const QStringList &cardNames);
/*
* Get a card by its simple name. The name will be simplified in this
* function, so you don't need to simplify it beforehand.
*/
- CardInfo *getCardBySimpleName(const QString &cardName = QString(), bool createIfNotFound = true);
+ CardInfo *getCardBySimpleName(const QString &cardName = QString(), bool createIfNotFound = false);
CardSet *getSet(const QString &setName);
QList getCardList() const { return cards.values(); }
@@ -275,20 +226,13 @@ public:
QStringList getAllMainCardTypes() const;
LoadStatus getLoadStatus() const { return loadStatus; }
bool getLoadSuccess() const { return loadStatus == Ok; }
- void cacheCardPixmaps(const QStringList &cardNames);
- void loadImage(CardInfo *card);
bool hasDetectedFirstRun();
+ void refreshCachedReverseRelatedCards();
public slots:
- void clearPixmapCache();
LoadStatus loadCardDatabase(const QString &path, bool tokens = false);
void loadCustomCardDatabases(const QString &path);
void emitCardListChanged();
private slots:
- void imageLoaded(CardInfo *card, QImage image);
- void picDownloadChanged();
- void picDownloadHqChanged();
- void picsPathChanged();
-
void loadCardDatabase();
void loadTokenDatabase();
signals:
diff --git a/cockatrice/src/cardinfopicture.cpp b/cockatrice/src/cardinfopicture.cpp
index 9bce273e..d4f7e179 100644
--- a/cockatrice/src/cardinfopicture.cpp
+++ b/cockatrice/src/cardinfopicture.cpp
@@ -6,6 +6,7 @@
#include "carditem.h"
#include "carddatabase.h"
+#include "pictureloader.h"
#include "main.h"
CardInfoPicture::CardInfoPicture(QWidget *parent)
@@ -40,13 +41,13 @@ void CardInfoPicture::updatePixmap()
void CardInfoPicture::loadPixmap()
{
if(info)
- info->getPixmap(size(), resizedPixmap);
+ PictureLoader::getPixmap(resizedPixmap, info, size());
else
resizedPixmap = QPixmap();
if (resizedPixmap.isNull())
- db->getCard()->getPixmap(size(), resizedPixmap);
+ PictureLoader::getPixmap(resizedPixmap, db->getCard(), size());
}
void CardInfoPicture::paintEvent(QPaintEvent *)
diff --git a/cockatrice/src/cardinfowidget.cpp b/cockatrice/src/cardinfowidget.cpp
index 8ee5663c..807221c6 100644
--- a/cockatrice/src/cardinfowidget.cpp
+++ b/cockatrice/src/cardinfowidget.cpp
@@ -8,6 +8,7 @@
#include "cardinfowidget.h"
#include "carditem.h"
#include "carddatabase.h"
+#include "pictureloader.h"
#include "main.h"
#include "settingscache.h"
@@ -195,10 +196,10 @@ void CardInfoWidget::updatePixmap()
return;
QPixmap resizedPixmap;
- info->getPixmap(QSize(pixmapWidth, pixmapWidth * aspectRatio), resizedPixmap);
+ PictureLoader::getPixmap(resizedPixmap, info, QSize(pixmapWidth, pixmapWidth * aspectRatio));
if (resizedPixmap.isNull())
- getCard()->getPixmap(QSize(pixmapWidth, pixmapWidth * aspectRatio), resizedPixmap);
+ PictureLoader::getPixmap(resizedPixmap, getCard(), QSize(pixmapWidth, pixmapWidth * aspectRatio));
cardPicture->setPixmap(resizedPixmap);
}
diff --git a/cockatrice/src/deck_loader.cpp b/cockatrice/src/deck_loader.cpp
index 4443b43b..02866c95 100644
--- a/cockatrice/src/deck_loader.cpp
+++ b/cockatrice/src/deck_loader.cpp
@@ -5,7 +5,7 @@
#include "decklist.h"
const QStringList DeckLoader::fileNameFilters = QStringList()
- << QObject::tr("Common deck formats (*.cod *.dec *.mwDeck)")
+ << QObject::tr("Common deck formats (*.cod *.dec *.txt *.mwDeck)")
<< QObject::tr("All files (*.*)");
DeckLoader::DeckLoader()
diff --git a/cockatrice/src/dlg_connect.cpp b/cockatrice/src/dlg_connect.cpp
index 13cf5480..ccdc02f8 100644
--- a/cockatrice/src/dlg_connect.cpp
+++ b/cockatrice/src/dlg_connect.cpp
@@ -8,6 +8,7 @@
#include
#include
#include
+#include
#include
#include "dlg_connect.h"
#include "settingscache.h"
@@ -149,6 +150,12 @@ void DlgConnect::actOk()
settingsCache->servers().setPreviousHostList(hostList);
settingsCache->servers().setPrevioushostindex(previousHosts->currentIndex());
+ if(playernameEdit->text().isEmpty())
+ {
+ QMessageBox::critical(this, tr("Connect Warning"), tr("The player name can't be empty."));
+ return;
+ }
+
accept();
}
diff --git a/cockatrice/src/dlg_creategame.cpp b/cockatrice/src/dlg_creategame.cpp
index 0f8cea4f..611a9fef 100644
--- a/cockatrice/src/dlg_creategame.cpp
+++ b/cockatrice/src/dlg_creategame.cpp
@@ -133,6 +133,7 @@ DlgCreateGame::DlgCreateGame(TabRoom *_room, const QMap &_gameType
actReset();
}
+ descriptionEdit->setFocus();
clearButton = new QPushButton(tr("&Clear"));
buttonBox->addButton(QDialogButtonBox::Cancel);
buttonBox->addButton(clearButton, QDialogButtonBox::ActionRole);
@@ -205,7 +206,7 @@ void DlgCreateGame::actReset()
gameTypeCheckBoxIterator.value()->setChecked(false);
}
-descriptionEdit->setFocus();
+ descriptionEdit->setFocus();
}
diff --git a/cockatrice/src/dlg_register.cpp b/cockatrice/src/dlg_register.cpp
index 2ccde232..db9ba363 100644
--- a/cockatrice/src/dlg_register.cpp
+++ b/cockatrice/src/dlg_register.cpp
@@ -361,6 +361,11 @@ void DlgRegister::actOk()
QMessageBox::critical(this, tr("Registration Warning"), tr("Your email addresses do not match, please try again."));
return;
}
+ if(playernameEdit->text().isEmpty())
+ {
+ QMessageBox::critical(this, tr("Registration Warning"), tr("The player name can't be empty."));
+ return;
+ }
settingsCache->servers().setHostName(hostEdit->text());
settingsCache->servers().setPort(portEdit->text());
diff --git a/cockatrice/src/dlg_settings.cpp b/cockatrice/src/dlg_settings.cpp
index 8f84001f..c05648bc 100644
--- a/cockatrice/src/dlg_settings.cpp
+++ b/cockatrice/src/dlg_settings.cpp
@@ -30,7 +30,7 @@
#include "soundengine.h"
#include "sequenceEdit/shortcutstab.h"
-#define LINKING_FAQ_URL "https://github.com/Cockatrice/Cockatrice/wiki/Custom-Download-URLs"
+#define WIKI_CUSTOM_PIC_URL "https://github.com/Cockatrice/Cockatrice/wiki/Custom-Download-URLs"
GeneralSettingsPage::GeneralSettingsPage()
{
@@ -44,7 +44,6 @@ GeneralSettingsPage::GeneralSettingsPage()
}
picDownloadCheckBox.setChecked(settingsCache->getPicDownload());
- picDownloadHqCheckBox.setChecked(settingsCache->getPicDownloadHq());
updateNotificationCheckBox.setChecked(settingsCache->getNotifyAboutUpdates());
pixmapCacheEdit.setMinimum(PIXMAPCACHE_SIZE_MIN);
@@ -53,20 +52,22 @@ GeneralSettingsPage::GeneralSettingsPage()
pixmapCacheEdit.setSingleStep(64);
pixmapCacheEdit.setValue(settingsCache->getPixmapCacheSize());
pixmapCacheEdit.setSuffix(" MB");
- picDownloadHqCheckBox.setChecked(settingsCache->getPicDownloadHq());
- picDownloadCheckBox.setChecked(settingsCache->getPicDownload());
- highQualityURLEdit = new QLineEdit(settingsCache->getPicUrlHq());
- highQualityURLEdit->setEnabled(settingsCache->getPicDownloadHq());
+ defaultUrlEdit = new QLineEdit(settingsCache->getPicUrl());
+ fallbackUrlEdit = new QLineEdit(settingsCache->getPicUrlFallback());
connect(&clearDownloadedPicsButton, SIGNAL(clicked()), this, SLOT(clearDownloadedPicsButtonClicked()));
connect(&languageBox, SIGNAL(currentIndexChanged(int)), this, SLOT(languageBoxChanged(int)));
connect(&picDownloadCheckBox, SIGNAL(stateChanged(int)), settingsCache, SLOT(setPicDownload(int)));
- connect(&picDownloadHqCheckBox, SIGNAL(stateChanged(int)), settingsCache, SLOT(setPicDownloadHq(int)));
connect(&pixmapCacheEdit, SIGNAL(valueChanged(int)), settingsCache, SLOT(setPixmapCacheSize(int)));
- connect(&picDownloadHqCheckBox, SIGNAL(clicked(bool)), this, SLOT(setEnabledStatus(bool)));
connect(&updateNotificationCheckBox, SIGNAL(stateChanged(int)), settingsCache, SLOT(setNotifyAboutUpdate(int)));
- connect(highQualityURLEdit, SIGNAL(textChanged(QString)), settingsCache, SLOT(setPicUrlHq(QString)));
+ connect(&picDownloadCheckBox, SIGNAL(clicked(bool)), this, SLOT(setEnabledStatus(bool)));
+ connect(defaultUrlEdit, SIGNAL(textChanged(QString)), settingsCache, SLOT(setPicUrl(QString)));
+ connect(fallbackUrlEdit, SIGNAL(textChanged(QString)), settingsCache, SLOT(setPicUrlFallback(QString)));
+ connect(&defaultUrlRestoreButton, SIGNAL(clicked()), this, SLOT(defaultUrlRestoreButtonClicked()));
+ connect(&fallbackUrlRestoreButton, SIGNAL(clicked()), this, SLOT(fallbackUrlRestoreButtonClicked()));
+
+ setEnabledStatus(settingsCache->getPicDownload());
QGridLayout *personalGrid = new QGridLayout;
personalGrid->addWidget(&languageLabel, 0, 0);
@@ -74,15 +75,18 @@ GeneralSettingsPage::GeneralSettingsPage()
personalGrid->addWidget(&pixmapCacheLabel, 1, 0);
personalGrid->addWidget(&pixmapCacheEdit, 1, 1);
personalGrid->addWidget(&updateNotificationCheckBox, 2, 0);
- personalGrid->addWidget(&picDownloadCheckBox, 3, 0);
- personalGrid->addWidget(&picDownloadHqCheckBox, 4, 0);
- personalGrid->addWidget(&highQualityURLLabel, 5, 0);
- personalGrid->addWidget(highQualityURLEdit, 5, 1);
- personalGrid->addWidget(&highQualityURLLinkLabel, 6, 1);
- personalGrid->addWidget(&clearDownloadedPicsButton, 6, 0);
+ personalGrid->addWidget(&picDownloadCheckBox, 3, 0, 1, 3);
+ personalGrid->addWidget(&defaultUrlLabel, 4, 0, 1, 1);
+ personalGrid->addWidget(defaultUrlEdit, 4, 1, 1, 1);
+ personalGrid->addWidget(&defaultUrlRestoreButton, 4, 2, 1, 1);
+ personalGrid->addWidget(&fallbackUrlLabel, 5, 0, 1, 1);
+ personalGrid->addWidget(fallbackUrlEdit, 5, 1, 1, 1);
+ personalGrid->addWidget(&fallbackUrlRestoreButton, 5, 2, 1, 1);
+ personalGrid->addWidget(&urlLinkLabel, 6, 1, 1, 1);
+ personalGrid->addWidget(&clearDownloadedPicsButton, 7, 0, 1, 3);
- highQualityURLLinkLabel.setTextInteractionFlags(Qt::LinksAccessibleByMouse);
- highQualityURLLinkLabel.setOpenExternalLinks(true);
+ urlLinkLabel.setTextInteractionFlags(Qt::LinksAccessibleByMouse);
+ urlLinkLabel.setOpenExternalLinks(true);
personalGroupBox = new QGroupBox;
personalGroupBox->setLayout(personalGrid);
@@ -154,6 +158,20 @@ QString GeneralSettingsPage::languageName(const QString &qmFile)
return translator.translate("GeneralSettingsPage", "English");
}
+void GeneralSettingsPage::defaultUrlRestoreButtonClicked()
+{
+ QString path = PIC_URL_DEFAULT;
+ defaultUrlEdit->setText(path);
+ settingsCache->setPicUrl(path);
+}
+
+void GeneralSettingsPage::fallbackUrlRestoreButtonClicked()
+{
+ QString path = PIC_URL_FALLBACK;
+ fallbackUrlEdit->setText(path);
+ settingsCache->setPicUrlFallback(path);
+}
+
void GeneralSettingsPage::deckPathButtonClicked()
{
QString path = QFileDialog::getExistingDirectory(this, tr("Choose path"));
@@ -238,7 +256,6 @@ void GeneralSettingsPage::retranslateUi()
personalGroupBox->setTitle(tr("Personal settings"));
languageLabel.setText(tr("Language:"));
picDownloadCheckBox.setText(tr("Download card pictures on the fly"));
- picDownloadHqCheckBox.setText(tr("Download card pictures from a custom URL"));
pathsGroupBox->setTitle(tr("Paths"));
deckPathLabel.setText(tr("Decks directory:"));
replaysPathLabel.setText(tr("Replays directory:"));
@@ -246,15 +263,21 @@ void GeneralSettingsPage::retranslateUi()
cardDatabasePathLabel.setText(tr("Card database:"));
tokenDatabasePathLabel.setText(tr("Token database:"));
pixmapCacheLabel.setText(tr("Picture cache size:"));
- highQualityURLLabel.setText(tr("Custom Card Download URL:"));
- highQualityURLLinkLabel.setText(QString("%2").arg(LINKING_FAQ_URL).arg(tr("Linking FAQ")));
+ defaultUrlLabel.setText(tr("Primary download URL:"));
+ fallbackUrlLabel.setText(tr("Fallback download URL:"));
+ urlLinkLabel.setText(QString("%2").arg(WIKI_CUSTOM_PIC_URL).arg(tr("How to set a custom picture url")));
clearDownloadedPicsButton.setText(tr("Reset/Clear Downloaded Pictures"));
updateNotificationCheckBox.setText(tr("Notify when new client features are available"));
+ defaultUrlRestoreButton.setText(tr("Reset"));
+ fallbackUrlRestoreButton.setText(tr("Reset"));
}
void GeneralSettingsPage::setEnabledStatus(bool status)
{
- highQualityURLEdit->setEnabled(status);
+ defaultUrlEdit->setEnabled(status);
+ fallbackUrlEdit->setEnabled(status);
+ defaultUrlRestoreButton.setEnabled(status);
+ fallbackUrlRestoreButton.setEnabled(status);
}
AppearanceSettingsPage::AppearanceSettingsPage()
diff --git a/cockatrice/src/dlg_settings.h b/cockatrice/src/dlg_settings.h
index 7b746564..52d3c9ba 100644
--- a/cockatrice/src/dlg_settings.h
+++ b/cockatrice/src/dlg_settings.h
@@ -45,6 +45,8 @@ private slots:
void tokenDatabasePathButtonClicked();
void languageBoxChanged(int index);
void setEnabledStatus(bool);
+ void defaultUrlRestoreButtonClicked();
+ void fallbackUrlRestoreButtonClicked();
private:
QStringList findQmFiles();
QString languageName(const QString &qmFile);
@@ -53,13 +55,13 @@ private:
QLineEdit *picsPathEdit;
QLineEdit *cardDatabasePathEdit;
QLineEdit *tokenDatabasePathEdit;
- QLineEdit *highQualityURLEdit;
+ QLineEdit *defaultUrlEdit;
+ QLineEdit *fallbackUrlEdit;
QSpinBox pixmapCacheEdit;
QGroupBox *personalGroupBox;
QGroupBox *pathsGroupBox;
QComboBox languageBox;
QCheckBox picDownloadCheckBox;
- QCheckBox picDownloadHqCheckBox;
QCheckBox updateNotificationCheckBox;
QLabel languageLabel;
QLabel pixmapCacheLabel;
@@ -68,9 +70,12 @@ private:
QLabel picsPathLabel;
QLabel cardDatabasePathLabel;
QLabel tokenDatabasePathLabel;
- QLabel highQualityURLLabel;
- QLabel highQualityURLLinkLabel;
+ QLabel defaultUrlLabel;
+ QLabel fallbackUrlLabel;
+ QLabel urlLinkLabel;
QPushButton clearDownloadedPicsButton;
+ QPushButton defaultUrlRestoreButton;
+ QPushButton fallbackUrlRestoreButton;
};
class AppearanceSettingsPage : public AbstractSettingsPage {
diff --git a/cockatrice/src/main.cpp b/cockatrice/src/main.cpp
index 058eea63..9d91eb72 100644
--- a/cockatrice/src/main.cpp
+++ b/cockatrice/src/main.cpp
@@ -54,11 +54,7 @@ QSystemTrayIcon *trayIcon;
ThemeManager *themeManager;
const QString translationPrefix = "cockatrice";
-#ifdef TRANSLATION_PATH
-QString translationPath = TRANSLATION_PATH;
-#else
-QString translationPath = QString();
-#endif
+QString translationPath;
#if QT_VERSION < 0x050000
static void myMessageOutput(QtMsgType /*type*/, const char *msg)
@@ -136,13 +132,17 @@ int main(int argc, char *argv[])
QCoreApplication::setOrganizationDomain("cockatrice.de");
QCoreApplication::setApplicationName("Cockatrice");
- if (translationPath.isEmpty()) {
#ifdef Q_OS_MAC
- translationPath = QLibraryInfo::location(QLibraryInfo::TranslationsPath);
-#elif defined(Q_OS_WIN)
- translationPath = app.applicationDirPath() + "/translations";
+ qApp->setAttribute(Qt::AA_DontShowIconsInMenus, true);
+#endif
+
+#ifdef Q_OS_MAC
+ translationPath = qApp->applicationDirPath() + "/../Resources/translations";
+#elif defined(Q_OS_WIN)
+ translationPath = qApp->applicationDirPath() + "/translations";
+#else // linux
+ translationPath = qApp->applicationDirPath() + "/../share/cockatrice/translations";
#endif
- }
rng = new RNG_SFMT;
settingsCache = new SettingsCache;
@@ -200,6 +200,9 @@ int main(int argc, char *argv[])
qDebug() << "Could not create " + dataDir + "/customsets folder.";
}
+ // when all the cards have been loaded, resolve the reverse-related tags
+ db->refreshCachedReverseRelatedCards();
+
if (settingsValid()) {
qDebug("main(): starting main program");
@@ -212,7 +215,9 @@ int main(int argc, char *argv[])
ui.show();
qDebug("main(): ui.show() finished");
-
+#if QT_VERSION > 0x050000
+ app.setAttribute(Qt::AA_UseHighDpiPixmaps);
+#endif
app.exec();
}
diff --git a/cockatrice/src/messagelogwidget.cpp b/cockatrice/src/messagelogwidget.cpp
index 8f28e4e3..ac5b5a32 100644
--- a/cockatrice/src/messagelogwidget.cpp
+++ b/cockatrice/src/messagelogwidget.cpp
@@ -190,8 +190,15 @@ void MessageLogWidget::logShuffle(Player *player, CardZone *zone)
void MessageLogWidget::logRollDie(Player *player, int sides, int roll)
{
+ QString coinOptions[2] = {tr("Heads (1)"), tr("Tails (2)")};
soundEngine->playSound("roll_dice");
- if (isFemale(player))
+
+ if (sides == 2)
+ if (isFemale(player))
+ appendHtmlServerMessage(tr("%1 flipped a coin. It landed as %2.", "female").arg(sanitizeHtml(player->getName())).arg("" + coinOptions[roll - 1] + ""));
+ else
+ appendHtmlServerMessage(tr("%1 flipped a coin. It landed as %2.", "male").arg(sanitizeHtml(player->getName())).arg("" + coinOptions[roll - 1] + ""));
+ else if (isFemale(player))
appendHtmlServerMessage(tr("%1 rolls a %2 with a %3-sided die.", "female").arg(sanitizeHtml(player->getName())).arg("" + QString::number(roll) + "").arg("" + QString::number(sides) + ""));
else
appendHtmlServerMessage(tr("%1 rolls a %2 with a %3-sided die.", "male").arg(sanitizeHtml(player->getName())).arg("" + QString::number(roll) + "").arg("" + QString::number(sides) + ""));
diff --git a/cockatrice/src/pictureloader.cpp b/cockatrice/src/pictureloader.cpp
new file mode 100644
index 00000000..bd5c9aa1
--- /dev/null
+++ b/cockatrice/src/pictureloader.cpp
@@ -0,0 +1,476 @@
+#include "pictureloader.h"
+#include "carddatabase.h"
+#include "main.h"
+#include "settingscache.h"
+#include "thememanager.h"
+
+#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
+
+class PictureToLoad::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()(CardSet *a, CardSet *b) const
+ {
+ if(a->getEnabled())
+ {
+ if(b->getEnabled())
+ {
+ // both enabled: sort by key
+ return a->getSortKey() < b->getSortKey();
+ } else {
+ // only a enabled
+ return true;
+ }
+ } else {
+ if(b->getEnabled())
+ {
+ // only b enabled
+ return false;
+ } else {
+ // both disabled: sort by key
+ return a->getSortKey() < b->getSortKey();
+ }
+ }
+ }
+};
+
+PictureToLoad::PictureToLoad(CardInfo *_card)
+ : card(_card), setIndex(0)
+{
+ if (card) {
+ sortedSets = card->getSets();
+ qSort(sortedSets.begin(), sortedSets.end(), SetDownloadPriorityComparator());
+ }
+}
+
+bool PictureToLoad::nextSet()
+{
+ if (setIndex == sortedSets.size() - 1)
+ return false;
+ ++setIndex;
+ return true;
+}
+
+QString PictureToLoad::getSetName() const
+{
+ if (setIndex < sortedSets.size())
+ return sortedSets[setIndex]->getCorrectedShortName();
+ else
+ return QString("");
+}
+
+CardSet *PictureToLoad::getCurrentSet() const
+{
+ if (setIndex < sortedSets.size())
+ return sortedSets[setIndex];
+ else
+ return 0;
+}
+
+QStringList PictureLoader::md5Blacklist = QStringList()
+ << "db0c48db407a907c16ade38de048a441"; // card back returned by gatherer when card is not found
+
+PictureLoader::PictureLoader()
+ : QObject(0),
+ downloadRunning(false), loadQueueRunning(false)
+{
+ picsPath = settingsCache->getPicsPath();
+ picDownload = settingsCache->getPicDownload();
+
+ connect(this, SIGNAL(startLoadQueue()), this, SLOT(processLoadQueue()), Qt::QueuedConnection);
+ connect(settingsCache, SIGNAL(picsPathChanged()), this, SLOT(picsPathChanged()));
+ connect(settingsCache, SIGNAL(picDownloadChanged()), this, SLOT(picDownloadChanged()));
+
+ networkManager = new QNetworkAccessManager(this);
+ connect(networkManager, SIGNAL(finished(QNetworkReply *)), this, SLOT(picDownloadFinished(QNetworkReply *)));
+
+ pictureLoaderThread = new QThread;
+ pictureLoaderThread->start(QThread::LowPriority);
+ moveToThread(pictureLoaderThread);
+}
+
+PictureLoader::~PictureLoader()
+{
+ pictureLoaderThread->deleteLater();
+}
+
+void PictureLoader::processLoadQueue()
+{
+ if (loadQueueRunning)
+ return;
+
+ loadQueueRunning = true;
+ forever {
+ mutex.lock();
+ if (loadQueue.isEmpty()) {
+ mutex.unlock();
+ loadQueueRunning = false;
+ return;
+ }
+ cardBeingLoaded = loadQueue.takeFirst();
+ mutex.unlock();
+
+ QString setName = cardBeingLoaded.getSetName();
+ QString correctedCardname = cardBeingLoaded.getCard()->getCorrectedName();
+ qDebug() << "Trying to load picture (set: " << setName << " card: " << correctedCardname << ")";
+
+ if(cardImageExistsOnDisk(setName, correctedCardname))
+ continue;
+
+ if (picDownload) {
+ qDebug() << "Picture NOT found, trying to download (set: " << setName << " card: " << correctedCardname << ")";
+ cardsToDownload.append(cardBeingLoaded);
+ cardBeingLoaded=0;
+ if (!downloadRunning)
+ startNextPicDownload();
+ } else {
+ if (cardBeingLoaded.nextSet())
+ {
+ qDebug() << "Picture NOT found and download disabled, moving to next set (newset: " << setName << " card: " << correctedCardname << ")";
+ mutex.lock();
+ loadQueue.prepend(cardBeingLoaded);
+ cardBeingLoaded=0;
+ mutex.unlock();
+ } else {
+ qDebug() << "Picture NOT found, download disabled, no more sets to try: BAILING OUT (oldset: " << setName << " card: " << correctedCardname << ")";
+ imageLoaded(cardBeingLoaded.getCard(), QImage());
+ }
+ }
+ }
+}
+
+bool PictureLoader::cardImageExistsOnDisk(QString & setName, QString & correctedCardname)
+{
+ QImage image;
+ QImageReader imgReader;
+ imgReader.setDecideFormatFromContent(true);
+
+ //The list of paths to the folders in which to search for images
+ QList picsPaths = QList() << picsPath + "/CUSTOM/" + correctedCardname;
+
+ if(!setName.isEmpty())
+ {
+ picsPaths << picsPath + "/" + setName + "/" + correctedCardname
+ << picsPath + "/downloadedPics/" + setName + "/" + correctedCardname;
+ }
+
+ //Iterates through the list of paths, searching for images with the desired name with any QImageReader-supported extension
+ for (int i = 0; i < picsPaths.length(); i ++) {
+ imgReader.setFileName(picsPaths.at(i));
+ if (imgReader.read(&image)) {
+ qDebug() << "Picture found on disk (set: " << setName << " card: " << correctedCardname << ")";
+ imageLoaded(cardBeingLoaded.getCard(), image);
+ return true;
+ }
+ imgReader.setFileName(picsPaths.at(i) + ".full");
+ if (imgReader.read(&image)) {
+ qDebug() << "Picture.full found on disk (set: " << setName << " card: " << correctedCardname << ")";
+ imageLoaded(cardBeingLoaded.getCard(), image);
+ return true;
+ }
+ }
+
+ return false;
+}
+
+QString PictureLoader::getPicUrl()
+{
+ if (!picDownload) return QString("");
+
+ CardInfo *card = cardBeingDownloaded.getCard();
+ CardSet *set=cardBeingDownloaded.getCurrentSet();
+ QString picUrl = QString("");
+
+ // if sets have been defined for the card, they can contain custom picUrls
+ if(set)
+ {
+ picUrl = card->getCustomPicURL(set->getShortName());
+ if (!picUrl.isEmpty())
+ return picUrl;
+ }
+
+ // if a card has a muid, use the default url; if not, use the fallback
+ int muid = set ? card->getMuId(set->getShortName()) : 0;
+ picUrl = muid ? settingsCache->getPicUrl() : settingsCache->getPicUrlFallback();
+
+ picUrl.replace("!name!", QUrl::toPercentEncoding(card->getCorrectedName()));
+ picUrl.replace("!name_lower!", QUrl::toPercentEncoding(card->getCorrectedName().toLower()));
+ picUrl.replace("!cardid!", QUrl::toPercentEncoding(QString::number(muid)));
+ if (set)
+ {
+ picUrl.replace("!setcode!", QUrl::toPercentEncoding(set->getShortName()));
+ picUrl.replace("!setcode_lower!", QUrl::toPercentEncoding(set->getShortName().toLower()));
+ picUrl.replace("!setname!", QUrl::toPercentEncoding(set->getLongName()));
+ picUrl.replace("!setname_lower!", QUrl::toPercentEncoding(set->getLongName().toLower()));
+ }
+
+ if (
+ picUrl.contains("!name!") ||
+ picUrl.contains("!name_lower!") ||
+ picUrl.contains("!setcode!") ||
+ picUrl.contains("!setcode_lower!") ||
+ picUrl.contains("!setname!") ||
+ picUrl.contains("!setname_lower!") ||
+ picUrl.contains("!cardid!")
+ )
+ {
+ qDebug() << "Insufficient card data to download" << card->getName() << "Url:" << picUrl;
+ return QString("");
+ }
+
+ return picUrl;
+}
+
+void PictureLoader::startNextPicDownload()
+{
+ if (cardsToDownload.isEmpty()) {
+ cardBeingDownloaded = 0;
+ downloadRunning = false;
+ return;
+ }
+
+ downloadRunning = true;
+
+ cardBeingDownloaded = cardsToDownload.takeFirst();
+
+ QString picUrl = getPicUrl();
+ if (picUrl.isEmpty()) {
+ downloadRunning = false;
+ picDownloadFailed();
+ } else {
+ QUrl url(picUrl);
+
+ QNetworkRequest req(url);
+ qDebug() << "starting picture download:" << cardBeingDownloaded.getCard()->getName() << "Url:" << req.url();
+ networkManager->get(req);
+ }
+}
+
+void PictureLoader::picDownloadFailed()
+{
+ if (cardBeingDownloaded.nextSet())
+ {
+ qDebug() << "Picture NOT found, download failed, moving to next set (newset: " << cardBeingDownloaded.getSetName() << " card: " << cardBeingDownloaded.getCard()->getCorrectedName() << ")";
+ mutex.lock();
+ loadQueue.prepend(cardBeingDownloaded);
+ mutex.unlock();
+ emit startLoadQueue();
+ } else {
+ qDebug() << "Picture NOT found, download failed, no more sets to try: BAILING OUT (oldset: " << cardBeingDownloaded.getSetName() << " card: " << cardBeingDownloaded.getCard()->getCorrectedName() << ")";
+ cardBeingDownloaded = 0;
+ imageLoaded(cardBeingDownloaded.getCard(), QImage());
+ }
+}
+
+bool PictureLoader::imageIsBlackListed(const QByteArray &picData)
+{
+ QString md5sum = QCryptographicHash::hash(picData, QCryptographicHash::Md5).toHex();
+ return md5Blacklist.contains(md5sum);
+}
+
+void PictureLoader::picDownloadFinished(QNetworkReply *reply)
+{
+ if (reply->error()) {
+ qDebug() << "Download failed:" << reply->errorString();
+ }
+
+ int statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
+ if (statusCode == 301 || statusCode == 302) {
+ QUrl redirectUrl = reply->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl();
+ QNetworkRequest req(redirectUrl);
+ qDebug() << "following redirect:" << cardBeingDownloaded.getCard()->getName() << "Url:" << req.url();
+ networkManager->get(req);
+ return;
+ }
+
+ const QByteArray &picData = reply->peek(reply->size()); //peek is used to keep the data in the buffer for use by QImageReader
+
+ if(imageIsBlackListed(picData))
+ {
+ qDebug() << "Picture downloaded, but blacklisted, will consider it as not found";
+ picDownloadFailed();
+ reply->deleteLater();
+ startNextPicDownload();
+ return;
+ }
+
+ QImage testImage;
+
+ QImageReader imgReader;
+ imgReader.setDecideFormatFromContent(true);
+ imgReader.setDevice(reply);
+ QString extension = "." + imgReader.format(); //the format is determined prior to reading the QImageReader data into a QImage object, as that wipes the QImageReader buffer
+ if (extension == ".jpeg")
+ extension = ".jpg";
+
+ if (imgReader.read(&testImage)) {
+ QString setName = cardBeingDownloaded.getSetName();
+ if(!setName.isEmpty())
+ {
+ if (!QDir().mkpath(picsPath + "/downloadedPics/" + setName)) {
+ qDebug() << picsPath + "/downloadedPics/" + setName + " could not be created.";
+ return;
+ }
+
+ QFile newPic(picsPath + "/downloadedPics/" + setName + "/" + cardBeingDownloaded.getCard()->getCorrectedName() + extension);
+ if (!newPic.open(QIODevice::WriteOnly))
+ return;
+ newPic.write(picData);
+ newPic.close();
+ }
+
+ imageLoaded(cardBeingDownloaded.getCard(), testImage);
+ } else {
+ picDownloadFailed();
+ }
+
+ reply->deleteLater();
+ startNextPicDownload();
+}
+
+void PictureLoader::enqueueImageLoad(CardInfo *card)
+{
+ QMutexLocker locker(&mutex);
+
+ // avoid queueing the same card more than once
+ if(card == 0 || card == cardBeingLoaded.getCard() || card == cardBeingDownloaded.getCard())
+ return;
+
+ foreach(PictureToLoad pic, loadQueue)
+ {
+ if(pic.getCard() == card)
+ return;
+ }
+
+ loadQueue.append(PictureToLoad(card));
+ emit startLoadQueue();
+}
+
+void PictureLoader::picDownloadChanged()
+{
+ QMutexLocker locker(&mutex);
+ picDownload = settingsCache->getPicDownload();
+
+ QPixmapCache::clear();
+}
+
+void PictureLoader::picsPathChanged()
+{
+ QMutexLocker locker(&mutex);
+ picsPath = settingsCache->getPicsPath();
+
+ QPixmapCache::clear();
+}
+
+void PictureLoader::internalGetCardBackPixmap(QPixmap &pixmap, QSize size)
+{
+ QString backCacheKey = "_trice_card_back_" + QString::number(size.width()) + QString::number(size.height());
+ if(!QPixmapCache::find(backCacheKey, &pixmap))
+ {
+qDebug() << "cache fail for" << backCacheKey;
+ pixmap = QPixmap("theme:cardback").scaled(size, Qt::KeepAspectRatio, Qt::SmoothTransformation);
+ QPixmapCache::insert(backCacheKey, pixmap);
+ }
+}
+
+void PictureLoader::getPixmap(QPixmap &pixmap, CardInfo *card, QSize size)
+{
+ if(card)
+ {
+ if (card->getName().isEmpty()) {
+ internalGetCardBackPixmap(pixmap, size);
+ return;
+ }
+
+ // search for an exact size copy of the picure 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))
+ {
+ pixmap = bigPixmap.scaled(size, Qt::KeepAspectRatio, Qt::SmoothTransformation);
+ QPixmapCache::insert(sizekey, pixmap);
+ return;
+ }
+ }
+
+ // load a temporary back picture
+ internalGetCardBackPixmap(pixmap, size);
+
+ if(card)
+ {
+ // add the card to the load queue
+ getInstance().enqueueImageLoad(card);
+ }
+}
+
+
+void PictureLoader::imageLoaded(CardInfo *card, const QImage &image)
+{
+ if(image.isNull())
+ return;
+
+ 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(CardInfo *card)
+{
+ if(card)
+ QPixmapCache::remove(card->getPixmapCacheKey());
+}
+
+void PictureLoader::clearPixmapCache()
+{
+ QPixmapCache::clear();
+}
+
+void PictureLoader::cacheCardPixmaps(QList cards)
+{
+ QPixmap tmp;
+ int max = qMin(cards.size(), CACHED_CARD_PER_DECK_MAX);
+ for (int i = 0; i < max; ++i)
+ {
+ CardInfo * card = cards.at(i);
+ if(!card)
+ continue;
+
+ QString key = card->getPixmapCacheKey();
+ if(QPixmapCache::find(key, &tmp))
+ continue;
+
+ getInstance().enqueueImageLoad(card);
+ }
+}
diff --git a/cockatrice/src/pictureloader.h b/cockatrice/src/pictureloader.h
new file mode 100644
index 00000000..9a93a1c3
--- /dev/null
+++ b/cockatrice/src/pictureloader.h
@@ -0,0 +1,82 @@
+#ifndef PICTURELOADER_H
+#define PICTURELOADER_H
+
+#include
+#include
+#include
+#include
+
+class CardInfo;
+class CardSet;
+class QNetworkAccessManager;
+class QNetworkReply;
+class QThread;
+
+class PictureToLoad {
+private:
+ class SetDownloadPriorityComparator;
+
+ CardInfo *card;
+ QList sortedSets;
+ int setIndex;
+ bool hq;
+public:
+ PictureToLoad(CardInfo *_card = 0);
+ CardInfo *getCard() const { return card; }
+ CardSet *getCurrentSet() const;
+ QString getSetName() const;
+ bool nextSet();
+};
+
+class PictureLoader : public QObject {
+Q_OBJECT
+public:
+ static PictureLoader& getInstance()
+ {
+ static PictureLoader instance;
+ return instance;
+ }
+private:
+ PictureLoader();
+ ~PictureLoader();
+ // Don't implement
+ PictureLoader(PictureLoader const&);
+ void operator=(PictureLoader const&);
+
+ static QStringList md5Blacklist;
+
+ QThread *pictureLoaderThread;
+ QString picsPath;
+ QList loadQueue;
+ QMutex mutex;
+ QNetworkAccessManager *networkManager;
+ QList cardsToDownload;
+ PictureToLoad cardBeingLoaded;
+ PictureToLoad cardBeingDownloaded;
+ bool picDownload, downloadRunning, loadQueueRunning;
+ void startNextPicDownload();
+ void imageLoaded(CardInfo *card, const QImage &image);
+ QString getPicUrl();
+ bool cardImageExistsOnDisk(QString & setName, QString & correctedCardname);
+ bool imageIsBlackListed(const QByteArray &picData);
+public:
+ void enqueueImageLoad(CardInfo *card);
+ static void getPixmap(QPixmap &pixmap, CardInfo *card, QSize size);
+ static void clearPixmapCache(CardInfo *card);
+ static void clearPixmapCache();
+ static void cacheCardPixmaps(QList cards);
+protected:
+ static void internalGetCardBackPixmap(QPixmap &pixmap, QSize size);
+private slots:
+ void picDownloadFinished(QNetworkReply *reply);
+ void picDownloadFailed();
+
+ void picDownloadChanged();
+ void picsPathChanged();
+public slots:
+ void processLoadQueue();
+signals:
+ void startLoadQueue();
+};
+
+#endif
diff --git a/cockatrice/src/player.cpp b/cockatrice/src/player.cpp
index ca5ab211..32552352 100644
--- a/cockatrice/src/player.cpp
+++ b/cockatrice/src/player.cpp
@@ -1135,7 +1135,16 @@ void Player::actCreateRelatedCard()
// get the target card name
QAction *action = static_cast(sender());
- CardInfo *cardInfo = db->getCard(action->text());
+
+ // removes p/t from tokens (and leading space))
+ QStringList spaces = action->text().split(" ");
+ if (spaces.at(0).indexOf("/") != -1) // Strip space from creatures
+ spaces.removeFirst();
+ CardInfo *cardInfo = db->getCard(spaces.join(" "));
+
+ // get the target token's location
+ // TODO: Define this QPoint into its own function along with the one below
+ QPoint gridPoint = QPoint(-1, table->clampValidTableRow(2 - cardInfo->getTableRow()));
// create the token for the related card
Command_CreateToken cmd;
@@ -1146,7 +1155,11 @@ void Player::actCreateRelatedCard()
cmd.set_annotation(settingsCache->getAnnotateTokens() ? cardInfo->getText().toStdString() : QString().toStdString());
cmd.set_destroy_on_zone_change(true);
cmd.set_target_zone(sourceCard->getZone()->getName().toStdString());
- cmd.set_target_card_id(sourceCard->getId());
+ cmd.set_x(gridPoint.x());
+ cmd.set_y(gridPoint.y());
+
+ if(!cardInfo->getIsToken())
+ cmd.set_target_card_id(sourceCard->getId());
sendGameCommand(cmd);
}
@@ -2115,6 +2128,9 @@ void Player::actSetPT()
void Player::actDrawArrow()
{
+ if(!game->getActiveCard())
+ return;
+
game->getActiveCard()->drawArrow(Qt::red);
}
@@ -2183,6 +2199,9 @@ void Player::actSetAnnotation()
void Player::actAttach()
{
+ if(!game->getActiveCard())
+ return;
+
ArrowAttachItem *arrow = new ArrowAttachItem(game->getActiveCard());
scene()->addItem(arrow);
arrow->grabMouse();
@@ -2190,6 +2209,9 @@ void Player::actAttach()
void Player::actUnattach()
{
+ if(!game->getActiveCard())
+ return;
+
Command_AttachCard cmd;
cmd.set_start_zone(game->getActiveCard()->getZone()->getName().toStdString());
cmd.set_card_id(game->getActiveCard()->getId());
@@ -2268,16 +2290,25 @@ void Player::actCardCounterTrigger()
void Player::actPlay()
{
+ if(!game->getActiveCard())
+ return;
+
playCard(game->getActiveCard(), false, game->getActiveCard()->getInfo()->getCipt());
}
void Player::actHide()
{
+ if(!game->getActiveCard())
+ return;
+
game->getActiveCard()->getZone()->removeCard(game->getActiveCard());
}
void Player::actPlayFacedown()
{
+ if(!game->getActiveCard())
+ return;
+
playCard(game->getActiveCard(), true, false);
}
@@ -2344,7 +2375,8 @@ void Player::updateCardMenu(CardItem *card)
cardMenu->addAction(aPeek);
QStringList relatedCards = card->getInfo()->getRelatedCards();
- if(relatedCards.size())
+ QStringList reverserelatedCards2Me = card->getInfo()->getReverseRelatedCards2Me();
+ if(relatedCards.size() || reverserelatedCards2Me.size())
{
QMenu * createRelatedCardMenu = cardMenu->addMenu(tr("Cr&eate related card"));
@@ -2352,6 +2384,11 @@ void Player::updateCardMenu(CardItem *card)
QAction *a = createRelatedCardMenu->addAction(relatedCards.at(i));
connect(a, SIGNAL(triggered()), this, SLOT(actCreateRelatedCard()));
}
+
+ for (int i = 0; i < reverserelatedCards2Me.size(); ++i) {
+ QAction *a = createRelatedCardMenu->addAction(reverserelatedCards2Me.at(i));
+ connect(a, SIGNAL(triggered()), this, SLOT(actCreateRelatedCard()));
+ }
}
cardMenu->addSeparator();
cardMenu->addAction(aAttach);
@@ -2375,6 +2412,23 @@ void Player::updateCardMenu(CardItem *card)
} else if (card->getZone()->getName() == "stack") {
cardMenu->addAction(aDrawArrow);
cardMenu->addMenu(moveMenu);
+
+ QStringList relatedCards = card->getInfo()->getRelatedCards();
+ QStringList reverserelatedCards2Me = card->getInfo()->getReverseRelatedCards2Me();
+ if(relatedCards.size() || reverserelatedCards2Me.size())
+ {
+ QMenu * createRelatedCardMenu = cardMenu->addMenu(tr("Cr&eate related card"));
+
+ for (int i = 0; i < relatedCards.size(); ++i) {
+ QAction *a = createRelatedCardMenu->addAction(relatedCards.at(i));
+ connect(a, SIGNAL(triggered()), this, SLOT(actCreateRelatedCard()));
+ }
+
+ for (int i = 0; i < reverserelatedCards2Me.size(); ++i) {
+ QAction *a = createRelatedCardMenu->addAction(reverserelatedCards2Me.at(i));
+ connect(a, SIGNAL(triggered()), this, SLOT(actCreateRelatedCard()));
+ }
+ }
} else {
cardMenu->addAction(aPlay);
cardMenu->addAction(aPlayFacedown);
diff --git a/cockatrice/src/settingscache.cpp b/cockatrice/src/settingscache.cpp
index d5eb062d..dba3b134 100644
--- a/cockatrice/src/settingscache.cpp
+++ b/cockatrice/src/settingscache.cpp
@@ -165,18 +165,15 @@ SettingsCache::SettingsCache()
pixmapCacheSize = PIXMAPCACHE_SIZE_DEFAULT;
picDownload = settings->value("personal/picturedownload", true).toBool();
- picDownloadHq = settings->value("personal/picturedownloadhq", true).toBool();
picUrl = settings->value("personal/picUrl", PIC_URL_DEFAULT).toString();
- picUrlHq = settings->value("personal/picUrlHq", PIC_URL_HQ_DEFAULT).toString();
picUrlFallback = settings->value("personal/picUrlFallback", PIC_URL_FALLBACK).toString();
- picUrlHqFallback = settings->value("personal/picUrlHqFallback", PIC_URL_HQ_FALLBACK).toString();
mainWindowGeometry = settings->value("interface/main_window_geometry").toByteArray();
notificationsEnabled = settings->value("interface/notificationsenabled", true).toBool();
spectatorNotificationsEnabled = settings->value("interface/specnotificationsenabled", false).toBool();
doubleClickToPlay = settings->value("interface/doubleclicktoplay", true).toBool();
- playToStack = settings->value("interface/playtostack", false).toBool();
+ playToStack = settings->value("interface/playtostack", true).toBool();
annotateTokens = settings->value("interface/annotatetokens", false).toBool();
cardInfoMinimized = settings->value("interface/cardinfominimized", 0).toInt();
tabGameSplitterSizes = settings->value("interface/tabgame_splittersizes").toByteArray();
@@ -325,37 +322,18 @@ void SettingsCache::setPicDownload(int _picDownload)
emit picDownloadChanged();
}
-void SettingsCache::setPicDownloadHq(int _picDownloadHq)
-{
- picDownloadHq = _picDownloadHq;
- settings->setValue("personal/picturedownloadhq", picDownloadHq);
- emit picDownloadHqChanged();
-}
-
void SettingsCache::setPicUrl(const QString &_picUrl)
{
picUrl = _picUrl;
settings->setValue("personal/picUrl", picUrl);
}
-void SettingsCache::setPicUrlHq(const QString &_picUrlHq)
-{
- picUrlHq = _picUrlHq;
- settings->setValue("personal/picUrlHq", picUrlHq);
-}
-
void SettingsCache::setPicUrlFallback(const QString &_picUrlFallback)
{
picUrlFallback = _picUrlFallback;
settings->setValue("personal/picUrlFallback", picUrlFallback);
}
-void SettingsCache::setPicUrlHqFallback(const QString &_picUrlHqFallback)
-{
- picUrlHqFallback = _picUrlHqFallback;
- settings->setValue("personal/picUrlHqFallback", picUrlHqFallback);
-}
-
void SettingsCache::setNotificationsEnabled(int _notificationsEnabled)
{
notificationsEnabled = _notificationsEnabled;
diff --git a/cockatrice/src/settingscache.h b/cockatrice/src/settingscache.h
index 722ccac1..036f6966 100644
--- a/cockatrice/src/settingscache.h
+++ b/cockatrice/src/settingscache.h
@@ -14,8 +14,6 @@
// the falbacks are used for cards without a muid
#define PIC_URL_DEFAULT "http://gatherer.wizards.com/Handlers/Image.ashx?multiverseid=!cardid!&type=card"
#define PIC_URL_FALLBACK "http://gatherer.wizards.com/Handlers/Image.ashx?name=!name!&type=card"
-#define PIC_URL_HQ_DEFAULT "http://gatherer.wizards.com/Handlers/Image.ashx?multiverseid=!cardid!&type=card"
-#define PIC_URL_HQ_FALLBACK "http://gatherer.wizards.com/Handlers/Image.ashx?name=!name!&type=card"
// size should be a multiple of 64
#define PIXMAPCACHE_SIZE_DEFAULT 2047
#define PIXMAPCACHE_SIZE_MIN 64
@@ -32,7 +30,6 @@ signals:
void tokenDatabasePathChanged();
void themeChanged();
void picDownloadChanged();
- void picDownloadHqChanged();
void displayCardNamesChanged();
void horizontalHandChanged();
void handJustificationChanged();
@@ -60,7 +57,6 @@ private:
QString deckPath, replaysPath, picsPath, cardDatabasePath, tokenDatabasePath, themeName;
bool notifyAboutUpdates;
bool picDownload;
- bool picDownloadHq;
bool notificationsEnabled;
bool spectatorNotificationsEnabled;
bool doubleClickToPlay;
@@ -87,9 +83,7 @@ private:
bool ignoreUnregisteredUsers;
bool ignoreUnregisteredUserMessages;
QString picUrl;
- QString picUrlHq;
QString picUrlFallback;
- QString picUrlHqFallback;
QString clientID;
int pixmapCacheSize;
bool scaleCards;
@@ -127,7 +121,6 @@ public:
QString getChatMentionColor() const { return chatMentionColor; }
QString getChatHighlightColor() const { return chatHighlightColor; }
bool getPicDownload() const { return picDownload; }
- bool getPicDownloadHq() const { return picDownloadHq; }
bool getNotificationsEnabled() const { return notificationsEnabled; }
bool getSpectatorNotificationsEnabled() const { return spectatorNotificationsEnabled; }
bool getNotifyAboutUpdates() const { return notifyAboutUpdates; }
@@ -160,9 +153,7 @@ public:
bool getIgnoreUnregisteredUsers() const { return ignoreUnregisteredUsers; }
bool getIgnoreUnregisteredUserMessages() const { return ignoreUnregisteredUserMessages; }
QString getPicUrl() const { return picUrl; }
- QString getPicUrlHq() const { return picUrlHq; }
QString getPicUrlFallback() const { return picUrlFallback; }
- QString getPicUrlHqFallback() const { return picUrlHqFallback; }
int getPixmapCacheSize() const { return pixmapCacheSize; }
bool getScaleCards() const { return scaleCards; }
bool getShowMessagePopup() const { return showMessagePopups; }
@@ -204,7 +195,6 @@ public slots:
void setChatMentionColor(const QString &_chatMentionColor);
void setChatHighlightColor(const QString &_chatHighlightColor);
void setPicDownload(int _picDownload);
- void setPicDownloadHq(int _picDownloadHq);
void setNotificationsEnabled(int _notificationsEnabled);
void setSpectatorNotificationsEnabled(int _spectatorNotificationsEnabled);
void setDoubleClickToPlay(int _doubleClickToPlay);
@@ -231,9 +221,7 @@ public slots:
void setIgnoreUnregisteredUsers(int _ignoreUnregisteredUsers);
void setIgnoreUnregisteredUserMessages(int _ignoreUnregisteredUserMessages);
void setPicUrl(const QString &_picUrl);
- void setPicUrlHq(const QString &_picUrlHq);
void setPicUrlFallback(const QString &_picUrlFallback);
- void setPicUrlHqFallback(const QString &_picUrlHqFallback);
void setPixmapCacheSize(const int _pixmapCacheSize);
void setCardScaling(const int _scaleCards);
void setShowMessagePopups(const int _showMessagePopups);
diff --git a/cockatrice/src/soundengine.cpp b/cockatrice/src/soundengine.cpp
index 9686ecef..b3cd3c6a 100644
--- a/cockatrice/src/soundengine.cpp
+++ b/cockatrice/src/soundengine.cpp
@@ -89,7 +89,7 @@ void SoundEngine::playSound(QString fileName)
inputBuffer->setData(audioData[fileName]);
inputBuffer->open(QIODevice::ReadOnly);
#if QT_VERSION >= 0x050000
- player->setVolume(settingsCache->getMasterVolume());
+ player->setVolume(settingsCache->getMasterVolume() / 100.0);
#endif
player->stop();
player->start(inputBuffer);
diff --git a/cockatrice/src/tab_deck_editor.cpp b/cockatrice/src/tab_deck_editor.cpp
index d750e83b..1e27434b 100644
--- a/cockatrice/src/tab_deck_editor.cpp
+++ b/cockatrice/src/tab_deck_editor.cpp
@@ -26,6 +26,7 @@
#include "tab_deck_editor.h"
#include "window_sets.h"
#include "carddatabase.h"
+#include "pictureloader.h"
#include "carddatabasemodel.h"
#include "decklistmodel.h"
#include "cardinfowidget.h"
@@ -1058,7 +1059,7 @@ void TabDeckEditor::setDeck(DeckLoader *_deck)
deckView->expandAll();
setModified(false);
- db->cacheCardPixmaps(deckModel->getDeckList()->getCardList());
+ PictureLoader::cacheCardPixmaps(db->getCards(deckModel->getDeckList()->getCardList()));
deckView->expandAll();
setModified(false);
}
diff --git a/cockatrice/src/tab_game.cpp b/cockatrice/src/tab_game.cpp
index 86b6308d..dd52f64a 100644
--- a/cockatrice/src/tab_game.cpp
+++ b/cockatrice/src/tab_game.cpp
@@ -32,6 +32,7 @@
#include "main.h"
#include "settingscache.h"
#include "carddatabase.h"
+#include "pictureloader.h"
#include "replay_timeline_widget.h"
#include "lineeditcompleter.h"
@@ -242,7 +243,7 @@ void DeckViewContainer::deckSelectFinished(const Response &r)
{
const Response_DeckDownload &resp = r.GetExtension(Response_DeckDownload::ext);
DeckLoader newDeck(QString::fromStdString(resp.deck()));
- db->cacheCardPixmaps(newDeck.getCardList());
+ PictureLoader::cacheCardPixmaps(db->getCards(newDeck.getCardList()));
setDeck(newDeck);
}
@@ -452,7 +453,6 @@ TabGame::TabGame(TabSupervisor *_tabSupervisor, QList &_client
scene = new GameScene(phasesToolbar, this);
gameView = new GameView(scene);
gameView->hide();
- gameView->setFocusPolicy(Qt::ClickFocus);
cardInfo = new CardFrame();
playerListWidget = new PlayerListWidget(tabSupervisor, clients.first(), this);
@@ -1044,7 +1044,7 @@ void TabGame::eventGameStateChanged(const Event_GameStateChanged &event, int /*e
DeckViewContainer *deckViewContainer = deckViewContainers.value(playerId);
if (playerInfo.has_deck_list()) {
DeckLoader newDeck(QString::fromStdString(playerInfo.deck_list()));
- db->cacheCardPixmaps(newDeck.getCardList());
+ PictureLoader::cacheCardPixmaps(db->getCards(newDeck.getCardList()));
deckViewContainer->setDeck(newDeck);
player->setDeck(newDeck);
}
diff --git a/cockatrice/src/tab_logs.cpp b/cockatrice/src/tab_logs.cpp
index 8be008cd..bd4a32e2 100644
--- a/cockatrice/src/tab_logs.cpp
+++ b/cockatrice/src/tab_logs.cpp
@@ -322,10 +322,10 @@ void TabLog::viewLogHistory_processResponse(const Response &resp)
}
} else
- QMessageBox::information(static_cast(parent()), tr("Message History"), tr("There is no messages for the selected iilters."));
+ QMessageBox::information(static_cast(parent()), tr("Message History"), tr("There are no messages for the selected filters."));
} else
- QMessageBox::critical(static_cast(parent()), tr("Message History"), tr("Failed to collecting message history information."));
+ QMessageBox::critical(static_cast(parent()), tr("Message History"), tr("Failed to collect message history information."));
}
void TabLog::restartLayout()
diff --git a/cockatrice/src/tab_server.cpp b/cockatrice/src/tab_server.cpp
index d15f6d8c..36e1b42d 100644
--- a/cockatrice/src/tab_server.cpp
+++ b/cockatrice/src/tab_server.cpp
@@ -12,6 +12,7 @@
#include "tab_server.h"
#include "abstractclient.h"
#include "userlist.h"
+#include "tab_supervisor.h"
#include
#include "pending_command.h"
@@ -35,7 +36,6 @@ RoomSelector::RoomSelector(AbstractClient *_client, QWidget *parent)
#else
roomList->header()->setSectionResizeMode(0, QHeaderView::ResizeToContents);
roomList->header()->setSectionResizeMode(1, QHeaderView::Stretch);
- roomList->header()->setSectionResizeMode(1, QHeaderView::ResizeToContents);
roomList->header()->setSectionResizeMode(2, QHeaderView::ResizeToContents);
roomList->header()->setSectionResizeMode(3, QHeaderView::ResizeToContents);
#endif
@@ -111,43 +111,19 @@ void RoomSelector::processListRoomsEvent(const Event_ListRooms &event)
roomList->addTopLevelItem(twi);
if (room.has_auto_join())
if (room.auto_join())
- joinRoom(room.room_id(), false);
+ emit joinRoomRequest(room.room_id(), false);
}
}
-void RoomSelector::joinRoom(int id, bool setCurrent)
-{
- Command_JoinRoom cmd;
- cmd.set_room_id(id);
-
- PendingCommand *pend = client->prepareSessionCommand(cmd);
- pend->setExtraData(setCurrent);
- connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this, SLOT(joinFinished(Response, CommandContainer, QVariant)));
-
- client->sendCommand(pend);
-}
-
void RoomSelector::joinClicked()
{
QTreeWidgetItem *twi = roomList->currentItem();
if (!twi)
return;
- joinRoom(twi->data(0, Qt::UserRole).toInt(), true);
-}
+ int id = twi->data(0, Qt::UserRole).toInt();
-void RoomSelector::joinFinished(const Response &r, const CommandContainer & /*commandContainer*/, const QVariant &extraData)
-{
- switch (r.response_code()) {
- case Response::RespOk: break;
- case Response::RespUserLevelTooLow: QMessageBox::critical(this, tr("Error"), tr("You do not have the proper permission to join this room.")); return;
- default:
- QMessageBox::critical(this, tr("Error"), tr("Failed to join the room due to an unknown error."));
- return;
- }
-
- const Response_JoinRoom &resp = r.GetExtension(Response_JoinRoom::ext);
- emit roomJoined(resp.room_info(), extraData.toBool());
+ emit joinRoomRequest(id, true);
}
TabServer::TabServer(TabSupervisor *_tabSupervisor, AbstractClient *_client, QWidget *parent)
@@ -157,7 +133,7 @@ TabServer::TabServer(TabSupervisor *_tabSupervisor, AbstractClient *_client, QWi
serverInfoBox = new QTextBrowser;
serverInfoBox->setOpenExternalLinks(true);
- connect(roomSelector, SIGNAL(roomJoined(const ServerInfo_Room &, bool)), this, SIGNAL(roomJoined(const ServerInfo_Room &, bool)));
+ connect(roomSelector, SIGNAL(joinRoomRequest(int, bool)), this, SLOT(joinRoom(int, bool)));
connect(client, SIGNAL(serverMessageEventReceived(const Event_ServerMessage &)), this, SLOT(processServerMessageEvent(const Event_ServerMessage &)));
@@ -178,3 +154,47 @@ void TabServer::processServerMessageEvent(const Event_ServerMessage &event)
serverInfoBox->setHtml(QString::fromStdString(event.message()));
emit userEvent();
}
+
+void TabServer::joinRoom(int id, bool setCurrent)
+{
+ TabRoom *room = tabSupervisor->getRoomTabs().value(id);
+ if(!room)
+ {
+ Command_JoinRoom cmd;
+ cmd.set_room_id(id);
+
+ PendingCommand *pend = client->prepareSessionCommand(cmd);
+ pend->setExtraData(setCurrent);
+ connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this, SLOT(joinRoomFinished(Response, CommandContainer, QVariant)));
+
+ client->sendCommand(pend);
+
+ return;
+ }
+
+ if(setCurrent)
+ tabSupervisor->setCurrentWidget((QWidget*)room);
+}
+
+void TabServer::joinRoomFinished(const Response &r, const CommandContainer & /*commandContainer*/, const QVariant &extraData)
+{
+ switch (r.response_code()) {
+ case Response::RespOk:
+ break;
+ case Response::RespNameNotFound:
+ QMessageBox::critical(this, tr("Error"), tr("Failed to join the room: it doesn't exists on the server."));
+ return;
+ case Response::RespContextError:
+ QMessageBox::critical(this, tr("Error"), tr("The server thinks you are in the room but Cockatrice is unable to display it. Try restarting Cockatrice."));
+ return;
+ case Response::RespUserLevelTooLow:
+ QMessageBox::critical(this, tr("Error"), tr("You do not have the required permission to join this room."));
+ return;
+ default:
+ QMessageBox::critical(this, tr("Error"), tr("Failed to join the room due to an unknown error: %1.").arg(r.response_code()));
+ return;
+ }
+
+ const Response_JoinRoom &resp = r.GetExtension(Response_JoinRoom::ext);
+ emit roomJoined(resp.room_info(), extraData.toBool());
+}
diff --git a/cockatrice/src/tab_server.h b/cockatrice/src/tab_server.h
index a057660b..b7cd4e2a 100644
--- a/cockatrice/src/tab_server.h
+++ b/cockatrice/src/tab_server.h
@@ -24,14 +24,11 @@ private:
QTreeWidget *roomList;
QPushButton *joinButton;
AbstractClient *client;
-
- void joinRoom(int id, bool setCurrent);
private slots:
void processListRoomsEvent(const Event_ListRooms &event);
void joinClicked();
- void joinFinished(const Response &resp, const CommandContainer &commandContainer, const QVariant &extraData);
signals:
- void roomJoined(const ServerInfo_Room &info, bool setCurrent);
+ void joinRoomRequest(int, bool setCurrent);
public:
RoomSelector(AbstractClient *_client, QWidget *parent = 0);
void retranslateUi();
@@ -43,6 +40,8 @@ signals:
void roomJoined(const ServerInfo_Room &info, bool setCurrent);
private slots:
void processServerMessageEvent(const Event_ServerMessage &event);
+ void joinRoom(int id, bool setCurrent);
+ void joinRoomFinished(const Response &resp, const CommandContainer &commandContainer, const QVariant &extraData);
private:
AbstractClient *client;
RoomSelector *roomSelector;
diff --git a/cockatrice/src/thememanager.cpp b/cockatrice/src/thememanager.cpp
index 40ecdf2f..390d859a 100644
--- a/cockatrice/src/thememanager.cpp
+++ b/cockatrice/src/thememanager.cpp
@@ -3,6 +3,7 @@
#include
#include
#include
+#include
#include
#if QT_VERSION < 0x050000
#include
@@ -115,6 +116,7 @@ void ThemeManager::themeChangedSlot()
playerBgBrush = loadBrush(PLAYERZONE_BG_NAME, QColor(200, 200, 200));
stackBgBrush = loadBrush(STACKZONE_BG_NAME, QColor(113, 43, 43));
+ QPixmapCache::clear();
emit themeChanged();
}
diff --git a/cockatrice/src/window_main.cpp b/cockatrice/src/window_main.cpp
index 5bc42118..91a52223 100644
--- a/cockatrice/src/window_main.cpp
+++ b/cockatrice/src/window_main.cpp
@@ -485,6 +485,7 @@ void MainWindow::retranslateUi()
aFullScreen->setText(tr("&Full screen"));
aRegister->setText(tr("&Register to server..."));
aSettings->setText(tr("&Settings..."));
+ aSettings->setIcon(QPixmap("theme:icons/settings"));
aExit->setText(tr("&Exit"));
#if defined(__APPLE__) /* For OSX */
@@ -797,6 +798,7 @@ void MainWindow::cardUpdateFinished(int, QProcess::ExitStatus)
// this will force a database reload
settingsCache->setCardDatabasePath(settingsCache->getCardDatabasePath());
+ settingsCache->setTokenDatabasePath(settingsCache->getTokenDatabasePath());
}
void MainWindow::refreshShortcuts()
diff --git a/cockatrice/src/window_sets.cpp b/cockatrice/src/window_sets.cpp
index b647e0a9..5e0a49e2 100644
--- a/cockatrice/src/window_sets.cpp
+++ b/cockatrice/src/window_sets.cpp
@@ -1,6 +1,8 @@
#include "window_sets.h"
#include "setsmodel.h"
+#include "pictureloader.h"
#include "main.h"
+
#include
#include
#include
@@ -123,7 +125,7 @@ WndSets::~WndSets()
void WndSets::actSave()
{
model->save(db);
- db->clearPixmapCache();
+ PictureLoader::clearPixmapCache();
QMessageBox::information(this, tr("Success"), tr("The sets database has been saved successfully."));
close();
}
diff --git a/cockatrice/translations/cockatrice_en.ts b/cockatrice/translations/cockatrice_en.ts
index 6d351363..cd4b6c80 100644
--- a/cockatrice/translations/cockatrice_en.ts
+++ b/cockatrice/translations/cockatrice_en.ts
@@ -80,106 +80,106 @@
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.
-
+
&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.
@@ -709,55 +709,65 @@ This is only saved for moderators and cannot be seen by the banned person.
DlgConnect
-
+
Previous Host
-
+
New Host
-
+
&Host:
-
+
Enter host name
-
+
&Port:
-
+
Player &name:
-
+
P&assword:
-
+
&Save password
-
+
A&uto connect at start
-
+
Connect to server
+
+
+ Connect Warning
+
+
+
+
+ The player name can't be empty.
+
+
DlgCreateGame
@@ -827,27 +837,27 @@ This is only saved for moderators and cannot be seen by the banned person.
-
+
&Clear
-
+
Create game
-
+
Game information
-
+
Error
-
+
Server error.
@@ -1307,6 +1317,7 @@ Make sure to enable the 'token set' in the 'Edit sets...' di
+
Registration Warning
@@ -1320,6 +1331,11 @@ Make sure to enable the 'token set' in the 'Edit sets...' di
Your email addresses do not match, please try again.
+
+
+ The player name can't be empty.
+
+
Country:
@@ -1339,19 +1355,19 @@ Make sure to enable the 'token set' in the 'Edit sets...' di
DlgSettings
+
-
Error
-
+
Unknown Error loading card database
-
+
Your card database is invalid.
Cockatrice may not function correctly with an invalid database
@@ -1362,7 +1378,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
@@ -1373,21 +1389,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?
-
+
Your card database did not finish loading
Please file a ticket at http://github.com/Cockatrice/Cockatrice/issues with your cards.xml attached
@@ -1396,7 +1412,7 @@ Would you like to change your database location setting?
-
+
Unknown card database load status
Please file a ticket at http://github.com/Cockatrice/Cockatrice/issues
@@ -1405,52 +1421,52 @@ Would you like to change your database location setting?
-
+
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
-
+
Deck Editor
-
+
Chat
-
+
Sound
-
+
Shortcuts
@@ -1728,7 +1744,7 @@ Would you like to change your database location setting?
- Linking FAQ
+ How to set a custom picture url
@@ -1964,8 +1980,8 @@ Will now login.
-
-
+
+
Error
@@ -2246,35 +2262,35 @@ Local version is %1, remote version is %2.
-
+
A card database update is already running.
-
+
Unable to run the card database updater:
-
+
The card database updater exited with an error: %1
-
+
Update completed successfully. Cockatrice will now reload the card database.
-
+
Your client appears to be missing features that the server supports.
This usually means that your client version is out of date, please check to see if there is a new client available for download.
-
-
-
+
+
+
Information
@@ -2295,32 +2311,32 @@ You need to provide the activation token received in the activation email
-
+
failed to start.
-
+
crashed.
-
+
timed out.
-
+
write error.
-
+
read error.
-
+
unknown error.
@@ -2328,17 +2344,17 @@ You need to provide the activation token received in the activation email
MessageLogWidget
-
+
The game has been closed.
-
+
%1 is now watching the game.
-
+
%1 is not watching the game any more.
@@ -2386,136 +2402,136 @@ You need to provide the activation token received in the activation email
-
+
%1 has left the game.
female
-
+
%1 has left the game.
male
-
+
You have been kicked out of the game.
-
+
%1 has loaded a deck (%2).
-
+
%1 has loaded a deck with %2 sideboard cards (%3).
-
+
%1 is ready to start the game.
female
-
+
%1 is ready to start the game.
male
-
+
%1 is not ready to start the game any more.
female
-
+
%1 is not ready to start the game any more.
male
-
+
%1 has locked her sideboard.
female
-
+
%1 has locked his sideboard.
male
-
+
%1 has unlocked her sideboard.
female
-
+
%1 has unlocked his sideboard.
male
-
+
%1 has conceded the game.
female
-
+
%1 has conceded the game.
male
-
-
- %1 has restored connection to the game.
- female
-
-
-
-
- %1 has restored connection to the game.
- male
-
-
- %1 has lost connection to the game.
+ %1 has restored connection to the game.
female
+ %1 has restored connection to the game.
+ male
+
+
+
+
+ %1 has lost connection to the game.
+ female
+
+
+
+
%1 has lost connection to the game.
male
-
+
%1 shuffles %2.
female
-
+
%1 shuffles %2.
male
-
+
%1 rolls a %2 with a %3-sided die.
female
-
+
%1 rolls a %2 with a %3-sided die.
male
@@ -2537,182 +2553,182 @@ You need to provide the activation token received in the activation email
-
+
%1 undoes his last draw.
-
+
%1 undoes her last draw.
-
+
%1 undoes his last draw (%2).
-
+
%1 undoes her last draw (%2).
-
+
from exile
-
+
the bottom card of %1's library
-
+
the bottom card of his library
-
+
the bottom card of her library
-
+
from the bottom of %1's library
-
+
from the bottom of his library
-
+
from the bottom of her library
-
+
the top card of %1's library
-
+
the top card of his library
-
+
the top card of her library
-
+
from the top of %1's library
-
+
from the top of his library
-
+
from the top of her library
-
+
from %1's library
-
+
from library
-
+
from sideboard
-
+
from the stack
-
+
%1 gives %2 control over %3.
-
+
%1 puts %2 into play tapped%3.
-
+
%1 puts %2 into play%3.
-
+
%1 exiles %2%3.
-
+
%1 puts %2%3 into his library.
-
+
%1 puts %2%3 into her library.
-
+
%1 puts %2%3 on bottom of his library.
-
+
%1 puts %2%3 on bottom of her library.
-
+
%1 puts %2%3 on top of his library.
-
+
%1 puts %2%3 on top of her library.
-
+
%1 puts %2%3 into his library at position %4.
-
+
%1 puts %2%3 into her library at position %4.
-
+
%1 moves %2%3 to sideboard.
-
+
%1 plays %2%3.
-
+
%1 takes a mulligan to %n.
female
@@ -2721,7 +2737,7 @@ You need to provide the activation token received in the activation email
-
+
%1 takes a mulligan to %n.
male
@@ -2730,277 +2746,277 @@ You need to provide the activation token received in the activation email
-
+
%1 flips %2 face-down.
female
-
+
%1 flips %2 face-down.
male
-
-
- %1 flips %2 face-up.
- female
-
-
-
-
- %1 flips %2 face-up.
- male
-
-
-
-
- %1 destroys %2.
- female
-
-
+ %1 flips %2 face-up.
+ female
+
+
+
+
+ %1 flips %2 face-up.
+ male
+
+
+
+
+ %1 destroys %2.
+ female
+
+
+
+
%1 destroys %2.
male
-
+
%1 unattaches %2.
female
-
+
%1 unattaches %2.
male
-
+
%1 creates token: %2%3.
female
-
-
- %1 creates token: %2%3.
- male
-
-
+ %1 creates token: %2%3.
+ male
+
+
+
+
%1 points from her %2 to herself.
female
-
+
%1 points from his %2 to himself.
male
-
+
%1 points from her %2 to %3.
p1 female, p2 female
-
+
%1 points from her %2 to %3.
p1 female, p2 male
-
+
%1 points from his %2 to %3.
p1 male, p2 female
-
+
%1 points from his %2 to %3.
p1 male, p2 male
-
+
%1 points from %2's %3 to herself.
card owner female, target female
-
+
%1 points from %2's %3 to herself.
card owner male, target female
-
+
%1 points from %2's %3 to himself.
card owner female, target male
-
+
%1 points from %2's %3 to himself.
card owner male, target male
-
+
%1 points from %2's %3 to %4.
p1 female, p2 female, p3 female
-
+
%1 points from %2's %3 to %4.
p1 female, p2 female, p3 male
-
+
%1 points from %2's %3 to %4.
p1 female, p2 male, p3 female
-
+
%1 points from %2's %3 to %4.
p1 female, p2 male, p3 male
-
+
%1 points from %2's %3 to %4.
p1 male, p2 female, p3 female
-
+
%1 points from %2's %3 to %4.
p1 male, p2 female, p3 male
-
+
%1 points from %2's %3 to %4.
p1 male, p2 male, p3 female
-
+
%1 points from %2's %3 to %4.
p1 male, p2 male, p3 male
-
+
%1 points from her %2 to her %3.
female
-
+
%1 points from his %2 to his %3.
male
-
+
%1 points from her %2 to %3's %4.
p1 female, p2 female
-
+
%1 points from her %2 to %3's %4.
p1 female, p2 male
-
+
%1 points from his %2 to %3's %4.
p1 male, p2 female
-
+
%1 points from his %2 to %3's %4.
p1 male, p2 male
-
+
%1 points from %2's %3 to her own %4.
card owner female, target female
-
+
%1 points from %2's %3 to her own %4.
card owner male, target female
-
+
%1 points from %2's %3 to his own %4.
card owner female, target male
-
+
%1 points from %2's %3 to his own %4.
card owner male, target male
-
+
%1 points from %2's %3 to %4's %5.
p1 female, p2 female, p3 female
-
+
%1 points from %2's %3 to %4's %5.
p1 female, p2 female, p3 male
-
+
%1 points from %2's %3 to %4's %5.
p1 female, p2 male, p3 female
-
+
%1 points from %2's %3 to %4's %5.
p1 female, p2 male, p3 male
-
+
%1 points from %2's %3 to %4's %5.
p1 male, p2 female, p3 female
-
+
%1 points from %2's %3 to %4's %5.
p1 male, p2 female, p3 male
-
+
%1 points from %2's %3 to %4's %5.
p1 male, p2 male, p3 female
-
+
%1 points from %2's %3 to %4's %5.
p1 male, p2 male, p3 male
@@ -3038,121 +3054,121 @@ You need to provide the activation token received in the activation email
-
+
%1 taps her permanents.
female
-
+
%1 untaps her permanents.
female
-
+
%1 taps his permanents.
male
-
+
%1 untaps his permanents.
male
-
+
%1 taps %2.
female
-
+
%1 untaps %2.
female
-
+
%1 taps %2.
male
-
+
%1 untaps %2.
male
-
+
%1 sets counter %2 to %3 (%4%5).
female
-
+
%1 sets counter %2 to %3 (%4%5).
male
-
+
%1 sets %2 to not untap normally.
female
-
-
- %1 sets %2 to not untap normally.
- male
-
-
-
-
- %1 sets %2 to untap normally.
- female
-
-
-
-
- %1 sets %2 to untap normally.
- male
-
-
+ %1 sets %2 to not untap normally.
+ male
+
+
+
+
+ %1 sets %2 to untap normally.
+ female
+
+
+
+
+ %1 sets %2 to untap normally.
+ male
+
+
+
+
%1 sets PT of %2 to %3.
female
-
+
%1 sets PT of %2 to %3.
male
-
+
%1 sets annotation of %2 to %3.
female
-
+
%1 sets annotation of %2 to %3.
male
-
+
%1 is looking at %2.
female
-
+
%1 is looking at %2.
male
@@ -3174,174 +3190,174 @@ You need to provide the activation token received in the activation email
-
+
%1 stops looking at %2.
female
-
+
%1 stops looking at %2.
male
-
+
%1 reveals %2 to %3.
p1 female, p2 female
-
+
%1 reveals %2 to %3.
p1 female, p2 male
-
+
%1 reveals %2 to %3.
p1 male, p2 female
-
+
%1 reveals %2 to %3.
p1 male, p2 male
-
+
%1 reveals %2.
female
-
+
%1 reveals %2.
male
-
+
%1 randomly reveals %2%3 to %4.
p1 female, p2 female
-
+
%1 randomly reveals %2%3 to %4.
p1 female, p2 male
-
-
- %1 randomly reveals %2%3 to %4.
- p1 male, p2 female
-
-
-
-
- %1 randomly reveals %2%3 to %4.
- p1 male, p2 male
-
-
-
-
- %1 randomly reveals %2%3.
- female
-
-
-
-
- %1 randomly reveals %2%3.
- male
-
-
-
-
- %1 peeks at face down card #%2.
- female
-
-
+ %1 randomly reveals %2%3 to %4.
+ p1 male, p2 female
+
+
+
+
+ %1 randomly reveals %2%3 to %4.
+ p1 male, p2 male
+
+
+
+
+ %1 randomly reveals %2%3.
+ female
+
+
+
+
+ %1 randomly reveals %2%3.
+ male
+
+
+
+
+ %1 peeks at face down card #%2.
+ female
+
+
+
+
%1 peeks at face down card #%2.
male
-
+
%1 peeks at face down card #%2: %3.
female
-
+
%1 peeks at face down card #%2: %3.
male
-
+
%1 reveals %2%3 to %4.
p1 female, p2 female
-
+
%1 reveals %2%3 to %4.
p1 female, p2 male
-
+
%1 reveals %2%3 to %4.
p1 male, p2 female
-
+
%1 reveals %2%3 to %4.
p1 male, p2 male
-
+
%1 reveals %2%3.
female
-
+
%1 reveals %2%3.
male
-
+
%1 is now keeping the top card %2 revealed.
-
+
%1 is not revealing the top card %2 any longer.
-
+
It is now %1's turn.
female
-
+
It is now %1's turn.
male
-
-
+
+
a card
@@ -3360,7 +3376,7 @@ You need to provide the activation token received in the activation email
-
+
red
@@ -3368,7 +3384,7 @@ You need to provide the activation token received in the activation email
-
+
yellow
@@ -3376,7 +3392,7 @@ You need to provide the activation token received in the activation email
-
+
green
@@ -3384,17 +3400,17 @@ You need to provide the activation token received in the activation email
-
+
The game has started.
-
+
%1 draws his initial hand.
-
+
%1 draws her initial hand.
@@ -3413,179 +3429,179 @@ You need to provide the activation token received in the activation email
-
+
ending phase
-
+
untap step
-
+
%1 draws %2 card(s).
female
-
+
%1 draws %2 card(s).
male
-
+
from play
-
+
from her graveyard
-
+
from his graveyard
-
+
from her hand
-
+
from his hand
-
+
%1 puts %2%3 into her graveyard.
-
+
%1 puts %2%3 into his graveyard.
-
+
%1 moves %2%3 to her hand.
-
+
%1 moves %2%3 to his hand.
-
+
%1 attaches %2 to %3's %4.
p1 female, p2 female
-
+
%1 attaches %2 to %3's %4.
p1 female, p2 male
-
+
%1 attaches %2 to %3's %4.
p1 male, p2 female
-
+
%1 attaches %2 to %3's %4.
p1 male, p2 male
-
+
%1 places %2 %3 counter(s) on %4 (now %5).
female
-
+
%1 places %2 %3 counter(s) on %4 (now %5).
male
-
+
%1 removes %2 %3 counter(s) from %4 (now %5).
female
-
+
%1 removes %2 %3 counter(s) from %4 (now %5).
male
-
+
%1 is looking at the top %2 card(s) %3.
female
-
+
%1 is looking at the top %2 card(s) %3.
male
-
+
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
-
+
It is now the %1.
@@ -3593,74 +3609,79 @@ You need to provide the activation token received in the activation email
MessagesSettingsPage
-
+
Chat settings
-
+
Custom alert words
-
+
Enable chat mentions
-
+
Enable mention completer
-
+
In-game message macros
-
+
Ignore chat room messages sent by unregistered users
-
+
Ignore private messages sent by unregistered users
-
+
Enable desktop notifications for private messages
-
+
+ Enable desktop notification for mentions
+
+
+
+
+ Enable room message history on join
+
+
+
+
Separate words with a space, alphanumeric characters only
-
-
-
- Invert text color
-
-
-
-
- Enable desktop notification for mentions.
-
-
+ Invert text color
+
+
+
+
+
(Color is hexadecimal)
-
+
Add message
-
+
Message:
@@ -4104,7 +4125,7 @@ You need to provide the activation token received in the activation email
-
+
Number:
@@ -4139,22 +4160,22 @@ You need to provide the activation token received in the activation email
-
+
Set annotation
-
+
Please enter the new annotation:
-
+
Set counters
-
+
Cr&eate related card
@@ -4206,7 +4227,7 @@ You need to provide the activation token received in the activation email
- Common deck formats (*.cod *.dec *.mwDeck)
+ Common deck formats (*.cod *.dec *.txt *.mwDeck)
@@ -4303,27 +4324,11 @@ You need to provide the activation token received in the activation emailGames
-
-
-
- Error
-
-
-
-
- You do not have the proper permission to join this room.
-
-
-
-
- Failed to join the room due to an unknown error.
-
-
SequenceEdit
-
+
Shortcut already in use
@@ -4356,6 +4361,29 @@ You need to provide the activation token received in the activation email
+
+ ShortcutsTab
+
+
+ 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?
+
+
+
ShutdownDialog
@@ -4377,37 +4405,32 @@ You need to provide the activation token received in the activation email
SoundSettingsPage
-
- Choose path
-
-
-
-
+
Enable &sounds
-
- Path to sounds directory:
+
+ Current sounds theme:
-
+
Test system sound engine
-
+
Sound settings
-
+
Master volume requires QT5
-
+
Master volume
@@ -4603,12 +4626,12 @@ You need to provide the activation token received in the activation email
-
+
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 "Edit Sets" window.
@@ -4867,40 +4890,198 @@ Please enter a name:
+
+ TabLog
+
+
+ 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
+
+
+
+
+ Room Logs
+
+
+
+
+
+
+ Time;SenderName;SenderIP;Message;TargetID;TargetName
+
+
+
+
+ Game Logs
+
+
+
+
+ Chat Logs
+
+
+
+
+
+ Message History
+
+
+
+
+ There are no messages for the selected filters.
+
+
+
+
+ Failed to collect message history information.
+
+
+
TabMessage
-
+
Private &chat
-
+
&Leave
-
+
%1 - Private chat
-
+
This user is ignoring you.
-
+
Private message from
-
+
%1 has left the server.
-
+
%1 has joined the server.
@@ -4968,47 +5149,47 @@ Please enter a name:
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.
@@ -5016,33 +5197,72 @@ Please enter a name:
TabServer
-
+
Server
+
+
+
+
+
+ Error
+
+
+
+
+ Failed to join the room: it doesn't exists on the server.
+
+
+
+
+ The server thinks you are in the room but Cockatrice is unable to display it. Try restarting Cockatrice.
+
+
+
+
+ You do not have the required permission to join this room.
+
+
+
+
+ Failed to join the room due to an unknown error: %1.
+
+
TabSupervisor
-
+
Are you sure?
-
+
There are still open games. Are you sure you want to quit?
-
+
Promotion
-
+
You have been promoted to moderator. 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.
+
+
TabUserLists
@@ -5065,94 +5285,153 @@ Please enter a name:
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
-
+
%1's games
-
-
+
+
+
+ Ban History
+
+
+
+
+ Ban Time;Moderator;Ban Length;Ban Reason;Visible Reason
+
+
+
+
+ User has never been banned.
+
+
+
+
+ Failed to collecting ban information.
+
+
+
+
+
+
+ Warning History
+
+
+
+
+ Warning Time;Moderator;User Name;Reason
+
+
+
+
+ User has never been warned.
+
+
+
+
+ Failed to collecting warning information.
+
+
+
+
+
Success
-
+
Successfully promoted user.
-
+
Successfully demoted user.
-
-
+
+
Failed
-
+
Failed to promote user.
-
+
Failed to demote user.
@@ -5367,26 +5646,65 @@ Please enter a name:
UserList
-
+
Users connected to server: %1
-
+
Users in this room: %1
-
+
Buddies online: %1 / %2
-
+
Ignored users online: %1 / %2
+
+ WarningDialog
+
+
+ Which warning would you like to send?
+
+
+
+
+ &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
@@ -5463,22 +5781,22 @@ Please enter a name:
ZoneViewWidget
-
+
sort by name
-
+
sort by type
-
+
shuffle when closing
-
+
pile view
@@ -5486,557 +5804,572 @@ Please enter a name:
shortcutsTab
-
+
Form
-
+
Main Window
-
+
Deck editor
-
+
Local gameplay
-
+
Watch replay
-
+
Connect
-
+
Register
-
+
Full screen
-
+
Settings
-
+
Check for card updates
-
+
Exit
-
+
Deck Editor
-
+
Analyze deck
-
+
Load deck (clipboard)
-
+
Clerar all filters
-
+
New deck
-
+
Clear one filter
-
+
Open custom folder
-
+
Close
-
+
Print deck
-
+
Edit sets
-
+
Delete card
-
+
Edit tokens
-
+
Reset layout
-
+
Add card
-
+
Save deck
-
+
Remove card
-
+
Save deck as
-
+
Load deck
-
+
Save deck (clipboard)
-
-
+
+
Counters
-
+
Life
-
-
-
-
-
+
+
+
+
+
Set
-
-
-
-
+
+
+
+
Add
-
-
-
-
+
+
+
+
Remove
-
+
Red
-
+
Green
-
+
Yellow
-
-
- Mainwindow / Deck editor
-
-
-
-
- Power / toughness
-
-
-
-
- Power and toughness
-
-
-
-
- Add (+1/+1)
-
-
-
-
- Remove (-1/-1)
-
-
-
-
- Toughness
-
-
-
-
- Remove (-0/-1)
-
-
-
-
- Add (+0/+1)
-
-
-
-
- Power
-
-
-
-
- Remove (-1/-0)
-
-
-
-
- Add (+1/+0)
-
-
-
-
- Game Phases
-
-
-
-
- Untap
-
-
-
-
- Disconnect
-
-
-
-
- Upkeep
-
-
-
-
-
- Draw
-
-
-
-
- Main 1
-
-
-
-
- Start combat
-
-
-
-
- Attack
-
-
-
-
- Block
-
-
-
-
- Damage
-
-
-
-
- End combat
-
-
-
-
- Main 2
-
-
-
-
- End
-
-
-
-
- Next phase
-
-
-
-
- Next turn
-
-
-
-
- Player
-
-
-
-
- Tap Card
-
-
-
-
- Untap Card
-
-
-
-
- Untap all
-
-
-
-
- Toogle untap
-
-
-
-
- Flip card
-
-
-
-
- Peek card
-
-
-
-
- Play card
-
-
-
-
- Attach card
-
-
-
-
- Unattach card
-
-
-
-
- Clone card
-
-
-
-
- Create token
-
-
-
-
- Create another token
-
-
-
-
- Set annotation
-
-
-
-
- Phases / P/T / Player
-
-
-
-
- Move card to
-
-
-
-
- Bottom library
-
-
-
-
- Top library
-
-
-
-
-
- Graveyard
-
-
-
-
-
- Exile
-
-
-
-
- Hand
-
-
-
-
- View
-
-
- Library
-
-
-
-
- Tops card of library
-
-
-
-
- Sideboard
-
-
-
-
- Close recent view
-
-
-
-
- Pre-play
-
-
-
-
- Load remote deck
-
-
-
-
- Load local deck
-
-
-
-
- Game play
-
-
-
-
- Draw arrow
-
-
-
-
- Leave game
-
-
-
-
- Remove local arrows
-
-
-
-
- Concede
-
-
-
-
- Roll dice
+ Playing Area
- Rotate view CW
+ Phases | P/T | Playing Area
+
+
+
+
+ Game Lobby
+
+
+
+
+ Gameplay
+
+
+
+
+ Draw | Move | View | Gameplay
+
+
+
+
+ How to set custom shortcuts
+
+
+
+
+ Restore all default shortcuts
+
+
+
+
+ Clear all shortcuts
+
+
+
+
+ Add (+1/+1)
+
+
+
+
+ Remove (-1/-1)
+
+
+
+
+ Toughness
+
+
+
+
+ Remove (-0/-1)
+
+
+
+
+ Add (+0/+1)
+
+
+
+
+ Power
+
+
+
+
+ Remove (-1/-0)
+
+
+
+
+ Add (+1/+0)
+
+
+
+
+ Game Phases
+
+
+
+
+ Untap
+
+
+
+
+ Disconnect
+
+
+
+
+ Main Window | Deck Editor
+
+
+
+
+ Power / Toughness
+
+
+
+
+ Power and Toughness
+
+
+
+
+ Upkeep
+
+
+
+
+
+ Draw
+
+
+
+
+ Main 1
+
+
+
+
+ Start combat
+
+
+
+
+ Attack
+
+
+
+
+ Block
+
+
+
+
+ Damage
+
+
+
+
+ End combat
+
+
+
+
+ Main 2
+
+
+
+
+ End
+
+
+
+
+ Next phase
+
+
+
+
+ Next turn
+
+
+
+
+ Tap Card
+
+
+
+
+ Untap Card
+
+
+
+
+ Untap all
+
+
+
+
+ Toogle untap
+
+
+
+
+ Flip card
+
+
+
+
+ Peek card
+
+
+
+
+ Play card
+
+
+
+
+ Attach card
+
+
+
+
+ Unattach card
+
+
+
+
+ Clone card
+
+
+
+
+ Create token
+
+
+
+
+ Create another token
+
+
+
+
+ Set annotation
- Shuffle library
+ Move card to
- Rotate view CCW
+ Bottom library
+
+
+
+
+ Top library
- Mulligan
+
+ Graveyard
- Draw card
+
+ Exile
- Draw cards
+ Hand
- Undo draw
-
-
-
-
- Always reveal top card
+ View
- Draw / Move / View / Game play
+ Library
+
+
+
+
+ Tops card of library
+
+
+
+
+ Sideboard
+
+
+
+
+ Close recent view
+
+
+
+
+ Load remote deck
+
+
+
+
+ Load local deck
+
+
+
+
+ Draw arrow
+
+
+
+
+ Leave game
+
+
+
+
+ Remove local arrows
+
+
+
+
+ Concede
+
+
+
+
+ Roll dice
+
+
+
+
+ Rotate view CW
+
+
+
+
+ Shuffle library
+
+
+
+
+ Rotate view CCW
+
+
+
+
+ Mulligan
+
+
+
+
+ Draw card
+
+
+
+
+ Draw cards
+
+
+
+
+ Undo draw
+
+
+
+
+ Always reveal top card
diff --git a/common/server_abstractuserinterface.cpp b/common/server_abstractuserinterface.cpp
index d00c5034..632bd320 100644
--- a/common/server_abstractuserinterface.cpp
+++ b/common/server_abstractuserinterface.cpp
@@ -86,6 +86,8 @@ void Server_AbstractUserInterface::joinPersistentGames(ResponseContainer &rc)
QMutexLocker gameLocker(&game->gameMutex);
Server_Player *player = game->getPlayers().value(pr.getPlayerId());
+ if (!player)
+ continue;
player->setUserInterface(this);
playerAddedToGame(game->getGameId(), room->getId(), player->getPlayerId());
diff --git a/common/server_cardzone.cpp b/common/server_cardzone.cpp
index 5ae65093..3e128a3e 100644
--- a/common/server_cardzone.cpp
+++ b/common/server_cardzone.cpp
@@ -147,7 +147,12 @@ int Server_CardZone::getFreeGridColumn(int x, int y, const QString &cardName, bo
if (x == -1) {
if (!dontStackSameName && freePilesMap[y].contains(cardName)) {
x = (freePilesMap[y].value(cardName) / 3) * 3;
- if (!coordMap.contains(x))
+
+ if(coordMap.contains(x) &&
+ (coordMap[x]->getFaceDown() ||
+ !coordMap[x]->getAttachedCards().isEmpty())) {
+ // don't pile up on: 1. facedown cards 2. cards with attached cards
+ } else if (!coordMap.contains(x))
return x;
else if (!coordMap.contains(x + 1))
return x + 1;
diff --git a/common/server_protocolhandler.cpp b/common/server_protocolhandler.cpp
index 0be08125..406e20e7 100644
--- a/common/server_protocolhandler.cpp
+++ b/common/server_protocolhandler.cpp
@@ -387,7 +387,7 @@ Response::ResponseCode Server_ProtocolHandler::cmdLogin(const Command_Login &cmd
QString clientId = QString::fromStdString(cmd.clientid()).simplified();
QString clientVersion = QString::fromStdString(cmd.clientver()).simplified();
- if (userName.isEmpty() || (userInfo != 0))
+ if (userInfo != 0)
return Response::RespContextError;
// check client feature set against server feature set
@@ -603,8 +603,11 @@ Response::ResponseCode Server_ProtocolHandler::cmdJoinRoom(const Command_JoinRoo
joinMessageEvent.set_message_type(Event_RoomSay::Welcome);
rc.enqueuePostResponseItem(ServerMessage::ROOM_EVENT, r->prepareRoomEvent(joinMessageEvent));
- ServerInfo_ChatMessage chatMessage; for (int i = 0; i < r->chatHistory.size(); ++i) {
- chatMessage = r->chatHistory.at(i);
+ QReadLocker chatHistoryLocker(&r->historyLock);
+ QList chatHistory = r->getChatHistory();
+ ServerInfo_ChatMessage chatMessage;
+ for (int i = 0; i < chatHistory.size(); ++i) {
+ chatMessage = chatHistory.at(i);
qDebug() << QString::fromStdString(chatMessage.message()).simplified();
Event_RoomSay roomChatHistory;
roomChatHistory.set_message(chatMessage.sender_name() + ": " + chatMessage.message());
diff --git a/common/server_room.cpp b/common/server_room.cpp
index a53fdd97..e9338627 100644
--- a/common/server_room.cpp
+++ b/common/server_room.cpp
@@ -236,7 +236,6 @@ void Server_Room::say(const QString &userName, const QString &s, bool sendToIsl)
sendRoomEvent(prepareRoomEvent(event), sendToIsl);
if (chatHistorySize != 0) {
-
ServerInfo_ChatMessage chatMessage;
QDateTime dateTime = dateTime.currentDateTimeUtc();
QString dateTimeString = dateTime.toString();
@@ -244,10 +243,12 @@ void Server_Room::say(const QString &userName, const QString &s, bool sendToIsl)
chatMessage.set_sender_name(userName.toStdString());
chatMessage.set_message(s.simplified().toStdString());
+ historyLock.lockForWrite();
if (chatHistory.size() >= chatHistorySize)
chatHistory.removeAt(0);
chatHistory << chatMessage;
+ historyLock.unlock();
}
}
diff --git a/common/server_room.h b/common/server_room.h
index 6e79ba6b..af652944 100644
--- a/common/server_room.h
+++ b/common/server_room.h
@@ -42,12 +42,13 @@ private:
QMap externalGames;
QMap users;
QMap externalUsers;
+ QList chatHistory;
private slots:
void broadcastGameListUpdate(const ServerInfo_Game &gameInfo, bool sendToIsl = true);
public:
mutable QReadWriteLock usersLock;
mutable QReadWriteLock gamesLock;
- QList chatHistory;
+ mutable QReadWriteLock historyLock;
Server_Room(int _id, int _chatHistorySize, const QString &_name, const QString &_description, const QString &_permissionLevel, bool _autoJoin, const QString &_joinMessage, const QStringList &_gameTypes, Server *parent );
~Server_Room();
int getId() const { return id; }
@@ -63,7 +64,7 @@ public:
const ServerInfo_Room &getInfo(ServerInfo_Room &result, bool complete, bool showGameTypes = false, bool includeExternalData = true) const;
int getGamesCreatedByUser(const QString &name) const;
QList getGamesOfUser(const QString &name) const;
- QList getChatHistory() { return chatHistory; }
+ QList & getChatHistory() { return chatHistory; }
void addClient(Server_ProtocolHandler *client);
void removeClient(Server_ProtocolHandler *client);
diff --git a/doc/cards.xsd b/doc/cards.xsd
index d10032a7..1e145e32 100644
--- a/doc/cards.xsd
+++ b/doc/cards.xsd
@@ -29,12 +29,13 @@
-
+
+
diff --git a/oracle/CMakeLists.txt b/oracle/CMakeLists.txt
index 05c8a2e4..7851c1d2 100644
--- a/oracle/CMakeLists.txt
+++ b/oracle/CMakeLists.txt
@@ -12,6 +12,7 @@ SET(oracle_SOURCES
src/oraclewizard.cpp
src/oracleimporter.cpp
../cockatrice/src/carddatabase.cpp
+ ../cockatrice/src/pictureloader.cpp
../cockatrice/src/settingscache.cpp
../cockatrice/src/shortcutssettings.cpp
../cockatrice/src/settings/carddatabasesettings.cpp
@@ -24,10 +25,6 @@ SET(oracle_SOURCES
../cockatrice/src/qt-json/json.cpp
)
-if (UNIX AND NOT APPLE)
- set_source_files_properties(src/main.cpp PROPERTIES COMPILE_FLAGS -DTRANSLATION_PATH=\\"${CMAKE_INSTALL_PREFIX}/share/oracle/translations\\")
-endif (UNIX AND NOT APPLE)
-
set(oracle_RESOURCES oracle.qrc)
IF(UPDATE_TRANSLATIONS)
@@ -185,6 +182,7 @@ if(APPLE)
# these needs to be relative to CMAKE_INSTALL_PREFIX
set(plugin_dest_dir oracle.app/Contents/Plugins)
set(qtconf_dest_dir oracle.app/Contents/Resources)
+ get_filename_component(QT_LIBRARY_DIR "${QT_LIBRARY_DIR}/.." ABSOLUTE)
# qt4: codecs, iconengines, imageformats
# qt5: iconengines, platforms
@@ -214,10 +212,13 @@ IF(WIN32)
set(qtconf_dest_dir .)
list(APPEND libSearchDirs ${QT_LIBRARY_DIR})
IF(ZLIB_FOUND)
+ # look for dll in the bin/ directory (gnuwin32 package)
get_filename_component(ZLIB_DLL_DIR "${ZLIB_INCLUDE_DIRS}/../bin/" REALPATH)
list(APPEND libSearchDirs ${ZLIB_DLL_DIR})
+ # look for dll in the lib/ directory (nuget package)
+ get_filename_component(ZLIB_DLL_DIR "${ZLIB_LIBRARY}" DIRECTORY)
+ list(APPEND libSearchDirs ${ZLIB_DLL_DIR})
ENDIF()
- MESSAGE(STATUS "Oracle: ZLIB dll found at ${ZLIB_DLL_DIR}")
# qt4: codecs, iconengines, imageformats
# qt5: iconengines, imageformats, platforms
diff --git a/oracle/src/main.cpp b/oracle/src/main.cpp
index 60e5e8d9..5a6e0367 100644
--- a/oracle/src/main.cpp
+++ b/oracle/src/main.cpp
@@ -14,11 +14,7 @@ SettingsCache *settingsCache;
ThemeManager *themeManager;
const QString translationPrefix = "oracle";
-#ifdef TRANSLATION_PATH
-QString translationPath = TRANSLATION_PATH;
-#else
-QString translationPath = QString();
-#endif
+QString translationPath;
void installNewTranslator()
{
@@ -44,13 +40,13 @@ int main(int argc, char *argv[])
// this can't be changed, as it influences the default savepath for cards.xml
QCoreApplication::setApplicationName("Cockatrice");
- if (translationPath.isEmpty()) {
#ifdef Q_OS_MAC
- translationPath = QLibraryInfo::location(QLibraryInfo::TranslationsPath);
+ translationPath = qApp->applicationDirPath() + "/../Resources/translations";
#elif defined(Q_OS_WIN)
- translationPath = app.applicationDirPath() + "/translations";
+ translationPath = qApp->applicationDirPath() + "/translations";
+#else // linux
+ translationPath = qApp->applicationDirPath() + "/../share/cockatrice/translations";
#endif
- }
settingsCache = new SettingsCache;
themeManager = new ThemeManager;
diff --git a/oracle/src/oracleimporter.cpp b/oracle/src/oracleimporter.cpp
index 8eef9a2f..7659f465 100644
--- a/oracle/src/oracleimporter.cpp
+++ b/oracle/src/oracleimporter.cpp
@@ -67,6 +67,7 @@ CardInfo *OracleImporter::addCard(const QString &setName,
const QString &cardText,
const QStringList & colors,
const QStringList & relatedCards,
+ const QStringList & reverseRelatedCards,
bool upsideDown
)
{
@@ -95,7 +96,7 @@ CardInfo *OracleImporter::addCard(const QString &setName,
bool cipt = cardText.contains("Hideaway") || (cardText.contains(cardName + " enters the battlefield tapped") && !cardText.contains(cardName + " enters the battlefield tapped unless"));
// insert the card and its properties
- card = new CardInfo(this, cardName, isToken, cardCost, cmc, cardType, cardPT, cardText, colors, relatedCards, upsideDown, cardLoyalty, cipt);
+ card = new CardInfo(this, cardName, isToken, cardCost, cmc, cardType, cardPT, cardText, colors, relatedCards, reverseRelatedCards, upsideDown, cardLoyalty, cipt);
int tableRow = 1;
QString mainCardType = card->getMainCardType();
if ((mainCardType == "Land") || mArtifact)
@@ -146,6 +147,7 @@ int OracleImporter::importTextSpoiler(CardSet *set, const QVariant &data)
QString cardText;
QStringList colors;
QStringList relatedCards;
+ QStringList reverseRelatedCards; // dummy
int cardId;
int cardLoyalty;
bool upsideDown = false;
@@ -192,7 +194,7 @@ int OracleImporter::importTextSpoiler(CardSet *set, const QVariant &data)
colors.clear();
extractColors(map.value("colors").toStringList(), colors);
- CardInfo *card = addCard(set->getShortName(), cardName, false, cardId, cardCost, cmc, cardType, cardPT, cardLoyalty, cardText, colors, relatedCards, upsideDown);
+ CardInfo *card = addCard(set->getShortName(), cardName, false, cardId, cardCost, cmc, cardType, cardPT, cardLoyalty, cardText, colors, relatedCards, reverseRelatedCards, upsideDown);
if (!set->contains(card)) {
card->addToSet(set);
@@ -276,10 +278,11 @@ int OracleImporter::importTextSpoiler(CardSet *set, const QVariant &data)
colors.removeDuplicates();
relatedCards = QStringList();
+ reverseRelatedCards = QStringList();
upsideDown = false;
// add the card
- CardInfo *card = addCard(set->getShortName(), cardName, false, muid, cardCost, cmc, cardType, cardPT, cardLoyalty, cardText, colors, relatedCards, upsideDown);
+ CardInfo *card = addCard(set->getShortName(), cardName, false, muid, cardCost, cmc, cardType, cardPT, cardLoyalty, cardText, colors, relatedCards, reverseRelatedCards, upsideDown);
if (!set->contains(card)) {
card->addToSet(set);
diff --git a/oracle/src/oracleimporter.h b/oracle/src/oracleimporter.h
index 888eabaf..6a30e65b 100644
--- a/oracle/src/oracleimporter.h
+++ b/oracle/src/oracleimporter.h
@@ -2,6 +2,7 @@
#define ORACLEIMPORTER_H
#include
+#include
#include
@@ -29,7 +30,7 @@ private:
QVariantMap setsMap;
QString dataDir;
- CardInfo *addCard(const QString &setName, QString cardName, bool isToken, int cardId, QString &cardCost, QString &cmc, const QString &cardType, const QString &cardPT, int cardLoyalty, const QString &cardText, const QStringList & colors, const QStringList & relatedCards, bool upsideDown);
+ CardInfo *addCard(const QString &setName, QString cardName, bool isToken, int cardId, QString &cardCost, QString &cmc, const QString &cardType, const QString &cardPT, int cardLoyalty, const QString &cardText, const QStringList & colors, const QStringList & relatedCards, const QStringList & reverseRelatedCards, bool upsideDown);
signals:
void setIndexChanged(int cardsImported, int setIndex, const QString &setName);
void dataReadProgress(int bytesRead, int totalBytes);
diff --git a/servatrice/CMakeLists.txt b/servatrice/CMakeLists.txt
index 047a3f53..d3522fd3 100644
--- a/servatrice/CMakeLists.txt
+++ b/servatrice/CMakeLists.txt
@@ -83,6 +83,28 @@ endif()
SET(QT_DONT_USE_QTGUI TRUE)
+# Mysql connector
+if(UNIX)
+ if(APPLE)
+ SET(MYSQLCLIENT_DEFAULT_PATHS "/usr/local/lib" "/opt/local/lib/mysql55/mysql/" "/opt/local/lib/mysql56/mysql/")
+ else()
+ SET(MYSQLCLIENT_DEFAULT_PATHS "/usr/lib" "/usr/local/lib")
+ endif()
+elseif(WIN32)
+ SET(MYSQLCLIENT_DEFAULT_PATHS "C:\\Program Files\\MySQL\\MySQL Server 5.5\\lib" "C:\\Program Files\\MySQL\\MySQL Server 5.6\\lib")
+endif()
+
+find_library(MYSQLCLIENT_LIBRARIES NAMES mysqlclient PATHS ${MYSQLCLIENT_DEFAULT_PATHS} PATH_SUFFIXES mysql mariadb)
+if(${MYSQLCLIENT_LIBRARIES} MATCHES "NOTFOUND")
+ set(MYSQLCLIENT_FOUND FALSE CACHE INTERNAL "")
+ MESSAGE(STATUS "Mysql connector NOT FOUND: servatrice won't be able to connect to a mysql server")
+ unset(MYSQLCLIENT_LIBRARIES)
+else()
+ set(MYSQLCLIENT_FOUND TRUE CACHE INTERNAL "")
+ get_filename_component(MYSQLCLIENT_LIBRARY_DIR ${MYSQLCLIENT_LIBRARIES} PATH)
+ MESSAGE(STATUS "Mysql connector found at: ${MYSQLCLIENT_LIBRARY_DIR}")
+endif()
+
# Declare path variables
set(ICONDIR share/icons CACHE STRING "icon dir")
set(DESKTOPDIR share/applications CACHE STRING "desktop file destination")
@@ -146,12 +168,13 @@ if(APPLE)
# these needs to be relative to CMAKE_INSTALL_PREFIX
set(plugin_dest_dir servatrice.app/Contents/Plugins)
set(qtconf_dest_dir servatrice.app/Contents/Resources)
+ get_filename_component(QT_LIBRARY_DIR "${QT_LIBRARY_DIR}/.." ABSOLUTE)
# qt4: codecs, sqldrivers
# qt5: platforms, sqldrivers
install(DIRECTORY "${QT_PLUGINS_DIR}/" DESTINATION ${plugin_dest_dir} COMPONENT Runtime
- FILES_MATCHING REGEX "(codecs|platforms|sqldrivers)/.*\\.dylib"
+ FILES_MATCHING REGEX "(codecs/.*|platforms/.*|sqldrivers/libqsqlmysql)\\.dylib"
REGEX ".*_debug\\.dylib" EXCLUDE)
install(CODE "
@@ -165,7 +188,7 @@ Translations = Resources/translations\")
\"\${CMAKE_INSTALL_PREFIX}/${plugin_dest_dir}/*.dylib\")
set(BU_CHMOD_BUNDLE_ITEMS ON)
include(BundleUtilities)
- fixup_bundle(\"\${CMAKE_INSTALL_PREFIX}/servatrice.app\" \"\${QTPLUGINS}\" \"${QT_LIBRARY_DIR}\")
+ fixup_bundle(\"\${CMAKE_INSTALL_PREFIX}/servatrice.app\" \"\${QTPLUGINS}\" \"${QT_LIBRARY_DIR};${MYSQLCLIENT_LIBRARY_DIR}\")
" COMPONENT Runtime)
endif()
@@ -178,7 +201,8 @@ if(WIN32)
# qt5: platforms, sqldrivers
install(DIRECTORY "${QT_PLUGINS_DIR}/" DESTINATION ${plugin_dest_dir} COMPONENT Runtime
- FILES_MATCHING REGEX "(codecs|platforms|sqldrivers)/.*[^d]\\.dll")
+ FILES_MATCHING REGEX "(codecs/.*|platforms/.*|sqldrivers/libqsqlmysql)\\.dll"
+ REGEX ".*d\\.dll" EXCLUDE)
install(CODE "
file(WRITE \"\${CMAKE_INSTALL_PREFIX}/${qtconf_dest_dir}/qt.conf\" \"[Paths]
@@ -191,7 +215,7 @@ Translations = Resources/translations\")
\"\${CMAKE_INSTALL_PREFIX}/${plugin_dest_dir}/*.dll\")
set(BU_CHMOD_BUNDLE_ITEMS ON)
include(BundleUtilities)
- fixup_bundle(\"\${CMAKE_INSTALL_PREFIX}/servatrice.exe\" \"\${QTPLUGINS}\" \"${QT_LIBRARY_DIR}\")
+ fixup_bundle(\"\${CMAKE_INSTALL_PREFIX}/servatrice.exe\" \"\${QTPLUGINS}\" \"${QT_LIBRARY_DIR};${MYSQLCLIENT_LIBRARY_DIR}\")
" COMPONENT Runtime)
endif()
#Compile a portable version, default off
diff --git a/servatrice/check_schema_version.sh b/servatrice/check_schema_version.sh
new file mode 100755
index 00000000..d59edc7a
--- /dev/null
+++ b/servatrice/check_schema_version.sh
@@ -0,0 +1,33 @@
+#!/bin/bash
+
+set -e
+
+schema_ver="$(grep 'INSERT INTO cockatrice_schema_version' servatrice/servatrice.sql | sed 's/.*VALUES(//' | sed 's/).*//')"
+
+latest_migration="$(ls -1 servatrice/migrations/ | tail -n1)"
+xtoysql="${latest_migration#servatrice_}"
+xtoy="${xtoysql%.sql}"
+old_ver="$(echo ${xtoy%%_to_*} | bc)"
+new_ver="$(echo ${xtoy##*_to_} | bc)"
+
+if ((old_ver >= new_ver)); then
+ echo "New version $new_ver is not newer than $old_ver"
+ exit 1
+fi
+
+if ((schema_ver != new_ver)); then
+ echo "Schema version $schema_ver does not equal new version $new_ver"
+ exit 1
+fi
+
+expected_sql="^UPDATE cockatrice_schema_version SET version=${new_ver} WHERE version=${old_ver};$"
+if ! grep -q "$expected_sql" servatrice/migrations/$latest_migration; then
+ echo "$latest_migration does not contain expected sql: $expected_sql"
+ exit 1
+fi
+
+expected_define="^#define DATABASE_SCHEMA_VERSION $new_ver$"
+if ! grep -q "$expected_define" servatrice/src/servatrice_database_interface.h; then
+ echo "servatrice_database_interface.h does not contain expected #define: $expected_define"
+ exit 1
+fi
diff --git a/servatrice/migrations/servatrice_0011_to_0012.sql b/servatrice/migrations/servatrice_0011_to_0012.sql
new file mode 100644
index 00000000..37336320
--- /dev/null
+++ b/servatrice/migrations/servatrice_0011_to_0012.sql
@@ -0,0 +1,5 @@
+-- Servatrice db migration from version 11 to version 12
+
+alter table cockatrice_users modify token binary(16) NULL;
+
+UPDATE cockatrice_schema_version SET version=12 WHERE version=11;
diff --git a/servatrice/servatrice.sql b/servatrice/servatrice.sql
index 38f454e5..7a35b695 100644
--- a/servatrice/servatrice.sql
+++ b/servatrice/servatrice.sql
@@ -20,7 +20,7 @@ CREATE TABLE IF NOT EXISTS `cockatrice_schema_version` (
PRIMARY KEY (`version`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
-INSERT INTO cockatrice_schema_version VALUES(11);
+INSERT INTO cockatrice_schema_version VALUES(12);
CREATE TABLE IF NOT EXISTS `cockatrice_decklist_files` (
`id` int(7) unsigned zerofill NOT NULL auto_increment,
@@ -82,7 +82,7 @@ CREATE TABLE IF NOT EXISTS `cockatrice_users` (
`avatar_bmp` blob NOT NULL,
`registrationDate` datetime NOT NULL,
`active` tinyint(1) NOT NULL,
- `token` binary(16) NOT NULL,
+ `token` binary(16),
`clientid` varchar(15) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `name` (`name`),
diff --git a/servatrice/src/servatrice.cpp b/servatrice/src/servatrice.cpp
index df7dea96..95e01ef6 100644
--- a/servatrice/src/servatrice.cpp
+++ b/servatrice/src/servatrice.cpp
@@ -176,7 +176,8 @@ bool Servatrice::initServer()
bool registrationEnabled = settingsCache->value("registration/enabled", false).toBool();
bool requireEmailForRegistration = settingsCache->value("registration/requireemail", true).toBool();
- qDebug() << "Registration enabled: " << regServerOnly;
+ qDebug() << "Accept registered users only: " << regServerOnly;
+ qDebug() << "Registration enabled: " << registrationEnabled;
if (registrationEnabled)
qDebug() << "Require email address to register: " << requireEmailForRegistration;
diff --git a/servatrice/src/servatrice_database_interface.cpp b/servatrice/src/servatrice_database_interface.cpp
index b4f84667..e1f53ae0 100644
--- a/servatrice/src/servatrice_database_interface.cpp
+++ b/servatrice/src/servatrice_database_interface.cpp
@@ -124,6 +124,8 @@ bool Servatrice_DatabaseInterface::execSqlQuery(QSqlQuery *query)
bool Servatrice_DatabaseInterface::usernameIsValid(const QString &user, QString & error)
{
int minNameLength = settingsCache->value("users/minnamelength", 6).toInt();
+ if(minNameLength < 1)
+ minNameLength = 1;
int maxNameLength = settingsCache->value("users/maxnamelength", 12).toInt();
bool allowLowercase = settingsCache->value("users/allowlowercase", true).toBool();
bool allowUppercase = settingsCache->value("users/allowuppercase", true).toBool();
diff --git a/servatrice/src/servatrice_database_interface.h b/servatrice/src/servatrice_database_interface.h
index 01f53d79..14912131 100644
--- a/servatrice/src/servatrice_database_interface.h
+++ b/servatrice/src/servatrice_database_interface.h
@@ -9,7 +9,7 @@
#include "server.h"
#include "server_database_interface.h"
-#define DATABASE_SCHEMA_VERSION 11
+#define DATABASE_SCHEMA_VERSION 12
class Servatrice;
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
new file mode 100644
index 00000000..93deb7df
--- /dev/null
+++ b/tests/CMakeLists.txt
@@ -0,0 +1,38 @@
+enable_testing()
+add_test(NAME dummy_test COMMAND dummy_test)
+
+# Find GTest
+
+add_executable(dummy_test dummy_test.cpp)
+
+find_package(GTest)
+
+if(NOT GTEST_FOUND)
+ IF(NOT EXISTS "${CMAKE_BINARY_DIR}/gtest-build")
+ message(STATUS "Downloading googletest")
+ configure_file("${CMAKE_SOURCE_DIR}/cmake/gtest-CMakeLists.txt.in" "${CMAKE_BINARY_DIR}/gtest-download/CMakeLists.txt")
+ execute_process(COMMAND ${CMAKE_COMMAND} -G "${CMAKE_GENERATOR}" .
+ WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/gtest-download )
+ execute_process(COMMAND ${CMAKE_COMMAND} --build .
+ WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/gtest-download )
+ ELSE()
+ message(STATUS "GoogleTest directory exists")
+ ENDIF()
+
+ # Add gtest directly to our build
+ add_subdirectory(${CMAKE_BINARY_DIR}/gtest-src
+ ${CMAKE_BINARY_DIR}/gtest-build
+ EXCLUDE_FROM_ALL )
+
+ # Add the gtest include directory, since gtest
+ # doesn't add that dependency to its gtest target
+ target_include_directories(gtest INTERFACE
+ "${CMAKE_BINARY_DIR}/gtest-src/include" )
+
+ SET(GTEST_INCLUDE_DIRS "${CMAKE_BINARY_DIR}/gtest-src/include")
+ SET(GTEST_BOTH_LIBRARIES gtest)
+ add_dependencies(dummy_test gtest)
+endif()
+
+include_directories(${GTEST_INCLUDE_DIRS})
+target_link_libraries(dummy_test ${GTEST_BOTH_LIBRARIES})
\ No newline at end of file
diff --git a/tests/dummy_test.cpp b/tests/dummy_test.cpp
new file mode 100644
index 00000000..a5416d09
--- /dev/null
+++ b/tests/dummy_test.cpp
@@ -0,0 +1,16 @@
+#include "gtest/gtest.h"
+
+namespace {
+ class FooTest : public ::testing::Test {
+
+ };
+
+ TEST(DummyTest, Works) {
+ ASSERT_EQ(1, 1) << "One is not equal to one";
+ }
+}
+
+int main(int argc, char **argv) {
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
\ No newline at end of file
diff --git a/themes/VelvetMarble/zones/playerzone.jpg b/themes/VelvetMarble/zones/playerzone.jpg
index 9f511491..dd13cab7 100644
Binary files a/themes/VelvetMarble/zones/playerzone.jpg and b/themes/VelvetMarble/zones/playerzone.jpg differ
diff --git a/themes/VelvetMarble/zones/tablezone.jpg b/themes/VelvetMarble/zones/tablezone.jpg
index dd13cab7..9f511491 100644
Binary files a/themes/VelvetMarble/zones/tablezone.jpg and b/themes/VelvetMarble/zones/tablezone.jpg differ
diff --git a/travis-compile.sh b/travis-compile.sh
index 65f48b2c..0d26c5e4 100755
--- a/travis-compile.sh
+++ b/travis-compile.sh
@@ -2,14 +2,24 @@
set -e
+./servatrice/check_schema_version.sh
+
mkdir build
cd build
prefix=""
if [[ $TRAVIS_OS_NAME == "osx" && $QT4 == 0 ]]; then
- prefix="-DCMAKE_PREFIX_PATH=`echo /usr/local/Cellar/qt5/5.*/`"
+ prefix="-DCMAKE_PREFIX_PATH=$(echo /usr/local/Cellar/qt5/5.*/)"
fi
if [[ $TRAVIS_OS_NAME == "linux" && $QT4 == 0 ]]; then
- prefix="-DCMAKE_PREFIX_PATH=/opt/qt52/lib/cmake/"
+ prefix="-DCMAKE_PREFIX_PATH=$(echo /opt/qt5*/lib/cmake/)"
+ export LD_LIBRARY_PATH="$LD_LIBRARY_PATH:$(echo /opt/qt5*/lib/)"
+fi
+
+if [[ $BUILDTYPE == "Debug" ]]; then
+ cmake .. -DWITH_SERVER=1 -DTEST=1 -DCMAKE_BUILD_TYPE=$BUILDTYPE -DWITH_QT4=$QT4 $prefix
+ make -j2
+ make test
+else
+ cmake .. -DWITH_SERVER=1 -DCMAKE_BUILD_TYPE=$BUILDTYPE -DWITH_QT4=$QT4 $prefix
+ make package -j2
fi
-cmake .. -DWITH_SERVER=1 -DCMAKE_BUILD_TYPE=Debug -DWITH_QT4=$QT4 $prefix
-make -j2
diff --git a/travis-dependencies.sh b/travis-dependencies.sh
index 63fdc24c..8a64dd3c 100755
--- a/travis-dependencies.sh
+++ b/travis-dependencies.sh
@@ -1,21 +1,49 @@
#!/bin/bash
if [[ $TRAVIS_OS_NAME == "osx" ]] ; then
- brew update
+ brew update > /dev/null
if (( QT4 )); then
- brew install qt protobuf libgcrypt
+ brew install qt protobuf libgcrypt > /dev/null
else
- brew install qt5 protobuf libgcrypt
+ brew install qt5 protobuf libgcrypt > /dev/null
fi
+ brew unlink cmake
+ brew upgrade cmake
+
else
+
+ # common prerequisites
+ sudo apt-get update -qq
+ sudo apt-get install -y libprotobuf-dev protobuf-compiler cmake bc
+
if (( QT4 )); then
- sudo apt-get update -qq
- sudo apt-get install -y qtmobility-dev libprotobuf-dev protobuf-compiler libqt4-dev
+ # qt4 prerequisites
+ sudo apt-get install -y qtmobility-dev libqt4-dev
else
- sudo add-apt-repository -y ppa:beineri/opt-qt521
- sudo add-apt-repository -y ppa:kalakris/cmake
- sudo apt-get update -qq
- sudo apt-get install -y libprotobuf-dev protobuf-compiler cmake libsqlite3-dev\
- qt52base qt52webkit qt52tools qt52svg qt52multimedia
+ # qt5 prerequisites
+ sudo apt-get install -y libprotobuf-dev protobuf-compiler \
+ qt5-default qttools5-dev qttools5-dev-tools \
+ qtmultimedia5-dev libqt5multimedia5-plugins libqt5svg5-dev libqt5sql5-mysql
+ fi
+
+ # prerequisites for tests
+ if [[ $BUILDTYPE == "Debug" ]]; then
+ if [[ $DIST == "precise" ]]; then
+ sudo add-apt-repository -y ppa:george-edison55/precise-backports
+ sudo apt-get update -qq
+ sudo apt-get install -y cmake cmake-data libgtest-dev
+ else
+ sudo add-apt-repository -y ppa:george-edison55/cmake-3.x
+ sudo apt-get update -qq
+ sudo apt-get install -y cmake cmake-extras libgtest-dev
+ fi
+
+ sudo mkdir /usr/src/gtest/build
+ cd /usr/src/gtest/build
+ sudo cmake .. -DBUILD_SHARED_LIBS=1
+ sudo make -j2
+ sudo ln -s /usr/src/gtest/build/libgtest.so /usr/lib/libgtest.so
+ sudo ln -s /usr/src/gtest/build/libgtest_main.so /usr/lib/libgtest_main.so
+ cd -
fi
fi