Merge branch 'master' into mod_notify_onbanwarn

This commit is contained in:
woogerboy21 2015-12-30 12:58:29 -05:00
commit 3ed3919349
60 changed files with 2474 additions and 1644 deletions

View file

@ -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

View file

@ -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()

View file

@ -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.<br>
# 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: [ ![Download](https://api.bintray.com/packages/cockatrice/Cockatrice/Cockatrice/images/download.svg) ](https://bintray.com/cockatrice/Cockatrice/Cockatrice/_latestVersion)
- Latest development (unstable) version download: [ ![Download](https://api.bintray.com/packages/cockatrice/Cockatrice/Cockatrice-git/images/download.svg) ](https://bintray.com/cockatrice/Cockatrice/Cockatrice-git/_latestVersion)
# Get Involved [![Gitter chat](https://badges.gitter.im/Cockatrice/Cockatrice.png)](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!<br>
# Building [![Build Status](https://travis-ci.org/Cockatrice/Cockatrice.svg?branch=master)](https://travis-ci.org/Cockatrice/Cockatrice)
# Building [![Travis Build Status - master](https://travis-ci.org/Cockatrice/Cockatrice.svg?branch=master)](https://travis-ci.org/Cockatrice/Cockatrice) [![Appveyor Build Status - master](https://ci.appveyor.com/api/projects/status/lp5h0dhk4mhmeps7/branch/master?svg=true)](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 .`<br>

92
appveyor.yml Normal file
View file

@ -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-(?<content>.*)\.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

View file

@ -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
}

View file

@ -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} )

View file

@ -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} )
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})")

View file

@ -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 ""
)

View file

@ -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()

View file

@ -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;

View file

@ -1,20 +1,14 @@
#include "carddatabase.h"
#include "pictureloader.h"
#include "settingscache.h"
#include "thememanager.h"
#include <QCryptographicHash>
#include <QDebug>
#include <QDir>
#include <QDirIterator>
#include <QFile>
#include <QTextStream>
#include <QPainter>
#include <QUrl>
#include <QSet>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QNetworkRequest>
#include <QDebug>
#include <QImageReader>
#include <QMessageBox>
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<QString> picsPaths = QList<QString>() << _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<CardInfo *> CardDatabase::getCards(const QStringList &cardNames)
{
QList<CardInfo *> 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<QString, CardInfo *> 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<QString, CardInfo *> cardIterator(cards);
while (cardIterator.hasNext())
cardIterator.next().value()->clearPixmapCacheMiss();
}
}
void CardDatabase::picDownloadHqChanged()
{
pictureLoader->setPicDownloadHq(settingsCache->getPicDownloadHq());
if (settingsCache->getPicDownloadHq()) {
QHashIterator<QString, CardInfo *> 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<QString> 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();

View file

@ -8,17 +8,9 @@
#include <QDataStream>
#include <QList>
#include <QXmlStreamReader>
#include <QNetworkRequest>
#include <QThread>
#include <QMutex>
#include <QWaitCondition>
#include <QPixmapCache>
class CardDatabase;
class CardInfo;
class QNetworkAccessManager;
class QNetworkReply;
class QNetworkRequest;
typedef QMap<QString, QString> QStringMap;
@ -55,9 +47,7 @@ public:
class SetList : public QList<CardSet *> {
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<PictureToLoad> loadQueue;
QMutex mutex;
QNetworkAccessManager *networkManager;
QList<PictureToLoad> 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 <CardInfo *> 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<CardInfo *> 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:

View file

@ -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 *)

View file

@ -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);
}

View file

@ -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()

View file

@ -8,6 +8,7 @@
#include <QDebug>
#include <QEvent>
#include <QKeyEvent>
#include <QMessageBox>
#include <iostream>
#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();
}

View file

@ -133,6 +133,7 @@ DlgCreateGame::DlgCreateGame(TabRoom *_room, const QMap<int, QString> &_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();
}

View file

@ -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());

View file

@ -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("<a href='%1'>%2</a>").arg(LINKING_FAQ_URL).arg(tr("Linking FAQ")));
defaultUrlLabel.setText(tr("Primary download URL:"));
fallbackUrlLabel.setText(tr("Fallback download URL:"));
urlLinkLabel.setText(QString("<a href='%1'>%2</a>").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()

View file

@ -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 {

View file

@ -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();
}

View file

@ -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("<font color=\"blue\">" + coinOptions[roll - 1] + "</font>"));
else
appendHtmlServerMessage(tr("%1 flipped a coin. It landed as %2.", "male").arg(sanitizeHtml(player->getName())).arg("<font color=\"blue\">" + coinOptions[roll - 1] + "</font>"));
else if (isFemale(player))
appendHtmlServerMessage(tr("%1 rolls a %2 with a %3-sided die.", "female").arg(sanitizeHtml(player->getName())).arg("<font color=\"blue\">" + QString::number(roll) + "</font>").arg("<font color=\"blue\">" + QString::number(sides) + "</font>"));
else
appendHtmlServerMessage(tr("%1 rolls a %2 with a %3-sided die.", "male").arg(sanitizeHtml(player->getName())).arg("<font color=\"blue\">" + QString::number(roll) + "</font>").arg("<font color=\"blue\">" + QString::number(sides) + "</font>"));

View file

@ -0,0 +1,476 @@
#include "pictureloader.h"
#include "carddatabase.h"
#include "main.h"
#include "settingscache.h"
#include "thememanager.h"
#include <QApplication>
#include <QCryptographicHash>
#include <QDebug>
#include <QDir>
#include <QFile>
#include <QImageReader>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QNetworkRequest>
#include <QPainter>
#include <QPixmapCache>
#include <QSet>
#include <QSvgRenderer>
#include <QThread>
#include <QUrl>
// 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<QString> picsPaths = QList<QString>() << 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<CardInfo *> 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);
}
}

View file

@ -0,0 +1,82 @@
#ifndef PICTURELOADER_H
#define PICTURELOADER_H
#include <QMap>
#include <QList>
#include <QNetworkRequest>
#include <QMutex>
class CardInfo;
class CardSet;
class QNetworkAccessManager;
class QNetworkReply;
class QThread;
class PictureToLoad {
private:
class SetDownloadPriorityComparator;
CardInfo *card;
QList<CardSet *> 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<PictureToLoad> loadQueue;
QMutex mutex;
QNetworkAccessManager *networkManager;
QList<PictureToLoad> 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<CardInfo *> 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

View file

@ -1135,7 +1135,16 @@ void Player::actCreateRelatedCard()
// get the target card name
QAction *action = static_cast<QAction *>(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);

View file

@ -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;

View file

@ -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);

View file

@ -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);

View file

@ -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);
}

View file

@ -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<AbstractClient *> &_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);
}

View file

@ -322,10 +322,10 @@ void TabLog::viewLogHistory_processResponse(const Response &resp)
}
} else
QMessageBox::information(static_cast<QWidget *>(parent()), tr("Message History"), tr("There is no messages for the selected iilters."));
QMessageBox::information(static_cast<QWidget *>(parent()), tr("Message History"), tr("There are no messages for the selected filters."));
} else
QMessageBox::critical(static_cast<QWidget *>(parent()), tr("Message History"), tr("Failed to collecting message history information."));
QMessageBox::critical(static_cast<QWidget *>(parent()), tr("Message History"), tr("Failed to collect message history information."));
}
void TabLog::restartLayout()

View file

@ -12,6 +12,7 @@
#include "tab_server.h"
#include "abstractclient.h"
#include "userlist.h"
#include "tab_supervisor.h"
#include <QDebug>
#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());
}

View file

@ -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;

View file

@ -3,6 +3,7 @@
#include <QApplication>
#include <QDebug>
#include <QColor>
#include <QPixmapCache>
#include <QLibraryInfo>
#if QT_VERSION < 0x050000
#include <QDesktopServices>
@ -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();
}

View file

@ -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()

View file

@ -1,6 +1,8 @@
#include "window_sets.h"
#include "setsmodel.h"
#include "pictureloader.h"
#include "main.h"
#include <QTreeView>
#include <QGridLayout>
#include <QHeaderView>
@ -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();
}

File diff suppressed because it is too large Load diff

View file

@ -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());

View file

@ -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;

View file

@ -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<ServerInfo_ChatMessage> 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());

View file

@ -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();
}
}

View file

@ -42,12 +42,13 @@ private:
QMap<int, ServerInfo_Game> externalGames;
QMap<QString, Server_ProtocolHandler *> users;
QMap<QString, ServerInfo_User_Container> externalUsers;
QList<ServerInfo_ChatMessage> chatHistory;
private slots:
void broadcastGameListUpdate(const ServerInfo_Game &gameInfo, bool sendToIsl = true);
public:
mutable QReadWriteLock usersLock;
mutable QReadWriteLock gamesLock;
QList<ServerInfo_ChatMessage> 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<ServerInfo_Game> getGamesOfUser(const QString &name) const;
QList<ServerInfo_ChatMessage> getChatHistory() { return chatHistory; }
QList<ServerInfo_ChatMessage> & getChatHistory() { return chatHistory; }
void addClient(Server_ProtocolHandler *client);
void removeClient(Server_ProtocolHandler *client);

View file

@ -29,12 +29,13 @@
<xs:extension base="xs:string">
<xs:attribute type="xs:int" name="muId" use="optional"/>
<xs:attribute type="xs:anyURI" name="picUrl" use="optional"/>
<xs:attribute type="xs:anyURI" name="picUrlHq" use="optional"/>
</xs:extension>
</xs:simpleContent>
</xs:complexType>
</xs:element>
<xs:element type="xs:string" name="color" minOccurs="0" maxOccurs="unbounded"/>
<xs:element type="xs:string" name="related" minOccurs="0" maxOccurs="unbounded"/>
<xs:element type="xs:string" name="reverse-related" minOccurs="0" maxOccurs="unbounded"/>
<xs:element type="xs:string" name="manacost"/>
<xs:element type="xs:string" name="type"/>
<xs:element type="xs:string" name="pt" minOccurs="0"/>

View file

@ -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

View file

@ -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;

View file

@ -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);

View file

@ -2,6 +2,7 @@
#define ORACLEIMPORTER_H
#include <QMap>
#include <QVariant>
#include <carddatabase.h>
@ -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);

View file

@ -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

View file

@ -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

View file

@ -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;

View file

@ -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`),

View file

@ -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;

View file

@ -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();

View file

@ -9,7 +9,7 @@
#include "server.h"
#include "server_database_interface.h"
#define DATABASE_SCHEMA_VERSION 11
#define DATABASE_SCHEMA_VERSION 12
class Servatrice;

38
tests/CMakeLists.txt Normal file
View file

@ -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})

16
tests/dummy_test.cpp Normal file
View file

@ -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();
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 110 KiB

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

After

Width:  |  Height:  |  Size: 110 KiB

View file

@ -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

View file

@ -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