diff --git a/.ci/app.rc b/.ci/app.rc new file mode 100644 index 0000000..8a785d2 --- /dev/null +++ b/.ci/app.rc @@ -0,0 +1,33 @@ +IDI_ICON1 ICON DISCARDABLE "5sync.ico" +#define RT_MANIFEST 24 +#define CREATEPROCESS_MANIFEST_RESOURCE_ID 1 +CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST "gta5view.exe.manifest" +#include +VS_VERSION_INFO VERSIONINFO +FILEVERSION MAJOR_VER, MINOR_VER, PATCH_VER, INT_BUILD_VER +PRODUCTVERSION MAJOR_VER, MINOR_VER, PATCH_VER, INT_BUILD_VER +FILEFLAGSMASK 0x3fL +FILEFLAGS 0 +FILEOS VOS_NT_WINDOWS32 +FILETYPE VFT_APP +FILESUBTYPE VFT2_UNKNOWN +BEGIN + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x0809, 1200 + END + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" + BEGIN + VALUE "CompanyName", "Syping" + VALUE "FileDescription", "gta5view" + VALUE "FileVersion", "MAJOR_VER.MINOR_VER.PATCH_VERSTR_BUILD_VER" + VALUE "InternalName", "gta5view" + VALUE "LegalCopyright", "Copyright © 2016-2023 Syping" + VALUE "OriginalFilename", "gta5view.exe" + VALUE "ProductName", "gta5view" + VALUE "ProductVersion", "MAJOR_VER.MINOR_VER.PATCH_VERSTR_BUILD_VER" + END + END +END diff --git a/.ci/ci.sh b/.ci/ci.sh new file mode 100755 index 0000000..8cc49f6 --- /dev/null +++ b/.ci/ci.sh @@ -0,0 +1,72 @@ +#!/usr/bin/env bash + +if [ $(git name-rev --tags --name-only $(git rev-parse HEAD)) == "undefined" ]; then + export APPLICATION_VERSION=$(lua -e 'for line in io.lines("config.h") do local m = string.match(line, "#define GTA5SYNC_APPVER \"(.+)\"$"); if m then print(m); os.exit(0) end end') +else + export APPLICATION_VERSION=$(git name-rev --tags --name-only $(git rev-parse HEAD)) +fi +export PACKAGE_VERSION=$(grep -oE '^[^\-]*' <<< $APPLICATION_VERSION) +export PACKAGE_BUILD=$(grep -oP '\-\K.+' <<< $APPLICATION_VERSION) +export EXECUTABLE_VERSION=${PACKAGE_VERSION}${PACKAGE_BUILD}${EXECUTABLE_TAG} + +export APPLICATION_MAJOR_VERSION=$(cut -d. -f1 <<< $APPLICATION_VERSION) +export APPLICATION_MINOR_VERSION=$(cut -d. -f2 <<< $APPLICATION_VERSION) +export APPLICATION_PATCH_VERSION=$(cut -d. -f3 <<< $APPLICATION_VERSION) + +if [ "${PACKAGE_BUILD}" == "" ]; then + export PACKAGE_BUILD=1 +else + export APPLICATION_BUILD_INT_VERSION=$(grep -oE '[1-9]*$' <<< $PACKAGE_BUILD) + export APPLICATION_BUILD_STR_VERSION=-${PACKAGE_BUILD} +fi + +cat ".ci/app.rc" | sed \ + -e "s/MAJOR_VER/$APPLICATION_MAJOR_VERSION/g" \ + -e "s/MINOR_VER/$APPLICATION_MINOR_VERSION/g" \ + -e "s/PATCH_VER/$APPLICATION_PATCH_VERSION/g" \ + -e "s/INT_BUILD_VER/0/g" \ + -e "s/STR_BUILD_VER/$APPLICATION_BUILD_STR_VERSION/g" \ + -e "s/STR_BUILD_VER/$APPLICATION_BUILD_STR_VERSION/g" \ + > "res/app.rc" + +if [ "${BUILD_TYPE}" == "ALPHA" ]; then + export CMAKE_BUILD_TYPE="-DGTA5VIEW_BUILDTYPE=Alpha" + export QMAKE_BUILD_TYPE="DEFINES+=GTA5SYNC_BUILDTYPE=\\\\\\\"Alpha\\\\\\\"" +elif [ "${BUILD_TYPE}" == "Alpha" ]; then + export CMAKE_BUILD_TYPE="-DGTA5VIEW_BUILDTYPE=Alpha" + export QMAKE_BUILD_TYPE="DEFINES+=GTA5SYNC_BUILDTYPE=\\\\\\\"Alpha\\\\\\\"" +elif [ "${BUILD_TYPE}" == "BETA" ]; then + export CMAKE_BUILD_TYPE="-DGTA5VIEW_BUILDTYPE=Beta" + export QMAKE_BUILD_TYPE="DEFINES+=GTA5SYNC_BUILDTYPE=\\\\\\\"Beta\\\\\\\"" +elif [ "${BUILD_TYPE}" == "Beta" ]; then + export CMAKE_BUILD_TYPE="-DGTA5VIEW_BUILDTYPE=Beta" + export QMAKE_BUILD_TYPE="DEFINES+=GTA5SYNC_BUILDTYPE=\\\\\\\"Beta\\\\\\\"" +elif [ "${BUILD_TYPE}" == "DEV" ]; then + export CMAKE_BUILD_TYPE="-DGTA5VIEW_BUILDTYPE=Developer" + export QMAKE_BUILD_TYPE="DEFINES+=GTA5SYNC_BUILDTYPE=\\\\\\\"Developer\\\\\\\"" +elif [ "${BUILD_TYPE}" == "Development" ]; then + export CMAKE_BUILD_TYPE="-DGTA5VIEW_BUILDTYPE=Developer" + export QMAKE_BUILD_TYPE="DEFINES+=GTA5SYNC_BUILDTYPE=\\\\\\\"Developer\\\\\\\"" +elif [ "${BUILD_TYPE}" == "DAILY" ]; then + export CMAKE_BUILD_TYPE="-DGTA5VIEW_BUILDTYPE=Daily Build" + export QMAKE_BUILD_TYPE="DEFINES+=GTA5SYNC_BUILDTYPE=\\\\\\\"Daily Build\\\\\\\"" +elif [ "${BUILD_TYPE}" == "Daily" ]; then + export CMAKE_BUILD_TYPE="-DGTA5VIEW_BUILDTYPE=Daily Build" + export QMAKE_BUILD_TYPE="DEFINES+=GTA5SYNC_BUILDTYPE=\\\\\\\"Daily Build\\\\\\\"" +elif [ "${BUILD_TYPE}" == "RC" ]; then + export CMAKE_BUILD_TYPE="-DGTA5VIEW_BUILDTYPE=Release Candidate" + export QMAKE_BUILD_TYPE="DEFINES+=GTA5SYNC_BUILDTYPE=\\\\\\\"Release Candidate\\\\\\\"" +elif [ "${BUILD_TYPE}" == "Release Candidate" ]; then + export CMAKE_BUILD_TYPE="-DGTA5VIEW_BUILDTYPE=Release Candidate" + export QMAKE_BUILD_TYPE="DEFINES+=GTA5SYNC_BUILDTYPE=\\\\\\\"Release Candidate\\\\\\\"" +elif [ "${BUILD_TYPE}" == "REL" ]; then + export CMAKE_BUILD_TYPE="-DGTA5VIEW_BUILDTYPE=Release" + export QMAKE_BUILD_TYPE="DEFINES+=GTA5SYNC_BUILDTYPE=\\\\\\\"Release\\\\\\\"" +elif [ "${BUILD_TYPE}" == "Release" ]; then + export CMAKE_BUILD_TYPE="-DGTA5VIEW_BUILDTYPE=Release" + export QMAKE_BUILD_TYPE="DEFINES+=GTA5SYNC_BUILDTYPE=\\\\\\\"Release\\\\\\\"" +fi + +export PROJECT_DIR=$(pwd) + +.ci/${BUILD_SCRIPT} diff --git a/.ci/debian_build.sh b/.ci/debian_build.sh new file mode 100755 index 0000000..57aa9c9 --- /dev/null +++ b/.ci/debian_build.sh @@ -0,0 +1,38 @@ +#!/usr/bin/env bash + +# Creating folders +cd ${PROJECT_DIR} && \ +echo "gta5view build version is ${APPLICATION_VERSION}" && \ +mkdir -p build && \ +mkdir -p assets && \ +chmod -x res/gta5sync_*.qm res/*.desktop res/*gta5view*.png && \ +cd build && \ +mkdir -p qt5 && \ +cd qt5 && \ +echo "Grand Theft Auto V Snapmatic and Savegame viewer/editor" > ./description-pak && \ +cd .. && \ + +# Set compiler +export CC=clang && \ +export CXX=clang++ && \ + +# Prepare checkinstall step +mkdir -p /usr/share/gta5view && \ + +# Starting build +cd qt5 && \ +cmake \ + "-DCMAKE_INSTALL_PREFIX=/usr" \ + "${CMAKE_BUILD_TYPE}" \ + "-DFORCE_QT_VERSION=5" \ + "-DGTA5VIEW_BUILDCODE=${PACKAGE_CODE}" \ + "-DGTA5VIEW_APPVER=${APPLICATION_VERSION}" \ + "-DGTA5VIEW_COMMIT=${APPLICATION_COMMIT}" \ + "-DWITH_DONATE=ON" \ + "-DWITH_TELEMETRY=ON" \ + "-DDONATE_ADDRESSES=$(cat ${PROJECT_DIR}/.ci/donate.txt)" \ + "-DTELEMETRY_WEBURL=https://dev.syping.de/gta5view-userstats/" \ + "-DQCONF_BUILD=ON" \ + ../../ && \ +make -j 4 && \ +checkinstall -D --default --nodoc --install=no --pkgname=gta5view --pkgversion=${PACKAGE_VERSION} --pkgrelease=${PACKAGE_BUILD} --pkggroup=utility --maintainer="Syping \" --requires=libqt5core5a,libqt5gui5,libqt5network5,libqt5svg5,libqt5widgets5,qttranslations5-l10n --conflicts=gta5view-qt4,gta5view-qt5 --replaces=gta5view-qt4,gta5view-qt5 --pakdir=${PROJECT_DIR}/assets diff --git a/.ci/debian_ci.sh b/.ci/debian_ci.sh new file mode 100755 index 0000000..bc14a86 --- /dev/null +++ b/.ci/debian_ci.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash + +# Install packages +.ci/debian_install.sh && \ + +# Build gta5view +.ci/debian_build.sh && \ +cd ${PROJECT_DIR} diff --git a/.ci/debian_docker.sh b/.ci/debian_docker.sh new file mode 100755 index 0000000..80f6c42 --- /dev/null +++ b/.ci/debian_docker.sh @@ -0,0 +1,15 @@ +#!/usr/bin/env bash + +if [ "${DOCKER_USER}" != "" ]; then + DOCKER_IMAGE=${DOCKER_USER}/debian:${DEBIAN_VERSION} +else + DOCKER_IMAGE=debian:${DEBIAN_VERSION} +fi +PROJECT_DIR_DOCKER=/gta5view + +cd ${PROJECT_DIR} && \ +docker pull ${DOCKER_IMAGE} && \ +docker run --rm \ + -v "${PROJECT_DIR}:${PROJECT_DIR_DOCKER}" \ + ${DOCKER_IMAGE} \ + /bin/bash -c "export PROJECT_DIR=${PROJECT_DIR_DOCKER} && export QT_SELECT=${QT_SELECT} && export APPLICATION_VERSION=${APPLICATION_VERSION} && export APPLICATION_COMMIT=${APPLICATION_COMMIT} && export BUILD_TYPE=${BUILD_TYPE} && export APT_INSTALL=${APT_INSTALL} && export QMAKE_FLAGS_QT4=${QMAKE_FLAGS_QT4} && export QMAKE_FLAGS_QT5=${QMAKE_FLAGS_QT5} && export CMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} && export QMAKE_BUILD_TYPE=${QMAKE_BUILD_TYPE} && export PACKAGE_VERSION=${PACKAGE_VERSION} && export PACKAGE_BUILD=${PACKAGE_BUILD} && export PACKAGE_CODE=${PACKAGE_CODE} && export EXECUTABLE_VERSION=${EXECUTABLE_VERSION} && export EXECUTABLE_ARCH=${EXECUTABLE_ARCH} && cd ${PROJECT_DIR_DOCKER} && .ci/debian_install.sh && .ci/debian_build.sh" diff --git a/.ci/debian_install.sh b/.ci/debian_install.sh new file mode 100755 index 0000000..12356e9 --- /dev/null +++ b/.ci/debian_install.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash + +# Source OS Release +source /etc/os-release + +# When Debian add backports +if [ "${ID}" == "debian" ]; then + echo "deb http://deb.debian.org/debian ${VERSION_CODENAME}-backports main" >> /etc/apt/sources.list +fi + +# Install packages +apt-get update -qq && \ +apt-get install -qq ${APT_INSTALL} checkinstall cmake dpkg-dev fakeroot g++ gcc qtbase5-dev qt5-qmake qttranslations5-l10n libqt5svg5-dev diff --git a/.ci/donate.txt b/.ci/donate.txt new file mode 100644 index 0000000..407130d --- /dev/null +++ b/.ci/donate.txt @@ -0,0 +1 @@ +btc:187NSQSPzdMpQDGhxZAuw4AhZ7LgoAPV7D,eth:0x19d71DfCa86104d37a13D3c5d419936421CDC569,ltc:LKr6yvBoMMGmcxViS8Kc1A2sDjVSWTXn4m,xmr:43TB3ZMP5nk1pu5EQXRGPzdTKvmFEBGgccX3tNhRknLLiUYQ7z7dNedVHEA6WrWdByZv1isvFmjSGhCF7ddx3eRxFdm5Fzz \ No newline at end of file diff --git a/.ci/dropbox_uploader.sh b/.ci/dropbox_uploader.sh new file mode 100755 index 0000000..ca8ee36 --- /dev/null +++ b/.ci/dropbox_uploader.sh @@ -0,0 +1,1763 @@ +#!/usr/bin/env bash +# +# Dropbox Uploader +# +# Copyright (C) 2010-2017 Andrea Fabrizi +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# + +#Default configuration file +CONFIG_FILE=~/.dropbox_uploader + +#Default chunk size in Mb for the upload process +#It is recommended to increase this value only if you have enough free space on your /tmp partition +#Lower values may increase the number of http requests +CHUNK_SIZE=50 + +#Curl location +#If not set, curl will be searched into the $PATH +#CURL_BIN="/usr/bin/curl" + +#Default values +TMP_DIR="/tmp" +DEBUG=0 +QUIET=0 +SHOW_PROGRESSBAR=0 +SKIP_EXISTING_FILES=0 +ERROR_STATUS=0 +EXCLUDE=() + +#Don't edit these... +API_LONGPOLL_FOLDER="https://notify.dropboxapi.com/2/files/list_folder/longpoll" +API_CHUNKED_UPLOAD_START_URL="https://content.dropboxapi.com/2/files/upload_session/start" +API_CHUNKED_UPLOAD_FINISH_URL="https://content.dropboxapi.com/2/files/upload_session/finish" +API_CHUNKED_UPLOAD_APPEND_URL="https://content.dropboxapi.com/2/files/upload_session/append_v2" +API_UPLOAD_URL="https://content.dropboxapi.com/2/files/upload" +API_DOWNLOAD_URL="https://content.dropboxapi.com/2/files/download" +API_DELETE_URL="https://api.dropboxapi.com/2/files/delete" +API_MOVE_URL="https://api.dropboxapi.com/2/files/move" +API_COPY_URL="https://api.dropboxapi.com/2/files/copy" +API_METADATA_URL="https://api.dropboxapi.com/2/files/get_metadata" +API_LIST_FOLDER_URL="https://api.dropboxapi.com/2/files/list_folder" +API_LIST_FOLDER_CONTINUE_URL="https://api.dropboxapi.com/2/files/list_folder/continue" +API_ACCOUNT_INFO_URL="https://api.dropboxapi.com/2/users/get_current_account" +API_ACCOUNT_SPACE_URL="https://api.dropboxapi.com/2/users/get_space_usage" +API_MKDIR_URL="https://api.dropboxapi.com/2/files/create_folder" +API_SHARE_URL="https://api.dropboxapi.com/2/sharing/create_shared_link_with_settings" +API_SHARE_LIST="https://api.dropboxapi.com/2/sharing/list_shared_links" +API_SAVEURL_URL="https://api.dropboxapi.com/2/files/save_url" +API_SAVEURL_JOBSTATUS_URL="https://api.dropboxapi.com/2/files/save_url/check_job_status" +API_SEARCH_URL="https://api.dropboxapi.com/2/files/search" +APP_CREATE_URL="https://www.dropbox.com/developers/apps" +RESPONSE_FILE="$TMP_DIR/du_resp_$RANDOM" +CHUNK_FILE="$TMP_DIR/du_chunk_$RANDOM" +TEMP_FILE="$TMP_DIR/du_tmp_$RANDOM" +BIN_DEPS="sed basename date grep stat dd mkdir" +VERSION="1.0" + +umask 077 + +#Check the shell +if [ -z "$BASH_VERSION" ]; then + echo -e "Error: this script requires the BASH shell!" + exit 1 +fi + +shopt -s nullglob #Bash allows filename patterns which match no files to expand to a null string, rather than themselves +shopt -s dotglob #Bash includes filenames beginning with a "." in the results of filename expansion + +#Check temp folder +if [[ ! -d "$TMP_DIR" ]]; then + echo -e "Error: the temporary folder $TMP_DIR doesn't exists!" + echo -e "Please edit this script and set the TMP_DIR variable to a valid temporary folder to use." + exit 1 +fi + +#Look for optional config file parameter +while getopts ":qpskdhf:x:" opt; do + case $opt in + + f) + CONFIG_FILE=$OPTARG + ;; + + d) + DEBUG=1 + ;; + + q) + QUIET=1 + ;; + + p) + SHOW_PROGRESSBAR=1 + ;; + + k) + CURL_ACCEPT_CERTIFICATES="-k" + ;; + + s) + SKIP_EXISTING_FILES=1 + ;; + + h) + HUMAN_READABLE_SIZE=1 + ;; + + x) + EXCLUDE+=( $OPTARG ) + ;; + + \?) + echo "Invalid option: -$OPTARG" >&2 + exit 1 + ;; + + :) + echo "Option -$OPTARG requires an argument." >&2 + exit 1 + ;; + + esac +done + +if [[ $DEBUG != 0 ]]; then + echo $VERSION + uname -a 2> /dev/null + cat /etc/issue 2> /dev/null + set -x + RESPONSE_FILE="$TMP_DIR/du_resp_debug" +fi + +if [[ $CURL_BIN == "" ]]; then + BIN_DEPS="$BIN_DEPS curl" + CURL_BIN="curl" +fi + +#Dependencies check +which $BIN_DEPS > /dev/null +if [[ $? != 0 ]]; then + for i in $BIN_DEPS; do + which $i > /dev/null || + NOT_FOUND="$i $NOT_FOUND" + done + echo -e "Error: Required program could not be found: $NOT_FOUND" + exit 1 +fi + +#Check if readlink is installed and supports the -m option +#It's not necessary, so no problem if it's not installed +which readlink > /dev/null +if [[ $? == 0 && $(readlink -m "//test" 2> /dev/null) == "/test" ]]; then + HAVE_READLINK=1 +else + HAVE_READLINK=0 +fi + +#Forcing to use the builtin printf, if it's present, because it's better +#otherwise the external printf program will be used +#Note that the external printf command can cause character encoding issues! +builtin printf "" 2> /dev/null +if [[ $? == 0 ]]; then + PRINTF="builtin printf" + PRINTF_OPT="-v o" +else + PRINTF=$(which printf) + if [[ $? != 0 ]]; then + echo -e "Error: Required program could not be found: printf" + fi + PRINTF_OPT="" +fi + +#Print the message based on $QUIET variable +function print +{ + if [[ $QUIET == 0 ]]; then + echo -ne "$1"; + fi +} + +#Returns unix timestamp +function utime +{ + date '+%s' +} + +#Remove temporary files +function remove_temp_files +{ + if [[ $DEBUG == 0 ]]; then + rm -fr "$RESPONSE_FILE" + rm -fr "$CHUNK_FILE" + rm -fr "$TEMP_FILE" + fi +} + +#Converts bytes to human readable format +function convert_bytes +{ + if [[ $HUMAN_READABLE_SIZE == 1 && "$1" != "" ]]; then + if (($1 > 1073741824));then + echo $(($1/1073741824)).$(($1%1073741824/100000000))"G"; + elif (($1 > 1048576));then + echo $(($1/1048576)).$(($1%1048576/100000))"M"; + elif (($1 > 1024));then + echo $(($1/1024)).$(($1%1024/100))"K"; + else + echo $1; + fi + else + echo $1; + fi +} + +#Returns the file size in bytes +function file_size +{ + #Generic GNU + SIZE=$(stat --format="%s" "$1" 2> /dev/null) + if [ $? -eq 0 ]; then + echo $SIZE + return + fi + + #Some embedded linux devices + SIZE=$(stat -c "%s" "$1" 2> /dev/null) + if [ $? -eq 0 ]; then + echo $SIZE + return + fi + + #BSD, OSX and other OSs + SIZE=$(stat -f "%z" "$1" 2> /dev/null) + if [ $? -eq 0 ]; then + echo $SIZE + return + fi + + echo "0" +} + + +#Usage +function usage +{ + echo -e "Dropbox Uploader v$VERSION" + echo -e "Andrea Fabrizi - andrea.fabrizi@gmail.com\n" + echo -e "Usage: $0 [PARAMETERS] COMMAND..." + echo -e "\nCommands:" + + echo -e "\t upload " + echo -e "\t download [LOCAL_FILE/DIR]" + echo -e "\t delete " + echo -e "\t move " + echo -e "\t copy " + echo -e "\t mkdir " + echo -e "\t list [REMOTE_DIR]" + echo -e "\t monitor [REMOTE_DIR] [TIMEOUT]" + echo -e "\t share " + echo -e "\t saveurl " + echo -e "\t search " + echo -e "\t info" + echo -e "\t space" + echo -e "\t unlink" + + echo -e "\nOptional parameters:" + echo -e "\t-f Load the configuration file from a specific file" + echo -e "\t-s Skip already existing files when download/upload. Default: Overwrite" + echo -e "\t-d Enable DEBUG mode" + echo -e "\t-q Quiet mode. Don't show messages" + echo -e "\t-h Show file sizes in human readable format" + echo -e "\t-p Show cURL progress meter" + echo -e "\t-k Doesn't check for SSL certificates (insecure)" + echo -e "\t-x Ignores/excludes directories or files from syncing. -x filename -x directoryname. example: -x .git" + + echo -en "\nFor more info and examples, please see the README file.\n\n" + remove_temp_files + exit 1 +} + +#Check the curl exit code +function check_http_response +{ + CODE=$? + + #Checking curl exit code + case $CODE in + + #OK + 0) + + ;; + + #Proxy error + 5) + print "\nError: Couldn't resolve proxy. The given proxy host could not be resolved.\n" + + remove_temp_files + exit 1 + ;; + + #Missing CA certificates + 60|58|77) + print "\nError: cURL is not able to performs peer SSL certificate verification.\n" + print "Please, install the default ca-certificates bundle.\n" + print "To do this in a Debian/Ubuntu based system, try:\n" + print " sudo apt-get install ca-certificates\n\n" + print "If the problem persists, try to use the -k option (insecure).\n" + + remove_temp_files + exit 1 + ;; + + 6) + print "\nError: Couldn't resolve host.\n" + + remove_temp_files + exit 1 + ;; + + 7) + print "\nError: Couldn't connect to host.\n" + + remove_temp_files + exit 1 + ;; + + esac + + #Checking response file for generic errors + if grep -q "HTTP/1.1 400" "$RESPONSE_FILE"; then + ERROR_MSG=$(sed -n -e 's/{"error": "\([^"]*\)"}/\1/p' "$RESPONSE_FILE") + + case $ERROR_MSG in + *access?attempt?failed?because?this?app?is?not?configured?to?have*) + echo -e "\nError: The Permission type/Access level configured doesn't match the DropBox App settings!\nPlease run \"$0 unlink\" and try again." + exit 1 + ;; + esac + + fi + +} + +#Urlencode +function urlencode +{ + #The printf is necessary to correctly decode unicode sequences + local string=$($PRINTF "${1}") + local strlen=${#string} + local encoded="" + + for (( pos=0 ; pos /dev/null + check_http_response + + local TYPE=$(sed -n 's/{".tag": *"*\([^"]*\)"*.*/\1/p' "$RESPONSE_FILE") + + case $TYPE in + + file) + echo "FILE" + ;; + + folder) + echo "DIR" + ;; + + deleted) + echo "ERR" + ;; + + *) + echo "ERR" + ;; + + esac +} + +#Generic upload wrapper around db_upload_file and db_upload_dir functions +#$1 = Local source file/dir +#$2 = Remote destination file/dir +function db_upload +{ + local SRC=$(normalize_path "$1") + local DST=$(normalize_path "$2") + + for j in "${EXCLUDE[@]}" + do : + if [[ $(echo "$SRC" | grep "$j" | wc -l) -gt 0 ]]; then + print "Skipping excluded file/dir: "$j + return + fi + done + + #Checking if the file/dir exists + if [[ ! -e $SRC && ! -d $SRC ]]; then + print " > No such file or directory: $SRC\n" + ERROR_STATUS=1 + return + fi + + #Checking if the file/dir has read permissions + if [[ ! -r $SRC ]]; then + print " > Error reading file $SRC: permission denied\n" + ERROR_STATUS=1 + return + fi + + TYPE=$(db_stat "$DST") + + #If DST it's a file, do nothing, it's the default behaviour + if [[ $TYPE == "FILE" ]]; then + DST="$DST" + + #if DST doesn't exists and doesn't ends with a /, it will be the destination file name + elif [[ $TYPE == "ERR" && "${DST: -1}" != "/" ]]; then + DST="$DST" + + #if DST doesn't exists and ends with a /, it will be the destination folder + elif [[ $TYPE == "ERR" && "${DST: -1}" == "/" ]]; then + local filename=$(basename "$SRC") + DST="$DST/$filename" + + #If DST it's a directory, it will be the destination folder + elif [[ $TYPE == "DIR" ]]; then + local filename=$(basename "$SRC") + DST="$DST/$filename" + fi + + #It's a directory + if [[ -d $SRC ]]; then + db_upload_dir "$SRC" "$DST" + + #It's a file + elif [[ -e $SRC ]]; then + db_upload_file "$SRC" "$DST" + + #Unsupported object... + else + print " > Skipping not regular file \"$SRC\"\n" + fi +} + +#Generic upload wrapper around db_chunked_upload_file and db_simple_upload_file +#The final upload function will be choosen based on the file size +#$1 = Local source file +#$2 = Remote destination file +function db_upload_file +{ + local FILE_SRC=$(normalize_path "$1") + local FILE_DST=$(normalize_path "$2") + + shopt -s nocasematch + + #Checking not allowed file names + basefile_dst=$(basename "$FILE_DST") + if [[ $basefile_dst == "thumbs.db" || \ + $basefile_dst == "desktop.ini" || \ + $basefile_dst == ".ds_store" || \ + $basefile_dst == "icon\r" || \ + $basefile_dst == ".dropbox" || \ + $basefile_dst == ".dropbox.attr" \ + ]]; then + print " > Skipping not allowed file name \"$FILE_DST\"\n" + return + fi + + shopt -u nocasematch + + #Checking file size + FILE_SIZE=$(file_size "$FILE_SRC") + + #Checking if the file already exists + TYPE=$(db_stat "$FILE_DST") + if [[ $TYPE != "ERR" && $SKIP_EXISTING_FILES == 1 ]]; then + print " > Skipping already existing file \"$FILE_DST\"\n" + return + fi + + # Checking if the file has the correct check sum + if [[ $TYPE != "ERR" ]]; then + sha_src=$(db_sha_local "$FILE_SRC") + sha_dst=$(db_sha "$FILE_DST") + if [[ $sha_src == $sha_dst && $sha_src != "ERR" ]]; then + print "> Skipping file \"$FILE_SRC\", file exists with the same hash\n" + return + fi + fi + + if [[ $FILE_SIZE -gt 157286000 ]]; then + #If the file is greater than 150Mb, the chunked_upload API will be used + db_chunked_upload_file "$FILE_SRC" "$FILE_DST" + else + db_simple_upload_file "$FILE_SRC" "$FILE_DST" + fi + +} + +#Simple file upload +#$1 = Local source file +#$2 = Remote destination file +function db_simple_upload_file +{ + local FILE_SRC=$(normalize_path "$1") + local FILE_DST=$(normalize_path "$2") + + if [[ $SHOW_PROGRESSBAR == 1 && $QUIET == 0 ]]; then + CURL_PARAMETERS="--progress-bar" + LINE_CR="\n" + else + CURL_PARAMETERS="-L -s" + LINE_CR="" + fi + + print " > Uploading \"$FILE_SRC\" to \"$FILE_DST\"... $LINE_CR" + $CURL_BIN $CURL_ACCEPT_CERTIFICATES $CURL_PARAMETERS -X POST -i --globoff -o "$RESPONSE_FILE" --header "Authorization: Bearer $OAUTH_ACCESS_TOKEN" --header "Dropbox-API-Arg: {\"path\": \"$FILE_DST\",\"mode\": \"overwrite\",\"autorename\": true,\"mute\": false}" --header "Content-Type: application/octet-stream" --data-binary @"$FILE_SRC" "$API_UPLOAD_URL" + check_http_response + + #Check + if grep -q "^HTTP/1.1 200 OK" "$RESPONSE_FILE"; then + print "DONE\n" + else + print "FAILED\n" + print "An error occurred requesting /upload\n" + ERROR_STATUS=1 + fi +} + +#Chunked file upload +#$1 = Local source file +#$2 = Remote destination file +function db_chunked_upload_file +{ + local FILE_SRC=$(normalize_path "$1") + local FILE_DST=$(normalize_path "$2") + + + if [[ $SHOW_PROGRESSBAR == 1 && $QUIET == 0 ]]; then + VERBOSE=1 + CURL_PARAMETERS="--progress-bar" + else + VERBOSE=0 + CURL_PARAMETERS="-L -s" + fi + + + + local FILE_SIZE=$(file_size "$FILE_SRC") + local OFFSET=0 + local UPLOAD_ID="" + local UPLOAD_ERROR=0 + local CHUNK_PARAMS="" + + ## Ceil division + let NUMBEROFCHUNK=($FILE_SIZE/1024/1024+$CHUNK_SIZE-1)/$CHUNK_SIZE + + if [[ $VERBOSE == 1 ]]; then + print " > Uploading \"$FILE_SRC\" to \"$FILE_DST\" by $NUMBEROFCHUNK chunks ...\n" + else + print " > Uploading \"$FILE_SRC\" to \"$FILE_DST\" by $NUMBEROFCHUNK chunks " + fi + + #Starting a new upload session + $CURL_BIN $CURL_ACCEPT_CERTIFICATES -X POST -L -s --show-error --globoff -i -o "$RESPONSE_FILE" --header "Authorization: Bearer $OAUTH_ACCESS_TOKEN" --header "Dropbox-API-Arg: {\"close\": false}" --header "Content-Type: application/octet-stream" --data-binary @/dev/null "$API_CHUNKED_UPLOAD_START_URL" 2> /dev/null + check_http_response + + SESSION_ID=$(sed -n 's/{"session_id": *"*\([^"]*\)"*.*/\1/p' "$RESPONSE_FILE") + + chunkNumber=1 + #Uploading chunks... + while ([[ $OFFSET != "$FILE_SIZE" ]]); do + + let OFFSET_MB=$OFFSET/1024/1024 + + #Create the chunk + dd if="$FILE_SRC" of="$CHUNK_FILE" bs=1048576 skip=$OFFSET_MB count=$CHUNK_SIZE 2> /dev/null + local CHUNK_REAL_SIZE=$(file_size "$CHUNK_FILE") + + if [[ $VERBOSE == 1 ]]; then + print " >> Uploading chunk $chunkNumber of $NUMBEROFCHUNK\n" + fi + + #Uploading the chunk... + echo > "$RESPONSE_FILE" + $CURL_BIN $CURL_ACCEPT_CERTIFICATES -X POST $CURL_PARAMETERS --show-error --globoff -i -o "$RESPONSE_FILE" --header "Authorization: Bearer $OAUTH_ACCESS_TOKEN" --header "Dropbox-API-Arg: {\"cursor\": {\"session_id\": \"$SESSION_ID\",\"offset\": $OFFSET},\"close\": false}" --header "Content-Type: application/octet-stream" --data-binary @"$CHUNK_FILE" "$API_CHUNKED_UPLOAD_APPEND_URL" + #check_http_response not needed, because we have to retry the request in case of error + + #Check + if grep -q "^HTTP/1.1 200 OK" "$RESPONSE_FILE"; then + let OFFSET=$OFFSET+$CHUNK_REAL_SIZE + UPLOAD_ERROR=0 + if [[ $VERBOSE != 1 ]]; then + print "." + fi + ((chunkNumber=chunkNumber+1)) + else + if [[ $VERBOSE != 1 ]]; then + print "*" + fi + let UPLOAD_ERROR=$UPLOAD_ERROR+1 + + #On error, the upload is retried for max 3 times + if [[ $UPLOAD_ERROR -gt 2 ]]; then + print " FAILED\n" + print "An error occurred requesting /chunked_upload\n" + ERROR_STATUS=1 + return + fi + fi + + done + + UPLOAD_ERROR=0 + + #Commit the upload + while (true); do + + echo > "$RESPONSE_FILE" + $CURL_BIN $CURL_ACCEPT_CERTIFICATES -X POST -L -s --show-error --globoff -i -o "$RESPONSE_FILE" --header "Authorization: Bearer $OAUTH_ACCESS_TOKEN" --header "Dropbox-API-Arg: {\"cursor\": {\"session_id\": \"$SESSION_ID\",\"offset\": $OFFSET},\"commit\": {\"path\": \"$FILE_DST\",\"mode\": \"overwrite\",\"autorename\": true,\"mute\": false}}" --header "Content-Type: application/octet-stream" --data-binary @/dev/null "$API_CHUNKED_UPLOAD_FINISH_URL" 2> /dev/null + #check_http_response not needed, because we have to retry the request in case of error + + #Check + if grep -q "^HTTP/1.1 200 OK" "$RESPONSE_FILE"; then + UPLOAD_ERROR=0 + break + else + print "*" + let UPLOAD_ERROR=$UPLOAD_ERROR+1 + + #On error, the commit is retried for max 3 times + if [[ $UPLOAD_ERROR -gt 2 ]]; then + print " FAILED\n" + print "An error occurred requesting /commit_chunked_upload\n" + ERROR_STATUS=1 + return + fi + fi + + done + + print " DONE\n" +} + +#Directory upload +#$1 = Local source dir +#$2 = Remote destination dir +function db_upload_dir +{ + local DIR_SRC=$(normalize_path "$1") + local DIR_DST=$(normalize_path "$2") + + #Creatig remote directory + db_mkdir "$DIR_DST" + + for file in "$DIR_SRC/"*; do + db_upload "$file" "$DIR_DST" + done +} + +#Generic download wrapper +#$1 = Remote source file/dir +#$2 = Local destination file/dir +function db_download +{ + local SRC=$(normalize_path "$1") + local DST=$(normalize_path "$2") + + TYPE=$(db_stat "$SRC") + + #It's a directory + if [[ $TYPE == "DIR" ]]; then + + #If the DST folder is not specified, I assume that is the current directory + if [[ $DST == "" ]]; then + DST="." + fi + + #Checking if the destination directory exists + if [[ ! -d $DST ]]; then + local basedir="" + else + local basedir=$(basename "$SRC") + fi + + local DEST_DIR=$(normalize_path "$DST/$basedir") + print " > Downloading folder \"$SRC\" to \"$DEST_DIR\"... \n" + + if [[ ! -d "$DEST_DIR" ]]; then + print " > Creating local directory \"$DEST_DIR\"... " + mkdir -p "$DEST_DIR" + + #Check + if [[ $? == 0 ]]; then + print "DONE\n" + else + print "FAILED\n" + ERROR_STATUS=1 + return + fi + fi + + if [[ $SRC == "/" ]]; then + SRC_REQ="" + else + SRC_REQ="$SRC" + fi + + OUT_FILE=$(db_list_outfile "$SRC_REQ") + if [ $? -ne 0 ]; then + # When db_list_outfile fail, the error message is OUT_FILE + print "$OUT_FILE\n" + ERROR_STATUS=1 + return + fi + + #For each entry... + while read -r line; do + + local FILE=${line%:*} + local META=${line##*:} + local TYPE=${META%;*} + local SIZE=${META#*;} + + #Removing unneeded / + FILE=${FILE##*/} + + if [[ $TYPE == "file" ]]; then + db_download_file "$SRC/$FILE" "$DEST_DIR/$FILE" + elif [[ $TYPE == "folder" ]]; then + db_download "$SRC/$FILE" "$DEST_DIR" + fi + + done < $OUT_FILE + + rm -fr $OUT_FILE + + #It's a file + elif [[ $TYPE == "FILE" ]]; then + + #Checking DST + if [[ $DST == "" ]]; then + DST=$(basename "$SRC") + fi + + #If the destination is a directory, the file will be download into + if [[ -d $DST ]]; then + DST="$DST/$SRC" + fi + + db_download_file "$SRC" "$DST" + + #Doesn't exists + else + print " > No such file or directory: $SRC\n" + ERROR_STATUS=1 + return + fi +} + +#Simple file download +#$1 = Remote source file +#$2 = Local destination file +function db_download_file +{ + local FILE_SRC=$(normalize_path "$1") + local FILE_DST=$(normalize_path "$2") + + if [[ $SHOW_PROGRESSBAR == 1 && $QUIET == 0 ]]; then + CURL_PARAMETERS="-L --progress-bar" + LINE_CR="\n" + else + CURL_PARAMETERS="-L -s" + LINE_CR="" + fi + + #Checking if the file already exists + if [[ -e $FILE_DST && $SKIP_EXISTING_FILES == 1 ]]; then + print " > Skipping already existing file \"$FILE_DST\"\n" + return + fi + + # Checking if the file has the correct check sum + if [[ $TYPE != "ERR" ]]; then + sha_src=$(db_sha "$FILE_SRC") + sha_dst=$(db_sha_local "$FILE_DST") + if [[ $sha_src == $sha_dst && $sha_src != "ERR" ]]; then + print "> Skipping file \"$FILE_SRC\", file exists with the same hash\n" + return + fi + fi + + #Creating the empty file, that for two reasons: + #1) In this way I can check if the destination file is writable or not + #2) Curl doesn't automatically creates files with 0 bytes size + dd if=/dev/zero of="$FILE_DST" count=0 2> /dev/null + if [[ $? != 0 ]]; then + print " > Error writing file $FILE_DST: permission denied\n" + ERROR_STATUS=1 + return + fi + + print " > Downloading \"$FILE_SRC\" to \"$FILE_DST\"... $LINE_CR" + $CURL_BIN $CURL_ACCEPT_CERTIFICATES $CURL_PARAMETERS -X POST --globoff -D "$RESPONSE_FILE" -o "$FILE_DST" --header "Authorization: Bearer $OAUTH_ACCESS_TOKEN" --header "Dropbox-API-Arg: {\"path\": \"$FILE_SRC\"}" "$API_DOWNLOAD_URL" + check_http_response + + #Check + if grep -q "^HTTP/1.1 200 OK" "$RESPONSE_FILE"; then + print "DONE\n" + else + print "FAILED\n" + rm -fr "$FILE_DST" + ERROR_STATUS=1 + return + fi +} + +#Saveurl +#$1 = URL +#$2 = Remote file destination +function db_saveurl +{ + local URL="$1" + local FILE_DST=$(normalize_path "$2") + local FILE_NAME=$(basename "$URL") + + print " > Downloading \"$URL\" to \"$FILE_DST\"..." + $CURL_BIN $CURL_ACCEPT_CERTIFICATES -X POST -L -s --show-error --globoff -i -o "$RESPONSE_FILE" --header "Authorization: Bearer $OAUTH_ACCESS_TOKEN" --header "Content-Type: application/json" --data "{\"path\": \"$FILE_DST/$FILE_NAME\", \"url\": \"$URL\"}" "$API_SAVEURL_URL" 2> /dev/null + check_http_response + + JOB_ID=$(sed -n 's/.*"async_job_id": *"*\([^"]*\)"*.*/\1/p' "$RESPONSE_FILE") + if [[ $JOB_ID == "" ]]; then + print " > Error getting the job id\n" + return + fi + + #Checking the status + while (true); do + + $CURL_BIN $CURL_ACCEPT_CERTIFICATES -X POST -L -s --show-error --globoff -i -o "$RESPONSE_FILE" --header "Authorization: Bearer $OAUTH_ACCESS_TOKEN" --header "Content-Type: application/json" --data "{\"async_job_id\": \"$JOB_ID\"}" "$API_SAVEURL_JOBSTATUS_URL" 2> /dev/null + check_http_response + + STATUS=$(sed -n 's/{".tag": *"*\([^"]*\)"*.*/\1/p' "$RESPONSE_FILE") + case $STATUS in + + in_progress) + print "+" + ;; + + complete) + print " DONE\n" + break + ;; + + failed) + print " ERROR\n" + MESSAGE=$(sed -n 's/.*"error_summary": *"*\([^"]*\)"*.*/\1/p' "$RESPONSE_FILE") + print " > Error: $MESSAGE\n" + break + ;; + + esac + + sleep 2 + + done +} + +#Prints account info +function db_account_info +{ + print "Dropbox Uploader v$VERSION\n\n" + print " > Getting info... " + $CURL_BIN $CURL_ACCEPT_CERTIFICATES -X POST -L -s --show-error --globoff -i -o "$RESPONSE_FILE" --header "Authorization: Bearer $OAUTH_ACCESS_TOKEN" "$API_ACCOUNT_INFO_URL" 2> /dev/null + check_http_response + + #Check + if grep -q "^HTTP/1.1 200 OK" "$RESPONSE_FILE"; then + + name=$(sed -n 's/.*"display_name": "\([^"]*\).*/\1/p' "$RESPONSE_FILE") + echo -e "\n\nName:\t\t$name" + + uid=$(sed -n 's/.*"account_id": "\([^"]*\).*/\1/p' "$RESPONSE_FILE") + echo -e "UID:\t\t$uid" + + email=$(sed -n 's/.*"email": "\([^"]*\).*/\1/p' "$RESPONSE_FILE") + echo -e "Email:\t\t$email" + + country=$(sed -n 's/.*"country": "\([^"]*\).*/\1/p' "$RESPONSE_FILE") + echo -e "Country:\t$country" + + echo "" + + else + print "FAILED\n" + ERROR_STATUS=1 + fi +} + +#Prints account space usage info +function db_account_space +{ + print "Dropbox Uploader v$VERSION\n\n" + print " > Getting space usage info... " + $CURL_BIN $CURL_ACCEPT_CERTIFICATES -X POST -L -s --show-error --globoff -i -o "$RESPONSE_FILE" --header "Authorization: Bearer $OAUTH_ACCESS_TOKEN" "$API_ACCOUNT_SPACE_URL" 2> /dev/null + check_http_response + + #Check + if grep -q "^HTTP/1.1 200 OK" "$RESPONSE_FILE"; then + + quota=$(sed -n 's/.*"allocated": \([0-9]*\).*/\1/p' "$RESPONSE_FILE") + let quota_mb=$quota/1024/1024 + echo -e "\n\nQuota:\t$quota_mb Mb" + + used=$(sed -n 's/.*"used": \([0-9]*\).*/\1/p' "$RESPONSE_FILE") + let used_mb=$used/1024/1024 + echo -e "Used:\t$used_mb Mb" + + let free_mb=$((quota-used))/1024/1024 + echo -e "Free:\t$free_mb Mb" + + echo "" + + else + print "FAILED\n" + ERROR_STATUS=1 + fi +} + +#Account unlink +function db_unlink +{ + echo -ne "Are you sure you want unlink this script from your Dropbox account? [y/n]" + read -r answer + if [[ $answer == "y" ]]; then + rm -fr "$CONFIG_FILE" + echo -ne "DONE\n" + fi +} + +#Delete a remote file +#$1 = Remote file to delete +function db_delete +{ + local FILE_DST=$(normalize_path "$1") + + print " > Deleting \"$FILE_DST\"... " + $CURL_BIN $CURL_ACCEPT_CERTIFICATES -X POST -L -s --show-error --globoff -i -o "$RESPONSE_FILE" --header "Authorization: Bearer $OAUTH_ACCESS_TOKEN" --header "Content-Type: application/json" --data "{\"path\": \"$FILE_DST\"}" "$API_DELETE_URL" 2> /dev/null + check_http_response + + #Check + if grep -q "^HTTP/1.1 200 OK" "$RESPONSE_FILE"; then + print "DONE\n" + else + print "FAILED\n" + ERROR_STATUS=1 + fi +} + +#Move/Rename a remote file +#$1 = Remote file to rename or move +#$2 = New file name or location +function db_move +{ + local FILE_SRC=$(normalize_path "$1") + local FILE_DST=$(normalize_path "$2") + + TYPE=$(db_stat "$FILE_DST") + + #If the destination it's a directory, the source will be moved into it + if [[ $TYPE == "DIR" ]]; then + local filename=$(basename "$FILE_SRC") + FILE_DST=$(normalize_path "$FILE_DST/$filename") + fi + + print " > Moving \"$FILE_SRC\" to \"$FILE_DST\" ... " + $CURL_BIN $CURL_ACCEPT_CERTIFICATES -X POST -L -s --show-error --globoff -i -o "$RESPONSE_FILE" --header "Authorization: Bearer $OAUTH_ACCESS_TOKEN" --header "Content-Type: application/json" --data "{\"from_path\": \"$FILE_SRC\", \"to_path\": \"$FILE_DST\"}" "$API_MOVE_URL" 2> /dev/null + check_http_response + + #Check + if grep -q "^HTTP/1.1 200 OK" "$RESPONSE_FILE"; then + print "DONE\n" + else + print "FAILED\n" + ERROR_STATUS=1 + fi +} + +#Copy a remote file to a remote location +#$1 = Remote file to rename or move +#$2 = New file name or location +function db_copy +{ + local FILE_SRC=$(normalize_path "$1") + local FILE_DST=$(normalize_path "$2") + + TYPE=$(db_stat "$FILE_DST") + + #If the destination it's a directory, the source will be copied into it + if [[ $TYPE == "DIR" ]]; then + local filename=$(basename "$FILE_SRC") + FILE_DST=$(normalize_path "$FILE_DST/$filename") + fi + + print " > Copying \"$FILE_SRC\" to \"$FILE_DST\" ... " + $CURL_BIN $CURL_ACCEPT_CERTIFICATES -X POST -L -s --show-error --globoff -i -o "$RESPONSE_FILE" --header "Authorization: Bearer $OAUTH_ACCESS_TOKEN" --header "Content-Type: application/json" --data "{\"from_path\": \"$FILE_SRC\", \"to_path\": \"$FILE_DST\"}" "$API_COPY_URL" 2> /dev/null + check_http_response + + #Check + if grep -q "^HTTP/1.1 200 OK" "$RESPONSE_FILE"; then + print "DONE\n" + else + print "FAILED\n" + ERROR_STATUS=1 + fi +} + +#Create a new directory +#$1 = Remote directory to create +function db_mkdir +{ + local DIR_DST=$(normalize_path "$1") + + print " > Creating Directory \"$DIR_DST\"... " + $CURL_BIN $CURL_ACCEPT_CERTIFICATES -X POST -L -s --show-error --globoff -i -o "$RESPONSE_FILE" --header "Authorization: Bearer $OAUTH_ACCESS_TOKEN" --header "Content-Type: application/json" --data "{\"path\": \"$DIR_DST\"}" "$API_MKDIR_URL" 2> /dev/null + check_http_response + + #Check + if grep -q "^HTTP/1.1 200 OK" "$RESPONSE_FILE"; then + print "DONE\n" + elif grep -q "^HTTP/1.1 403 Forbidden" "$RESPONSE_FILE"; then + print "ALREADY EXISTS\n" + else + print "FAILED\n" + ERROR_STATUS=1 + fi +} + +#List a remote folder and returns the path to the file containing the output +#$1 = Remote directory +#$2 = Cursor (Optional) +function db_list_outfile +{ + + local DIR_DST="$1" + local HAS_MORE="false" + local CURSOR="" + + if [[ -n "$2" ]]; then + CURSOR="$2" + HAS_MORE="true" + fi + + OUT_FILE="$TMP_DIR/du_tmp_out_$RANDOM" + + while (true); do + + if [[ $HAS_MORE == "true" ]]; then + $CURL_BIN $CURL_ACCEPT_CERTIFICATES -X POST -L -s --show-error --globoff -i -o "$RESPONSE_FILE" --header "Authorization: Bearer $OAUTH_ACCESS_TOKEN" --header "Content-Type: application/json" --data "{\"cursor\": \"$CURSOR\"}" "$API_LIST_FOLDER_CONTINUE_URL" 2> /dev/null + else + $CURL_BIN $CURL_ACCEPT_CERTIFICATES -X POST -L -s --show-error --globoff -i -o "$RESPONSE_FILE" --header "Authorization: Bearer $OAUTH_ACCESS_TOKEN" --header "Content-Type: application/json" --data "{\"path\": \"$DIR_DST\",\"include_media_info\": false,\"include_deleted\": false,\"include_has_explicit_shared_members\": false}" "$API_LIST_FOLDER_URL" 2> /dev/null + fi + + check_http_response + + HAS_MORE=$(sed -n 's/.*"has_more": *\([a-z]*\).*/\1/p' "$RESPONSE_FILE") + CURSOR=$(sed -n 's/.*"cursor": *"\([^"]*\)".*/\1/p' "$RESPONSE_FILE") + + #Check + if grep -q "^HTTP/1.1 200 OK" "$RESPONSE_FILE"; then + + #Extracting directory content [...] + #and replacing "}, {" with "}\n{" + #I don't like this piece of code... but seems to be the only way to do this with SED, writing a portable code... + local DIR_CONTENT=$(sed -n 's/.*: \[{\(.*\)/\1/p' "$RESPONSE_FILE" | sed 's/}, *{/}\ + {/g') + + #Converting escaped quotes to unicode format + echo "$DIR_CONTENT" | sed 's/\\"/\\u0022/' > "$TEMP_FILE" + + #Extracting files and subfolders + while read -r line; do + + local FILE=$(echo "$line" | sed -n 's/.*"path_display": *"\([^"]*\)".*/\1/p') + local TYPE=$(echo "$line" | sed -n 's/.*".tag": *"\([^"]*\).*/\1/p') + local SIZE=$(convert_bytes $(echo "$line" | sed -n 's/.*"size": *\([0-9]*\).*/\1/p')) + + echo -e "$FILE:$TYPE;$SIZE" >> "$OUT_FILE" + + done < "$TEMP_FILE" + + if [[ $HAS_MORE == "false" ]]; then + break + fi + + else + return + fi + + done + + echo $OUT_FILE +} + +#List remote directory +#$1 = Remote directory +function db_list +{ + local DIR_DST=$(normalize_path "$1") + + print " > Listing \"$DIR_DST\"... " + + if [[ "$DIR_DST" == "/" ]]; then + DIR_DST="" + fi + + OUT_FILE=$(db_list_outfile "$DIR_DST") + if [ -z "$OUT_FILE" ]; then + print "FAILED\n" + ERROR_STATUS=1 + return + else + print "DONE\n" + fi + + #Looking for the biggest file size + #to calculate the padding to use + local padding=0 + while read -r line; do + local FILE=${line%:*} + local META=${line##*:} + local SIZE=${META#*;} + + if [[ ${#SIZE} -gt $padding ]]; then + padding=${#SIZE} + fi + done < "$OUT_FILE" + + #For each entry, printing directories... + while read -r line; do + + local FILE=${line%:*} + local META=${line##*:} + local TYPE=${META%;*} + local SIZE=${META#*;} + + #Removing unneeded / + FILE=${FILE##*/} + + if [[ $TYPE == "folder" ]]; then + FILE=$(echo -e "$FILE") + $PRINTF " [D] %-${padding}s %s\n" "$SIZE" "$FILE" + fi + + done < "$OUT_FILE" + + #For each entry, printing files... + while read -r line; do + + local FILE=${line%:*} + local META=${line##*:} + local TYPE=${META%;*} + local SIZE=${META#*;} + + #Removing unneeded / + FILE=${FILE##*/} + + if [[ $TYPE == "file" ]]; then + FILE=$(echo -e "$FILE") + $PRINTF " [F] %-${padding}s %s\n" "$SIZE" "$FILE" + fi + + done < "$OUT_FILE" + + rm -fr "$OUT_FILE" +} + +#Longpoll remote directory only once +#$1 = Timeout +#$2 = Remote directory +function db_monitor_nonblock +{ + local TIMEOUT=$1 + local DIR_DST=$(normalize_path "$2") + + if [[ "$DIR_DST" == "/" ]]; then + DIR_DST="" + fi + + $CURL_BIN $CURL_ACCEPT_CERTIFICATES -X POST -L -s --show-error --globoff -i -o "$RESPONSE_FILE" --header "Authorization: Bearer $OAUTH_ACCESS_TOKEN" --header "Content-Type: application/json" --data "{\"path\": \"$DIR_DST\",\"include_media_info\": false,\"include_deleted\": false,\"include_has_explicit_shared_members\": false}" "$API_LIST_FOLDER_URL" 2> /dev/null + check_http_response + + if grep -q "^HTTP/1.1 200 OK" "$RESPONSE_FILE"; then + + local CURSOR=$(sed -n 's/.*"cursor": *"\([^"]*\)".*/\1/p' "$RESPONSE_FILE") + + $CURL_BIN $CURL_ACCEPT_CERTIFICATES -X POST -L -s --show-error --globoff -i -o "$RESPONSE_FILE" --header "Content-Type: application/json" --data "{\"cursor\": \"$CURSOR\",\"timeout\": ${TIMEOUT}}" "$API_LONGPOLL_FOLDER" 2> /dev/null + check_http_response + + if grep -q "^HTTP/1.1 200 OK" "$RESPONSE_FILE"; then + local CHANGES=$(sed -n 's/.*"changes" *: *\([a-z]*\).*/\1/p' "$RESPONSE_FILE") + else + ERROR_MSG=$(grep "Error in call" "$RESPONSE_FILE") + print "FAILED to longpoll (http error): $ERROR_MSG\n" + ERROR_STATUS=1 + return 1 + fi + + if [[ -z "$CHANGES" ]]; then + print "FAILED to longpoll (unexpected response)\n" + ERROR_STATUS=1 + return 1 + fi + + if [ "$CHANGES" == "true" ]; then + + OUT_FILE=$(db_list_outfile "$DIR_DST" "$CURSOR") + + if [ -z "$OUT_FILE" ]; then + print "FAILED to list changes\n" + ERROR_STATUS=1 + return + fi + + #For each entry, printing directories... + while read -r line; do + + local FILE=${line%:*} + local META=${line##*:} + local TYPE=${META%;*} + local SIZE=${META#*;} + + #Removing unneeded / + FILE=${FILE##*/} + + if [[ $TYPE == "folder" ]]; then + FILE=$(echo -e "$FILE") + $PRINTF " [D] %s\n" "$FILE" + elif [[ $TYPE == "file" ]]; then + FILE=$(echo -e "$FILE") + $PRINTF " [F] %s %s\n" "$SIZE" "$FILE" + elif [[ $TYPE == "deleted" ]]; then + FILE=$(echo -e "$FILE") + $PRINTF " [-] %s\n" "$FILE" + fi + + done < "$OUT_FILE" + + rm -fr "$OUT_FILE" + fi + + else + ERROR_STATUS=1 + return 1 + fi + +} + +#Longpoll continuously remote directory +#$1 = Timeout +#$2 = Remote directory +function db_monitor +{ + local TIMEOUT=$1 + local DIR_DST=$(normalize_path "$2") + + while (true); do + db_monitor_nonblock "$TIMEOUT" "$2" + done +} + +#Share remote file +#$1 = Remote file +function db_share +{ + local FILE_DST=$(normalize_path "$1") + + $CURL_BIN $CURL_ACCEPT_CERTIFICATES -X POST -L -s --show-error --globoff -i -o "$RESPONSE_FILE" --header "Authorization: Bearer $OAUTH_ACCESS_TOKEN" --header "Content-Type: application/json" --data "{\"path\": \"$FILE_DST\",\"settings\": {\"requested_visibility\": \"public\"}}" "$API_SHARE_URL" 2> /dev/null + check_http_response + + #Check + if grep -q "^HTTP/1.1 200 OK" "$RESPONSE_FILE"; then + print " > Share link: " + SHARE_LINK=$(sed -n 's/.*"url": "\([^"]*\).*/\1/p' "$RESPONSE_FILE") + echo "$SHARE_LINK" + else + get_Share "$FILE_DST" + fi +} + +#Query existing shared link +#$1 = Remote file +function get_Share +{ + local FILE_DST=$(normalize_path "$1") + $CURL_BIN $CURL_ACCEPT_CERTIFICATES -X POST -L -s --show-error --globoff -i -o "$RESPONSE_FILE" --header "Authorization: Bearer $OAUTH_ACCESS_TOKEN" --header "Content-Type: application/json" --data "{\"path\": \"$FILE_DST\",\"direct_only\": true}" "$API_SHARE_LIST" + check_http_response + + #Check + if grep -q "^HTTP/1.1 200 OK" "$RESPONSE_FILE"; then + print " > Share link: " + SHARE_LINK=$(sed -n 's/.*"url": "\([^"]*\).*/\1/p' "$RESPONSE_FILE") + echo "$SHARE_LINK" + else + print "FAILED\n" + MESSAGE=$(sed -n 's/.*"error_summary": *"*\([^"]*\)"*.*/\1/p' "$RESPONSE_FILE") + print " > Error: $MESSAGE\n" + ERROR_STATUS=1 + fi +} + +#Search on Dropbox +#$1 = query +function db_search +{ + local QUERY="$1" + + print " > Searching for \"$QUERY\"... " + + $CURL_BIN $CURL_ACCEPT_CERTIFICATES -X POST -L -s --show-error --globoff -i -o "$RESPONSE_FILE" --header "Authorization: Bearer $OAUTH_ACCESS_TOKEN" --header "Content-Type: application/json" --data "{\"path\": \"\",\"query\": \"$QUERY\",\"start\": 0,\"max_results\": 1000,\"mode\": \"filename\"}" "$API_SEARCH_URL" 2> /dev/null + check_http_response + + #Check + if grep -q "^HTTP/1.1 200 OK" "$RESPONSE_FILE"; then + print "DONE\n" + else + print "FAILED\n" + ERROR_STATUS=1 + fi + + #Extracting directory content [...] + #and replacing "}, {" with "}\n{" + #I don't like this piece of code... but seems to be the only way to do this with SED, writing a portable code... + local DIR_CONTENT=$(sed 's/}, *{/}\ +{/g' "$RESPONSE_FILE") + + #Converting escaped quotes to unicode format + echo "$DIR_CONTENT" | sed 's/\\"/\\u0022/' > "$TEMP_FILE" + + #Extracting files and subfolders + rm -fr "$RESPONSE_FILE" + while read -r line; do + + local FILE=$(echo "$line" | sed -n 's/.*"path_display": *"\([^"]*\)".*/\1/p') + local TYPE=$(echo "$line" | sed -n 's/.*".tag": *"\([^"]*\).*/\1/p') + local SIZE=$(convert_bytes $(echo "$line" | sed -n 's/.*"size": *\([0-9]*\).*/\1/p')) + + echo -e "$FILE:$TYPE;$SIZE" >> "$RESPONSE_FILE" + + done < "$TEMP_FILE" + + #Looking for the biggest file size + #to calculate the padding to use + local padding=0 + while read -r line; do + local FILE=${line%:*} + local META=${line##*:} + local SIZE=${META#*;} + + if [[ ${#SIZE} -gt $padding ]]; then + padding=${#SIZE} + fi + done < "$RESPONSE_FILE" + + #For each entry, printing directories... + while read -r line; do + + local FILE=${line%:*} + local META=${line##*:} + local TYPE=${META%;*} + local SIZE=${META#*;} + + if [[ $TYPE == "folder" ]]; then + FILE=$(echo -e "$FILE") + $PRINTF " [D] %-${padding}s %s\n" "$SIZE" "$FILE" + fi + + done < "$RESPONSE_FILE" + + #For each entry, printing files... + while read -r line; do + + local FILE=${line%:*} + local META=${line##*:} + local TYPE=${META%;*} + local SIZE=${META#*;} + + if [[ $TYPE == "file" ]]; then + FILE=$(echo -e "$FILE") + $PRINTF " [F] %-${padding}s %s\n" "$SIZE" "$FILE" + fi + + done < "$RESPONSE_FILE" + +} + +#Query the sha256-dropbox-sum of a remote file +#see https://www.dropbox.com/developers/reference/content-hash for more information +#$1 = Remote file +function db_sha +{ + local FILE=$(normalize_path "$1") + + if [[ $FILE == "/" ]]; then + echo "ERR" + return + fi + + #Checking if it's a file or a directory and get the sha-sum + $CURL_BIN $CURL_ACCEPT_CERTIFICATES -X POST -L -s --show-error --globoff -i -o "$RESPONSE_FILE" --header "Authorization: Bearer $OAUTH_ACCESS_TOKEN" --header "Content-Type: application/json" --data "{\"path\": \"$FILE\"}" "$API_METADATA_URL" 2> /dev/null + check_http_response + + local TYPE=$(sed -n 's/{".tag": *"*\([^"]*\)"*.*/\1/p' "$RESPONSE_FILE") + if [[ $TYPE == "folder" ]]; then + echo "ERR" + return + fi + + local SHA256=$(sed -n 's/.*"content_hash": "\([^"]*\).*/\1/p' "$RESPONSE_FILE") + echo "$SHA256" +} + +#Query the sha256-dropbox-sum of a local file +#see https://www.dropbox.com/developers/reference/content-hash for more information +#$1 = Local file +function db_sha_local +{ + local FILE=$(normalize_path "$1") + local FILE_SIZE=$(file_size "$FILE") + local OFFSET=0 + local SKIP=0 + local SHA_CONCAT="" + + which shasum > /dev/null + if [[ $? != 0 ]]; then + echo "ERR" + return + fi + + while ([[ $OFFSET -lt "$FILE_SIZE" ]]); do + dd if="$FILE" of="$CHUNK_FILE" bs=4194304 skip=$SKIP count=1 2> /dev/null + local SHA=$(shasum -a 256 "$CHUNK_FILE" | awk '{print $1}') + SHA_CONCAT="${SHA_CONCAT}${SHA}" + + let OFFSET=$OFFSET+4194304 + let SKIP=$SKIP+1 + done + + shaHex=$(echo $SHA_CONCAT | sed 's/\([0-9A-F]\{2\}\)/\\x\1/gI') + echo -ne $shaHex | shasum -a 256 | awk '{print $1}' +} + +################ +#### SETUP #### +################ + +#CHECKING FOR AUTH FILE +if [[ -e $CONFIG_FILE ]]; then + + #Loading data... and change old format config if necesary. + source "$CONFIG_FILE" 2>/dev/null || { + sed -i'' 's/:/=/' "$CONFIG_FILE" && source "$CONFIG_FILE" 2>/dev/null + } + + #Checking if it's still a v1 API configuration file + if [[ $APPKEY != "" || $APPSECRET != "" ]]; then + echo -ne "The config file contains the old deprecated v1 oauth tokens.\n" + echo -ne "Please run again the script and follow the configuration wizard. The old configuration file has been backed up to $CONFIG_FILE.old\n" + mv "$CONFIG_FILE" "$CONFIG_FILE".old + exit 1 + fi + + #Checking loaded data + if [[ $OAUTH_ACCESS_TOKEN = "" ]]; then + echo -ne "Error loading data from $CONFIG_FILE...\n" + echo -ne "It is recommended to run $0 unlink\n" + remove_temp_files + exit 1 + fi + +#NEW SETUP... +else + + echo -ne "\n This is the first time you run this script, please follow the instructions:\n\n" + echo -ne " 1) Open the following URL in your Browser, and log in using your account: $APP_CREATE_URL\n" + echo -ne " 2) Click on \"Create App\", then select \"Dropbox API app\"\n" + echo -ne " 3) Now go on with the configuration, choosing the app permissions and access restrictions to your DropBox folder\n" + echo -ne " 4) Enter the \"App Name\" that you prefer (e.g. MyUploader$RANDOM$RANDOM$RANDOM)\n\n" + + echo -ne " Now, click on the \"Create App\" button.\n\n" + + echo -ne " When your new App is successfully created, please click on the Generate button\n" + echo -ne " under the 'Generated access token' section, then copy and paste the new access token here:\n\n" + + echo -ne " # Access token: " + read -r OAUTH_ACCESS_TOKEN + + echo -ne "\n > The access token is $OAUTH_ACCESS_TOKEN. Looks ok? [y/N]: " + read -r answer + if [[ $answer != "y" ]]; then + remove_temp_files + exit 1 + fi + + echo "OAUTH_ACCESS_TOKEN=$OAUTH_ACCESS_TOKEN" > "$CONFIG_FILE" + echo " The configuration has been saved." + + remove_temp_files + exit 0 +fi + +################ +#### START #### +################ + +COMMAND="${*:$OPTIND:1}" +ARG1="${*:$OPTIND+1:1}" +ARG2="${*:$OPTIND+2:1}" + +let argnum=$#-$OPTIND + +#CHECKING PARAMS VALUES +case $COMMAND in + + upload) + + if [[ $argnum -lt 2 ]]; then + usage + fi + + FILE_DST="${*:$#:1}" + + for (( i=OPTIND+1; i<$#; i++ )); do + FILE_SRC="${*:$i:1}" + db_upload "$FILE_SRC" "/$FILE_DST" + done + + ;; + + download) + + if [[ $argnum -lt 1 ]]; then + usage + fi + + FILE_SRC="$ARG1" + FILE_DST="$ARG2" + + db_download "/$FILE_SRC" "$FILE_DST" + + ;; + + saveurl) + + if [[ $argnum -lt 1 ]]; then + usage + fi + + URL=$ARG1 + FILE_DST="$ARG2" + + db_saveurl "$URL" "/$FILE_DST" + + ;; + + share) + + if [[ $argnum -lt 1 ]]; then + usage + fi + + FILE_DST="$ARG1" + + db_share "/$FILE_DST" + + ;; + + info) + + db_account_info + + ;; + + space) + + db_account_space + + ;; + + delete|remove) + + if [[ $argnum -lt 1 ]]; then + usage + fi + + FILE_DST="$ARG1" + + db_delete "/$FILE_DST" + + ;; + + move|rename) + + if [[ $argnum -lt 2 ]]; then + usage + fi + + FILE_SRC="$ARG1" + FILE_DST="$ARG2" + + db_move "/$FILE_SRC" "/$FILE_DST" + + ;; + + copy) + + if [[ $argnum -lt 2 ]]; then + usage + fi + + FILE_SRC="$ARG1" + FILE_DST="$ARG2" + + db_copy "/$FILE_SRC" "/$FILE_DST" + + ;; + + mkdir) + + if [[ $argnum -lt 1 ]]; then + usage + fi + + DIR_DST="$ARG1" + + db_mkdir "/$DIR_DST" + + ;; + + search) + + if [[ $argnum -lt 1 ]]; then + usage + fi + + QUERY=$ARG1 + + db_search "$QUERY" + + ;; + + list) + + DIR_DST="$ARG1" + + #Checking DIR_DST + if [[ $DIR_DST == "" ]]; then + DIR_DST="/" + fi + + db_list "/$DIR_DST" + + ;; + + monitor) + + DIR_DST="$ARG1" + TIMEOUT=$ARG2 + + #Checking DIR_DST + if [[ $DIR_DST == "" ]]; then + DIR_DST="/" + fi + + print " > Monitoring \"$DIR_DST\" for changes...\n" + + if [[ -n $TIMEOUT ]]; then + db_monitor_nonblock $TIMEOUT "/$DIR_DST" + else + db_monitor 60 "/$DIR_DST" + fi + + ;; + + unlink) + + db_unlink + + ;; + + *) + + if [[ $COMMAND != "" ]]; then + print "Error: Unknown command: $COMMAND\n\n" + ERROR_STATUS=1 + fi + usage + + ;; + +esac + +remove_temp_files + +if [[ $ERROR_STATUS -ne 0 ]]; then + echo "Some error occured. Please check the log." +fi + +exit $ERROR_STATUS diff --git a/.travis/gta5view.nsi b/.ci/gta5view.nsi similarity index 52% rename from .travis/gta5view.nsi rename to .ci/gta5view.nsi index becda49..1ddd882 100644 --- a/.travis/gta5view.nsi +++ b/.ci/gta5view.nsi @@ -1,11 +1,12 @@ ###################################################################### !define APP_NAME "gta5view" +!define APP_EXT ".g5e" !define COMP_NAME "Syping" !define WEB_SITE "https://gta5view.syping.de/" -!define VERSION "1.5.4.0" -!define COPYRIGHT "Copyright © 2016-2018 Syping" -!define DESCRIPTION "Grand Theft Auto V Savegame and Snapmatic Viewer/Editor" +!define VERSION "1.10.2.0" +!define COPYRIGHT "Copyright © 2016-2022 Syping" +!define DESCRIPTION "Open Source Snapmatic and Savegame viewer/editor for GTA V" !define INSTALLER_NAME "gta5view_setup.exe" !define MAIN_APP_EXE "gta5view.exe" !define INSTALL_TYPE "SetShellVarContext all" @@ -33,6 +34,7 @@ Caption "${APP_NAME}" OutFile "${INSTALLER_NAME}" #BrandingText "${APP_NAME}" XPStyle on +Unicode true InstallDirRegKey "${REG_ROOT}" "${REG_APP_PATH}" "" InstallDir "$PROGRAMFILES64\Syping\gta5view" @@ -78,7 +80,9 @@ InstallDir "$PROGRAMFILES64\Syping\gta5view" !insertmacro MUI_LANGUAGE "English" !insertmacro MUI_LANGUAGE "French" !insertmacro MUI_LANGUAGE "German" +!insertmacro MUI_LANGUAGE "Korean" !insertmacro MUI_LANGUAGE "Russian" +!insertmacro MUI_LANGUAGE "Ukrainian" !insertmacro MUI_LANGUAGE "TradChinese" !insertmacro MUI_RESERVEFILE_LANGDLL @@ -88,7 +92,7 @@ InstallDir "$PROGRAMFILES64\Syping\gta5view" Function .onInit !insertmacro MUI_LANGDLL_DISPLAY !ifdef WIN32 - MessageBox MB_OK|MB_ICONSTOP "Can't install the 64bit version on a 32bit system, please download the 32bit version!" + MessageBox MB_OK|MB_ICONSTOP "Windows 32-Bit is not supported anymore!" Quit !endif SetRegView 64 @@ -100,45 +104,80 @@ Section -MainProgram ${INSTALL_TYPE} SetOverwrite ifnewer SetOutPath "$INSTDIR" -File "../build/release/gta5view.exe" -File "/usr/lib/gcc/x86_64-w64-mingw32/6.3-win32/libgcc_s_seh-1.dll" -File "/usr/lib/gcc/x86_64-w64-mingw32/6.3-win32/libstdc++-6.dll" -File "/opt/windev/libressl-latest_qt64d/bin/libcrypto-43.dll" -File "/opt/windev/libressl-latest_qt64d/bin/libssl-45.dll" -File "/opt/windev/libjpeg-turbo-latest_qt64d/bin/libjpeg-62.dll" -File "/opt/windev/qt64d-latest/bin/Qt5Core.dll" -File "/opt/windev/qt64d-latest/bin/Qt5Gui.dll" -File "/opt/windev/qt64d-latest/bin/Qt5Network.dll" -File "/opt/windev/qt64d-latest/bin/Qt5Svg.dll" -File "/opt/windev/qt64d-latest/bin/Qt5Widgets.dll" -File "/opt/windev/qt64d-latest/bin/Qt5WinExtras.dll" +File "../build/gta5view.exe" +File "/opt/llvm-mingw/x86_64-w64-mingw32/bin/libc++.dll" +File "/opt/llvm-mingw/x86_64-w64-mingw32/bin/libunwind.dll" +File "/usr/local/lib/x86_64-w64-mingw32/openssl/bin/libcrypto-1_1-x64.dll" +File "/usr/local/lib/x86_64-w64-mingw32/openssl/bin/libssl-1_1-x64.dll" +File "/usr/local/lib/x86_64-w64-mingw32/qt5/bin/Qt5Core.dll" +File "/usr/local/lib/x86_64-w64-mingw32/qt5/bin/Qt5Gui.dll" +File "/usr/local/lib/x86_64-w64-mingw32/qt5/bin/Qt5Network.dll" +File "/usr/local/lib/x86_64-w64-mingw32/qt5/bin/Qt5Svg.dll" +File "/usr/local/lib/x86_64-w64-mingw32/qt5/bin/Qt5Widgets.dll" SetOutPath "$INSTDIR\lang" -File "../res/gta5sync_en_US.qm" -File "../res/gta5sync_de.qm" -File "../res/gta5sync_fr.qm" -File "../res/gta5sync_ru.qm" -File "../res/gta5sync_uk.qm" -File "../res/gta5sync_zh_TW.qm" -File "../res/qtbase_en_GB.qm" -File "../res/qtbase_de.qm" -File "../res/qtbase_fr.qm" -File "../res/qtbase_ru.qm" -File "../res/qtbase_uk.qm" -File "../res/qtbase_zh_TW.qm" -SetOutPath "$INSTDIR\audio" -File "/opt/windev/qt64d-latest/plugins/audio/qtaudio_windows.dll" +File "../build/gta5sync_en_US.qm" +File "../build/gta5sync_de.qm" +File "../build/gta5sync_fr.qm" +File "../build/gta5sync_ko.qm" +File "../build/gta5sync_ru.qm" +File "../build/gta5sync_uk.qm" +File "../build/gta5sync_zh_TW.qm" +File "../build/qtbase_en_GB.qm" +File "../res/qt5/qtbase_de.qm" +File "../res/qt5/qtbase_fr.qm" +File "../res/qt5/qtbase_ko.qm" +File "../res/qt5/qtbase_ru.qm" +File "../res/qt5/qtbase_uk.qm" +File "../res/qt5/qtbase_zh_TW.qm" +SetOutPath "$INSTDIR\resources" +File "../res/add.svgz" +File "../res/avatararea.png" +File "../res/avatarareaimport.png" +File "../res/back.svgz" +File "../res/flag-de.png" +File "../res/flag-fr.png" +File "../res/flag-gb.png" +File "../res/flag-kr.png" +File "../res/flag-ru.png" +File "../res/flag-tw.png" +File "../res/flag-ua.png" +File "../res/flag-us.png" +File "../res/gta5view-16.png" +File "../res/gta5view-24.png" +File "../res/gta5view-32.png" +File "../res/gta5view-40.png" +File "../res/gta5view-48.png" +File "../res/gta5view-64.png" +File "../res/gta5view-96.png" +File "../res/gta5view-128.png" +File "../res/gta5view-256.png" +File "../res/mapcayoperico.jpg" +File "../res/mappreview.jpg" +File "../res/next.svgz" +File "../res/pointmaker-8.png" +File "../res/pointmaker-16.png" +File "../res/pointmaker-24.png" +File "../res/pointmaker-32.png" +File "../res/savegame.svgz" +File "../res/watermark_1b.png" +File "../res/watermark_2b.png" +File "../res/watermark_2r.png" SetOutPath "$INSTDIR\imageformats" -File "/opt/windev/qt64d-latest/plugins/imageformats/qgif.dll" -File "/opt/windev/qt64d-latest/plugins/imageformats/qicns.dll" -File "/opt/windev/qt64d-latest/plugins/imageformats/qico.dll" -File "/opt/windev/qt64d-latest/plugins/imageformats/qjpeg.dll" -File "/opt/windev/qt64d-latest/plugins/imageformats/qsvg.dll" -File "/opt/windev/qt64d-latest/plugins/imageformats/qtga.dll" -File "/opt/windev/qt64d-latest/plugins/imageformats/qtiff.dll" -File "/opt/windev/qt64d-latest/plugins/imageformats/qwbmp.dll" -File "/opt/windev/qt64d-latest/plugins/imageformats/qwebp.dll" +File "/usr/local/lib/x86_64-w64-mingw32/qt5/plugins/imageformats/qgif.dll" +File "/usr/local/lib/x86_64-w64-mingw32/qt5/plugins/imageformats/qicns.dll" +File "/usr/local/lib/x86_64-w64-mingw32/qt5/plugins/imageformats/qico.dll" +File "/usr/local/lib/x86_64-w64-mingw32/qt5/plugins/imageformats/qjpeg.dll" +File "/usr/local/lib/x86_64-w64-mingw32/qt5/plugins/imageformats/qsvg.dll" +File "/usr/local/lib/x86_64-w64-mingw32/qt5/plugins/imageformats/qtga.dll" +File "/usr/local/lib/x86_64-w64-mingw32/qt5/plugins/imageformats/qtiff.dll" +File "/usr/local/lib/x86_64-w64-mingw32/qt5/plugins/imageformats/qwbmp.dll" +File "/usr/local/lib/x86_64-w64-mingw32/qt5/plugins/imageformats/qwebp.dll" SetOutPath "$INSTDIR\platforms" -File "/opt/windev/qt64d-latest/plugins/platforms/qwindows.dll" +File "/usr/local/lib/x86_64-w64-mingw32/qt5/plugins/platforms/qwindows.dll" +SetOutPath "$INSTDIR\styles" +File "/usr/local/lib/x86_64-w64-mingw32/qt5/plugins/styles/qcleanlooksstyle.dll" +File "/usr/local/lib/x86_64-w64-mingw32/qt5/plugins/styles/qplastiquestyle.dll" +File "/usr/local/lib/x86_64-w64-mingw32/qt5/plugins/styles/qwindowsvistastyle.dll" SectionEnd ###################################################################### @@ -185,33 +224,88 @@ SectionEnd ###################################################################### +Section -ShellAssoc +WriteRegStr ${REG_ROOT} "Software\Classes\${APP_NAME}\DefaultIcon" "" "$INSTDIR\${MAIN_APP_EXE},0" +WriteRegStr ${REG_ROOT} "Software\Classes\${APP_NAME}\shell\open\command" "" '"$INSTDIR\${MAIN_APP_EXE}" "%1"' +WriteRegStr ${REG_ROOT} "Software\Classes\${APP_EXT}" "" "${APP_NAME}" +WriteRegStr ${REG_ROOT} "Software\Classes\${APP_EXT}" "Content Type" "application/x-gta5view-export" +System::Call 'SHELL32::SHChangeNotify(i0x8000000,i0,p0,p0)' +SectionEnd + +###################################################################### + +Section -un.ShellAssoc +ClearErrors +ReadRegStr $0 ${REG_ROOT} "Software\Classes\${APP_EXT}" "" +DeleteRegKey ${REG_ROOT} "Software\Classes\${APP_NAME}" +${IfNot} ${Errors} +${AndIf} $0 == "${APP_NAME}" +DeleteRegValue ${REG_ROOT} "Software\Classes\${APP_EXT}" "" +DeleteRegKey /IfEmpty ${REG_ROOT} "Software\Classes\${APP_EXT}" +${EndIf} +System::Call 'SHELL32::SHChangeNotify(i0x8000000,i0,p0,p0)' +SectionEnd + +###################################################################### + Section Uninstall ${INSTALL_TYPE} Delete "$INSTDIR\gta5view.exe" -Delete "$INSTDIR\libgcc_s_seh-1.dll" -Delete "$INSTDIR\libstdc++-6.dll" -Delete "$INSTDIR\libcrypto-43.dll" -Delete "$INSTDIR\libssl-45.dll" -Delete "$INSTDIR\libjpeg-62.dll" +Delete "$INSTDIR\libc++.dll" +Delete "$INSTDIR\libunwind.dll" +Delete "$INSTDIR\libcrypto-1_1-x64.dll" +Delete "$INSTDIR\libssl-1_1-x64.dll" Delete "$INSTDIR\Qt5Core.dll" Delete "$INSTDIR\Qt5Gui.dll" Delete "$INSTDIR\Qt5Network.dll" Delete "$INSTDIR\Qt5Svg.dll" Delete "$INSTDIR\Qt5Widgets.dll" -Delete "$INSTDIR\Qt5WinExtras.dll" Delete "$INSTDIR\lang\gta5sync_en_US.qm" Delete "$INSTDIR\lang\gta5sync_de.qm" Delete "$INSTDIR\lang\gta5sync_fr.qm" +Delete "$INSTDIR\lang\gta5sync_ko.qm" Delete "$INSTDIR\lang\gta5sync_ru.qm" Delete "$INSTDIR\lang\gta5sync_uk.qm" Delete "$INSTDIR\lang\gta5sync_zh_TW.qm" Delete "$INSTDIR\lang\qtbase_en_GB.qm" Delete "$INSTDIR\lang\qtbase_de.qm" Delete "$INSTDIR\lang\qtbase_fr.qm" +Delete "$INSTDIR\lang\qtbase_ko.qm" Delete "$INSTDIR\lang\qtbase_ru.qm" Delete "$INSTDIR\lang\qtbase_uk.qm" Delete "$INSTDIR\lang\qtbase_zh_TW.qm" -Delete "$INSTDIR\audio\qtaudio_windows.dll" +Delete "$INSTDIR\resources\add.svgz" +Delete "$INSTDIR\resources\avatararea.png" +Delete "$INSTDIR\resources\avatarareaimport.png" +Delete "$INSTDIR\resources\back.svgz" +Delete "$INSTDIR\resources\flag-de.png" +Delete "$INSTDIR\resources\flag-fr.png" +Delete "$INSTDIR\resources\flag-gb.png" +Delete "$INSTDIR\resources\flag-kr.png" +Delete "$INSTDIR\resources\flag-ru.png" +Delete "$INSTDIR\resources\flag-tw.png" +Delete "$INSTDIR\resources\flag-ua.png" +Delete "$INSTDIR\resources\flag-us.png" +Delete "$INSTDIR\resources\gta5view-16.png" +Delete "$INSTDIR\resources\gta5view-24.png" +Delete "$INSTDIR\resources\gta5view-32.png" +Delete "$INSTDIR\resources\gta5view-40.png" +Delete "$INSTDIR\resources\gta5view-48.png" +Delete "$INSTDIR\resources\gta5view-64.png" +Delete "$INSTDIR\resources\gta5view-96.png" +Delete "$INSTDIR\resources\gta5view-128.png" +Delete "$INSTDIR\resources\gta5view-256.png" +Delete "$INSTDIR\resources\mapcayoperico.jpg" +Delete "$INSTDIR\resources\mappreview.jpg" +Delete "$INSTDIR\resources\next.svgz" +Delete "$INSTDIR\resources\pointmaker-8.png" +Delete "$INSTDIR\resources\pointmaker-16.png" +Delete "$INSTDIR\resources\pointmaker-24.png" +Delete "$INSTDIR\resources\pointmaker-32.png" +Delete "$INSTDIR\resources\savegame.svgz" +Delete "$INSTDIR\resources\watermark_1b.png" +Delete "$INSTDIR\resources\watermark_2b.png" +Delete "$INSTDIR\resources\watermark_2r.png" Delete "$INSTDIR\imageformats\qgif.dll" Delete "$INSTDIR\imageformats\qicns.dll" Delete "$INSTDIR\imageformats\qico.dll" @@ -222,10 +316,13 @@ Delete "$INSTDIR\imageformats\qtiff.dll" Delete "$INSTDIR\imageformats\qwbmp.dll" Delete "$INSTDIR\imageformats\qwebp.dll" Delete "$INSTDIR\platforms\qwindows.dll" - +Delete "$INSTDIR\styles\qcleanlooksstyle.dll" +Delete "$INSTDIR\styles\qplastiquestyle.dll" +Delete "$INSTDIR\styles\qwindowsvistastyle.dll" RmDir "$INSTDIR\lang" -RmDir "$INSTDIR\platforms" RmDir "$INSTDIR\imageformats" +RmDir "$INSTDIR\platforms" +RmDir "$INSTDIR\styles" Delete "$INSTDIR\uninstall.exe" !ifdef WEB_SITE diff --git a/.ci/osx_build.sh b/.ci/osx_build.sh new file mode 100755 index 0000000..c8545c4 --- /dev/null +++ b/.ci/osx_build.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env bash + +# Creating folders +cd ${PROJECT_DIR} && \ +echo "gta5view build version is ${APPLICATION_VERSION}" && \ +echo "gta5view image name is gta5view-osx_${APPLICATION_VERSION}.dmg" && \ +mkdir -p build && \ +mkdir -p assets && \ +cd build && \ + +/usr/local/bin/cmake \ + "-DCMAKE_PREFIX_PATH=/usr/local/opt/qt" \ + "${CMAKE_BUILD_TYPE}" \ + "-DGTA5VIEW_BUILDCODE=${PACKAGE_CODE}" \ + "-DGTA5VIEW_APPVER=${APPLICATION_VERSION}" \ + "-DGTA5VIEW_COMMIT=${APPLICATION_COMMIT}" \ + "-DWITH_DONATE=ON" \ + "-DWITH_TELEMETRY=ON" \ + "-DDONATE_ADDRESSES=$(cat ${PROJECT_DIR}/.ci/donate.txt)" \ + "-DTELEMETRY_WEBURL=https://dev.syping.de/gta5view-userstats/" \ + ../ && \ +make -j 4 && \ +/usr/local/opt/qt/bin/macdeployqt gta5view.app -dmg && \ +cp -Rf gta5view.dmg ../assets/gta5view-osx_${APPLICATION_VERSION}.dmg diff --git a/.ci/osx_ci.sh b/.ci/osx_ci.sh new file mode 100755 index 0000000..59a722e --- /dev/null +++ b/.ci/osx_ci.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash + +# Install packages +.ci/osx_install.sh && \ + +# Build gta5view +.ci/osx_build.sh && \ +cd ${PROJECT_DIR} diff --git a/.ci/osx_install.sh b/.ci/osx_install.sh new file mode 100755 index 0000000..9c5c5f3 --- /dev/null +++ b/.ci/osx_install.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash + +# Install packages +brew upgrade cmake qt diff --git a/.ci/windows_build.sh b/.ci/windows_build.sh new file mode 100755 index 0000000..4b85c38 --- /dev/null +++ b/.ci/windows_build.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash + +# Prepare environment variable +export GTA5VIEW_EXECUTABLE=gta5view-${EXECUTABLE_VERSION}${EXECUTABLE_ARCH}.exe && \ + +# Creating folders +cd ${PROJECT_DIR} && \ +echo "gta5view build version is ${APPLICATION_VERSION}" && \ +echo "gta5view executable is ${GTA5VIEW_EXECUTABLE}" && \ +mkdir -p build && \ +mkdir -p assets && \ + +# Starting build +cd build && \ +mingw64-qt-cmake \ + "${CMAKE_BUILD_TYPE}" \ + "-DGTA5VIEW_BUILDCODE=${PACKAGE_CODE}" \ + "-DGTA5VIEW_APPVER=${APPLICATION_VERSION}" \ + "-DGTA5VIEW_COMMIT=${APPLICATION_COMMIT}" \ + "-DWITH_DONATE=ON" \ + "-DWITH_TELEMETRY=ON" \ + "-DDONATE_ADDRESSES=$(cat ${PROJECT_DIR}/.ci/donate.txt)" \ + "-DTELEMETRY_WEBURL=https://dev.syping.de/gta5view-userstats/" \ + .. && \ +make -j 4 && \ +x86_64-w64-mingw32-strip -s gta5view.exe && \ +cp -Rf *.exe ${PROJECT_DIR}/assets/${GTA5VIEW_EXECUTABLE} && \ +cd ${PROJECT_DIR}/assets diff --git a/.ci/windows_docker.sh b/.ci/windows_docker.sh new file mode 100755 index 0000000..54862f1 --- /dev/null +++ b/.ci/windows_docker.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env bash + +DOCKER_IMAGE=sypingauto/gta5view-build:1.10-static +PROJECT_DIR_DOCKER=/gta5view + +cd ${PROJECT_DIR} && \ +docker pull ${DOCKER_IMAGE} && \ +docker run --rm \ + -v "${PROJECT_DIR}:${PROJECT_DIR_DOCKER}" \ + ${DOCKER_IMAGE} \ + /bin/bash -c "export PROJECT_DIR=${PROJECT_DIR_DOCKER} && export QT_SELECT=${QT_SELECT} && export APPLICATION_VERSION=${APPLICATION_VERSION} && export APPLICATION_COMMIT=${APPLICATION_COMMIT} && export BUILD_TYPE=${BUILD_TYPE} && export QMAKE_FLAGS_QT4=${QMAKE_FLAGS_QT4} && export QMAKE_FLAGS_QT5=${QMAKE_FLAGS_QT5} && export CMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} && export QMAKE_BUILD_TYPE=${QMAKE_BUILD_TYPE} && export PACKAGE_VERSION=${PACKAGE_VERSION} && export PACKAGE_BUILD=${PACKAGE_BUILD} && export PACKAGE_CODE=${PACKAGE_CODE} && export EXECUTABLE_VERSION=${EXECUTABLE_VERSION} && export EXECUTABLE_ARCH=${EXECUTABLE_ARCH} && cd ${PROJECT_DIR_DOCKER} && .ci/windows_build.sh" && \ + +# Prepare environment variable +export GTA5VIEW_EXECUTABLE=gta5view-${EXECUTABLE_VERSION}${EXECUTABLE_ARCH}.exe && \ + +# Upload Assets to Dropbox +if [ "${PACKAGE_CODE}" == "gta5-mods" ]; then + ${PROJECT_DIR}/.ci/dropbox_uploader.sh mkdir gta5-mods/${PACKAGE_VERSION} + ${PROJECT_DIR}/.ci/dropbox_uploader.sh upload ${PROJECT_DIR}/assets/${GTA5VIEW_EXECUTABLE} gta5-mods/${PACKAGE_VERSION}/${GTA5VIEW_EXECUTABLE} && \ + rm -rf ${GTA5VIEW_EXECUTABLE} +elif [ "${PACKAGE_CODE}" == "gtainside" ]; then + ${PROJECT_DIR}/.ci/dropbox_uploader.sh mkdir gtainside/${PACKAGE_VERSION} + ${PROJECT_DIR}/.ci/dropbox_uploader.sh upload ${PROJECT_DIR}/assets/${GTA5VIEW_EXECUTABLE} gtainside/${PACKAGE_VERSION}/${GTA5VIEW_EXECUTABLE} && \ + rm -rf ${GTA5VIEW_EXECUTABLE} +fi diff --git a/.ci/wininstall_build.sh b/.ci/wininstall_build.sh new file mode 100755 index 0000000..8ecbe4e --- /dev/null +++ b/.ci/wininstall_build.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env bash + +# Creating folders +cd ${PROJECT_DIR} && \ +echo "gta5view build version is ${APPLICATION_VERSION}" && \ +mkdir -p build && \ +mkdir -p assets && \ + +# Starting build +cd build && \ +mingw64-qt-cmake \ + "${CMAKE_BUILD_TYPE}" \ + "-DGTA5VIEW_BUILDCODE=${PACKAGE_CODE}" \ + "-DGTA5VIEW_APPVER=${APPLICATION_VERSION}" \ + "-DGTA5VIEW_COMMIT=${APPLICATION_COMMIT}" \ + "-DWITH_DONATE=ON" \ + "-DWITH_TELEMETRY=ON" \ + "-DDONATE_ADDRESSES=$(cat ${PROJECT_DIR}/.ci/donate.txt)" \ + "-DTELEMETRY_WEBURL=https://dev.syping.de/gta5view-userstats/" \ + "-DQCONF_BUILD=ON" \ + "-DGTA5VIEW_INLANG=RUNDIR:SEPARATOR:lang" \ + "-DGTA5VIEW_LANG=RUNDIR:SEPARATOR:lang" \ + "-DGTA5VIEW_PLUG=RUNDIR:SEPARATOR:plugins" \ + .. && \ +make -j 4 && \ +x86_64-w64-mingw32-strip -s gta5view.exe && \ +cd ${PROJECT_DIR}/assets && \ +makensis "-XTarget amd64-unicode" -NOCD ${PROJECT_DIR}/.ci/gta5view.nsi && \ +mv -f gta5view_setup.exe gta5view-${EXECUTABLE_VERSION}_setup.exe diff --git a/.ci/wininstall_docker.sh b/.ci/wininstall_docker.sh new file mode 100755 index 0000000..7a51180 --- /dev/null +++ b/.ci/wininstall_docker.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash + +DOCKER_IMAGE=sypingauto/gta5view-build:1.10-shared +PROJECT_DIR_DOCKER=/gta5view + +cd ${PROJECT_DIR} && \ +docker pull ${DOCKER_IMAGE} && \ +docker run --rm \ + -v "${PROJECT_DIR}:${PROJECT_DIR_DOCKER}" \ + ${DOCKER_IMAGE} \ + /bin/bash -c "export PROJECT_DIR=${PROJECT_DIR_DOCKER} && export QT_SELECT=${QT_SELECT} && export APPLICATION_VERSION=${APPLICATION_VERSION} && export APPLICATION_COMMIT=${APPLICATION_COMMIT} && export BUILD_TYPE=${BUILD_TYPE} && export QMAKE_FLAGS_QT4=${QMAKE_FLAGS_QT4} && export QMAKE_FLAGS_QT5=${QMAKE_FLAGS_QT5} && export CMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} && export QMAKE_BUILD_TYPE=${QMAKE_BUILD_TYPE} && export PACKAGE_VERSION=${PACKAGE_VERSION} && export PACKAGE_BUILD=${PACKAGE_BUILD} && export PACKAGE_CODE=${PACKAGE_CODE} && export EXECUTABLE_VERSION=${EXECUTABLE_VERSION} && export EXECUTABLE_ARCH=${EXECUTABLE_ARCH} && cd ${PROJECT_DIR_DOCKER} && .ci/wininstall_build.sh" diff --git a/.drone.yml b/.drone.yml new file mode 100644 index 0000000..ba4a1c5 --- /dev/null +++ b/.drone.yml @@ -0,0 +1,36 @@ +kind: pipeline +type: docker + +environment: + BUILD_TYPE: "REL" + +steps: +- name: Windows Installer + image: sypingauto/gta5view-build:1.10-shared + environment: + BUILD_SCRIPT: "wininstall_build.sh" + QT_SELECT: "qt5-x86_64-w64-mingw32" + TCA_PASS: + from_secret: tca_pass + commands: + - .drone/drone.sh + volumes: + - name: gta5view + path: /srv/gta5view +- name: Windows Portable + image: sypingauto/gta5view-build:1.10-static + environment: + BUILD_SCRIPT: "windows_build.sh" + QT_SELECT: "qt5-x86_64-w64-mingw32" + TCA_PASS: + from_secret: tca_pass + commands: + - .drone/drone.sh + volumes: + - name: gta5view + path: /srv/gta5view + +volumes: +- name: gta5view + host: + path: /srv/gta5view diff --git a/.drone/TelemetryClassAuthenticator.cpp.enc b/.drone/TelemetryClassAuthenticator.cpp.enc new file mode 100644 index 0000000..e905d2f Binary files /dev/null and b/.drone/TelemetryClassAuthenticator.cpp.enc differ diff --git a/.drone/drone.sh b/.drone/drone.sh new file mode 100755 index 0000000..b489e12 --- /dev/null +++ b/.drone/drone.sh @@ -0,0 +1,33 @@ +#!/bin/bash + +# Cleanup previous Drone build +if [ -d "assets" ]; then + rm -rf assets +fi +if [ -d "build" ]; then + rm -rf build +fi + +# Decrypt Telemetry Authenticator +rm -rf tmext/TelemetryClassAuthenticator.cpp && \ +openssl aes-256-cbc -k ${TCA_PASS} -in .drone/TelemetryClassAuthenticator.cpp.enc -out tmext/TelemetryClassAuthenticator.cpp -d -pbkdf2 + +# Check if build is not tagged +if [ "${DRONE_TAG}" == "" ]; then + export EXECUTABLE_TAG=-$(git rev-parse --short HEAD) +else + export EXECUTABLE_TAG= +fi + +# Check if package code is not set +if [ "${PACKAGE_CODE}" == "" ]; then + export PACKAGE_CODE=Drone +fi + +# Init Application Commit Hash +export APPLICATION_COMMIT=$(git rev-parse --short HEAD) + +# Start CI script and copying assets into gta5view directory +.ci/ci.sh && \ +mkdir -p /srv/gta5view/${APPLICATION_COMMIT} && \ +cp -Rf assets/* /srv/gta5view/${APPLICATION_COMMIT}/ diff --git a/.flatpak/de.syping.gta5view.yaml b/.flatpak/de.syping.gta5view.yaml new file mode 100644 index 0000000..c9a9775 --- /dev/null +++ b/.flatpak/de.syping.gta5view.yaml @@ -0,0 +1,22 @@ +app-id: de.syping.gta5view +runtime: org.kde.Platform +runtime-version: '5.15-21.08' +sdk: org.kde.Sdk +command: gta5view +finish-args: + - --share=network + - --share=ipc + - --socket=fallback-x11 + - --socket=wayland + - --device=dri +modules: + - name: gta5view + buildsystem: cmake-ninja + config-opts: + - -DFLATPAK_BUILD=ON + - -DQCONF_BUILD=ON + - -DGTA5VIEW_BUILDCODE=Flatpak + - -DGTA5VIEW_BUILDTYPE=Release + sources: + - type: dir + path: ../ diff --git a/.gitignore b/.gitignore index 023ea2a..fcd678b 100644 --- a/.gitignore +++ b/.gitignore @@ -29,3 +29,7 @@ # Qt project user file *.pro.user + +# Gettext translation files +*.po +*.pot diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000..72ea2e6 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,31 @@ +stages: + - build + +variables: + BUILD_TYPE: "REL" + +Windows Installer: + stage: build + image: sypingauto/gta5view-build:1.10-shared + variables: + BUILD_SCRIPT: "wininstall_build.sh" + QT_SELECT: "qt5-x86_64-w64-mingw32" + script: + - .gitlab/gitlab.sh + artifacts: + name: "gta5view-$CI_COMMIT_REF_NAME-${CI_COMMIT_SHA:0:8}_setup" + paths: + - "gta5view-*.exe" + +Windows Portable: + stage: build + image: sypingauto/gta5view-build:1.10-static + variables: + BUILD_SCRIPT: "windows_build.sh" + QT_SELECT: "qt5-x86_64-w64-mingw32" + script: + - .gitlab/gitlab.sh + artifacts: + name: "gta5view-$CI_COMMIT_REF_NAME-${CI_COMMIT_SHA:0:8}_portable" + paths: + - "gta5view-*.exe" diff --git a/.gitlab/TelemetryClassAuthenticator.cpp.enc b/.gitlab/TelemetryClassAuthenticator.cpp.enc new file mode 100644 index 0000000..3c7445b Binary files /dev/null and b/.gitlab/TelemetryClassAuthenticator.cpp.enc differ diff --git a/.gitlab/gitlab.sh b/.gitlab/gitlab.sh new file mode 100755 index 0000000..87d2078 --- /dev/null +++ b/.gitlab/gitlab.sh @@ -0,0 +1,24 @@ +#!/bin/bash + +# Decrypt Telemetry Authenticator +rm -rf tmext/TelemetryClassAuthenticator.cpp && \ +openssl aes-256-cbc -k ${tca_pass} -in .gitlab/TelemetryClassAuthenticator.cpp.enc -out tmext/TelemetryClassAuthenticator.cpp -d + +# Check if build is not tagged +if [ "${CI_COMMIT_TAG}" == "" ]; then + export EXECUTABLE_TAG=-$(git rev-parse --short HEAD) +else + export EXECUTABLE_TAG= +fi + +# Check if package code is not set +if [ "${PACKAGE_CODE}" == "" ]; then + export PACKAGE_CODE=GitLab +fi + +# Init Application Commit Hash +export APPLICATION_COMMIT=$(git rev-parse --short HEAD) + +# Start CI script and copying assets into base directory +.ci/ci.sh && \ +cp -Rf assets/* ./ diff --git a/.travis.yml b/.travis.yml index 2bebc22..f6c3397 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,39 +1,40 @@ -dist: trusty +dist: bionic sudo: required language: cpp -service: +services: - docker +env: + global: + - BUILD_TYPE=REL + matrix: include: - env: - - BUILD_SCRIPT=debian_travis.sh - - QMAKE_FLAGS_QT4=QMAKE_CXXFLAGS+=-Wno-missing-field-initializers - - DEBIAN_VERSION=jessie - - DOCKER_USER=i386 - - APT_INSTALL=clang - - env: - - BUILD_SCRIPT=debian_travis.sh - - QMAKE_FLAGS_QT4=QMAKE_CXXFLAGS+=-Wno-missing-field-initializers - - DEBIAN_VERSION=jessie + - BUILD_SCRIPT=debian_docker.sh + - RELEASE_LABEL="Debian 64-Bit Package" + - DEBIAN_VERSION=buster - DOCKER_USER=amd64 - APT_INSTALL=clang - env: - - BUILD_SCRIPT=windows_travis.sh - - QT_SELECT=qt5-i686-w64-mingw32 - - env: - - BUILD_SCRIPT=windows_travis.sh + - BUILD_SCRIPT=windows_docker.sh - QT_SELECT=qt5-x86_64-w64-mingw32 - - EXECUTABLE_ARCH=_x64 + - RELEASE_LABEL="Windows 64-Bit Portable" - env: - - BUILD_SCRIPT=wininstall_travis.sh + - BUILD_SCRIPT=wininstall_docker.sh - QT_SELECT=qt5-x86_64-w64-mingw32 - + - RELEASE_LABEL="Windows 64-Bit Installer" + - os: osx + osx_image: xcode14.2 + env: + - BUILD_SCRIPT=osx_ci.sh + - RELEASE_LABEL="Mac OS X 64-Bit Disk Image" + before_install: - ".travis/source.sh" - + script: - ".travis/travis.sh" @@ -41,6 +42,7 @@ deploy: provider: releases api_key: secure: o7VneEz1aHfdVwZvOZLfopf6uJWNrFsZaBvunTmXFzpmNFhlNS1qwqgMUkIA2yBRbZ3wIzVs4vfwIHv7W9yE/PqK+AYL+R8+AwKGrwlgT4HqJNuk6VM/LNJ6GwT/qkQuaoOVw29bUjmzzgIRdHmw53SlJv6Hh1VE8HphlTT//aex6nCfcFhUZ0BETdZDWz5FSHwL3NalUoqfKfQrJeky5RXzCyCANQC2tKt0bV46GaWIgWrDo2KCTNqPtRWWf5GDmnkXE5IYRMQ3mXvO9iYh0v5Y2jo4PiXGUiFUU6Z3aAWFAiPdGclrBO697cf3lCTzDMhuCETR153qFYsLShUlFf61ITAmCeHAWETjZDri0lmPONo3GoNB6alGfYEA51qw14kXakrTpICtTJj7gw/gtUYOabW6hrzmieNzMBIy62RikDPjyakFnuwW2qNHRlD65e0jYv+6nCpb6E+OV16Ysh1zhV2vTfpfzVmSuyu2J+ELqXD3OZCXRSPpDIih9UQ8335p8FBji6jHORcgym/TRgdgRmENibh8tLzWp+UjpWHuWfcpvZgOskjfwU0iDMCayMJ7tDpOhXHcAhDRnd6XRIiOJ5YZCzflj2nEwmt3YUd7DwXS/AU+WHOmcNQBjXBxF/FJa35XXcy3HKJM5TTKqtph3medo30us5yXHeG6NNg= + label: ${RELEASE_LABEL} file_glob: true file: assets/* skip_cleanup: true diff --git a/.travis/TelemetryClassAuthenticator.cpp.enc b/.travis/TelemetryClassAuthenticator.cpp.enc new file mode 100644 index 0000000..498ad86 Binary files /dev/null and b/.travis/TelemetryClassAuthenticator.cpp.enc differ diff --git a/.travis/debian_build.sh b/.travis/debian_build.sh deleted file mode 100755 index be16a47..0000000 --- a/.travis/debian_build.sh +++ /dev/null @@ -1,31 +0,0 @@ -#!/bin/bash - -# Creating folders -cd ${PROJECT_DIR} && \ -echo "gta5view build version is ${APPLICATION_VERSION}" && \ -mkdir -p build && \ -mkdir -p assets && \ -chmod -x res/gta5sync_*.qm res/gta5view.desktop res/gta5view.png && \ -cd build && \ -mkdir -p qt4 && \ -cd qt4 && \ -echo "Grand Theft Auto V Snapmatic and Savegame viewer/manager" > ./description-pak && \ -cd .. && \ -mkdir -p qt5 && \ -cd qt5 && \ -echo "Grand Theft Auto V Snapmatic and Savegame viewer/manager" > ./description-pak && \ -cd .. && \ - -# Prepare checkinstall step -mkdir -p /usr/share/gta5view && \ - -# Starting build -cd qt5 && \ -qmake -qt=5 -spec linux-clang GTA5SYNC_PREFIX=/usr QMAKE_CXXFLAGS+=-std=gnu++11 ${QMAKE_FLAGS_QT5} DEFINES+=GTA5SYNC_BUILDTYPE_REL "DEFINES+=GTA5SYNC_BUILDCODE=\\\\\\\"GitHub\\\\\\\"" "DEFINES+=GTA5SYNC_APPVER=\\\\\\\"${APPLICATION_VERSION}\\\\\\\"" DEFINES+=GTA5SYNC_QCONF DEFINES+=GTA5SYNC_TELEMETRY "DEFINES+=GTA5SYNC_TELEMETRY_WEBURL=\\\\\\\"https://dev.syping.de/gta5view-userstats/\\\\\\\"" ../../gta5view.pro && \ -make -j 4 && \ -checkinstall -D --default --nodoc --install=no --pkgname=gta5view-qt5 --pkgversion=${PACKAGE_VERSION} --pkgrelease=${PACKAGE_BUILD} --pkggroup=utility --maintainer="Syping \" --requires=libqt5core5a,libqt5gui5,libqt5network5,libqt5widgets5,qttranslations5-l10n --conflicts=gta5view,gta5view-qt4 --replaces=gta5view,gta5view-qt4 --pakdir=${PROJECT_DIR}/assets && \ -cd .. && \ -cd qt4 && \ -qmake -qt=4 GTA5SYNC_PREFIX=/usr QMAKE_CXXFLAGS+=-std=gnu++11 ${QMAKE_FLAGS_QT4} DEFINES+=GTA5SYNC_BUILDTYPE_REL "DEFINES+=GTA5SYNC_BUILDCODE=\\\\\\\"GitHub\\\\\\\"" "DEFINES+=GTA5SYNC_APPVER=\\\\\\\"${APPLICATION_VERSION}\\\\\\\"" DEFINES+=GTA5SYNC_QCONF ../../gta5view.pro && \ -make -j 4 && \ -checkinstall -D --default --nodoc --install=no --pkgname=gta5view-qt4 --pkgversion=${PACKAGE_VERSION} --pkgrelease=${PACKAGE_BUILD} --pkggroup=utility --maintainer="Syping \" --requires=libqtcore4,libqtgui4,libqt4-network,qtcore4-l10n --conflicts=gta5view,gta5view-qt5 --replaces=gta5view,gta5view-qt5 --pakdir=${PROJECT_DIR}/assets diff --git a/.travis/debian_install.sh b/.travis/debian_install.sh deleted file mode 100755 index c0fb4e7..0000000 --- a/.travis/debian_install.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/bash - -# Install packages -apt-get update -qq && \ -apt-get install -qq ${APT_INSTALL} checkinstall dpkg-dev fakeroot g++ gcc qtbase5-dev qt5-qmake qttranslations5-l10n libqt4-dev diff --git a/.travis/debian_travis.sh b/.travis/debian_travis.sh deleted file mode 100755 index 8fa5ed8..0000000 --- a/.travis/debian_travis.sh +++ /dev/null @@ -1,15 +0,0 @@ -#!/bin/bash - -if [[ ${DOCKER_USER} ]]; then - DOCKER_IMAGE=${DOCKER_USER}/debian:${DEBIAN_VERSION} -else - DOCKER_IMAGE=debian:${DEBIAN_VERSION} -fi -PROJECT_DIR_DOCKER=/gta5view - -cd ${PROJECT_DIR} && \ -docker pull ${DOCKER_IMAGE} && \ -docker run --rm \ - -v "${PROJECT_DIR}:${PROJECT_DIR_DOCKER}" \ - ${DOCKER_IMAGE} \ - /bin/bash -c "export PROJECT_DIR=${PROJECT_DIR_DOCKER} && export QT_SELECT=${QT_SELECT} && export APPLICATION_VERSION=${APPLICATION_VERSION} && export APT_INSTALL=${APT_INSTALL} && export QMAKE_FLAGS_QT4=${QMAKE_FLAGS_QT4} && export QMAKE_FLAGS_QT5=${QMAKE_FLAGS_QT5} && export PACKAGE_VERSION=${PACKAGE_VERSION} && export PACKAGE_BUILD=${PACKAGE_BUILD} && export EXECUTABLE_VERSION=${EXECUTABLE_VERSION} && export EXECUTABLE_ARCH=${EXECUTABLE_ARCH} && cd ${PROJECT_DIR_DOCKER} && .travis/debian_install.sh && .travis/debian_build.sh" diff --git a/.travis/dropbox_uploader.enc b/.travis/dropbox_uploader.enc new file mode 100644 index 0000000..60a77f9 --- /dev/null +++ b/.travis/dropbox_uploader.enc @@ -0,0 +1 @@ +Po§èQƒŠN×<ì1x£%™{ ¬Èw|RtZvö[kÎçòéAZãå2Á›øŸÎ«`ïJÑ,4vÖÏ¥@€®¦eÊ¥~U$+‡žóP€÷|ˆy<à±&–Hê¤ \ No newline at end of file diff --git a/.travis/osx_build.sh b/.travis/osx_build.sh deleted file mode 100755 index fcdd0ac..0000000 --- a/.travis/osx_build.sh +++ /dev/null @@ -1,14 +0,0 @@ -#!/bin/bash - -# Creating folders -cd ${PROJECT_DIR} && \ -echo "gta5view build version is ${APPLICATION_VERSION}" && \ -echo "gta5view image name is gta5view-osx_${APPLICATION_VERSION}.dmg" && \ -mkdir -p build && \ -mkdir -p assets && \ -cd build && \ - -/usr/local/opt/qt/bin/qmake ${QMAKE_FLAGS_QT5} DEFINES+=GTA5SYNC_BUILDTYPE_REL "DEFINES+=GTA5SYNC_BUILDCODE=\\\\\\\"GitHub\\\\\\\"" "DEFINES+=GTA5SYNC_APPVER=\\\\\\\"${APPLICATION_VERSION}\\\\\\\"" DEFINES+=GTA5SYNC_TELEMETRY "DEFINES+=GTA5SYNC_TELEMETRY_WEBURL=\\\\\\\"https://dev.syping.de/gta5view-userstats/\\\\\\\"" ../gta5view.pro && \ -make -j 4 && \ -/usr/local/opt/qt/bin/macdeployqt gta5view.app -dmg && \ -cp -Rf gta5view.dmg ../assets/gta5view-osx_${APPLICATION_VERSION}.dmg diff --git a/.travis/osx_install.sh b/.travis/osx_install.sh deleted file mode 100755 index 76687bf..0000000 --- a/.travis/osx_install.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/bash - -# Install packages -brew install qt diff --git a/.travis/osx_travis.sh b/.travis/osx_travis.sh deleted file mode 100755 index 4e4ae37..0000000 --- a/.travis/osx_travis.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/bash - -# Install packages -.travis/osx_install.sh && \ - -# Build gta5view -.travis/osx_build.sh && \ -cd ${PROJECT_DIR} diff --git a/.travis/source.sh b/.travis/source.sh index 49d471d..833e125 100755 --- a/.travis/source.sh +++ b/.travis/source.sh @@ -1,4 +1,5 @@ #!/bin/bash rm -rf tmext/TelemetryClassAuthenticator.cpp && \ -openssl aes-256-cbc -K $encrypted_55502862a724_key -iv $encrypted_55502862a724_iv -in tmext/TelemetryClassAuthenticator.cpp.enc -out tmext/TelemetryClassAuthenticator.cpp -d +openssl aes-256-cbc -K $encrypted_db000a5d87d6_key -iv $encrypted_db000a5d87d6_iv -in .travis/TelemetryClassAuthenticator.cpp.enc -out tmext/TelemetryClassAuthenticator.cpp -d && \ +openssl aes-256-cbc -K $encrypted_d57e7d2f8877_key -iv $encrypted_d57e7d2f8877_iv -in .travis/dropbox_uploader.enc -out ~/.dropbox_uploader -d diff --git a/.travis/travis.sh b/.travis/travis.sh index 413c830..94154d5 100755 --- a/.travis/travis.sh +++ b/.travis/travis.sh @@ -1,18 +1,27 @@ #!/bin/bash # Install lua -if [ "${TRAVIS_OS_NAME}" == "linux" ]; then +if [ "${TRAVIS_OS_NAME}" == "osx" ]; then + brew install lua +else sudo apt-get update -qq && \ sudo apt-get install -qq lua5.2 -elif [ "${TRAVIS_OS_NAME}" == "osx" ]; then - brew install lua fi -if [ `git name-rev --tags --name-only $(git rev-parse HEAD)` == "undefined" ]; then export APPLICATION_VERSION=`lua -e 'for line in io.lines("config.h") do local m = string.match(line, "#define GTA5SYNC_APPVER \"(.+)\"$"); if m then print(m); os.exit(0) end end'`; else export APPLICATION_VERSION=`git name-rev --tags --name-only $(git rev-parse HEAD)`; fi -export PACKAGE_VERSION=$(grep -oE '^[^\-]*' <<< $APPLICATION_VERSION) -export PACKAGE_BUILD=$(grep -oP '\-\K.+' <<< $APPLICATION_VERSION) -export EXECUTABLE_VERSION=${PACKAGE_VERSION}${PACKAGE_BUILD} -if [[ ! ${PACKAGE_BUILD} ]]; then export PACKAGE_BUILD=1; fi -export PROJECT_DIR=$(pwd) +# Check if build is not tagged +if [ "${TRAVIS_TAG}" == "" ]; then + export EXECUTABLE_TAG=-$(git rev-parse --short HEAD) +else + export EXECUTABLE_TAG= +fi -.travis/${BUILD_SCRIPT} +# Check if package code is not set +if [ "${PACKAGE_CODE}" == "" ]; then + export PACKAGE_CODE=GitHub +fi + +# Init Application Commit Hash +export APPLICATION_COMMIT=$(git rev-parse --short HEAD) + +# Start CI script +.ci/ci.sh diff --git a/.travis/ubuntu_travis.sh b/.travis/ubuntu_travis.sh index 50de7d3..d93bcbd 100755 --- a/.travis/ubuntu_travis.sh +++ b/.travis/ubuntu_travis.sh @@ -1,8 +1,8 @@ #!/bin/bash # Install packages -sudo .travis/debian_install.sh && \ +sudo .ci/debian_install.sh && \ # Build gta5view -sudo .travis/debian_build.sh && \ +sudo .ci/debian_build.sh && \ cd ${PROJECT_DIR} diff --git a/.travis/windows_build.sh b/.travis/windows_build.sh deleted file mode 100755 index 53ba6ba..0000000 --- a/.travis/windows_build.sh +++ /dev/null @@ -1,18 +0,0 @@ -#!/bin/bash - -export GTA5VIEW_EXECUTABLE=gta5view-${EXECUTABLE_VERSION}${EXECUTABLE_ARCH}.exe && \ - -# Creating folders -cd ${PROJECT_DIR} && \ -echo "gta5view build version is ${APPLICATION_VERSION}" && \ -echo "gta5view executable is ${GTA5VIEW_EXECUTABLE}" && \ -mkdir -p build && \ -mkdir -p assets && \ - -# Starting build -cd build && \ -qmake-static ${QMAKE_FLAGS} DEFINES+=GTA5SYNC_BUILDTYPE_REL "DEFINES+=GTA5SYNC_BUILDCODE=\\\\\\\"GitHub\\\\\\\"" "DEFINES+=GTA5SYNC_APPVER=\\\\\\\"${APPLICATION_VERSION}\\\\\\\"" DEFINES+=GTA5SYNC_TELEMETRY "DEFINES+=GTA5SYNC_TELEMETRY_WEBURL=\\\\\\\"https://dev.syping.de/gta5view-userstats/\\\\\\\"" ../gta5view.pro && \ -make -j 4 && \ -cp -Rf release/*.exe ${PROJECT_DIR}/assets/${GTA5VIEW_EXECUTABLE} && \ -cd ${PROJECT_DIR}/assets && \ -upx --best ${GTA5VIEW_EXECUTABLE} diff --git a/.travis/windows_travis.sh b/.travis/windows_travis.sh deleted file mode 100755 index 4811eb2..0000000 --- a/.travis/windows_travis.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/bin/bash - -QT_VERSION=5.6.3 -DOCKER_IMAGE=syping/qt5-static-mingw:${QT_VERSION} -PROJECT_DIR_DOCKER=/gta5view - -cd ${PROJECT_DIR} && \ -docker pull ${DOCKER_IMAGE} && \ -docker run --rm \ - -v "${PROJECT_DIR}:${PROJECT_DIR_DOCKER}" \ - ${DOCKER_IMAGE} \ - /bin/bash -c "export PROJECT_DIR=${PROJECT_DIR_DOCKER} && export QT_SELECT=${QT_SELECT} && export APPLICATION_VERSION=${APPLICATION_VERSION} && export QMAKE_FLAGS_QT4=${QMAKE_FLAGS_QT4} && export QMAKE_FLAGS_QT5=${QMAKE_FLAGS_QT5} && export PACKAGE_VERSION=${PACKAGE_VERSION} && export PACKAGE_BUILD=${PACKAGE_BUILD} && export EXECUTABLE_VERSION=${EXECUTABLE_VERSION} && export EXECUTABLE_ARCH=${EXECUTABLE_ARCH} && cd ${PROJECT_DIR_DOCKER} && .travis/windows_build.sh" diff --git a/.travis/wininstall_build.sh b/.travis/wininstall_build.sh deleted file mode 100755 index 806292c..0000000 --- a/.travis/wininstall_build.sh +++ /dev/null @@ -1,17 +0,0 @@ -#!/bin/bash - -apt-get update -qq && \ -apt-get install -qq nsis && \ - -# Creating folders -cd ${PROJECT_DIR} && \ -echo "gta5view build version is ${APPLICATION_VERSION}" && \ -mkdir -p build && \ -mkdir -p assets && \ - -# Starting build -cd build && \ -qmake ${QMAKE_FLAGS} DEFINES+=GTA5SYNC_BUILDTYPE_REL "DEFINES+=GTA5SYNC_BUILDCODE=\\\\\\\"GitHub\\\\\\\"" "DEFINES+=GTA5SYNC_APPVER=\\\\\\\"${APPLICATION_VERSION}\\\\\\\"" DEFINES+=GTA5SYNC_TELEMETRY "DEFINES+=GTA5SYNC_TELEMETRY_WEBURL=\\\\\\\"https://dev.syping.de/gta5view-userstats/\\\\\\\"" DEFINES+=GTA5SYNC_QCONF DEFINES+=GTA5SYNC_INLANG='\\\"RUNDIR:SEPARATOR:lang\\\"' DEFINES+=GTA5SYNC_LANG='\\\"RUNDIR:SEPARATOR:lang\\\"' DEFINES+=GTA5SYNC_PLUG='\\\"RUNDIR:SEPARATOR:plugins\\\"' ../gta5view.pro && \ -make -j 4 && \ -cd ${PROJECT_DIR}/assets && \ -makensis -NOCD ${PROJECT_DIR}/.travis/gta5view.nsi diff --git a/.travis/wininstall_travis.sh b/.travis/wininstall_travis.sh deleted file mode 100755 index e9fabfc..0000000 --- a/.travis/wininstall_travis.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/bin/bash - -QT_VERSION=5.6.3 -DOCKER_IMAGE=syping/qt5-shared-mingw:${QT_VERSION} -PROJECT_DIR_DOCKER=/gta5view - -cd ${PROJECT_DIR} && \ -docker pull ${DOCKER_IMAGE} && \ -docker run --rm \ - -v "${PROJECT_DIR}:${PROJECT_DIR_DOCKER}" \ - ${DOCKER_IMAGE} \ - /bin/bash -c "export PROJECT_DIR=${PROJECT_DIR_DOCKER} && export QT_SELECT=${QT_SELECT} && export APPLICATION_VERSION=${APPLICATION_VERSION} && export QMAKE_FLAGS_QT4=${QMAKE_FLAGS_QT4} && export QMAKE_FLAGS_QT5=${QMAKE_FLAGS_QT5} && export PACKAGE_VERSION=${PACKAGE_VERSION} && export PACKAGE_BUILD=${PACKAGE_BUILD} && export EXECUTABLE_VERSION=${EXECUTABLE_VERSION} && export EXECUTABLE_ARCH=${EXECUTABLE_ARCH} && cd ${PROJECT_DIR_DOCKER} && .travis/wininstall_build.sh" diff --git a/AboutDialog.cpp b/AboutDialog.cpp index e9fe88e..12852cf 100644 --- a/AboutDialog.cpp +++ b/AboutDialog.cpp @@ -1,6 +1,6 @@ /***************************************************************************** -* gta5sync GRAND THEFT AUTO V SYNC -* Copyright (C) 2016-2018 Syping +* gta5view Grand Theft Auto V Profile Viewer +* Copyright (C) 2016-2021 Syping * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -29,47 +29,47 @@ AboutDialog::AboutDialog(QWidget *parent) : ui(new Ui::AboutDialog) { // Set Window Flags +#if QT_VERSION >= 0x050900 + setWindowFlag(Qt::WindowContextHelpButtonHint, false); +#else setWindowFlags(windowFlags()^Qt::WindowContextHelpButtonHint); +#endif // Build Strings - QString appVersion = qApp->applicationVersion(); - QString buildType = tr(GTA5SYNC_BUILDTYPE); - buildType.replace("_", " "); - QString projectBuild = AppEnv::getBuildDateTime(); - QString buildStr = GTA5SYNC_BUILDSTRING; + QString appVersion = QApplication::applicationVersion(); + const char* literalBuildType = GTA5SYNC_BUILDTYPE; + const QString buildType = tr(literalBuildType); + const QString projectBuild = AppEnv::getBuildDateTime(); + const QString buildStr = GTA5SYNC_BUILDSTRING; +#ifdef GTA5SYNC_COMMIT + if ((strcmp(literalBuildType, REL_BUILDTYPE) != 0) && !appVersion.contains("-")) + appVersion = appVersion % "-" % GTA5SYNC_COMMIT; +#endif // Translator Comments //: Translated by translator, example Translated by Syping - QString translatedByStr = tr("Translated by %1"); + const QString translatedByStr = tr("Translated by %1"); //: Insert your name here and profile here in following scheme, First Translator,First Profile\\nSecond Translator\\nThird Translator,Second Profile - QString translatorVal = tr("TRANSLATOR"); + const QString translatorVal = tr("TRANSLATOR"); QStringList translatorContent; - if (translatorVal != "TRANSLATOR") - { + if (translatorVal != "TRANSLATOR") { const QStringList translatorList = translatorVal.split('\n'); - for (const QString &translatorStr : translatorList) - { + for (const QString &translatorStr : translatorList) { QStringList translatorStrList = translatorStr.split(','); - QString translatorName = translatorStrList.at(0); + const QString translatorName = translatorStrList.at(0); translatorStrList.removeFirst(); QString translatorProfile = translatorStrList.join(QString()); - if (!translatorProfile.isEmpty()) - { + if (!translatorProfile.isEmpty()) { translatorContent += QString("%2").arg(translatorProfile, translatorName); } - else - { + else { translatorContent += translatorName; } } } // Project Description -#ifdef GTA5SYNC_ENABLED - QString projectDes = tr("A project for viewing and sync Grand Theft Auto V Snapmatic
\nPictures and Savegames"); -#else - QString projectDes = tr("A project for viewing Grand Theft Auto V Snapmatic
\nPictures and Savegames"); -#endif + const QString projectDes = tr("A project for viewing Grand Theft Auto V Snapmatic
\nPictures and Savegames"); // Copyright Description QString copyrightDes1 = tr("Copyright © %2 %3"); @@ -77,40 +77,34 @@ AboutDialog::AboutDialog(QWidget *parent) : QString copyrightDes2 = tr("%1 is licensed under GNU GPLv3"); copyrightDes2 = copyrightDes2.arg(GTA5SYNC_APPSTR); QString copyrightDesA; - if (!translatorContent.isEmpty()) - { + if (!translatorContent.isEmpty()) { copyrightDesA = copyrightDes1 % "
" % translatedByStr.arg(translatorContent.join(", ")) % "
" % copyrightDes2; } - else - { + else { copyrightDesA = copyrightDes1 % "
" % copyrightDes2; } // Setup User Interface ui->setupUi(this); aboutStr = ui->labAbout->text(); - titleStr = this->windowTitle(); + titleStr = windowTitle(); ui->labAbout->setText(aboutStr.arg(GTA5SYNC_APPSTR, projectDes, appVersion % " (" % buildType % ")", projectBuild, buildStr, qVersion(), copyrightDesA)); - this->setWindowTitle(titleStr.arg(GTA5SYNC_APPSTR)); + setWindowTitle(titleStr.arg(GTA5SYNC_APPSTR)); // Set Icon for Close Button - if (QIcon::hasThemeIcon("dialog-close")) - { + if (QIcon::hasThemeIcon("dialog-close")) { ui->cmdClose->setIcon(QIcon::fromTheme("dialog-close")); } - else if (QIcon::hasThemeIcon("gtk-close")) - { + else if (QIcon::hasThemeIcon("gtk-close")) { ui->cmdClose->setIcon(QIcon::fromTheme("gtk-close")); } // DPI calculation qreal screenRatio = AppEnv::screenRatio(); - if (!translatorContent.isEmpty()) - { + if (!translatorContent.isEmpty()) { resize(375 * screenRatio, 270 * screenRatio); } - else - { + else { resize(375 * screenRatio, 260 * screenRatio); } } @@ -122,13 +116,11 @@ AboutDialog::~AboutDialog() void AboutDialog::on_labAbout_linkActivated(const QString &link) { - if (link.left(12) == "g5e://about?") - { + if (link.left(12) == "g5e://about?") { QStringList aboutStrList = QString(link).remove(0, 12).split(":"); QMessageBox::information(this, QString::fromUtf8(QByteArray::fromBase64(aboutStrList.at(0).toUtf8())), QString::fromUtf8(QByteArray::fromBase64(aboutStrList.at(1).toUtf8()))); } - else - { + else { QDesktopServices::openUrl(QUrl(link)); } } diff --git a/AboutDialog.h b/AboutDialog.h index c9eecdf..a13dcc0 100644 --- a/AboutDialog.h +++ b/AboutDialog.h @@ -1,6 +1,6 @@ /***************************************************************************** -* gta5sync GRAND THEFT AUTO V SYNC -* Copyright (C) 2016-2017 Syping +* gta5view Grand Theft Auto V Profile Viewer +* Copyright (C) 2016-2021 Syping * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -28,7 +28,6 @@ class AboutDialog; class AboutDialog : public QDialog { Q_OBJECT - public: explicit AboutDialog(QWidget *parent = 0); ~AboutDialog(); diff --git a/AboutDialog.ui b/AboutDialog.ui index 1fa6003..3672e20 100644 --- a/AboutDialog.ui +++ b/AboutDialog.ui @@ -26,7 +26,7 @@ - <span style=" font-weight:600;">%1</span><br/> + <span style="font-weight:600">%1</span><br/> <br/> %2<br/> <br/> diff --git a/AppEnv.cpp b/AppEnv.cpp index 6180e5b..9d112a5 100644 --- a/AppEnv.cpp +++ b/AppEnv.cpp @@ -1,6 +1,6 @@ /***************************************************************************** -* gta5sync GRAND THEFT AUTO V SYNC -* Copyright (C) 2016-2017 Syping +* gta5view Grand Theft Auto V Profile Viewer +* Copyright (C) 2016-2021 Syping * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -22,19 +22,19 @@ #include "StandardPaths.h" #include #include -#include #include #include #include #include #include #include -#include -using namespace std; + +#if QT_VERSION < 0x050000 +#include +#endif AppEnv::AppEnv() { - } // Build Stuff @@ -55,18 +55,17 @@ QString AppEnv::getGameFolder(bool *ok) { QDir dir; QString GTAV_FOLDER = QString::fromUtf8(qgetenv("GTAV_FOLDER")); - if (GTAV_FOLDER != "") - { + if (GTAV_FOLDER != "") { dir.setPath(GTAV_FOLDER); - if (dir.exists()) - { - if (ok != NULL) *ok = true; + if (dir.exists()) { + if (ok != NULL) + *ok = true; qputenv("GTAV_FOLDER", dir.absolutePath().toUtf8()); return dir.absolutePath(); } } - QString GTAV_defaultFolder = StandardPaths::documentsLocation() % QDir::separator() % "Rockstar Games" % QDir::separator() % "GTA V"; + const QString GTAV_defaultFolder = StandardPaths::documentsLocation() % "/Rockstar Games/GTA V"; QString GTAV_returnFolder = GTAV_defaultFolder; QSettings settings(GTA5SYNC_APPVENDOR, GTA5SYNC_APPSTR); @@ -75,46 +74,44 @@ QString AppEnv::getGameFolder(bool *ok) GTAV_returnFolder = settings.value("dir", GTAV_defaultFolder).toString(); settings.endGroup(); - if (forceDir) - { + if (forceDir) { dir.setPath(GTAV_returnFolder); - if (dir.exists()) - { - if (ok != 0) *ok = true; + if (dir.exists()) { + if (ok != 0) + *ok = true; qputenv("GTAV_FOLDER", dir.absolutePath().toUtf8()); return dir.absolutePath(); } } dir.setPath(GTAV_defaultFolder); - if (dir.exists()) - { - if (ok != 0) *ok = true; + if (dir.exists()) { + if (ok != 0) + *ok = true; qputenv("GTAV_FOLDER", dir.absolutePath().toUtf8()); return dir.absolutePath(); } - if (!forceDir) - { + if (!forceDir) { dir.setPath(GTAV_returnFolder); - if (dir.exists()) - { - if (ok != 0) *ok = true; + if (dir.exists()) { + if (ok != 0) + *ok = true; qputenv("GTAV_FOLDER", dir.absolutePath().toUtf8()); return dir.absolutePath(); } } - if (ok != 0) *ok = false; - return ""; + if (ok != 0) + *ok = false; + return QString(); } bool AppEnv::setGameFolder(QString gameFolder) { QDir dir; dir.setPath(gameFolder); - if (dir.exists()) - { + if (dir.exists()) { qputenv("GTAV_FOLDER", dir.absolutePath().toUtf8()); return true; } @@ -132,13 +129,13 @@ QString AppEnv::getInLangFolder() #ifdef GTA5SYNC_INLANG return StringParser::convertBuildedString(GTA5SYNC_INLANG); #else - return StringParser::convertBuildedString(GTA5SYNC_SHARE % QLatin1String("SEPARATOR:APPNAME:SEPARATOR:translations")); + return StringParser::convertBuildedString(GTA5SYNC_SHARE % QLatin1String("/APPNAME:/translations")); #endif #else #ifdef GTA5SYNC_INLANG return StringParser::convertBuildedString(GTA5SYNC_INLANG); #else - return QString(":/tr"); + return QLatin1String(":/tr"); #endif #endif } @@ -148,18 +145,57 @@ QString AppEnv::getPluginsFolder() return StringParser::convertBuildedString(GTA5SYNC_PLUG); } +QString AppEnv::getImagesFolder() +{ +#if defined(GTA5SYNC_QCONF) && defined(GTA5SYNC_CMAKE) +#ifdef Q_OS_WIN + return StringParser::convertBuildedString(GTA5SYNC_SHARE % QLatin1String("/resources")); +#else + return StringParser::convertBuildedString(GTA5SYNC_SHARE % QLatin1String("/APPNAME:/resources")); +#endif +#else + return QLatin1String(":/img"); +#endif +} + +QString AppEnv::getShareFolder() +{ + return StringParser::convertBuildedString(GTA5SYNC_SHARE); +} + // Web Stuff QByteArray AppEnv::getUserAgent() { - return QString("Mozilla/5.0 (X11; Linux x86_64; rv:45.0) Gecko/20100101 Firefox/45.0 %1/%2").arg(GTA5SYNC_APPSTR, GTA5SYNC_APPVER).toUtf8(); +#if QT_VERSION >= 0x050400 +#ifdef Q_OS_WIN + QString kernelVersion = QSysInfo::kernelVersion(); + const QStringList &kernelVersionList = kernelVersion.split("."); + if (kernelVersionList.length() > 2) { + kernelVersion = kernelVersionList.at(0) % "." % kernelVersionList.at(1); + } + QString runArch = QSysInfo::buildCpuArchitecture(); + if (runArch == "x86_64") { + runArch = "Win64; x64"; + } + else if (runArch == "i686") { + const QString &curArch = QSysInfo::currentCpuArchitecture(); + if (curArch == "x86_64") { + runArch = "WOW64"; + } + else if (curArch == "i686") { + runArch = "Win32; x86"; + } + } + return QString("Mozilla/5.0 (Windows NT %1; %2) %3/%4").arg(kernelVersion, runArch, GTA5SYNC_APPSTR, GTA5SYNC_APPVER).toUtf8(); +#else + return QString("Mozilla/5.0 (%1; %2) %3/%4").arg(QSysInfo::kernelType(), QSysInfo::kernelVersion(), GTA5SYNC_APPSTR, GTA5SYNC_APPVER).toUtf8(); +#endif +#else + return QString("Mozilla/5.0 %1/%2").arg(GTA5SYNC_APPSTR, GTA5SYNC_APPVER).toUtf8(); +#endif } -// QUrl AppEnv::getCrewFetchingUrl(QString crewID) -// { -// return QUrl(QString("https://socialclub.rockstargames.com/reference/crewfeed/%1").arg(crewID)); -// } - QUrl AppEnv::getCrewFetchingUrl(QString crewID) { return QUrl(QString("https://socialclub.rockstargames.com/crew/%1/%1").arg(crewID)); @@ -167,7 +203,7 @@ QUrl AppEnv::getCrewFetchingUrl(QString crewID) QUrl AppEnv::getPlayerFetchingUrl(QString crewID, QString pageNumber) { - return QUrl(QString("https://socialclub.rockstargames.com/crewsapi/GetMembersList?crewId=%1&pageNumber=%2").arg(crewID, pageNumber)); + return QUrl(QString("https://socialclub.rockstargames.com/crewsapi/GetMembersList?crewId=%1&pageNumber=%2&pageSize=5000").arg(crewID, pageNumber)); } QUrl AppEnv::getPlayerFetchingUrl(QString crewID, int pageNumber) @@ -175,12 +211,234 @@ QUrl AppEnv::getPlayerFetchingUrl(QString crewID, int pageNumber) return getPlayerFetchingUrl(crewID, QString::number(pageNumber)); } +// Game Stuff + +GameVersion AppEnv::getGameVersion() +{ +#ifdef Q_OS_WIN + QString argumentValue; +#ifdef _WIN64 + argumentValue = "\\WOW6432Node"; +#endif + QSettings registrySettingsSc(QString("HKEY_LOCAL_MACHINE\\SOFTWARE%1\\Rockstar Games\\Grand Theft Auto V").arg(argumentValue), QSettings::NativeFormat); + QString installFolderSc = registrySettingsSc.value("InstallFolder", "").toString(); + QDir installFolderScDir(installFolderSc); + bool scVersionInstalled = false; + if (!installFolderSc.isEmpty() && installFolderScDir.exists()) { +#ifdef GTA5SYNC_DEBUG + qDebug() << "gameVersionFoundSocialClubVersion"; +#endif + scVersionInstalled = true; + } + + QSettings registrySettingsSteam(QString("HKEY_LOCAL_MACHINE\\SOFTWARE%1\\Rockstar Games\\GTAV").arg(argumentValue), QSettings::NativeFormat); + QString installFolderSteam = registrySettingsSteam.value("installfoldersteam", "").toString(); + if (installFolderSteam.right(5) == "\\GTAV") { + installFolderSteam = installFolderSteam.remove(installFolderSteam.length() - 5, 5); + } + QDir installFolderSteamDir(installFolderSteam); + bool steamVersionInstalled = false; + if (!installFolderSteam.isEmpty() && installFolderSteamDir.exists()) { +#ifdef GTA5SYNC_DEBUG + qDebug() << "gameVersionFoundSteamVersion"; +#endif + steamVersionInstalled = true; + } + + if (scVersionInstalled && steamVersionInstalled) { + return GameVersion::BothVersions; + } + else if (scVersionInstalled) { + return GameVersion::SocialClubVersion; + } + else if (steamVersionInstalled) { + return GameVersion::SteamVersion; + } + else { + return GameVersion::NoVersion; + } +#else + return GameVersion::NoVersion; +#endif +} + +GameLanguage AppEnv::getGameLanguage(GameVersion gameVersion) +{ + if (gameVersion == GameVersion::SocialClubVersion) { +#ifdef Q_OS_WIN + QString argumentValue; +#ifdef _WIN64 + argumentValue = "\\WOW6432Node"; +#endif + QSettings registrySettingsSc(QString("HKEY_LOCAL_MACHINE\\SOFTWARE%1\\Rockstar Games\\Grand Theft Auto V").arg(argumentValue), QSettings::NativeFormat); + QString languageSc = registrySettingsSc.value("Language", "").toString(); + return gameLanguageFromString(languageSc); +#else + return GameLanguage::Undefined; +#endif + } + else if (gameVersion == GameVersion::SteamVersion) { +#ifdef Q_OS_WIN + QString argumentValue; +#ifdef _WIN64 + argumentValue = "\\WOW6432Node"; +#endif + QSettings registrySettingsSteam(QString("HKEY_LOCAL_MACHINE\\SOFTWARE%1\\Rockstar Games\\Grand Theft Auto V Steam").arg(argumentValue), QSettings::NativeFormat); + QString languageSteam = registrySettingsSteam.value("Language", "").toString(); + return gameLanguageFromString(languageSteam); +#else + return GameLanguage::Undefined; +#endif + } + return GameLanguage::Undefined; +} + +GameLanguage AppEnv::gameLanguageFromString(QString gameLanguage) +{ + if (gameLanguage == "en-US") { + return GameLanguage::English; + } + else if (gameLanguage == "fr-FR") { + return GameLanguage::French; + } + else if (gameLanguage == "it-IT") { + return GameLanguage::Italian; + } + else if (gameLanguage == "de-DE") { + return GameLanguage::German; + } + else if (gameLanguage == "es-ES") { + return GameLanguage::Spanish; + } + else if (gameLanguage == "es-MX") { + return GameLanguage::Mexican; + } + else if (gameLanguage == "pt-BR") { + return GameLanguage::Brasilian; + } + else if (gameLanguage == "ru-RU") { + return GameLanguage::Russian; + } + else if (gameLanguage == "pl-PL") { + return GameLanguage::Polish; + } + else if (gameLanguage == "ja-JP") { + return GameLanguage::Japanese; + } + else if (gameLanguage == "zh-CHS") { + return GameLanguage::SChinese; + } + else if (gameLanguage == "zh-CHT") { + return GameLanguage::TChinese; + } + else if (gameLanguage == "ko-KR") { + return GameLanguage::Korean; + } + return GameLanguage::Undefined; +} + +QString AppEnv::gameLanguageToString(GameLanguage gameLanguage) +{ + switch (gameLanguage) { + case GameLanguage::English: + return "en-US"; + case GameLanguage::French: + return "fr-FR"; + case GameLanguage::Italian: + return "it-IT"; + case GameLanguage::German: + return "de-DE"; + case GameLanguage::Spanish: + return "es-ES"; + case GameLanguage::Mexican: + return "es-MX"; + case GameLanguage::Brasilian: + return "pt-BR"; + case GameLanguage::Polish: + return "pl-PL"; + case GameLanguage::Japanese: + return "ja-JP"; + case GameLanguage::SChinese: + return "zh-CHS"; + case GameLanguage::TChinese: + return "zh-CHT"; + case GameLanguage::Korean: + return "ko-KR"; + default: + return "Undefinied"; + } +} + +bool AppEnv::setGameLanguage(GameVersion gameVersion, GameLanguage gameLanguage) +{ + bool socialClubVersion = false; + bool steamVersion = false; + if (gameVersion == GameVersion::SocialClubVersion) { + socialClubVersion = true; + } + else if (gameVersion == GameVersion::SteamVersion) { + steamVersion = true; + } + else if (gameVersion == GameVersion::BothVersions) { + socialClubVersion = true; + steamVersion = true; + } + else { + return false; + } + if (socialClubVersion) { +#ifdef Q_OS_WIN + QString argumentValue; +#ifdef _WIN64 + argumentValue = "\\WOW6432Node"; +#endif + QSettings registrySettingsSc(QString("HKEY_LOCAL_MACHINE\\SOFTWARE%1\\Rockstar Games\\Grand Theft Auto V").arg(argumentValue), QSettings::NativeFormat); + if (gameLanguage != GameLanguage::Undefined) { + registrySettingsSc.setValue("Language", gameLanguageToString(gameLanguage)); + } + else { + registrySettingsSc.remove("Language"); + } + registrySettingsSc.sync(); + if (registrySettingsSc.status() != QSettings::NoError) { + return false; + } +#else + Q_UNUSED(gameLanguage) +#endif + } + if (steamVersion) { +#ifdef Q_OS_WIN + QString argumentValue; +#ifdef _WIN64 + argumentValue = "\\WOW6432Node"; +#endif + QSettings registrySettingsSteam(QString("HKEY_LOCAL_MACHINE\\SOFTWARE%1\\Rockstar Games\\Grand Theft Auto V Steam").arg(argumentValue), QSettings::NativeFormat); + if (gameLanguage != GameLanguage::Undefined) { + registrySettingsSteam.setValue("Language", gameLanguageToString(gameLanguage)); + } + else { + registrySettingsSteam.remove("Language"); + } + registrySettingsSteam.sync(); + if (registrySettingsSteam.status() != QSettings::NoError) { + return false; + } +#else + Q_UNUSED(gameLanguage) +#endif + } + return true; +} + +// Screen Stuff + qreal AppEnv::screenRatio() { #if QT_VERSION >= 0x050000 - qreal dpi = QGuiApplication::primaryScreen()->logicalDotsPerInch(); + qreal dpi = QApplication::primaryScreen()->logicalDotsPerInch(); #else - qreal dpi = qApp->desktop()->logicalDpiX(); + qreal dpi = QApplication::desktop()->logicalDpiX(); #endif #ifdef Q_OS_MAC return (dpi / 72); @@ -188,3 +446,12 @@ qreal AppEnv::screenRatio() return (dpi / 96); #endif } + +qreal AppEnv::screenRatioPR() +{ +#if QT_VERSION >= 0x050600 + return QApplication::primaryScreen()->devicePixelRatio(); +#else + return 1; +#endif +} diff --git a/AppEnv.h b/AppEnv.h index 2d911be..1e182a2 100644 --- a/AppEnv.h +++ b/AppEnv.h @@ -1,6 +1,6 @@ /***************************************************************************** -* gta5sync GRAND THEFT AUTO V SYNC -* Copyright (C) 2016-2018 Syping +* gta5view Grand Theft Auto V Profile Viewer +* Copyright (C) 2016-2021 Syping * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -22,6 +22,9 @@ #include #include +enum class GameVersion : int { NoVersion = 0, SocialClubVersion = 1, SteamVersion = 2, BothVersions = 3 }; +enum class GameLanguage : int { Undefined = 0, English = 1, French = 2, Italian = 3, German = 4, Spanish = 5, Mexican = 6, Brasilian = 7, Russian = 8, Polish = 9, Japanese = 10, SChinese = 11, TChinese = 12, Korean = 13 }; + class AppEnv { public: @@ -36,7 +39,9 @@ public: static bool setGameFolder(QString gameFolder); static QString getExLangFolder(); static QString getInLangFolder(); + static QString getImagesFolder(); static QString getPluginsFolder(); + static QString getShareFolder(); // Web Stuff static QByteArray getUserAgent(); @@ -44,8 +49,16 @@ public: static QUrl getPlayerFetchingUrl(QString crewID, QString pageNumber); static QUrl getPlayerFetchingUrl(QString crewID, int pageNumber); + // Game Stuff + static GameVersion getGameVersion(); + static GameLanguage getGameLanguage(GameVersion gameVersion); + static GameLanguage gameLanguageFromString(QString gameLanguage); + static QString gameLanguageToString(GameLanguage gameLanguage); + static bool setGameLanguage(GameVersion gameVersion, GameLanguage gameLanguage); + // Screen Stuff static qreal screenRatio(); + static qreal screenRatioPR(); }; #endif // APPENV_H diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..dbd21a8 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,382 @@ +cmake_minimum_required(VERSION 3.7) + +project(gta5view LANGUAGES C CXX) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +set(CMAKE_AUTOUIC ON) +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTORCC ON) + +set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +set(FORCE_QT_VERSION "" CACHE STRING "Force Qt Version") +if(FORCE_QT_VERSION) + set(QT_VERSION_MAJOR ${FORCE_QT_VERSION}) +else() + find_package(QT NAMES Qt6 Qt5 COMPONENTS Core REQUIRED) +endif() +find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Network Svg Widgets REQUIRED) +find_package(Qt${QT_VERSION_MAJOR} COMPONENTS LinguistTools QUIET) + +if(WIN32) + list(APPEND GTA5VIEW_LIBS + dwmapi + ) + list(APPEND GTA5VIEW_DEFINES + -DUNICODE + -D_UNICODE + -DWIN32 + ) + list(APPEND GTA5VIEW_RESOURCES + res/app.rc + ) +endif() +if(APPLE) + list(APPEND GTA5VIEW_RESOURCES + res/gta5view.icns + ) + set(MACOSX_BUNDLE_BUNDLE_NAME gta5view) + set(MACOSX_BUNDLE_BUNDLE_VERSION 1.10.2) + set(MACOSX_BUNDLE_ICON_FILE gta5view.icns) + set(MACOSX_BUNDLE_GUI_IDENTIFIER de.syping.gta5view) + set_source_files_properties(res/gta5view.icns PROPERTIES MACOSX_PACKAGE_LOCATION "Resources") +endif() + +list(APPEND GTA5VIEW_DEFINES + -DGTA5SYNC_CMAKE + -DGTA5SYNC_PROJECT +) + +set(GTA5VIEW_SOURCES + main.cpp + AboutDialog.cpp + AppEnv.cpp + CrewDatabase.cpp + DatabaseThread.cpp + ExportDialog.cpp + ExportThread.cpp + GlobalString.cpp + IconLoader.cpp + ImportDialog.cpp + JsonEditorDialog.cpp + MapLocationDialog.cpp + OptionsDialog.cpp + PictureDialog.cpp + PictureExport.cpp + PictureWidget.cpp + PlayerListDialog.cpp + ProfileDatabase.cpp + ProfileInterface.cpp + ProfileLoader.cpp + ProfileWidget.cpp + RagePhoto.cpp + SavegameCopy.cpp + SavegameData.cpp + SavegameDialog.cpp + SavegameWidget.cpp + SidebarGenerator.cpp + SnapmaticEditor.cpp + SnapmaticPicture.cpp + SnapmaticWidget.cpp + StandardPaths.cpp + StringParser.cpp + TranslationClass.cpp + UserInterface.cpp + anpro/imagecropper.cpp + pcg/pcg_basic.c + uimod/JSHighlighter.cpp + uimod/UiModLabel.cpp + uimod/UiModWidget.cpp +) + +set(GTA5VIEW_HEADERS + config.h + wrapper.h + AboutDialog.h + AppEnv.h + CrewDatabase.h + DatabaseThread.h + ExportDialog.h + ExportThread.h + GlobalString.h + IconLoader.h + ImportDialog.h + JsonEditorDialog.h + MapLocationDialog.h + OptionsDialog.h + PictureDialog.h + PictureExport.h + PictureWidget.h + PlayerListDialog.h + ProfileDatabase.h + ProfileInterface.h + ProfileLoader.h + ProfileWidget.h + RagePhoto.h + SavegameCopy.h + SavegameData.h + SavegameDialog.h + SavegameWidget.h + SidebarGenerator.h + SnapmaticEditor.h + SnapmaticPicture.h + SnapmaticWidget.h + StandardPaths.h + StringParser.h + TranslationClass.h + UserInterface.h + anpro/imagecropper.h + pcg/pcg_basic.h + uimod/JSHighlighter.h + uimod/UiModLabel.h + uimod/UiModWidget.h +) + +set(GTA5VIEW_INCLUDEDIR + anpro + pcg + uimod +) + +set(GTA5VIEW_FORMS + AboutDialog.ui + ExportDialog.ui + ImportDialog.ui + JsonEditorDialog.ui + MapLocationDialog.ui + OptionsDialog.ui + PictureDialog.ui + PlayerListDialog.ui + ProfileInterface.ui + SavegameDialog.ui + SavegameWidget.ui + SnapmaticEditor.ui + SnapmaticWidget.ui + UserInterface.ui +) + +set(GTA5VIEW_TRANSLATIONS + res/gta5sync_de.ts + res/gta5sync_en_US.ts + res/gta5sync_fr.ts + res/gta5sync_ko.ts + res/gta5sync_ru.ts + res/gta5sync_uk.ts + res/gta5sync_zh_TW.ts +) + +list(APPEND GTA5VIEW_RESOURCES + res/global.qrc + res/template.qrc +) +set_property(SOURCE res/global.qrc PROPERTY AUTORCC_OPTIONS "-threshold;0;-compress;9") + +if(Qt5LinguistTools_FOUND) + qt5_add_translation(GTA5VIEW_QMFILES + ${GTA5VIEW_TRANSLATIONS} + res/qt5/qtbase_en_GB.ts + ) + set(LINGUIST_FOUND TRUE) +elseif(Qt6LinguistTools_FOUND) + qt6_add_translation(GTA5VIEW_QMFILES + ${GTA5VIEW_TRANSLATIONS} + res/qt6/qtbase_en_GB.ts + ) + set(LINGUIST_FOUND TRUE) +else() + set(GTA5VIEW_QMFILES + res/gta5sync_de.qm + res/gta5sync_en_US.qm + res/gta5sync_fr.qm + res/gta5sync_ko.qm + res/gta5sync_ru.qm + res/gta5sync_uk.qm + res/gta5sync_zh_TW.qm + res/qt${QT_VERSION_MAJOR}/qtbase_en_GB.qm + ) +endif() + +option(QCONF_BUILD "System installation intended Qconf build" OFF) +if(QCONF_BUILD) + list(APPEND GTA5VIEW_DEFINES + -DGTA5SYNC_QCONF + ) +else() + list(APPEND GTA5VIEW_RESOURCES + res/img.qrc + res/tr_g5p.qrc + res/qt${QT_VERSION_MAJOR}/tr_qt.qrc + ) +endif() + +option(FLATPAK_BUILD "Flatpak modifications and optimisations" OFF) +if(FLATPAK_BUILD) + list(APPEND GTA5VIEW_DEFINES + -DGTA5SYNC_FLATPAK + ) +endif() + +option(WITH_DONATE "Donate menu option and donation dialog" OFF) +if(WITH_DONATE) + set(DONATE_ADDRESSES "" CACHE STRING "Donation addresses") + list(APPEND GTA5VIEW_HEADERS + anpro/QrCode.h + ) + list(APPEND GTA5VIEW_SOURCES + anpro/QrCode.cpp + ) + list(APPEND GTA5VIEW_DEFINES + -DGTA5SYNC_DONATE + ) + list(APPEND GTA5VIEW_RESOURCES + res/donate.qrc + ) + if(DONATE_ADDRESSES) + list(APPEND GTA5VIEW_DEFINES + "-DGTA5SYNC_DONATE_ADDRESSES=\"${DONATE_ADDRESSES}\"" + ) + endif() +endif() + +option(WITH_MOTD "Developer message system directed to users" OFF) +if(WITH_MOTD) + set(MOTD_WEBURL "" CACHE STRING "Messages WebURL") + list(APPEND GTA5VIEW_HEADERS + MessageThread.h + ) + list(APPEND GTA5VIEW_SOURCES + MessageThread.cpp + ) + list(APPEND GTA5VIEW_DEFINES + -DGTA5SYNC_MOTD + ) + if(MOTD_WEBURL) + list(APPEND GTA5VIEW_DEFINES + "-DGTA5SYNC_MOTD_WEBURL=\"${MOTD_WEBURL}\"" + ) + endif() +endif() + +option(WITH_TELEMETRY "Hardware survey and basic telemetry system" OFF) +if(WITH_TELEMETRY) + set(TELEMETRY_AUTHID "" CACHE STRING "Telemetry AuthID") + set(TELEMETRY_AUTHPW "" CACHE STRING "Telemetry AuthPW") + set(TELEMETRY_PUSHURL "" CACHE STRING "Telemetry PushURL") + set(TELEMETRY_REGURL "" CACHE STRING "Telemetry RegURL") + set(TELEMETRY_WEBURL "" CACHE STRING "Telemetry WebURL") + list(APPEND GTA5VIEW_HEADERS + TelemetryClass.h + tmext/TelemetryClassAuthenticator.h + ) + list(APPEND GTA5VIEW_SOURCES + TelemetryClass.cpp + tmext/TelemetryClassAuthenticator.cpp + ) + list(APPEND GTA5VIEW_INCLUDEDIR + tmext + ) + list(APPEND GTA5VIEW_DEFINES + -DGTA5SYNC_TELEMETRY + ) + if(TELEMETRY_AUTHID AND TELEMETRY_AUTHPW AND TELEMETRY_PUSHURL AND TELEMETRY_REGURL) + list(APPEND GTA5VIEW_DEFINES + "-DGTA5SYNC_TELEMETRY_AUTHID=\"${TELEMETRY_AUTHID}\"" + "-DGTA5SYNC_TELEMETRY_AUTHPW=\"${TELEMETRY_AUTHPW}\"" + "-DGTA5SYNC_TELEMETRY_PUSHURL=\"${TELEMETRY_PUSHURL}\"" + "-DGTA5SYNC_TELEMETRY_REGURL=\"${TELEMETRY_REGURL}\"" + ) + endif() + if(TELEMETRY_WEBURL) + list(APPEND GTA5VIEW_DEFINES + "-DGTA5SYNC_TELEMETRY_WEBURL=\"${TELEMETRY_WEBURL}\"" + ) + endif() + if(WIN32) + list(APPEND GTA5VIEW_LIBS + d3d9 + ) + endif() +endif() + +if(GTA5VIEW_APPVER) + list(APPEND GTA5VIEW_DEFINES + "-DGTA5SYNC_APPVER=\"${GTA5VIEW_APPVER}\"" + ) +endif() +if(GTA5VIEW_BUILDCODE) + list(APPEND GTA5VIEW_DEFINES + "-DGTA5SYNC_BUILDCODE=\"${GTA5VIEW_BUILDCODE}\"" + ) +endif() +if(GTA5VIEW_BUILDTYPE) + list(APPEND GTA5VIEW_DEFINES + "-DGTA5SYNC_BUILDTYPE=\"${GTA5VIEW_BUILDTYPE}\"" + ) +endif() +if(GTA5VIEW_COMMIT) + list(APPEND GTA5VIEW_DEFINES + "-DGTA5SYNC_COMMIT=\"${GTA5VIEW_COMMIT}\"" + ) +endif() + +if(GTA5VIEW_INLANG) + list(APPEND GTA5VIEW_DEFINES + "-DGTA5SYNC_INLANG=\"${GTA5VIEW_INLANG}\"" + ) +endif() +if(GTA5VIEW_LANG) + list(APPEND GTA5VIEW_DEFINES + "-DGTA5SYNC_LANG=\"${GTA5VIEW_LANG}\"" + ) +endif() +if(GTA5VIEW_PLUG) + list(APPEND GTA5VIEW_DEFINES + "-DGTA5SYNC_PLUG=\"${GTA5VIEW_PLUG}\"" + ) +endif() + +add_executable(gta5view + WIN32 MACOSX_BUNDLE + ${GTA5VIEW_HEADERS} + ${GTA5VIEW_SOURCES} + ${GTA5VIEW_FORMS} + ${GTA5VIEW_RESOURCES} +) + +if(LINGUIST_FOUND AND QCONF_BUILD) + add_custom_target(translations DEPENDS ${GTA5VIEW_QMFILES}) + add_dependencies(gta5view translations) +endif() + +if(CMAKE_VERSION VERSION_GREATER_EQUAL "3.16.0") + target_precompile_headers(gta5view PRIVATE config.h) +endif() + +if(Qt5Core_VERSION VERSION_GREATER_EQUAL "5.14.0") + qt5_import_plugins(gta5view INCLUDE Qt5::QSvgPlugin) +endif() + +target_compile_definitions(gta5view PRIVATE ${GTA5VIEW_DEFINES}) +target_include_directories(gta5view PRIVATE ${GTA5VIEW_INCLUDEDIR}) +target_link_libraries(gta5view PRIVATE Qt${QT_VERSION_MAJOR}::Network Qt${QT_VERSION_MAJOR}::Svg Qt${QT_VERSION_MAJOR}::Widgets ${GTA5VIEW_LIBS}) + +install(TARGETS gta5view DESTINATION bin) +install(FILES res/de.syping.gta5view.desktop DESTINATION share/applications) +install(FILES res/de.syping.gta5view.metainfo.xml DESTINATION share/metainfo) +install(FILES res/de.syping.gta5view.xml DESTINATION share/mime/packages) +install(FILES res/gta5view-16.png DESTINATION share/icons/hicolor/16x16/apps RENAME de.syping.gta5view.png) +install(FILES res/gta5view-24.png DESTINATION share/icons/hicolor/24x24/apps RENAME de.syping.gta5view.png) +install(FILES res/gta5view-32.png DESTINATION share/icons/hicolor/32x32/apps RENAME de.syping.gta5view.png) +install(FILES res/gta5view-48.png DESTINATION share/icons/hicolor/48x48/apps RENAME de.syping.gta5view.png) +install(FILES res/gta5view-64.png DESTINATION share/icons/hicolor/64x64/apps RENAME de.syping.gta5view.png) +install(FILES res/gta5view-96.png DESTINATION share/icons/hicolor/96x96/apps RENAME de.syping.gta5view.png) +install(FILES res/gta5view-128.png DESTINATION share/icons/hicolor/128x128/apps RENAME de.syping.gta5view.png) +install(FILES res/gta5view-256.png DESTINATION share/icons/hicolor/256x256/apps RENAME de.syping.gta5view.png) +install(FILES res/gta5view-512.png DESTINATION share/icons/hicolor/512x512/apps RENAME de.syping.gta5view.png) +if(QCONF_BUILD) + include(res/img.cmake) + install(FILES ${GTA5VIEW_IMGFILES} DESTINATION share/gta5view/resources) + install(FILES ${GTA5VIEW_QMFILES} DESTINATION share/gta5view/translations) +endif() diff --git a/CrewDatabase.cpp b/CrewDatabase.cpp index 73d9bed..0fa96c6 100644 --- a/CrewDatabase.cpp +++ b/CrewDatabase.cpp @@ -1,5 +1,5 @@ /***************************************************************************** -* gta5sync GRAND THEFT AUTO V SYNC +* gta5view Grand Theft Auto V Profile Viewer * Copyright (C) 2016-2017 Syping * * This program is free software: you can redistribute it and/or modify diff --git a/CrewDatabase.h b/CrewDatabase.h index 0bb9933..692ea79 100644 --- a/CrewDatabase.h +++ b/CrewDatabase.h @@ -1,5 +1,5 @@ /***************************************************************************** -* gta5sync GRAND THEFT AUTO V SYNC +* gta5view Grand Theft Auto V Profile Viewer * Copyright (C) 2016-2017 Syping * * This program is free software: you can redistribute it and/or modify diff --git a/DatabaseThread.cpp b/DatabaseThread.cpp index 2344dca..9173eac 100644 --- a/DatabaseThread.cpp +++ b/DatabaseThread.cpp @@ -1,6 +1,6 @@ /***************************************************************************** -* gta5sync GRAND THEFT AUTO V SYNC -* Copyright (C) 2016-2017 Syping +* gta5view Grand Theft Auto V Profile Viewer +* Copyright (C) 2016-2021 Syping * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -44,137 +44,65 @@ void DatabaseThread::run() { QEventLoop threadLoop; - QStringList crewList; - QStringList crewListR; - - // Register thread loop end signal QObject::connect(this, SIGNAL(threadTerminated()), &threadLoop, SLOT(quit())); - // Setup crewList for Quick time scan - crewList = crewDB->getCrews(); - if (!crewList.isEmpty()) - { - crewListR = deleteCompatibleCrews(crewList); - } - else - { - while (crewList.isEmpty() && threadRunning) - { - QTimer::singleShot(1000, &threadLoop, SLOT(quit())); - threadLoop.exec(); - if (!crewDB->isAddingCrews()) - { - crewList = crewDB->getCrews(); - } - } - if (threadRunning) - { - crewListR = deleteCompatibleCrews(crewList); - } - } - - // Only do QTS when Thread should be run - if (threadRunning) - { - // Quick time scan -#ifdef GTA5SYNC_DEBUG - qDebug() << "Start QTS"; -#endif - if (crewListR.length() <= 5) - { - scanCrewReference(crewListR, 2500); - emit crewNameUpdated(); - } - if (crewList.length() <= 3) - { - scanCrewMembersList(crewList, 3, 2500); - emit playerNameUpdated(); - } - else if (crewList.length() <= 5) - { - scanCrewMembersList(crewList, 2, 2500); - emit playerNameUpdated(); - } - - if (threadRunning) - { - QTimer::singleShot(10000, &threadLoop, SLOT(quit())); - threadLoop.exec(); - } - } - - while (threadRunning) - { - crewList = crewDB->getCrews(); - crewListR = deleteCompatibleCrews(crewList); - - // Long time scan -#ifdef GTA5SYNC_DEBUG - qDebug() << "Start LTS"; -#endif - scanCrewReference(crewListR, 10000); - emit crewNameUpdated(); - scanCrewMembersList(crewList, crewMaxPages, 10000); - emit playerNameUpdated(); - - if (threadRunning) - { - QTimer::singleShot(300000, &threadLoop, SLOT(quit())); - threadLoop.exec(); - } + while (threadRunning) { + QTimer::singleShot(300000, &threadLoop, SLOT(quit())); + threadLoop.exec(); } } void DatabaseThread::scanCrewReference(const QStringList &crewList, const int &requestDelay) { - for (QString crewID : crewList) - { - if (threadRunning && crewID != QLatin1String("0")) - { + for (const QString &crewID : crewList) { + if (threadRunning && crewID != QLatin1String("0")) { QNetworkAccessManager *netManager = new QNetworkAccessManager(); QNetworkRequest netRequest(AppEnv::getCrewFetchingUrl(crewID)); #if QT_VERSION >= 0x050600 +#if QT_VERSION < 0x060000 netRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); +#endif #endif netRequest.setRawHeader("User-Agent", AppEnv::getUserAgent()); - netRequest.setRawHeader("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"); - netRequest.setRawHeader("Accept-Language", "en-US;q=0.5,en;q=0.3"); + netRequest.setRawHeader("Accept", "text/html"); + netRequest.setRawHeader("Accept-Charset", "utf-8"); + netRequest.setRawHeader("Accept-Language", "en-US,en;q=0.9"); netRequest.setRawHeader("Connection", "keep-alive"); QNetworkReply *netReply = netManager->get(netRequest); QEventLoop *downloadLoop = new QEventLoop(); QObject::connect(netReply, SIGNAL(finished()), downloadLoop, SLOT(quit())); - if (!continueLastCrew) { QObject::connect(this, SIGNAL(threadTerminated()), downloadLoop, SLOT(quit())); } + if (!continueLastCrew) + QObject::connect(this, SIGNAL(threadTerminated()), downloadLoop, SLOT(quit())); QTimer::singleShot(30000, downloadLoop, SLOT(quit())); downloadLoop->exec(); downloadLoop->disconnect(); delete downloadLoop; - if (netReply->isFinished()) - { + if (netReply->isFinished()) { QString crewName; QByteArray crewHtml = netReply->readAll(); QStringList crewHtmlSplit1 = QString::fromUtf8(crewHtml).split("Rockstar Games Social Club - Crew : "); - if (crewHtmlSplit1.length() >= 2) - { + if (crewHtmlSplit1.length() >= 2) { QStringList crewHtmlSplit2 = QString(crewHtmlSplit1.at(1)).split(""); - if (crewHtmlSplit2.length() >= 1) - { + if (crewHtmlSplit2.length() >= 1) { crewName = crewHtmlSplit2.at(0); } } - if (!crewName.isEmpty()) - { + if (!crewName.isEmpty()) { emit crewNameFound(crewID.toInt(), crewName); } } + else { + netReply->abort(); + } - if (threadRunning) - { + if (threadRunning) { QEventLoop *waitingLoop = new QEventLoop(); QTimer::singleShot(requestDelay, waitingLoop, SLOT(quit())); - if (!continueLastCrew) { QObject::connect(this, SIGNAL(threadTerminated()), waitingLoop, SLOT(quit())); } + if (!continueLastCrew) + QObject::connect(this, SIGNAL(threadTerminated()), waitingLoop, SLOT(quit())); waitingLoop->exec(); waitingLoop->disconnect(); delete waitingLoop; @@ -188,39 +116,39 @@ void DatabaseThread::scanCrewReference(const QStringList &crewList, const int &r void DatabaseThread::scanCrewMembersList(const QStringList &crewList, const int &maxPages, const int &requestDelay) { - for (QString crewID : crewList) - { - if (threadRunning && crewID != QLatin1String("0")) - { + for (const QString &crewID : crewList) { + if (threadRunning && crewID != QLatin1String("0")) { int currentFail = 0; int currentPage = 0; int foundPlayers = 0; int totalPlayers = 1000; - while(foundPlayers < totalPlayers && currentPage < maxPages && (continueLastCrew ? true : threadRunning)) - { + while(foundPlayers < totalPlayers && currentPage < maxPages && (continueLastCrew ? true : threadRunning)) { QNetworkAccessManager *netManager = new QNetworkAccessManager(); QNetworkRequest netRequest(AppEnv::getPlayerFetchingUrl(crewID, currentPage)); #if QT_VERSION >= 0x050600 +#if QT_VERSION < 0x060000 netRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); +#endif #endif netRequest.setRawHeader("User-Agent", AppEnv::getUserAgent()); - netRequest.setRawHeader("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"); - netRequest.setRawHeader("Accept-Language", "en-US;q=0.5,en;q=0.3"); + netRequest.setRawHeader("Accept", "application/json"); + netRequest.setRawHeader("Accept-Charset", "utf-8"); + netRequest.setRawHeader("Accept-Language", "en-US,en;q=0.9"); netRequest.setRawHeader("Connection", "keep-alive"); QNetworkReply *netReply = netManager->get(netRequest); QEventLoop *downloadLoop = new QEventLoop(); QObject::connect(netReply, SIGNAL(finished()), downloadLoop, SLOT(quit())); - if (!continueLastCrew) { QObject::connect(this, SIGNAL(threadTerminated()), downloadLoop, SLOT(quit())); } + if (!continueLastCrew) + QObject::connect(this, SIGNAL(threadTerminated()), downloadLoop, SLOT(quit())); QTimer::singleShot(30000, downloadLoop, SLOT(quit())); downloadLoop->exec(); downloadLoop->disconnect(); delete downloadLoop; - if (netReply->isFinished()) - { + if (netReply->isFinished()) { QByteArray crewJson = netReply->readAll(); QJsonDocument crewDocument = QJsonDocument::fromJson(crewJson); QJsonObject crewObject = crewDocument.object(); @@ -228,32 +156,25 @@ void DatabaseThread::scanCrewMembersList(const QStringList &crewList, const int if (crewMap.contains("Total")) { totalPlayers = crewMap["Total"].toInt(); } - if (crewMap.contains("Members")) - { + if (crewMap.contains("Members")) { const QList memberList = crewMap["Members"].toList(); - for (QVariant memberVariant : memberList) - { + for (const QVariant &memberVariant : memberList) { QMap memberMap = memberVariant.toMap(); - if (memberMap.contains("RockstarId") && memberMap.contains("Name")) - { + if (memberMap.contains("RockstarId") && memberMap.contains("Name")) { int RockstarId = memberMap["RockstarId"].toInt(); QString memberName = memberMap["Name"].toString(); - if (!memberName.isEmpty() && RockstarId != 0) - { + if (!memberName.isEmpty() && RockstarId != 0) { foundPlayers++; emit playerNameFound(RockstarId, memberName); } } } } - currentPage++; } - else - { + else { currentFail++; - if (currentFail == maxLoadFails) - { + if (currentFail == maxLoadFails) { currentFail = 0; currentPage++; } @@ -262,8 +183,7 @@ void DatabaseThread::scanCrewMembersList(const QStringList &crewList, const int delete netReply; delete netManager; - if (foundPlayers < totalPlayers && currentPage < maxPages && (continueLastCrew ? true : threadRunning)) - { + if (foundPlayers < totalPlayers && currentPage < maxPages && (continueLastCrew ? true : threadRunning)) { QEventLoop *waitingLoop = new QEventLoop(); QTimer::singleShot(requestDelay, waitingLoop, SLOT(quit())); if (!continueLastCrew) { QObject::connect(this, SIGNAL(threadTerminated()), waitingLoop, SLOT(quit())); } @@ -278,10 +198,8 @@ void DatabaseThread::scanCrewMembersList(const QStringList &crewList, const int void DatabaseThread::deleteCompatibleCrews(QStringList *crewList) { - for (QString crewNID : *crewList) - { - if (crewDB->isCompatibleCrew(crewNID)) - { + for (const QString &crewNID : *crewList) { + if (crewDB->isCompatibleCrew(crewNID)) { crewList->removeAll(crewNID); } } @@ -290,10 +208,8 @@ void DatabaseThread::deleteCompatibleCrews(QStringList *crewList) QStringList DatabaseThread::deleteCompatibleCrews(const QStringList &crewList) { QStringList crewListR = crewList; - for (QString crewNID : crewListR) - { - if (crewDB->isCompatibleCrew(crewNID)) - { + for (const QString &crewNID : crewListR) { + if (crewDB->isCompatibleCrew(crewNID)) { crewListR.removeAll(crewNID); } } diff --git a/DatabaseThread.h b/DatabaseThread.h index 8d8bbfa..37c6f76 100644 --- a/DatabaseThread.h +++ b/DatabaseThread.h @@ -1,5 +1,5 @@ /***************************************************************************** -* gta5sync GRAND THEFT AUTO V SYNC +* gta5view Grand Theft Auto V Profile Viewer * Copyright (C) 2016-2017 Syping * * This program is free software: you can redistribute it and/or modify diff --git a/ExportDialog.cpp b/ExportDialog.cpp index 9a93c58..43d8d97 100644 --- a/ExportDialog.cpp +++ b/ExportDialog.cpp @@ -1,5 +1,5 @@ /***************************************************************************** -* gta5sync GRAND THEFT AUTO V SYNC +* gta5view Grand Theft Auto V Profile Viewer * Copyright (C) 2016-2017 Syping * * This program is free software: you can redistribute it and/or modify diff --git a/ExportDialog.h b/ExportDialog.h index 65354f0..5da4b91 100644 --- a/ExportDialog.h +++ b/ExportDialog.h @@ -1,5 +1,5 @@ /***************************************************************************** -* gta5sync GRAND THEFT AUTO V SYNC +* gta5view Grand Theft Auto V Profile Viewer * Copyright (C) 2016-2017 Syping * * This program is free software: you can redistribute it and/or modify diff --git a/ExportThread.cpp b/ExportThread.cpp index b795824..63aef4f 100644 --- a/ExportThread.cpp +++ b/ExportThread.cpp @@ -1,6 +1,6 @@ /***************************************************************************** -* gta5sync GRAND THEFT AUTO V SYNC -* Copyright (C) 2016-2017 Syping +* gta5view Grand Theft Auto V Profile Viewer +* Copyright (C) 2016-2020 Syping * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -22,14 +22,19 @@ #include "ProfileWidget.h" #include "ExportThread.h" #include "SavegameData.h" +#include "AppEnv.h" #include "config.h" #include -#include #include #include -#include #include +#if QT_VERSION >= 0x050000 +#include +#else +#include +#endif + ExportThread::ExportThread(QMap profileMap, QString exportDirectory, bool pictureCopyEnabled, bool pictureExportEnabled, int exportCount, QObject *parent) : QThread(parent), profileMap(profileMap), exportDirectory(exportDirectory), pictureCopyEnabled(pictureCopyEnabled), pictureExportEnabled(pictureExportEnabled), exportCount(exportCount) { @@ -44,7 +49,7 @@ void ExportThread::run() // Quality Settings settings.beginGroup("Pictures"); int defaultQuality = 100; - QSize defExportSize = QSize(960, 536); + QSize defExportSize = SnapmaticPicture::getSnapmaticResolution(); int customQuality = settings.value("CustomQuality", defaultQuality).toInt(); if (customQuality < 1 || customQuality > 100) { @@ -101,8 +106,17 @@ void ExportThread::run() QImage exportPicture = picture->getImage(); if (sizeMode == "Desktop") { - QRect desktopResolution = qApp->desktop()->screenGeometry(); - exportPicture = exportPicture.scaled(desktopResolution.width(), desktopResolution.height(), aspectRatio, Qt::SmoothTransformation); +#if QT_VERSION >= 0x050000 + qreal screenRatioPR = AppEnv::screenRatioPR(); + QRect desktopResolution = QApplication::primaryScreen()->geometry(); + int desktopSizeWidth = qRound((double)desktopResolution.width() * screenRatioPR); + int desktopSizeHeight = qRound((double)desktopResolution.height() * screenRatioPR); +#else + QRect desktopResolution = QApplication::desktop()->screenGeometry(); + int desktopSizeWidth = desktopResolution.width(); + int desktopSizeHeight = desktopResolution.height(); +#endif + exportPicture = exportPicture.scaled(desktopSizeWidth, desktopSizeHeight, aspectRatio, Qt::SmoothTransformation); } else if (sizeMode == "Custom") { diff --git a/ExportThread.h b/ExportThread.h index f5837a7..99ad28b 100644 --- a/ExportThread.h +++ b/ExportThread.h @@ -1,5 +1,5 @@ /***************************************************************************** -* gta5sync GRAND THEFT AUTO V SYNC +* gta5view Grand Theft Auto V Profile Viewer * Copyright (C) 2016-2017 Syping * * This program is free software: you can redistribute it and/or modify diff --git a/GlobalString.cpp b/GlobalString.cpp index 4aa7aad..0a04a59 100644 --- a/GlobalString.cpp +++ b/GlobalString.cpp @@ -1,6 +1,6 @@ /***************************************************************************** -* gta5sync GRAND THEFT AUTO V SYNC -* Copyright (C) 2016-2018 Syping +* gta5view Grand Theft Auto V Profile Viewer +* Copyright (C) 2016-2021 Syping * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -28,17 +28,17 @@ GlobalString::GlobalString() { - } QMap GlobalString::getGlobalMap() { QMap globalMap; QSettings globalFile(getLanguageFile(), QSettings::IniFormat); +#if QT_VERSION < 0x060000 globalFile.setIniCodec("UTF-8"); +#endif globalFile.beginGroup("Global"); - for (QString globalStr : globalFile.childKeys()) - { + for (const QString &globalStr : globalFile.childKeys()) { globalMap[globalStr] = globalFile.value(globalStr, globalStr).toString(); } globalFile.endGroup(); @@ -49,12 +49,14 @@ QString GlobalString::getString(QString valueStr, bool *ok) { QString globalString = valueStr; QSettings globalFile(getLanguageFile(), QSettings::IniFormat); +#if QT_VERSION < 0x060000 globalFile.setIniCodec("UTF-8"); +#endif globalFile.beginGroup("Global"); QStringList globalStrList = globalFile.childKeys(); - if (globalStrList.contains(valueStr)) - { - if (ok != nullptr) *ok = true; + if (globalStrList.contains(valueStr)) { + if (ok != nullptr) + *ok = true; globalString = globalFile.value(valueStr, valueStr).toString(); } globalFile.endGroup(); @@ -65,10 +67,14 @@ QString GlobalString::getLanguageFile() { QString language = getLanguage(); QString languageFile = ":/global/global." % language % ".ini"; - if (!QFileInfo(languageFile).exists()) - { +#if QT_VERSION >= 0x050200 + if (!QFileInfo::exists(languageFile)) languageFile = ":/global/global.en.ini"; - } +#else + if (!QFileInfo(languageFile).exists()) + languageFile = ":/global/global.en.ini"; +#endif + return languageFile; } diff --git a/GlobalString.h b/GlobalString.h index 2d1f4ba..711afa9 100644 --- a/GlobalString.h +++ b/GlobalString.h @@ -1,5 +1,5 @@ /***************************************************************************** -* gta5sync GRAND THEFT AUTO V SYNC +* gta5view Grand Theft Auto V Profile Viewer * Copyright (C) 2016-2017 Syping * * This program is free software: you can redistribute it and/or modify diff --git a/IconLoader.cpp b/IconLoader.cpp index 4de091d..71ccee1 100644 --- a/IconLoader.cpp +++ b/IconLoader.cpp @@ -1,6 +1,6 @@ /***************************************************************************** -* gta5sync GRAND THEFT AUTO V SYNC -* Copyright (C) 2016-2017 Syping +* gta5view Grand Theft Auto V Profile Viewer +* Copyright (C) 2016-2021 Syping * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -17,34 +17,45 @@ *****************************************************************************/ #include "IconLoader.h" +#include "AppEnv.h" +#include #include IconLoader::IconLoader() { - } QIcon IconLoader::loadingAppIcon() { QIcon appIcon; - appIcon.addFile(":/img/5sync-16.png", QSize(16, 16)); - appIcon.addFile(":/img/5sync-24.png", QSize(24, 24)); - appIcon.addFile(":/img/5sync-32.png", QSize(32, 32)); - appIcon.addFile(":/img/5sync-40.png", QSize(40, 40)); - appIcon.addFile(":/img/5sync-48.png", QSize(48, 48)); - appIcon.addFile(":/img/5sync-64.png", QSize(64, 64)); - appIcon.addFile(":/img/5sync-96.png", QSize(96, 96)); - appIcon.addFile(":/img/5sync-128.png", QSize(128, 128)); - appIcon.addFile(":/img/5sync-256.png", QSize(256, 256)); +#if defined(GTA5SYNC_QCONF) && defined(GTA5SYNC_CMAKE) +#ifdef Q_OS_WIN + const QString pattern = AppEnv::getImagesFolder() % QLatin1String("/gta5view-%1.png"); +#else + const QString pattern = AppEnv::getShareFolder() % QLatin1String("/icons/hicolor/%1x%1/apps/de.syping.gta5view.png"); +#endif +#else + const QString pattern = AppEnv::getImagesFolder() % QLatin1String("/gta5view-%1.png"); +#endif + appIcon.addFile(pattern.arg("16"), QSize(16, 16)); + appIcon.addFile(pattern.arg("24"), QSize(24, 24)); + appIcon.addFile(pattern.arg("32"), QSize(32, 32)); + appIcon.addFile(pattern.arg("40"), QSize(40, 40)); + appIcon.addFile(pattern.arg("48"), QSize(48, 48)); + appIcon.addFile(pattern.arg("64"), QSize(64, 64)); + appIcon.addFile(pattern.arg("96"), QSize(96, 96)); + appIcon.addFile(pattern.arg("128"), QSize(128, 128)); + appIcon.addFile(pattern.arg("256"), QSize(256, 256)); return appIcon; } QIcon IconLoader::loadingPointmakerIcon() { QIcon pointmakerIcon; - pointmakerIcon.addFile(":/img/pointmaker-8.png", QSize(8, 8)); - pointmakerIcon.addFile(":/img/pointmaker-16.png", QSize(16, 16)); - pointmakerIcon.addFile(":/img/pointmaker-24.png", QSize(24, 24)); - pointmakerIcon.addFile(":/img/pointmaker-32.png", QSize(32, 32)); + const QString pattern = AppEnv::getImagesFolder() % QLatin1String("/pointmaker-%1.png"); + pointmakerIcon.addFile(pattern.arg("8"), QSize(8, 8)); + pointmakerIcon.addFile(pattern.arg("16"), QSize(16, 16)); + pointmakerIcon.addFile(pattern.arg("24"), QSize(24, 24)); + pointmakerIcon.addFile(pattern.arg("32"), QSize(32, 32)); return pointmakerIcon; } diff --git a/IconLoader.h b/IconLoader.h index fe8669b..8456688 100644 --- a/IconLoader.h +++ b/IconLoader.h @@ -1,5 +1,5 @@ /***************************************************************************** -* gta5sync GRAND THEFT AUTO V SYNC +* gta5view Grand Theft Auto V Profile Viewer * Copyright (C) 2016-2017 Syping * * This program is free software: you can redistribute it and/or modify diff --git a/ImageEditorDialog.cpp b/ImageEditorDialog.cpp deleted file mode 100644 index 749e412..0000000 --- a/ImageEditorDialog.cpp +++ /dev/null @@ -1,211 +0,0 @@ -/***************************************************************************** -* gta5sync GRAND THEFT AUTO V SYNC -* Copyright (C) 2017-2018 Syping -* -* This program is free software: you can redistribute it and/or modify -* it under the terms of the GNU General Public License as published by -* the Free Software Foundation, either version 3 of the License, or -* (at your option) any later version. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU General Public License for more details. -* -* You should have received a copy of the GNU General Public License -* along with this program. If not, see . -*****************************************************************************/ - -#include "ImageEditorDialog.h" -#include "ui_ImageEditorDialog.h" -#include "ProfileInterface.h" -#include "SidebarGenerator.h" -#include "StandardPaths.h" -#include "ImportDialog.h" -#include "AppEnv.h" -#include "config.h" -#include -#include -#include -#include - -ImageEditorDialog::ImageEditorDialog(SnapmaticPicture *picture, QString profileName, QWidget *parent) : - QDialog(parent), smpic(picture), profileName(profileName), - ui(new Ui::ImageEditorDialog) -{ - // Set Window Flags - setWindowFlags(windowFlags()^Qt::WindowContextHelpButtonHint); - - ui->setupUi(this); - ui->cmdClose->setFocus(); - - // Set Icon for Close Button - if (QIcon::hasThemeIcon("dialog-close")) - { - ui->cmdClose->setIcon(QIcon::fromTheme("dialog-close")); - } - else if (QIcon::hasThemeIcon("gtk-close")) - { - ui->cmdClose->setIcon(QIcon::fromTheme("gtk-close")); - } - - // Set Icon for Import Button - if (QIcon::hasThemeIcon("document-import")) - { - ui->cmdReplace->setIcon(QIcon::fromTheme("document-import")); - } - - // Set Icon for Overwrite Button - if (QIcon::hasThemeIcon("document-save")) - { - ui->cmdSave->setIcon(QIcon::fromTheme("document-save")); - } - else if (QIcon::hasThemeIcon("gtk-save")) - { - ui->cmdSave->setIcon(QIcon::fromTheme("gtk-save")); - } - - // DPI calculation - qreal screenRatio = AppEnv::screenRatio(); - - snapmaticResolutionLW = 516 * screenRatio; // 430 - snapmaticResolutionLH = 288 * screenRatio; // 240 - ui->labPicture->setMinimumSize(snapmaticResolutionLW, snapmaticResolutionLH); - ui->labCapacity->setText(tr("Capacity: %1").arg(QString::number(qRound((double)picture->getContentMaxLength() / 1024)) % " KB")); - - imageIsChanged = false; - pictureCache = picture->getImage(); - ui->labPicture->setPixmap(QPixmap::fromImage(pictureCache).scaled(snapmaticResolutionLW, snapmaticResolutionLH, Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); - - setMaximumSize(sizeHint()); - setMinimumSize(sizeHint()); - setFixedSize(sizeHint()); -} - -ImageEditorDialog::~ImageEditorDialog() -{ - delete ui; -} - -void ImageEditorDialog::on_cmdClose_clicked() -{ - close(); -} - -void ImageEditorDialog::on_cmdReplace_clicked() -{ - QSettings settings(GTA5SYNC_APPVENDOR, GTA5SYNC_APPSTR); - settings.beginGroup("FileDialogs"); - bool dontUseNativeDialog = settings.value("DontUseNativeDialog", false).toBool(); - settings.beginGroup("ImportReplace"); - -fileDialogPreOpen: //Work? - QFileDialog fileDialog(this); - fileDialog.setFileMode(QFileDialog::ExistingFile); - fileDialog.setViewMode(QFileDialog::Detail); - fileDialog.setAcceptMode(QFileDialog::AcceptOpen); - fileDialog.setOption(QFileDialog::DontUseNativeDialog, dontUseNativeDialog); - fileDialog.setWindowFlags(fileDialog.windowFlags()^Qt::WindowContextHelpButtonHint); - fileDialog.setWindowTitle(ProfileInterface::tr("Import...")); - fileDialog.setLabelText(QFileDialog::Accept, ProfileInterface::tr("Import")); - - // Getting readable Image formats - QString imageFormatsStr = " "; - for (QByteArray imageFormat : QImageReader::supportedImageFormats()) - { - imageFormatsStr += QString("*.") % QString::fromUtf8(imageFormat).toLower() % " "; - } - - QStringList filters; - filters << ProfileInterface::tr("All image files (%1)").arg(imageFormatsStr.trimmed()); - filters << ProfileInterface::tr("All files (**)"); - fileDialog.setNameFilters(filters); - - QList sidebarUrls = SidebarGenerator::generateSidebarUrls(fileDialog.sidebarUrls()); - - fileDialog.setSidebarUrls(sidebarUrls); - fileDialog.setDirectory(settings.value(profileName % "+Directory", StandardPaths::documentsLocation()).toString()); - fileDialog.restoreGeometry(settings.value(profileName % "+Geometry", "").toByteArray()); - - if (fileDialog.exec()) - { - QStringList selectedFiles = fileDialog.selectedFiles(); - if (selectedFiles.length() == 1) - { - QString selectedFile = selectedFiles.at(0); - QString selectedFileName = QFileInfo(selectedFile).fileName(); - - QFile snapmaticFile(selectedFile); - if (!snapmaticFile.open(QFile::ReadOnly)) - { - QMessageBox::warning(this, ProfileInterface::tr("Import"), ProfileInterface::tr("Can't import %1 because file can't be open").arg("\""+selectedFileName+"\"")); - goto fileDialogPreOpen; - } - QImage *importImage = new QImage(); - QImageReader snapmaticImageReader; - snapmaticImageReader.setDecideFormatFromContent(true); - snapmaticImageReader.setDevice(&snapmaticFile); - if (!snapmaticImageReader.read(importImage)) - { - QMessageBox::warning(this, ProfileInterface::tr("Import"), ProfileInterface::tr("Can't import %1 because file can't be parsed properly").arg("\""+selectedFileName+"\"")); - delete importImage; - goto fileDialogPreOpen; - } - ImportDialog *importDialog = new ImportDialog(this); - importDialog->setImage(importImage); - importDialog->setModal(true); - importDialog->show(); - importDialog->exec(); - if (importDialog->isImportAgreed()) - { - pictureCache = importDialog->image(); - ui->labPicture->setPixmap(QPixmap::fromImage(pictureCache).scaled(snapmaticResolutionLW, snapmaticResolutionLH, Qt::KeepAspectRatio, Qt::SmoothTransformation)); - imageIsChanged = true; - } - delete importDialog; - } - } - - settings.setValue(profileName % "+Geometry", fileDialog.saveGeometry()); - settings.setValue(profileName % "+Directory", fileDialog.directory().absolutePath()); - settings.endGroup(); - settings.endGroup(); -} - -void ImageEditorDialog::on_cmdSave_clicked() -{ - if (imageIsChanged) - { - const QByteArray previousPicture = smpic->getPictureStream(); - bool success = smpic->setImage(pictureCache); - if (success) - { - QString currentFilePath = smpic->getPictureFilePath(); - QString originalFilePath = smpic->getOriginalPictureFilePath(); - QString backupFileName = originalFilePath % ".bak"; - if (!QFile::exists(backupFileName)) - { - QFile::copy(currentFilePath, backupFileName); - } - if (!smpic->exportPicture(currentFilePath)) - { - smpic->setPictureStream(previousPicture); - QMessageBox::warning(this, tr("Snapmatic Image Editor"), tr("Patching of Snapmatic Image failed because of I/O Error")); - return; - } - smpic->emitCustomSignal("PictureUpdated"); - } - else - { - QMessageBox::warning(this, tr("Snapmatic Image Editor"), tr("Patching of Snapmatic Image failed because of Image Error")); - return; - } - } - close(); -} - -void ImageEditorDialog::on_cmdQuestion_clicked() -{ - QMessageBox::information(this, tr("Snapmatic Image Editor"), tr("Every taken Snapmatic have a different Capacity, a Snapmatic with higher Capacity can store a picture with better quality.")); - -} diff --git a/ImageEditorDialog.ui b/ImageEditorDialog.ui deleted file mode 100644 index 090941c..0000000 --- a/ImageEditorDialog.ui +++ /dev/null @@ -1,130 +0,0 @@ - - - ImageEditorDialog - - - - 0 - 0 - 516 - 335 - - - - Overwrite Image... - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 516 - 288 - - - - - - - - - - - QFrame::NoFrame - - - QFrame::Plain - - - 0 - - - - - - - - Capacity: %1 - - - - - - - ? - - - - - - - Qt::Horizontal - - - - 0 - 0 - - - - - - - - - - - - &Import... - - - - - - - Qt::Horizontal - - - - 0 - 0 - - - - - - - - &Overwrite - - - - - - - &Close - - - - - - - - - - - - - diff --git a/ImportDialog.cpp b/ImportDialog.cpp index 0d0f6d4..594cbf8 100644 --- a/ImportDialog.cpp +++ b/ImportDialog.cpp @@ -1,6 +1,6 @@ /***************************************************************************** -* gta5sync GRAND THEFT AUTO V SYNC -* Copyright (C) 2017-2018 Syping +* gta5view Grand Theft Auto V Profile Viewer +* Copyright (C) 2017-2022 Syping * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -16,13 +16,16 @@ * along with this program. If not, see . *****************************************************************************/ -#include "ImportDialog.h" #include "ui_ImportDialog.h" +#include "SnapmaticPicture.h" #include "SidebarGenerator.h" #include "StandardPaths.h" +#include "ImportDialog.h" +#include "imagecropper.h" #include "AppEnv.h" #include "config.h" #include +#include #include #include #include @@ -32,46 +35,50 @@ #include #include #include +#include #include #include // IMAGES VALUES -#define snapmaticResolutionW 960 -#define snapmaticResolutionH 536 #define snapmaticAvatarResolution 470 #define snapmaticAvatarPlacementW 145 #define snapmaticAvatarPlacementH 66 -ImportDialog::ImportDialog(QWidget *parent) : - QDialog(parent), +ImportDialog::ImportDialog(QString profileName, QWidget *parent) : + QDialog(parent), profileName(profileName), ui(new Ui::ImportDialog) { // Set Window Flags +#if QT_VERSION >= 0x050900 + setWindowFlag(Qt::WindowContextHelpButtonHint, false); +#else setWindowFlags(windowFlags()^Qt::WindowContextHelpButtonHint); +#endif ui->setupUi(this); + ui->cmdOK->setDefault(true); + ui->cmdOK->setFocus(); importAgreed = false; + settingsLocked = false; + watermarkAvatar = true; + watermarkPicture = false; insideAvatarZone = false; - avatarAreaImage = QImage(":/img/avatarareaimport.png"); + avatarAreaImage = QImage(AppEnv::getImagesFolder() % "/avatarareaimport.png"); selectedColour = QColor::fromRgb(0, 0, 0, 255); // Set Icon for OK Button - if (QIcon::hasThemeIcon("dialog-ok")) - { + if (QIcon::hasThemeIcon("dialog-ok")) { ui->cmdOK->setIcon(QIcon::fromTheme("dialog-ok")); } - else if (QIcon::hasThemeIcon("gtk-ok")) - { + else if (QIcon::hasThemeIcon("gtk-ok")) { ui->cmdOK->setIcon(QIcon::fromTheme("gtk-ok")); } // Set Icon for Cancel Button - if (QIcon::hasThemeIcon("dialog-cancel")) - { + if (QIcon::hasThemeIcon("dialog-cancel")) { ui->cmdCancel->setIcon(QIcon::fromTheme("dialog-cancel")); } - else if (QIcon::hasThemeIcon("gtk-cancel")) - { + else if (QIcon::hasThemeIcon("gtk-cancel")) { ui->cmdCancel->setIcon(QIcon::fromTheme("gtk-cancel")); } @@ -80,6 +87,21 @@ ImportDialog::ImportDialog(QWidget *parent) : ui->labBackgroundImage->setText(tr("Background Image:")); ui->cmdBackgroundWipe->setVisible(false); + // Snapmatic Resolution + snapmaticResolution = SnapmaticPicture::getSnapmaticResolution(); + ui->cbResolution->addItem("GTA V", snapmaticResolution); + ui->cbResolution->addItem("FiveM", QSize(1920, 1072)); + ui->cbResolution->addItem("1280x720", QSize(1280, 720)); + ui->cbResolution->addItem("1920x1080", QSize(1920, 1080)); + ui->cbResolution->addItem("2560x1440", QSize(2560, 1440)); + + // Set Import Settings + QSettings settings(GTA5SYNC_APPVENDOR, GTA5SYNC_APPSTR); + settings.beginGroup("Import"); + QString currentProfile = settings.value("Profile", "Default").toString(); + settings.endGroup(); + processSettings(currentProfile); + // DPI calculation qreal screenRatio = AppEnv::screenRatio(); snapmaticResolutionLW = 516 * screenRatio; // 430 @@ -90,19 +112,29 @@ ImportDialog::ImportDialog(QWidget *parent) : #ifndef Q_OS_MAC ui->vlButtom->setContentsMargins(9 * screenRatio, 6 * screenRatio, 9 * screenRatio, 9 * screenRatio); #else - if (QApplication::style()->objectName() == "macintosh") - { +#if QT_VERSION >= 0x060000 + if (QApplication::style()->objectName() == "macos") { +#else + if (QApplication::style()->objectName() == "macintosh") { +#endif ui->vlButtom->setContentsMargins(9 * screenRatio, 9 * screenRatio, 9 * screenRatio, 9 * screenRatio); } - else - { + else { ui->vlButtom->setContentsMargins(9 * screenRatio, 6 * screenRatio, 9 * screenRatio, 9 * screenRatio); } #endif - setMaximumSize(sizeHint()); - setMinimumSize(sizeHint()); - setFixedSize(sizeHint()); + // Options menu + optionsMenu.addAction(tr("&Import new Picture..."), this, SLOT(importNewPicture())); + optionsMenu.addAction(tr("&Crop Picture..."), this, SLOT(cropPicture())); + optionsMenu.addSeparator(); + optionsMenu.addAction(tr("&Load Settings..."), this, SLOT(loadImportSettings())); + optionsMenu.addAction(tr("&Save Settings..."), this, SLOT(saveImportSettings())); + ui->cmdOptions->setMenu(&optionsMenu); + + const QSize windowSize = sizeHint(); + setMinimumSize(windowSize); + setMaximumSize(windowSize); } ImportDialog::~ImportDialog() @@ -112,134 +144,591 @@ ImportDialog::~ImportDialog() void ImportDialog::processImage() { - if (workImage.isNull()) return; + if (workImage.isNull()) + return; + QImage snapmaticImage = workImage; - QPixmap snapmaticPixmap(snapmaticResolutionW, snapmaticResolutionH); + QPixmap snapmaticPixmap(snapmaticResolution); snapmaticPixmap.fill(selectedColour); QPainter snapmaticPainter(&snapmaticPixmap); - if (!backImage.isNull()) - { - if (!ui->cbStretch->isChecked()) - { + qreal screenRatioPR = AppEnv::screenRatioPR(); + if (!backImage.isNull()) { + if (!ui->cbStretch->isChecked()) { int diffWidth = 0; int diffHeight = 0; - if (backImage.width() != snapmaticResolutionW) - { - diffWidth = snapmaticResolutionW - backImage.width(); + if (backImage.width() != snapmaticResolution.width()) { + diffWidth = snapmaticResolution.width() - backImage.width(); diffWidth = diffWidth / 2; } - else if (backImage.height() != snapmaticResolutionH) - { - diffHeight = snapmaticResolutionH - backImage.height(); + else if (backImage.height() != snapmaticResolution.height()) { + diffHeight = snapmaticResolution.height() - backImage.height(); diffHeight = diffHeight / 2; } snapmaticPainter.drawImage(0 + diffWidth, 0 + diffHeight, backImage); } - else - { - snapmaticPainter.drawImage(0, 0, QImage(backImage).scaled(snapmaticResolutionW, snapmaticResolutionH, Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); + else { + snapmaticPainter.drawImage(0, 0, QImage(backImage).scaled(snapmaticResolution, Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); } - if (ui->cbAvatar->isChecked() && ui->cbForceAvatarColour->isChecked()) - { + if (ui->cbAvatar->isChecked() && ui->cbForceAvatarColour->isChecked()) { snapmaticPainter.fillRect(snapmaticAvatarPlacementW, snapmaticAvatarPlacementH, snapmaticAvatarResolution, snapmaticAvatarResolution, selectedColour); } } - if (insideAvatarZone) - { + if (insideAvatarZone) { // Avatar mode int diffWidth = 0; int diffHeight = 0; - if (!ui->cbIgnore->isChecked()) - { + if (ui->cbIgnore->isChecked()) { + snapmaticImage = snapmaticImage.scaled(snapmaticAvatarResolution, snapmaticAvatarResolution, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); + } + else if (ui->cbBorderless->isChecked()) { + snapmaticImage = snapmaticImage.scaled(snapmaticAvatarResolution, snapmaticAvatarResolution, Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation); + if (snapmaticImage.width() > snapmaticAvatarResolution) { + int diffWidth = snapmaticImage.width() - snapmaticAvatarResolution; + diffWidth = diffWidth / 2; + QImage croppedImage(snapmaticAvatarResolution, snapmaticAvatarResolution, QImage::Format_ARGB32); + croppedImage.fill(Qt::transparent); + QPainter croppedPainter(&croppedImage); + croppedPainter.drawImage(0 - diffWidth, 0, snapmaticImage); + croppedPainter.end(); + snapmaticImage = croppedImage; + } + else if (snapmaticImage.height() > snapmaticAvatarResolution) { + int diffHeight = snapmaticImage.height() - snapmaticAvatarResolution; + diffHeight = diffHeight / 2; + QImage croppedImage(snapmaticAvatarResolution, snapmaticAvatarResolution, QImage::Format_ARGB32); + croppedImage.fill(Qt::transparent); + QPainter croppedPainter(&croppedImage); + croppedPainter.drawImage(0, 0 - diffHeight, snapmaticImage); + croppedPainter.end(); + snapmaticImage = croppedImage; + } + } + else { snapmaticImage = snapmaticImage.scaled(snapmaticAvatarResolution, snapmaticAvatarResolution, Qt::KeepAspectRatio, Qt::SmoothTransformation); - if (snapmaticImage.width() > snapmaticImage.height()) - { + if (snapmaticImage.width() > snapmaticImage.height()) { diffHeight = snapmaticAvatarResolution - snapmaticImage.height(); diffHeight = diffHeight / 2; } - else if (snapmaticImage.width() < snapmaticImage.height()) - { + else if (snapmaticImage.width() < snapmaticImage.height()) { diffWidth = snapmaticAvatarResolution - snapmaticImage.width(); diffWidth = diffWidth / 2; } } - else - { - snapmaticImage = snapmaticImage.scaled(snapmaticAvatarResolution, snapmaticAvatarResolution, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); - } snapmaticPainter.drawImage(snapmaticAvatarPlacementW + diffWidth, snapmaticAvatarPlacementH + diffHeight, snapmaticImage); + if (ui->cbWatermark->isChecked()) + processWatermark(&snapmaticPainter); imageTitle = tr("Custom Avatar", "Custom Avatar Description in SC, don't use Special Character!"); } - else - { + else { // Picture mode int diffWidth = 0; int diffHeight = 0; - if (!ui->cbIgnore->isChecked()) - { - snapmaticImage = snapmaticImage.scaled(snapmaticResolutionW, snapmaticResolutionH, Qt::KeepAspectRatio, Qt::SmoothTransformation); - if (snapmaticImage.width() != snapmaticResolutionW) - { - diffWidth = snapmaticResolutionW - snapmaticImage.width(); + if (ui->cbIgnore->isChecked()) { + snapmaticImage = snapmaticImage.scaled(snapmaticResolution, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); + } + else if (ui->cbBorderless->isChecked()) { + snapmaticImage = snapmaticImage.scaled(snapmaticResolution, Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation); + if (snapmaticImage.width() > snapmaticResolution.width()) { + int diffWidth = snapmaticImage.width() - snapmaticResolution.width(); + diffWidth = diffWidth / 2; + QImage croppedImage(snapmaticResolution, QImage::Format_ARGB32); + croppedImage.fill(Qt::transparent); + QPainter croppedPainter(&croppedImage); + croppedPainter.drawImage(0 - diffWidth, 0, snapmaticImage); + croppedPainter.end(); + snapmaticImage = croppedImage; + } + else if (snapmaticImage.height() > snapmaticResolution.height()) { + int diffHeight = snapmaticImage.height() - snapmaticResolution.height(); + diffHeight = diffHeight / 2; + QImage croppedImage(snapmaticResolution, QImage::Format_ARGB32); + croppedImage.fill(Qt::transparent); + QPainter croppedPainter(&croppedImage); + croppedPainter.drawImage(0, 0 - diffHeight, snapmaticImage); + croppedPainter.end(); + snapmaticImage = croppedImage; + } + } + else { + snapmaticImage = snapmaticImage.scaled(snapmaticResolution, Qt::KeepAspectRatio, Qt::SmoothTransformation); + if (snapmaticImage.width() != snapmaticResolution.width()) { + diffWidth = snapmaticResolution.width() - snapmaticImage.width(); diffWidth = diffWidth / 2; } - else if (snapmaticImage.height() != snapmaticResolutionH) - { - diffHeight = snapmaticResolutionH - snapmaticImage.height(); + else if (snapmaticImage.height() != snapmaticResolution.height()) { + diffHeight = snapmaticResolution.height() - snapmaticImage.height(); diffHeight = diffHeight / 2; } } - else - { - snapmaticImage = snapmaticImage.scaled(snapmaticResolutionW, snapmaticResolutionH, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); - } snapmaticPainter.drawImage(0 + diffWidth, 0 + diffHeight, snapmaticImage); + if (ui->cbWatermark->isChecked()) + processWatermark(&snapmaticPainter); imageTitle = tr("Custom Picture", "Custom Picture Description in SC, don't use Special Character!"); } snapmaticPainter.end(); newImage = snapmaticPixmap.toImage(); - ui->labPicture->setPixmap(snapmaticPixmap.scaled(snapmaticResolutionLW, snapmaticResolutionLH, Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); +#if QT_VERSION >= 0x050600 + snapmaticPixmap.setDevicePixelRatio(screenRatioPR); +#endif + ui->labPicture->setPixmap(snapmaticPixmap.scaled(snapmaticResolutionLW * screenRatioPR, snapmaticResolutionLH * screenRatioPR, Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); +} + +void ImportDialog::reworkImage() +{ + workImage = QImage(); + if (origImage.width() == origImage.height()) { + if (ui->cbResolution->currentIndex() == 0) { + insideAvatarZone = true; + ui->cbAvatar->setChecked(true); + } + else { + insideAvatarZone = false; + ui->cbAvatar->setChecked(false); + } + if (origImage.height() > snapmaticResolution.height()) { + workImage = origImage.scaled(snapmaticResolution.height(), snapmaticResolution.height(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation); + } + else { + workImage = origImage; + } + } + else if (origImage.width() > snapmaticResolution.width() && origImage.width() > origImage.height()) { + insideAvatarZone = false; + ui->cbAvatar->setChecked(false); + workImage = origImage.scaledToWidth(snapmaticResolution.width(), Qt::SmoothTransformation); + } + else if (origImage.height() > snapmaticResolution.height() && origImage.height() > origImage.width()) { + insideAvatarZone = false; + ui->cbAvatar->setChecked(false); + workImage = origImage.scaledToHeight(snapmaticResolution.height(), Qt::SmoothTransformation); + } + else { + insideAvatarZone = false; + ui->cbAvatar->setChecked(false); + workImage = origImage; + } + processImage(); +} + +void ImportDialog::processWatermark(QPainter *snapmaticPainter) +{ + bool blackWatermark = false; + bool redWatermark = false; + if (selectedColour.red() > 127) { + if (selectedColour.green() > 127 || selectedColour.blue() > 127) { + redWatermark = true; + } + } + else { + redWatermark = true; + } + if (selectedColour.lightness() > 127) { + blackWatermark = true; + } + // draw watermark + if (redWatermark) { + const QImage viewWatermark = QImage(AppEnv::getImagesFolder() % "/watermark_2r.png"); + snapmaticPainter->drawImage(snapmaticResolution.width() - viewWatermark.width(), 0, viewWatermark); + } + else + { + QImage viewWatermark = QImage(AppEnv::getImagesFolder() % "/watermark_2b.png"); + if (!blackWatermark) { + viewWatermark.invertPixels(QImage::InvertRgb); + } + snapmaticPainter->drawImage(snapmaticResolution.width() - viewWatermark.width(), 0, viewWatermark); + } + QImage textWatermark = QImage(AppEnv::getImagesFolder() % "/watermark_1b.png"); + if (!blackWatermark) { + textWatermark.invertPixels(QImage::InvertRgb); + } + snapmaticPainter->drawImage(snapmaticResolution.width() - textWatermark.width(), 0, textWatermark); +} + +void ImportDialog::processSettings(QString settingsProfile, bool setDefault) +{ + QSettings settings(GTA5SYNC_APPVENDOR, GTA5SYNC_APPSTR); + settings.beginGroup("Import"); + if (setDefault) { + settings.setValue("Profile", settingsProfile); + } + if (settingsProfile == "Default") { + watermarkAvatar = true; + watermarkPicture = false; + selectedColour = QColor::fromRgb(0, 0, 0, 255); + backImage = QImage(); + ui->cbBorderless->setChecked(false); + ui->cbStretch->setChecked(false); + ui->cbForceAvatarColour->setChecked(false); + ui->cbUnlimited->setChecked(false); + ui->cbImportAsIs->setChecked(false); + ui->cbResolution->setCurrentIndex(0); + } + else { + settings.beginGroup(settingsProfile); + watermarkAvatar = settings.value("WatermarkAvatar", true).toBool(); + watermarkPicture = settings.value("WatermarkPicture", false).toBool(); + backImage = qvariant_cast(settings.value("BackgroundImage", QImage())); + selectedColour = qvariant_cast(settings.value("SelectedColour", QColor::fromRgb(0, 0, 0, 255))); + ui->cbBorderless->setChecked(settings.value("BorderlessImage", false).toBool()); + ui->cbStretch->setChecked(settings.value("BackgroundStretch", false).toBool()); + ui->cbForceAvatarColour->setChecked(settings.value("ForceAvatarColour", false).toBool()); + ui->cbUnlimited->setChecked(settings.value("UnlimitedBuffer", false).toBool()); + ui->cbImportAsIs->setChecked(settings.value("ImportAsIs", false).toBool()); + const QVariant data = settings.value("Resolution", SnapmaticPicture::getSnapmaticResolution()); +#if QT_VERSION >= 0x060000 + if (data.typeId() == QMetaType::QSize) +#else + if (data.type() == QVariant::Size) +#endif + { + int index = ui->cbResolution->findData(data); + if (index != -1) { + ui->cbResolution->setCurrentIndex(index); + } + } + settings.endGroup(); + } + if (!workImage.isNull()) { + if (ui->cbAvatar->isChecked()) { + ui->cbWatermark->setChecked(watermarkAvatar); + } + else { + ui->cbWatermark->setChecked(watermarkPicture); + } + } + ui->labColour->setText(tr("Background Colour: %1").arg(selectedColour.name())); + if (!backImage.isNull()) { + ui->labBackgroundImage->setText(tr("Background Image: %1").arg(tr("Storage", "Background Image: Storage"))); + ui->cmdBackgroundWipe->setVisible(true); + } + else { + ui->labBackgroundImage->setText(tr("Background Image:")); + ui->cmdBackgroundWipe->setVisible(false); + } + settings.endGroup(); +} + +void ImportDialog::saveSettings(QString settingsProfile) +{ + QSettings settings(GTA5SYNC_APPVENDOR, GTA5SYNC_APPSTR); + settings.beginGroup("Import"); + settings.beginGroup(settingsProfile); + settings.setValue("WatermarkAvatar", watermarkAvatar); + settings.setValue("WatermarkPicture", watermarkPicture); + settings.setValue("BackgroundImage", backImage); + settings.setValue("SelectedColour", selectedColour); + settings.setValue("BorderlessImage", ui->cbBorderless->isChecked()); + settings.setValue("BackgroundStretch", ui->cbStretch->isChecked()); + settings.setValue("ForceAvatarColour", ui->cbForceAvatarColour->isChecked()); +#if QT_VERSION >= 0x050000 + const QVariant data = ui->cbResolution->currentData(); +#else + const QVariant data = ui->cbResolution->itemData(ui->cbResolution->currentIndex()); +#endif +#if QT_VERSION >= 0x060000 + if (data.typeId() == QMetaType::QSize) +#else + if (data.type() == QVariant::Size) +#endif + { + settings.setValue("Resolution", data); + } + else { + settings.setValue("Resolution", SnapmaticPicture::getSnapmaticResolution()); + } + settings.setValue("UnlimitedBuffer", ui->cbUnlimited->isChecked()); + settings.setValue("ImportAsIs", ui->cbImportAsIs->isChecked()); + settings.endGroup(); + settings.setValue("Profile", settingsProfile); + settings.endGroup(); +} + +void ImportDialog::cropPicture() +{ + qreal screenRatio = AppEnv::screenRatio(); + + QDialog cropDialog(this); +#if QT_VERSION >= 0x050000 + cropDialog.setObjectName(QStringLiteral("CropDialog")); +#else + cropDialog.setObjectName(QString::fromUtf8("CropDialog")); +#endif + cropDialog.setWindowTitle(tr("Crop Picture...")); + cropDialog.setWindowFlags(cropDialog.windowFlags()^Qt::WindowContextHelpButtonHint); + cropDialog.setModal(true); + + QVBoxLayout cropLayout; +#if QT_VERSION >= 0x050000 + cropLayout.setObjectName(QStringLiteral("CropLayout")); +#else + cropLayout.setObjectName(QString::fromUtf8("CropLayout")); +#endif + cropLayout.setContentsMargins(0, 0, 0, 0); + cropLayout.setSpacing(0); + cropDialog.setLayout(&cropLayout); + + ImageCropper imageCropper(&cropDialog); +#if QT_VERSION >= 0x050000 + imageCropper.setObjectName(QStringLiteral("ImageCropper")); +#else + imageCropper.setObjectName(QString::fromUtf8("ImageCropper")); +#endif + imageCropper.setBackgroundColor(Qt::black); + imageCropper.setCroppingRectBorderColor(QColor(255, 255, 255, 127)); + imageCropper.setImage(QPixmap::fromImage(origImage, Qt::AutoColor)); + imageCropper.setProportion(QSize(1, 1)); + imageCropper.setFixedSize(workImage.size()); + cropLayout.addWidget(&imageCropper); + + QHBoxLayout buttonLayout; +#if QT_VERSION >= 0x050000 + cropLayout.setObjectName(QStringLiteral("ButtonLayout")); +#else + cropLayout.setObjectName(QString::fromUtf8("ButtonLayout")); +#endif + cropLayout.addLayout(&buttonLayout); + + QPushButton cropButton(&cropDialog); +#if QT_VERSION >= 0x050000 + cropButton.setObjectName(QStringLiteral("CropButton")); +#else + cropButton.setObjectName(QString::fromUtf8("CropButton")); +#endif + cropButton.setMinimumSize(0, 40 * screenRatio); + cropButton.setText(tr("&Crop")); + cropButton.setToolTip(tr("Crop Picture")); + QObject::connect(&cropButton, SIGNAL(clicked(bool)), &cropDialog, SLOT(accept())); + + buttonLayout.addWidget(&cropButton); + + cropDialog.show(); + cropDialog.setFixedSize(cropDialog.sizeHint()); + if (cropDialog.exec() == QDialog::Accepted) { + QImage *croppedImage = new QImage(imageCropper.cropImage().toImage()); + setImage(croppedImage); + } +} + +void ImportDialog::importNewPicture() +{ + QSettings settings(GTA5SYNC_APPVENDOR, GTA5SYNC_APPSTR); + settings.beginGroup("FileDialogs"); + bool dontUseNativeDialog = settings.value("DontUseNativeDialog", false).toBool(); + settings.beginGroup("ImportCopy"); + +fileDialogPreOpen: //Work? + QFileDialog fileDialog(this); + fileDialog.setFileMode(QFileDialog::ExistingFile); + fileDialog.setViewMode(QFileDialog::Detail); + fileDialog.setAcceptMode(QFileDialog::AcceptOpen); + fileDialog.setOption(QFileDialog::DontUseNativeDialog, dontUseNativeDialog); + fileDialog.setWindowFlags(fileDialog.windowFlags()^Qt::WindowContextHelpButtonHint); + fileDialog.setWindowTitle(QApplication::translate("ProfileInterface", "Import...")); + fileDialog.setLabelText(QFileDialog::Accept, QApplication::translate("ProfileInterface", "Import")); + + // Getting readable Image formats + QString imageFormatsStr = " "; + for (const QByteArray &imageFormat : QImageReader::supportedImageFormats()) { + imageFormatsStr += QString("*.") % QString::fromUtf8(imageFormat).toLower() % " "; + } + + QStringList filters; + filters << QApplication::translate("ProfileInterface", "All image files (%1)").arg(imageFormatsStr.trimmed()); + filters << QApplication::translate("ProfileInterface", "All files (**)"); + fileDialog.setNameFilters(filters); + + QList sidebarUrls = SidebarGenerator::generateSidebarUrls(fileDialog.sidebarUrls()); + + fileDialog.setSidebarUrls(sidebarUrls); + fileDialog.setDirectory(settings.value(profileName % "+Directory", StandardPaths::documentsLocation()).toString()); + fileDialog.restoreGeometry(settings.value(profileName % "+Geometry", "").toByteArray()); + + if (fileDialog.exec()) { + QStringList selectedFiles = fileDialog.selectedFiles(); + if (selectedFiles.length() == 1) { + QString selectedFile = selectedFiles.at(0); + QString selectedFileName = QFileInfo(selectedFile).fileName(); + + QFile snapmaticFile(selectedFile); + if (!snapmaticFile.open(QFile::ReadOnly)) { + QMessageBox::warning(this, QApplication::translate("ProfileInterface", "Import"), QApplication::translate("ProfileInterface", "Can't import %1 because file can't be open").arg("\""+selectedFileName+"\"")); + goto fileDialogPreOpen; + } + QImage *importImage = new QImage(); + QImageReader snapmaticImageReader; + snapmaticImageReader.setDecideFormatFromContent(true); + snapmaticImageReader.setDevice(&snapmaticFile); + if (!snapmaticImageReader.read(importImage)) { + QMessageBox::warning(this, QApplication::translate("ProfileInterface", "Import"), QApplication::translate("ProfileInterface", "Can't import %1 because file can't be parsed properly").arg("\""+selectedFileName+"\"")); + delete importImage; + goto fileDialogPreOpen; + } + setImage(importImage); + } + } + + settings.setValue(profileName % "+Geometry", fileDialog.saveGeometry()); + settings.setValue(profileName % "+Directory", fileDialog.directory().absolutePath()); + settings.endGroup(); + settings.endGroup(); +} + +void ImportDialog::loadImportSettings() +{ + if (settingsLocked) { + QMessageBox::information(this, tr("Load Settings..."), tr("Please import a new picture first")); + return; + } + bool ok; + QStringList profileList; + profileList << tr("Default", "Default as Default Profile") + << tr("Profile %1", "Profile %1 as Profile 1").arg("1") + << tr("Profile %1", "Profile %1 as Profile 1").arg("2") + << tr("Profile %1", "Profile %1 as Profile 1").arg("3") + << tr("Profile %1", "Profile %1 as Profile 1").arg("4") + << tr("Profile %1", "Profile %1 as Profile 1").arg("5"); + QString sProfile = QInputDialog::getItem(this, tr("Load Settings..."), tr("Please select your settings profile"), profileList, 0, false, &ok, windowFlags()); + if (ok) { + QString pProfile; + if (sProfile == tr("Default", "Default as Default Profile")) { + pProfile = "Default"; + } + else if (sProfile == tr("Profile %1", "Profile %1 as Profile 1").arg("1")) { + pProfile = "Profile 1"; + } + else if (sProfile == tr("Profile %1", "Profile %1 as Profile 1").arg("2")) { + pProfile = "Profile 2"; + } + else if (sProfile == tr("Profile %1", "Profile %1 as Profile 1").arg("3")) { + pProfile = "Profile 3"; + } + else if (sProfile == tr("Profile %1", "Profile %1 as Profile 1").arg("4")) + { + pProfile = "Profile 4"; + } + else if (sProfile == tr("Profile %1", "Profile %1 as Profile 1").arg("5")) { + pProfile = "Profile 5"; + } + processSettings(pProfile, true); + processImage(); + } +} + +void ImportDialog::saveImportSettings() +{ + if (settingsLocked) { + QMessageBox::information(this, tr("Save Settings..."), tr("Please import a new picture first")); + return; + } + bool ok; + QStringList profileList; + profileList << tr("Profile %1", "Profile %1 as Profile 1").arg("1") + << tr("Profile %1", "Profile %1 as Profile 1").arg("2") + << tr("Profile %1", "Profile %1 as Profile 1").arg("3") + << tr("Profile %1", "Profile %1 as Profile 1").arg("4") + << tr("Profile %1", "Profile %1 as Profile 1").arg("5"); + QString sProfile = QInputDialog::getItem(this, tr("Save Settings..."), tr("Please select your settings profile"), profileList, 0, false, &ok, windowFlags()); + if (ok) { + QString pProfile; + if (sProfile == tr("Profile %1", "Profile %1 as Profile 1").arg("1")) { + pProfile = "Profile 1"; + } + else if (sProfile == tr("Profile %1", "Profile %1 as Profile 1").arg("2")) { + pProfile = "Profile 2"; + } + else if (sProfile == tr("Profile %1", "Profile %1 as Profile 1").arg("3")) { + pProfile = "Profile 3"; + } + else if (sProfile == tr("Profile %1", "Profile %1 as Profile 1").arg("4")) { + pProfile = "Profile 4"; + } + else if (sProfile == tr("Profile %1", "Profile %1 as Profile 1").arg("5")) { + pProfile = "Profile 5"; + } + saveSettings(pProfile); + } } QImage ImportDialog::image() { - return newImage; + if (ui->cbImportAsIs->isChecked()) { + return origImage; + } + else { + return newImage; + } } void ImportDialog::setImage(QImage *image_) { + origImage = *image_; workImage = QImage(); - if (image_->width() == image_->height()) - { - insideAvatarZone = true; - ui->cbAvatar->setChecked(true); - if (image_->height() > snapmaticResolutionH) - { - workImage = image_->scaled(snapmaticResolutionH, snapmaticResolutionH, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); + if (image_->width() == image_->height()) { + if (ui->cbResolution->currentIndex() == 0) { + insideAvatarZone = true; + ui->cbAvatar->setChecked(true); + } + else { + insideAvatarZone = false; + ui->cbAvatar->setChecked(false); + } + if (image_->height() > snapmaticResolution.height()) { + workImage = image_->scaled(snapmaticResolution.height(), snapmaticResolution.height(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation); delete image_; } - else - { + else { workImage = *image_; delete image_; } } - else if (image_->width() > snapmaticResolutionW && image_->width() > image_->height()) - { - workImage = image_->scaledToWidth(snapmaticResolutionW, Qt::SmoothTransformation); + else if (image_->width() > snapmaticResolution.width() && image_->width() > image_->height()) { + insideAvatarZone = false; + ui->cbAvatar->setChecked(false); + workImage = image_->scaledToWidth(snapmaticResolution.width(), Qt::SmoothTransformation); delete image_; } - else if (image_->height() > snapmaticResolutionH && image_->height() > image_->width()) - { - workImage = image_->scaledToHeight(snapmaticResolutionH, Qt::SmoothTransformation); + else if (image_->height() > snapmaticResolution.height() && image_->height() > image_->width()) { + insideAvatarZone = false; + ui->cbAvatar->setChecked(false); + workImage = image_->scaledToHeight(snapmaticResolution.height(), Qt::SmoothTransformation); delete image_; } - else - { + else { + insideAvatarZone = false; + ui->cbAvatar->setChecked(false); workImage = *image_; delete image_; } processImage(); + lockSettings(false); +} + +void ImportDialog::lockSettings(bool lock) +{ + ui->gbAdvanced->setDisabled(lock); + if (ui->cbImportAsIs->isChecked()) { + ui->gbBackground->setDisabled(true); + ui->gbSettings->setDisabled(true); + } + else { + ui->gbBackground->setDisabled(lock); + ui->gbSettings->setDisabled(lock); + } + ui->cmdOK->setDisabled(lock); + settingsLocked = lock; +} + +void ImportDialog::enableOverwriteMode() +{ + setWindowTitle(QApplication::translate("ImageEditorDialog", "Overwrite Image...")); + ui->cmdOK->setText(QApplication::translate("ImageEditorDialog", "&Overwrite")); + ui->cmdOK->setToolTip(QApplication::translate("ImageEditorDialog", "Apply changes")); + ui->cmdCancel->setText(QApplication::translate("ImageEditorDialog", "&Close")); + ui->cmdCancel->setToolTip(QApplication::translate("ImageEditorDialog", "Discard changes")); + ui->cmdCancel->setDefault(true); + ui->cmdCancel->setFocus(); + lockSettings(true); } bool ImportDialog::isImportAgreed() @@ -247,29 +736,53 @@ bool ImportDialog::isImportAgreed() return importAgreed; } +bool ImportDialog::isUnlimitedBuffer() +{ + return ui->cbUnlimited->isChecked(); +} + +bool ImportDialog::areSettingsLocked() +{ + return settingsLocked; +} + QString ImportDialog::getImageTitle() { - return imageTitle; + if (ui->cbImportAsIs->isChecked()) { + return tr("Custom Picture", "Custom Picture Description in SC, don't use Special Character!"); + } + else { + return imageTitle; + } } void ImportDialog::on_cbIgnore_toggled(bool checked) { - Q_UNUSED(checked) + ui->cbBorderless->setDisabled(checked); processImage(); } void ImportDialog::on_cbAvatar_toggled(bool checked) { - if (workImage.width() == workImage.height() && !checked) - { - if (QMessageBox::No == QMessageBox::warning(this, tr("Snapmatic Avatar Zone"), tr("Are you sure to use a square image outside of the Avatar Zone?\nWhen you want to use it as Avatar the image will be detached!"), QMessageBox::Yes | QMessageBox::No, QMessageBox::No)) - { + if (ui->cbResolution->currentIndex() != 0) + return; + + if (!workImage.isNull() && workImage.width() == workImage.height() && !checked) { + if (QMessageBox::No == QMessageBox::warning(this, tr("Snapmatic Avatar Zone"), tr("Are you sure to use a square image outside of the Avatar Zone?\nWhen you want to use it as Avatar the image will be detached!"), QMessageBox::Yes | QMessageBox::No, QMessageBox::No)) { ui->cbAvatar->setChecked(true); insideAvatarZone = true; return; } } insideAvatarZone = ui->cbAvatar->isChecked(); + watermarkBlock = true; + if (insideAvatarZone) { + ui->cbWatermark->setChecked(watermarkAvatar); + } + else { + ui->cbWatermark->setChecked(watermarkPicture); + } + watermarkBlock = false; processImage(); } @@ -286,11 +799,9 @@ void ImportDialog::on_cmdOK_clicked() void ImportDialog::on_labPicture_labelPainted() { - if (insideAvatarZone) - { + if (insideAvatarZone) { QImage avatarAreaFinalImage(avatarAreaImage); - if (selectedColour.lightness() > 127) - { + if (selectedColour.lightness() > 127) { avatarAreaFinalImage.setColor(1, qRgb(0, 0, 0)); } QPainter labelPainter(ui->labPicture); @@ -302,8 +813,7 @@ void ImportDialog::on_labPicture_labelPainted() void ImportDialog::on_cmdColourChange_clicked() { QColor newSelectedColour = QColorDialog::getColor(selectedColour, this, tr("Select Colour...")); - if (newSelectedColour.isValid()) - { + if (newSelectedColour.isValid()) { selectedColour = newSelectedColour; ui->labColour->setText(tr("Background Colour: %1").arg(selectedColour.name())); processImage(); @@ -329,8 +839,7 @@ fileDialogPreOpen: // Getting readable Image formats QString imageFormatsStr = " "; - for (QByteArray imageFormat : QImageReader::supportedImageFormats()) - { + for (const QByteArray &imageFormat : QImageReader::supportedImageFormats()) { imageFormatsStr += QString("*.") % QString::fromUtf8(imageFormat).toLower() % " "; } @@ -345,17 +854,14 @@ fileDialogPreOpen: fileDialog.setDirectory(settings.value("Directory", StandardPaths::documentsLocation()).toString()); fileDialog.restoreGeometry(settings.value("Geometry", "").toByteArray()); - if (fileDialog.exec()) - { + if (fileDialog.exec()) { QStringList selectedFiles = fileDialog.selectedFiles(); - if (selectedFiles.length() == 1) - { + if (selectedFiles.length() == 1) { QString selectedFile = selectedFiles.at(0); QString selectedFileName = QFileInfo(selectedFile).fileName(); QFile snapmaticFile(selectedFile); - if (!snapmaticFile.open(QFile::ReadOnly)) - { + if (!snapmaticFile.open(QFile::ReadOnly)) { QMessageBox::warning(this, QApplication::translate("ProfileInterface", "Import"), QApplication::translate("ProfileInterface", "Can't import %1 because file can't be open").arg("\""+selectedFileName+"\"")); goto fileDialogPreOpen; } @@ -363,12 +869,11 @@ fileDialogPreOpen: QImageReader snapmaticImageReader; snapmaticImageReader.setDecideFormatFromContent(true); snapmaticImageReader.setDevice(&snapmaticFile); - if (!snapmaticImageReader.read(&importImage)) - { + if (!snapmaticImageReader.read(&importImage)) { QMessageBox::warning(this, QApplication::translate("ProfileInterface", "Import"), QApplication::translate("ProfileInterface", "Can't import %1 because file can't be parsed properly").arg("\""+selectedFileName+"\"")); goto fileDialogPreOpen; } - backImage = importImage.scaled(snapmaticResolutionW, snapmaticResolutionH, Qt::KeepAspectRatio, Qt::SmoothTransformation); + backImage = importImage.scaled(snapmaticResolution, Qt::KeepAspectRatio, Qt::SmoothTransformation); backgroundPath = selectedFile; ui->labBackgroundImage->setText(tr("Background Image: %1").arg(tr("File", "Background Image: File"))); ui->cmdBackgroundWipe->setVisible(true); @@ -401,3 +906,69 @@ void ImportDialog::on_cbForceAvatarColour_toggled(bool checked) Q_UNUSED(checked) processImage(); } + +void ImportDialog::on_cbWatermark_toggled(bool checked) +{ + if (!watermarkBlock) { + if (insideAvatarZone) { + watermarkAvatar = checked; + } + else { + watermarkPicture = checked; + } + processImage(); + } +} + +void ImportDialog::on_cbBorderless_toggled(bool checked) +{ + ui->cbIgnore->setDisabled(checked); + processImage(); +} + +void ImportDialog::on_cbImportAsIs_toggled(bool checked) +{ + ui->cbResolution->setDisabled(checked); + ui->labResolution->setDisabled(checked); + ui->gbBackground->setDisabled(checked); + ui->gbSettings->setDisabled(checked); +} + +void ImportDialog::on_cbResolution_currentIndexChanged(int index) +{ + Q_UNUSED(index) +#if QT_VERSION >= 0x050000 + const QVariant data = ui->cbResolution->currentData(); +#else + const QVariant data = ui->cbResolution->itemData(ui->cbResolution->currentIndex()); +#endif +#if QT_VERSION >= 0x060000 + if (data.typeId() == QMetaType::QSize) +#else + if (data.type() == QVariant::Size) +#endif + { + const QSize dataSize = data.toSize(); + if (dataSize == SnapmaticPicture::getSnapmaticResolution()) { + ui->cbAvatar->setEnabled(true); + snapmaticResolution = dataSize; + reworkImage(); + } + else { + if (!workImage.isNull() && workImage.width() == workImage.height() && ui->cbAvatar->isChecked()) { + if (QMessageBox::No == QMessageBox::warning(this, tr("Snapmatic Avatar Zone"), tr("Are you sure to use a square image outside of the Avatar Zone?\nWhen you want to use it as Avatar the image will be detached!"), QMessageBox::Yes | QMessageBox::No, QMessageBox::No)) { + ui->cbResolution->setCurrentIndex(0); + ui->cbAvatar->setChecked(true); + insideAvatarZone = true; + return; + } + } + ui->cbAvatar->setChecked(false); + ui->cbAvatar->setDisabled(true); + insideAvatarZone = false; + ui->cbWatermark->setChecked(watermarkPicture); + snapmaticResolution = dataSize; + reworkImage(); + } + } +} diff --git a/ImportDialog.h b/ImportDialog.h index e095ff2..1cd93ac 100644 --- a/ImportDialog.h +++ b/ImportDialog.h @@ -1,6 +1,6 @@ /***************************************************************************** -* gta5sync GRAND THEFT AUTO V SYNC -* Copyright (C) 2017 Syping +* gta5view Grand Theft Auto V Profile Viewer +* Copyright (C) 2017-2021 Syping * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -20,6 +20,7 @@ #define IMPORTDIALOG_H #include +#include namespace Ui { class ImportDialog; @@ -30,15 +31,24 @@ class ImportDialog : public QDialog Q_OBJECT public: - explicit ImportDialog(QWidget *parent = 0); + explicit ImportDialog(QString profileName, QWidget *parent = 0); ~ImportDialog(); QImage image(); QString getImageTitle(); void setImage(QImage *image); + void lockSettings(bool lock); + void enableOverwriteMode(); bool isImportAgreed(); + bool isUnlimitedBuffer(); + bool areSettingsLocked(); private slots: void processImage(); + void reworkImage(); + void cropPicture(); + void importNewPicture(); + void loadImportSettings(); + void saveImportSettings(); void on_cbIgnore_toggled(bool checked); void on_cbAvatar_toggled(bool checked); void on_cmdCancel_clicked(); @@ -49,20 +59,35 @@ private slots: void on_cmdBackgroundWipe_clicked(); void on_cbStretch_toggled(bool checked); void on_cbForceAvatarColour_toggled(bool checked); + void on_cbWatermark_toggled(bool checked); + void on_cbBorderless_toggled(bool checked); + void on_cbImportAsIs_toggled(bool checked); + void on_cbResolution_currentIndexChanged(int index); private: + QString profileName; Ui::ImportDialog *ui; QImage avatarAreaImage; QString backgroundPath; QString imageTitle; QImage backImage; QImage workImage; + QImage origImage; QImage newImage; QColor selectedColour; + QMenu optionsMenu; + QSize snapmaticResolution; bool insideAvatarZone; + bool watermarkPicture; + bool watermarkAvatar; + bool watermarkBlock; + bool settingsLocked; bool importAgreed; int snapmaticResolutionLW; int snapmaticResolutionLH; + void processWatermark(QPainter *snapmaticPainter); + void processSettings(QString settingsProfile, bool setDefault = false); + void saveSettings(QString settingsProfile); }; #endif // IMPORTDIALOG_H diff --git a/ImportDialog.ui b/ImportDialog.ui index 61c15fe..5780456 100644 --- a/ImportDialog.ui +++ b/ImportDialog.ui @@ -7,13 +7,13 @@ 0 0 516 - 425 + 673 516 - 425 + 512 @@ -85,30 +85,32 @@ - - - - - - 0 - 0 - + + + + + Ignore Aspect Ratio + + + + Avatar - - - - - 0 - 0 - - + + - Ignore Aspect Ratio + Watermark + + + + + + + Crop to Aspect Ratio @@ -124,8 +126,8 @@ - - + + @@ -153,8 +155,11 @@ + + Select background colour + - ... + ... @@ -175,7 +180,24 @@ - + + + + true + + + Ignore Aspect Ratio + + + + + + + Force Colour in Avatar Zone + + + + @@ -203,15 +225,21 @@ + + Select background image + - ... + ... + + Remove background image + - X + X @@ -234,25 +262,61 @@ + + + + + + + Advanced + + - - - + + + + + Avoid compression and expand buffer instead, improves picture quality, but may break Snapmatic + - Force Colour in Avatar Zone + Unlimited Buffer - - - - true + + + + Import as-is, don't change the picture at all, guaranteed to break Snapmatic unless you know what you doing - Ignore Aspect Ratio + Import as-is + + + + + + Resolution: + + + + + + + + 0 + 0 + + + + Snapmatic resolution + + + + + @@ -273,6 +337,19 @@ + + + + Import options + + + &Options + + + false + + + diff --git a/JsonEditorDialog.cpp b/JsonEditorDialog.cpp index 9aaa392..ae13966 100644 --- a/JsonEditorDialog.cpp +++ b/JsonEditorDialog.cpp @@ -1,6 +1,6 @@ /***************************************************************************** -* gta5sync GRAND THEFT AUTO V SYNC -* Copyright (C) 2017-2018 Syping +* gta5view Grand Theft Auto V Profile Viewer +* Copyright (C) 2017-2021 Syping * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -20,13 +20,18 @@ #include "ui_JsonEditorDialog.h" #include "SnapmaticEditor.h" #include "AppEnv.h" +#include "config.h" #include #include +#include #include #if QT_VERSION >= 0x050200 #include -#include +#endif + +#ifdef GTA5SYNC_TELEMETRY +#include "TelemetryClass.h" #endif JsonEditorDialog::JsonEditorDialog(SnapmaticPicture *picture, QWidget *parent) : @@ -34,28 +39,30 @@ JsonEditorDialog::JsonEditorDialog(SnapmaticPicture *picture, QWidget *parent) : ui(new Ui::JsonEditorDialog) { // Set Window Flags +#if QT_VERSION >= 0x050900 + setWindowFlag(Qt::WindowContextHelpButtonHint, false); + setWindowFlag(Qt::WindowMinMaxButtonsHint, true); +#else setWindowFlags(windowFlags()^Qt::WindowContextHelpButtonHint^Qt::WindowMinMaxButtonsHint); +#endif ui->setupUi(this); + ui->cmdClose->setDefault(true); ui->cmdClose->setFocus(); // Set Icon for Close Button - if (QIcon::hasThemeIcon("dialog-close")) - { + if (QIcon::hasThemeIcon("dialog-close")) { ui->cmdClose->setIcon(QIcon::fromTheme("dialog-close")); } - else if (QIcon::hasThemeIcon("gtk-close")) - { + else if (QIcon::hasThemeIcon("gtk-close")) { ui->cmdClose->setIcon(QIcon::fromTheme("gtk-close")); } // Set Icon for Save Button - if (QIcon::hasThemeIcon("document-save")) - { + if (QIcon::hasThemeIcon("document-save")) { ui->cmdSave->setIcon(QIcon::fromTheme("document-save")); } - else if (QIcon::hasThemeIcon("gtk-save")) - { + else if (QIcon::hasThemeIcon("gtk-save")) { ui->cmdSave->setIcon(QIcon::fromTheme("gtk-save")); } @@ -63,9 +70,18 @@ JsonEditorDialog::JsonEditorDialog(SnapmaticPicture *picture, QWidget *parent) : #if QT_VERSION >= 0x050200 ui->txtJSON->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont)); +#else + QFont jsonFont = ui->txtJSON->font(); + jsonFont.setStyleHint(QFont::Monospace); + jsonFont.setFixedPitch(true); + ui->txtJSON->setFont(jsonFont); #endif QFontMetrics fontMetrics(ui->txtJSON->font()); +#if QT_VERSION >= 0x050B00 + ui->txtJSON->setTabStopDistance(fontMetrics.horizontalAdvance(" ")); +#else ui->txtJSON->setTabStopWidth(fontMetrics.width(" ")); +#endif QJsonDocument jsonDocument = QJsonDocument::fromJson(jsonCode.toUtf8()); ui->txtJSON->setStyleSheet("QPlainTextEdit{background-color: rgb(46, 47, 48); color: rgb(238, 231, 172);}"); @@ -83,10 +99,10 @@ JsonEditorDialog::JsonEditorDialog(SnapmaticPicture *picture, QWidget *parent) : ui->hlButtons->setContentsMargins(9 * screenRatio, 0, 9 * screenRatio, 0); ui->vlInterface->setContentsMargins(0, 0, 0, 9 * screenRatio); #endif - if (screenRatio > 1) - { + if (screenRatio > 1) { ui->lineJSON->setMinimumHeight(qRound(1 * screenRatio)); ui->lineJSON->setMaximumHeight(qRound(1 * screenRatio)); + ui->lineJSON->setLineWidth(qRound(1 * screenRatio)); } resize(450 * screenRatio, 550 * screenRatio); } @@ -104,28 +120,22 @@ void JsonEditorDialog::closeEvent(QCloseEvent *ev) QJsonDocument jsonOriginal = QJsonDocument::fromJson(jsonCode.toUtf8()); QString originalCode = QString::fromUtf8(jsonOriginal.toJson(QJsonDocument::Compact)); QString newCode = QString::fromUtf8(jsonNew.toJson(QJsonDocument::Compact)); - if (newCode != originalCode) - { + if (newCode != originalCode) { QMessageBox::StandardButton button = QMessageBox::warning(this, SnapmaticEditor::tr("Snapmatic Properties"), SnapmaticEditor::tr("

Unsaved changes detected

You want to save the JSON content before you quit?"), QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel, QMessageBox::Cancel); - if (button == QMessageBox::Yes) - { - if (saveJsonContent()) - { + if (button == QMessageBox::Yes) { + if (saveJsonContent()) { ev->accept(); } - else - { + else { ev->ignore(); } return; } - else if (button == QMessageBox::No) - { + else if (button == QMessageBox::No) { ev->accept(); return; } - else - { + else { ev->ignore(); return; } @@ -136,59 +146,69 @@ bool JsonEditorDialog::saveJsonContent() { QString jsonPatched = QString(ui->txtJSON->toPlainText()).replace("\t", " "); QJsonDocument jsonNew = QJsonDocument::fromJson(jsonPatched.toUtf8()); - if (!jsonNew.isEmpty()) - { + if (!jsonNew.isEmpty()) { QJsonDocument jsonOriginal = QJsonDocument::fromJson(jsonCode.toUtf8()); QString originalCode = QString::fromUtf8(jsonOriginal.toJson(QJsonDocument::Compact)); QString newCode = QString::fromUtf8(jsonNew.toJson(QJsonDocument::Compact)); - if (newCode != originalCode) - { + if (newCode != originalCode) { QString currentFilePath = smpic->getPictureFilePath(); QString originalFilePath = smpic->getOriginalPictureFilePath(); QString backupFileName = originalFilePath % ".bak"; - if (!QFile::exists(backupFileName)) - { + if (!QFile::exists(backupFileName)) { QFile::copy(currentFilePath, backupFileName); } smpic->setJsonStr(newCode, true); - if (!smpic->isJsonOk()) - { + if (!smpic->isJsonOk()) { QString lastStep = smpic->getLastStep(false); QString readableError; - if (lastStep.contains("JSONINCOMPLETE") && lastStep.contains("JSONERROR")) - { + if (lastStep.contains("JSONINCOMPLETE") && lastStep.contains("JSONERROR")) { readableError = SnapmaticPicture::tr("JSON is incomplete and malformed"); } - else if (lastStep.contains("JSONINCOMPLETE")) - { + else if (lastStep.contains("JSONINCOMPLETE")) { readableError = SnapmaticPicture::tr("JSON is incomplete"); } - else if (lastStep.contains("JSONERROR")) - { + else if (lastStep.contains("JSONERROR")) { readableError = SnapmaticPicture::tr("JSON is malformed"); } - else - { + else { readableError = tr("JSON Error"); } QMessageBox::warning(this, SnapmaticEditor::tr("Snapmatic Properties"), SnapmaticEditor::tr("Patching of Snapmatic Properties failed because of %1").arg(readableError)); smpic->setJsonStr(originalCode, true); return false; } - if (!smpic->exportPicture(currentFilePath)) - { + if (!smpic->exportPicture(currentFilePath)) { QMessageBox::warning(this, SnapmaticEditor::tr("Snapmatic Properties"), SnapmaticEditor::tr("Patching of Snapmatic Properties failed because of I/O Error")); smpic->setJsonStr(originalCode, true); return false; } jsonCode = newCode; + smpic->updateStrings(); smpic->emitUpdate(); +#ifdef GTA5SYNC_TELEMETRY + QSettings telemetrySettings(GTA5SYNC_APPVENDOR, GTA5SYNC_APPSTR); + telemetrySettings.beginGroup("Telemetry"); + bool pushUsageData = telemetrySettings.value("PushUsageData", false).toBool(); + telemetrySettings.endGroup(); + if (pushUsageData && Telemetry->canPush()) { + QJsonDocument jsonDocument; + QJsonObject jsonObject; + jsonObject["Type"] = "JSONEdited"; + jsonObject["EditedSize"] = QString::number(smpic->getContentMaxLength()); +#if QT_VERSION >= 0x060000 + jsonObject["EditedTime"] = QString::number(QDateTime::currentDateTimeUtc().toSecsSinceEpoch()); +#else + jsonObject["EditedTime"] = QString::number(QDateTime::currentDateTimeUtc().toTime_t()); +#endif + jsonDocument.setObject(jsonObject); + Telemetry->push(TelemetryCategory::PersonalData, jsonDocument); + } +#endif return true; } return true; } - else - { + else { QMessageBox::warning(this, SnapmaticEditor::tr("Snapmatic Properties"), SnapmaticEditor::tr("Patching of Snapmatic Properties failed because of JSON Error")); return false; } @@ -196,13 +216,11 @@ bool JsonEditorDialog::saveJsonContent() void JsonEditorDialog::on_cmdClose_clicked() { - this->close(); + close(); } void JsonEditorDialog::on_cmdSave_clicked() { if (saveJsonContent()) - { - this->close(); - } + close(); } diff --git a/JsonEditorDialog.h b/JsonEditorDialog.h index b881088..4e618bb 100644 --- a/JsonEditorDialog.h +++ b/JsonEditorDialog.h @@ -1,5 +1,5 @@ /***************************************************************************** -* gta5sync GRAND THEFT AUTO V SYNC +* gta5view Grand Theft Auto V Profile Viewer * Copyright (C) 2017 Syping * * This program is free software: you can redistribute it and/or modify diff --git a/JsonEditorDialog.ui b/JsonEditorDialog.ui index 333c2d0..a52f087 100644 --- a/JsonEditorDialog.ui +++ b/JsonEditorDialog.ui @@ -112,6 +112,9 @@ 0
+ + Apply changes + &Save @@ -125,6 +128,9 @@ 0 + + Discard changes + &Close diff --git a/MapLocationDialog.cpp b/MapLocationDialog.cpp index 9ebb53f..b233a28 100644 --- a/MapLocationDialog.cpp +++ b/MapLocationDialog.cpp @@ -1,6 +1,6 @@ /***************************************************************************** -* gta5sync GRAND THEFT AUTO V SYNC -* Copyright (C) 2017 Syping +* gta5view Grand Theft Auto V Profile Viewer +* Copyright (C) 2017-2021 Syping * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -20,15 +20,20 @@ #include "ui_MapLocationDialog.h" #include "IconLoader.h" #include "AppEnv.h" +#include #include -#include +#include MapLocationDialog::MapLocationDialog(double x, double y, QWidget *parent) : QDialog(parent), xpos_old(x), ypos_old(y), ui(new Ui::MapLocationDialog) { // Set Window Flags +#if QT_VERSION >= 0x050900 + setWindowFlag(Qt::WindowContextHelpButtonHint, false); +#else setWindowFlags(windowFlags()^Qt::WindowContextHelpButtonHint); +#endif ui->setupUi(this); ui->cmdDone->setVisible(false); @@ -44,12 +49,10 @@ MapLocationDialog::MapLocationDialog(double x, double y, QWidget *parent) : ui->vlMapDialog->setSpacing(widgetMargin); setMinimumSize(500 * screenRatio, 600 * screenRatio); setMaximumSize(500 * screenRatio, 600 * screenRatio); - setFixedSize(500 * screenRatio, 600 * screenRatio); - setMouseTracking(true); + zoomPercent = 100; changeMode = false; propUpdate = false; - drawPointOnMap(xpos_old, ypos_old); } MapLocationDialog::~MapLocationDialog() @@ -59,36 +62,224 @@ MapLocationDialog::~MapLocationDialog() void MapLocationDialog::drawPointOnMap(double xpos_d, double ypos_d) { - qreal screenRatio = AppEnv::screenRatio(); - int pointMakerSize = 8 * screenRatio; - QPixmap pointMakerPixmap = IconLoader::loadingPointmakerIcon().pixmap(QSize(pointMakerSize, pointMakerSize)); - QSize mapPixelSize = size(); - - int pointMakerHalfSize = pointMakerSize / 2; - long xpos_ms = qRound(xpos_d); - long ypos_ms = qRound(ypos_d); - double xpos_ma = xpos_ms + 4000; - double ypos_ma = ypos_ms + 4000; - double xrat = (double)mapPixelSize.width() / 10000; - double yrat = (double)mapPixelSize.height() / 12000; - long xpos_mp = qRound(xpos_ma * xrat); - long ypos_mp = qRound(ypos_ma * yrat); - long xpos_pr = xpos_mp - pointMakerHalfSize; - long ypos_pr = ypos_mp + pointMakerHalfSize; - - QPixmap mapPixmap(mapPixelSize); - QPainter mapPainter(&mapPixmap); - mapPainter.drawPixmap(0, 0, mapPixelSize.width(), mapPixelSize.height(), QPixmap(":/img/mappreview.jpg").scaled(mapPixelSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); - mapPainter.drawPixmap(xpos_pr, mapPixelSize.height() - ypos_pr, pointMakerSize, pointMakerSize, pointMakerPixmap); - mapPainter.end(); - - QPalette backgroundPalette; - backgroundPalette.setBrush(backgroundRole(), QBrush(mapPixmap)); - setPalette(backgroundPalette); - + ui->labPos->setText(tr("X: %1\nY: %2", "X and Y position").arg(QString::number(xpos_d), QString::number(ypos_d))); xpos_new = xpos_d; ypos_new = ypos_d; - ui->labPos->setText(tr("X: %1\nY: %2", "X and Y position").arg(QString::number(xpos_d), QString::number(ypos_d))); + repaint(); +} + +void MapLocationDialog::setCayoPerico(bool isCayoPerico) +{ + qreal screenRatio = AppEnv::screenRatio(); + p_isCayoPerico = isCayoPerico; + if (isCayoPerico) { + setMinimumSize(500 * screenRatio, 500 * screenRatio); + setMaximumSize(500 * screenRatio, 500 * screenRatio); + ui->hlMapDialog->removeItem(ui->vlMapDialog); + ui->hlMapDialog->insertLayout(0, ui->vlMapDialog); + ui->hlMapDialog->removeItem(ui->vlPosLayout); + ui->hlMapDialog->addLayout(ui->vlPosLayout); + ui->labPos->setAlignment(Qt::AlignRight); + mapImage = QImage(AppEnv::getImagesFolder() % "/mapcayoperico.jpg"); + } + else { + mapImage = QImage(AppEnv::getImagesFolder() % "/mappreview.jpg"); + } + drawPointOnMap(xpos_old, ypos_old); +} + +void MapLocationDialog::updatePosFromEvent(double x, double y) +{ + QSize mapPixelSize = size(); + double x_per = x / mapPixelSize.width(); // get X % + double y_per = y / mapPixelSize.height(); // get Y % + double x_pos, y_pos; + if (p_isCayoPerico) { + x_pos = x_per * 2340; // 2340 is 100% for X (Cayo Perico) + y_pos = y_per * -2340; // -2340 is 100% for Y (Cayo Perico) + x_pos = x_pos + 3560; // +3560 gets corrected for X (Cayo Perico) + y_pos = y_pos - 3980; // -3980 gets corrected for Y (Cayo Perico) + } + else { + x_pos = x_per * 10000; // 10000 is 100% for X (Los Santos) + y_pos = y_per * -12000; // -12000 is 100% for Y (Los Santos) + x_pos = x_pos - 4000; // -4000 gets corrected for X (Los Santos) + y_pos = y_pos + 8000; // +8000 gets corrected for Y (Los Santos) + } + drawPointOnMap(x_pos, y_pos); +} + +void MapLocationDialog::paintEvent(QPaintEvent *ev) +{ + QPainter painter(this); + painter.setRenderHint(QPainter::SmoothPixmapTransform, true); + + // Screen Ratio + qreal screenRatio = AppEnv::screenRatio(); + qreal screenRatioPR = AppEnv::screenRatioPR(); + + // Paint Map + const double zoomLevel = static_cast(zoomPercent) / 100; + const QSize mapImageSize = mapImage.size(); + const QPointF mapImageMid(static_cast(mapImageSize.width()) / 2, static_cast(mapImageSize.height()) / 2); + const QSizeF srcImageSize(static_cast(mapImageSize.width()) / zoomLevel , static_cast(mapImageSize.height()) / zoomLevel); + const QPointF mapImageTopLeft(mapImageMid.x() - (srcImageSize.width() / 2), mapImageMid.y() - (srcImageSize.height() / 2)); + const QPointF mapImageBottomRight(mapImageMid.x() + (srcImageSize.width() / 2), mapImageMid.y() + (srcImageSize.height() / 2)); + painter.drawImage(QRect(QPoint(0, 0), size()), mapImage, QRectF(mapImageTopLeft, mapImageBottomRight)); + + // Paint Marker + QSize mapPixelSize = size(); + int pointMarkerSize = 8 * screenRatio; + int pointMarkerHalfSize = pointMarkerSize / 2; + double xpos_mp, ypos_mp; + if (p_isCayoPerico) { + double xpos_per = xpos_new - 3560; // correct X in reserve + double ypos_per = ypos_new + 3980; // correct y in reserve + xpos_per = xpos_per / 2340; // divide 100% for X + ypos_per = ypos_per / -2340; // divide 100% for Y + xpos_mp = xpos_per * mapPixelSize.width(); // locate window width pos + ypos_mp = ypos_per * mapPixelSize.height(); // locate window height pos + } + else { + double xpos_per = xpos_new + 4000; // correct X in reserve + double ypos_per = ypos_new - 8000; // correct y in reserve + xpos_per = xpos_per / 10000; // divide 100% for X + ypos_per = ypos_per / -12000; // divide 100% for Y + xpos_mp = xpos_per * mapPixelSize.width(); // locate window width pos + ypos_mp = ypos_per * mapPixelSize.height(); // locate window height pos + } + QPointF pointMarkerPos(xpos_mp, ypos_mp); + if (screenRatioPR != 1) { + pointMarkerPos.setX(pointMarkerPos.x() - pointMarkerHalfSize + screenRatioPR); + pointMarkerPos.setY(pointMarkerPos.y() - pointMarkerHalfSize + screenRatioPR); + } + else { + pointMarkerPos.setX(pointMarkerPos.x() - pointMarkerHalfSize); + pointMarkerPos.setY(pointMarkerPos.y() - pointMarkerHalfSize); + } + QPixmap mapMarkerPixmap = IconLoader::loadingPointmakerIcon().pixmap(QSize(pointMarkerSize, pointMarkerSize)); + painter.drawPixmap(pointMarkerPos, mapMarkerPixmap); + + QDialog::paintEvent(ev); +} + +void MapLocationDialog::mouseMoveEvent(QMouseEvent *ev) +{ + if (changeMode && ev->buttons() & Qt::LeftButton) { +#if QT_VERSION >= 0x060000 + const QPointF localPos = ev->position(); +#elif QT_VERSION >= 0x050000 + const QPointF localPos = ev->localPos(); +#else + const QPointF localPos = ev->posF(); +#endif +#ifdef Q_OS_WIN + qreal screenRatioPR = AppEnv::screenRatioPR(); + if (screenRatioPR != 1) { + updatePosFromEvent(localPos.x() - screenRatioPR, localPos.y() - screenRatioPR); + } + else { + updatePosFromEvent(localPos.x(), localPos.y()); + } +#else + updatePosFromEvent(localPos.x(), localPos.y()); +#endif + } + else if (dragStart && ev->buttons() & Qt::LeftButton) { +#if QT_VERSION >= 0x060000 + const QPointF dragNewPosition = ev->position(); +#elif QT_VERSION >= 0x050000 + const QPointF dragNewPosition = ev->localPos(); +#else + const QPointF dragNewPosition = ev->posF(); +#endif + mapDiffPosition = dragNewPosition - dragPosition + mapDiffPosition; + dragPosition = dragNewPosition; + } +} + +void MapLocationDialog::mousePressEvent(QMouseEvent *ev) +{ + if (!changeMode && ev->button() == Qt::LeftButton) { +#if QT_VERSION >= 0x060000 + dragPosition = ev->position(); +#elif QT_VERSION >= 0x050000 + dragPosition = ev->localPos(); +#else + dragPosition = ev->posF(); +#endif + dragStart = true; + } +} + +void MapLocationDialog::mouseReleaseEvent(QMouseEvent *ev) +{ + if (changeMode && ev->button() == Qt::LeftButton) { +#if QT_VERSION >= 0x060000 + const QPointF localPos = ev->position(); +#elif QT_VERSION >= 0x050000 + const QPointF localPos = ev->localPos(); +#else + const QPointF localPos = ev->posF(); +#endif +#ifdef Q_OS_WIN + qreal screenRatioPR = AppEnv::screenRatioPR(); + if (screenRatioPR != 1) { + updatePosFromEvent(localPos.x() - screenRatioPR, localPos.y() - screenRatioPR); + } + else { + updatePosFromEvent(localPos.x(), localPos.y()); + } +#else + updatePosFromEvent(localPos.x(), localPos.y()); +#endif + } + else if (dragStart && ev->button() == Qt::LeftButton) { + dragStart = false; + } +} + +void MapLocationDialog::wheelEvent(QWheelEvent *ev) +{ +#ifdef GTA5SYNC_EXPERIMENTAL +#if QT_VERSION >= 0x050000 + const QPoint numPixels = ev->pixelDelta(); + const QPoint numDegrees = ev->angleDelta(); +#else + QPoint numDegrees; + if (ev->orientation() == Qt::Horizontal) { + numDegrees.setX(ev->delta()); + } + else { + numDegrees.setY(ev->delta()); + } +#endif +#if QT_VERSION >= 0x050000 + if (!numPixels.isNull()) { + if (numPixels.y() < 0 && zoomPercent != 100) { + zoomPercent = zoomPercent - 10; + repaint(); + } + else if (numPixels.y() > 0 && zoomPercent != 400) { + zoomPercent = zoomPercent + 10; + repaint(); + } + return; + } +#endif + if (!numDegrees.isNull()) { + if (numDegrees.y() < 0 && zoomPercent != 100) { + zoomPercent = zoomPercent - 10; + repaint(); + } + else if (numDegrees.y() > 0 && zoomPercent != 400) { + zoomPercent = zoomPercent + 10; + repaint(); + } + } +#else + Q_UNUSED(ev) +#endif } void MapLocationDialog::on_cmdChange_clicked() @@ -110,58 +301,14 @@ void MapLocationDialog::on_cmdDone_clicked() { ui->cmdDone->setVisible(false); ui->cmdChange->setVisible(true); - if (xpos_new != xpos_old || ypos_new != ypos_old) - { + if (xpos_new != xpos_old || ypos_new != ypos_old) { ui->cmdApply->setVisible(true); ui->cmdRevert->setVisible(true); } - setCursor(Qt::ArrowCursor); changeMode = false; } -void MapLocationDialog::updatePosFromEvent(int x, int y) -{ - QSize mapPixelSize = size(); - int xpos_ad = x; - int ypos_ad = mapPixelSize.height() - y; - double xrat = 10000 / (double)mapPixelSize.width(); - double yrat = 12000 / (double)mapPixelSize.height(); - double xpos_rv = xrat * xpos_ad; - double ypos_rv = yrat * ypos_ad; - double xpos_fp = xpos_rv - 4000; - double ypos_fp = ypos_rv - 4000; - drawPointOnMap(xpos_fp, ypos_fp); -} - -void MapLocationDialog::mouseMoveEvent(QMouseEvent *ev) -{ - if (!changeMode) { ev->ignore(); } - else if (ev->buttons() & Qt::LeftButton) - { - updatePosFromEvent(ev->x(), ev->y()); - ev->accept(); - } - else - { - ev->ignore(); - } -} - -void MapLocationDialog::mouseReleaseEvent(QMouseEvent *ev) -{ - if (!changeMode) { ev->ignore(); } - else if (ev->button() == Qt::LeftButton) - { - updatePosFromEvent(ev->x(), ev->y()); - ev->accept(); - } - else - { - ev->ignore(); - } -} - void MapLocationDialog::on_cmdApply_clicked() { propUpdate = true; diff --git a/MapLocationDialog.h b/MapLocationDialog.h index 1abfa93..c67e02f 100644 --- a/MapLocationDialog.h +++ b/MapLocationDialog.h @@ -1,6 +1,6 @@ /***************************************************************************** -* gta5sync GRAND THEFT AUTO V SYNC -* Copyright (C) 2017 Syping +* gta5view Grand Theft Auto V Profile Viewer +* Copyright (C) 2017-2021 Syping * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -33,30 +33,40 @@ class MapLocationDialog : public QDialog public: explicit MapLocationDialog(double x, double y, QWidget *parent = 0); void drawPointOnMap(double x, double y); + void setCayoPerico(bool isCayoPerico); bool propUpdated(); double getXpos(); double getYpos(); ~MapLocationDialog(); protected: + void paintEvent(QPaintEvent *ev); void mouseMoveEvent(QMouseEvent *ev); + void mousePressEvent(QMouseEvent *ev); void mouseReleaseEvent(QMouseEvent *ev); + void wheelEvent(QWheelEvent *ev); private slots: - void on_cmdDone_clicked(); void on_cmdApply_clicked(); + void on_cmdDone_clicked(); + void on_cmdClose_clicked(); void on_cmdChange_clicked(); void on_cmdRevert_clicked(); - void updatePosFromEvent(int x, int y); - void on_cmdClose_clicked(); + void updatePosFromEvent(double x, double y); private: + int zoomPercent; double xpos_old; double ypos_old; double xpos_new; double ypos_new; + bool dragStart; bool propUpdate; bool changeMode; + bool p_isCayoPerico; + QImage mapImage; + QPointF dragPosition; + QPointF mapDiffPosition; Ui::MapLocationDialog *ui; }; diff --git a/MapLocationDialog.ui b/MapLocationDialog.ui index d62edf0..06da9ed 100644 --- a/MapLocationDialog.ui +++ b/MapLocationDialog.ui @@ -25,6 +25,11 @@ Snapmatic Map Viewer + + QDialog#MapLocationDialog { + background-color: transparent; +} + 0 @@ -134,6 +139,9 @@ color: rgb(255, 255, 255); Qt::NoFocus + + Close viewer + &Close @@ -160,6 +168,9 @@ color: rgb(255, 255, 255); Qt::NoFocus + + Apply new position + &Apply @@ -173,6 +184,9 @@ color: rgb(255, 255, 255); Qt::NoFocus + + Revert old position + &Revert @@ -186,8 +200,11 @@ color: rgb(255, 255, 255); Qt::NoFocus + + Select new position + - &Set + &Select false @@ -199,6 +216,9 @@ color: rgb(255, 255, 255); Qt::NoFocus + + Quit select position + &Done diff --git a/MessageThread.cpp b/MessageThread.cpp new file mode 100644 index 0000000..2781ac6 --- /dev/null +++ b/MessageThread.cpp @@ -0,0 +1,111 @@ +/***************************************************************************** +* gta5view Grand Theft Auto V Profile Viewer +* Copyright (C) 2020 Syping +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +*****************************************************************************/ + +#include "TranslationClass.h" +#include "MessageThread.h" +#include "AppEnv.h" +#include "config.h" +#include +#include +#include +#include +#include +#include +#include +#include + +MessageThread::MessageThread(uint cacheId, QObject *parent) : QThread(parent), cacheId(cacheId) +{ + threadRunning = true; +} + +void MessageThread::run() +{ + QEventLoop threadLoop; + + QObject::connect(this, SIGNAL(threadTerminated()), &threadLoop, SLOT(quit())); + + while (threadRunning) { + { +#ifdef GTA5SYNC_MOTD_WEBURL + QUrl motdWebUrl = QUrl(GTA5SYNC_MOTD_WEBURL); +#else + QUrl motdWebUrl = QUrl("https://motd.syping.de/gta5view-dev/"); +#endif + QUrlQuery urlQuery(motdWebUrl); + urlQuery.addQueryItem("code", GTA5SYNC_BUILDCODE); + urlQuery.addQueryItem("cacheid", QString::number(cacheId)); + urlQuery.addQueryItem("lang", Translator->getCurrentLanguage()); + urlQuery.addQueryItem("version", GTA5SYNC_APPVER); + motdWebUrl.setQuery(urlQuery); + + QNetworkAccessManager *netManager = new QNetworkAccessManager(); + QNetworkRequest netRequest(motdWebUrl); + netRequest.setRawHeader("User-Agent", AppEnv::getUserAgent()); + QNetworkReply *netReply = netManager->get(netRequest); + + QEventLoop downloadLoop; + QObject::connect(netManager, SIGNAL(finished(QNetworkReply*)), &downloadLoop, SLOT(quit())); + QObject::connect(this, SIGNAL(threadTerminated()), &threadLoop, SLOT(quit())); + QTimer::singleShot(60000, &downloadLoop, SLOT(quit())); + downloadLoop.exec(); + + if (netReply->isFinished()) { + QByteArray jsonContent = netReply->readAll(); + QString headerData = QString::fromUtf8(netReply->rawHeader("gta5view")); + if (!headerData.isEmpty()) { + QMap headerMap; + const QStringList headerVarList = headerData.split(';'); + for (QString headerVar : headerVarList) { + QStringList varValueList = headerVar.split('='); + if (varValueList.length() >= 2) { + const QString variable = varValueList.at(0).trimmed(); + varValueList.removeFirst(); + const QString value = varValueList.join('='); + headerMap.insert(variable, value); + } + } + if (headerMap.value("update", "false") == "true") { + QJsonDocument jsonDocument = QJsonDocument::fromJson(jsonContent); + emit messagesArrived(jsonDocument.object()); + } + if (headerMap.contains("cache")) { + bool uintOk; + uint cacheVal = headerMap.value("cache").toUInt(&uintOk); + if (uintOk) { + cacheId = cacheVal; + emit updateCacheId(cacheId); + } + } + } + } + + delete netReply; + delete netManager; + } + + QTimer::singleShot(300000, &threadLoop, SLOT(quit())); + threadLoop.exec(); + } +} + +void MessageThread::terminateThread() +{ + threadRunning = false; + emit threadTerminated(); +} diff --git a/ImageEditorDialog.h b/MessageThread.h similarity index 52% rename from ImageEditorDialog.h rename to MessageThread.h index a9417cd..fa2d25b 100644 --- a/ImageEditorDialog.h +++ b/MessageThread.h @@ -1,6 +1,6 @@ /***************************************************************************** -* gta5sync GRAND THEFT AUTO V SYNC -* Copyright (C) 2016-2017 Syping +* gta5view Grand Theft Auto V Profile Viewer +* Copyright (C) 2020 Syping * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -16,38 +16,33 @@ * along with this program. If not, see . *****************************************************************************/ -#ifndef IMAGEEDITORDIALOG_H -#define IMAGEEDITORDIALOG_H +#ifndef MESSAGETHREAD_H +#define MESSAGETHREAD_H -#include "SnapmaticPicture.h" -#include +#include +#include +#include -namespace Ui { -class ImageEditorDialog; -} - -class ImageEditorDialog : public QDialog +class MessageThread : public QThread { Q_OBJECT - public: - explicit ImageEditorDialog(SnapmaticPicture *picture, QString profileName, QWidget *parent = 0); - ~ImageEditorDialog(); + explicit MessageThread(uint cacheId, QObject *parent = 0); -private slots: - void on_cmdClose_clicked(); - void on_cmdReplace_clicked(); - void on_cmdSave_clicked(); - void on_cmdQuestion_clicked(); +public slots: + void terminateThread(); private: - SnapmaticPicture *smpic; - QString profileName; - Ui::ImageEditorDialog *ui; - int snapmaticResolutionLW; - int snapmaticResolutionLH; - bool imageIsChanged; - QImage pictureCache; + bool threadRunning; + uint cacheId; + +protected: + void run(); + +signals: + void messagesArrived(const QJsonObject &messageObject); + void updateCacheId(uint cacheId); + void threadTerminated(); }; -#endif // IMAGEEDITORDIALOG_H +#endif // MESSAGETHREAD_H diff --git a/OptionsDialog.cpp b/OptionsDialog.cpp index 0528709..d096383 100644 --- a/OptionsDialog.cpp +++ b/OptionsDialog.cpp @@ -1,6 +1,6 @@ /***************************************************************************** -* gta5sync GRAND THEFT AUTO V SYNC -* Copyright (C) 2016-2018 Syping +* gta5view Grand Theft Auto V Profile Viewer +* Copyright (C) 2016-2021 Syping * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -21,15 +21,16 @@ #include "TranslationClass.h" #include "StandardPaths.h" #include "UserInterface.h" +#include "wrapper.h" #include "AppEnv.h" #include "config.h" #include -#include #include #include #include #include #include +#include #include #include #include @@ -40,6 +41,12 @@ #include #include +#if QT_VERSION >= 0x050000 +#include +#else +#include +#endif + #ifdef GTA5SYNC_TELEMETRY #include "TelemetryClass.h" #endif @@ -49,19 +56,31 @@ OptionsDialog::OptionsDialog(ProfileDatabase *profileDB, QWidget *parent) : ui(new Ui::OptionsDialog) { // Set Window Flags +#if QT_VERSION >= 0x050900 + setWindowFlag(Qt::WindowContextHelpButtonHint, false); +#else setWindowFlags(windowFlags()^Qt::WindowContextHelpButtonHint); +#endif // Setup User Interface ui->setupUi(this); ui->tabWidget->setCurrentIndex(0); ui->labPicCustomRes->setVisible(false); + ui->cmdCancel->setDefault(true); ui->cmdCancel->setFocus(); +#if QT_VERSION >= 0x050000 + qreal screenRatioPR = AppEnv::screenRatioPR(); + QRect desktopResolution = QApplication::primaryScreen()->geometry(); + int desktopSizeWidth = qRound((double)desktopResolution.width() * screenRatioPR); + int desktopSizeHeight = qRound((double)desktopResolution.height() * screenRatioPR); +#else QRect desktopResolution = QApplication::desktop()->screenGeometry(this); int desktopSizeWidth = desktopResolution.width(); int desktopSizeHeight = desktopResolution.height(); +#endif aspectRatio = Qt::KeepAspectRatio; - defExportSize = QSize(960, 536); + defExportSize = SnapmaticPicture::getSnapmaticResolution(); cusExportSize = defExportSize; defaultQuality = 100; customQuality = 100; @@ -74,25 +93,26 @@ OptionsDialog::OptionsDialog(ProfileDatabase *profileDB, QWidget *parent) : ui->rbPicDefaultRes->setText(ui->rbPicDefaultRes->text().arg(QString::number(defExportSize.width()), QString::number(defExportSize.height()))); // Set Icon for OK Button - if (QIcon::hasThemeIcon("dialog-ok")) - { + if (QIcon::hasThemeIcon("dialog-ok")) { ui->cmdOK->setIcon(QIcon::fromTheme("dialog-ok")); } - else if (QIcon::hasThemeIcon("gtk-ok")) - { + else if (QIcon::hasThemeIcon("gtk-ok")) { ui->cmdOK->setIcon(QIcon::fromTheme("gtk-ok")); } // Set Icon for Cancel Button - if (QIcon::hasThemeIcon("dialog-cancel")) - { + if (QIcon::hasThemeIcon("dialog-cancel")) { ui->cmdCancel->setIcon(QIcon::fromTheme("dialog-cancel")); } - else if (QIcon::hasThemeIcon("gtk-cancel")) - { + else if (QIcon::hasThemeIcon("gtk-cancel")) { ui->cmdCancel->setIcon(QIcon::fromTheme("gtk-cancel")); } + // Set Icon for Copy Button + if (QIcon::hasThemeIcon("edit-copy")) { + ui->cmdCopyStatsID->setIcon(QIcon::fromTheme("edit-copy")); + } + setupTreeWidget(); setupLanguageBox(); setupRadioButtons(); @@ -102,6 +122,7 @@ OptionsDialog::OptionsDialog(ProfileDatabase *profileDB, QWidget *parent) : setupInterfaceSettings(); setupStatisticsSettings(); setupSnapmaticPictureViewer(); + setupWindowsGameSettings(); #ifndef Q_QS_ANDROID // DPI calculation @@ -109,11 +130,9 @@ OptionsDialog::OptionsDialog(ProfileDatabase *profileDB, QWidget *parent) : resize(435 * screenRatio, 405 * screenRatio); #endif -#ifdef GTA5SYNC_DISABLED - ui->tabWidget->removeTab(ui->tabWidget->indexOf(ui->tabSync)); -#endif - - this->setWindowTitle(windowTitle().arg(GTA5SYNC_APPSTR)); + ui->rbModern->setText(ui->rbModern->text().arg(GTA5SYNC_APPSTR)); + ui->rbClassic->setText(ui->rbClassic->text().arg(GTA5SYNC_APPSTR)); + setWindowTitle(windowTitle().arg(GTA5SYNC_APPSTR)); } OptionsDialog::~OptionsDialog() @@ -126,24 +145,28 @@ OptionsDialog::~OptionsDialog() void OptionsDialog::setupTreeWidget() { - for (QString playerIDStr : profileDB->getPlayers()) - { - bool ok; - int playerID = playerIDStr.toInt(&ok); - if (ok) - { - QString playerName = profileDB->getPlayerName(playerID); + const QStringList players = profileDB->getPlayers(); + if (players.length() != 0) { + for (auto it = players.constBegin(); it != players.constEnd(); it++) { + bool ok; + int playerID = it->toInt(&ok); + if (ok) { + const QString playerName = profileDB->getPlayerName(playerID); - QStringList playerTreeViewList; - playerTreeViewList += playerIDStr; - playerTreeViewList += playerName; + QStringList playerTreeViewList; + playerTreeViewList += *it; + playerTreeViewList += playerName; - QTreeWidgetItem *playerItem = new QTreeWidgetItem(playerTreeViewList); - ui->twPlayers->addTopLevelItem(playerItem); - playerItems += playerItem; + QTreeWidgetItem *playerItem = new QTreeWidgetItem(playerTreeViewList); + ui->twPlayers->addTopLevelItem(playerItem); + playerItems += playerItem; + } } + ui->twPlayers->sortItems(1, Qt::AscendingOrder); + } + else { + ui->tabWidget->removeTab(ui->tabWidget->indexOf(ui->tabPlayers)); } - ui->twPlayers->sortItems(1, Qt::AscendingOrder); } void OptionsDialog::setupLanguageBox() @@ -153,9 +176,19 @@ void OptionsDialog::setupLanguageBox() currentAreaLanguage = settings->value("AreaLanguage", "Auto").toString(); settings->endGroup(); - QString cbSysStr = tr("%1 (Next Closest Language)", "First language a person can talk with a different person/application. \"Native\" or \"Not Native\".").arg(tr("System", - "System in context of System default")); - QString cbAutoStr = tr("%1 (Closest to Interface)", "Next closest language compared to the Interface").arg(tr("Auto", "Automatic language choice.")); + const QString cbSysStr = tr("%1 (Language priority)", "First language a person can talk with a different person/application. \"Native\" or \"Not Native\".").arg(tr("System", + "System in context of System default")); +#ifdef Q_OS_WIN + QString cbAutoStr; + if (AppEnv::getGameLanguage(AppEnv::getGameVersion()) != GameLanguage::Undefined) { + cbAutoStr = tr("%1 (Game language)", "Next closest language compared to the Game settings").arg(tr("Auto", "Automatic language choice.")); + } + else { + cbAutoStr = tr("%1 (Closest to Interface)", "Next closest language compared to the Interface").arg(tr("Auto", "Automatic language choice.")); + } +#else + const QString cbAutoStr = tr("%1 (Closest to Interface)", "Next closest language compared to the Interface").arg(tr("Auto", "Automatic language choice.")); +#endif ui->cbLanguage->addItem(cbSysStr, "System"); ui->cbAreaLanguage->addItem(cbAutoStr, "Auto"); @@ -168,15 +201,18 @@ void OptionsDialog::setupLanguageBox() availableLanguages.removeDuplicates(); availableLanguages.sort(); - for (QString lang : availableLanguages) - { + for (const QString &lang : qAsConst(availableLanguages)) { QLocale langLocale(lang); - QString cbLangStr = langLocale.nativeLanguageName() % " (" % langLocale.nativeCountryName() % ") [" % lang % "]"; - QString langIconStr = "flag-" % TranslationClass::getCountryCode(langLocale); + const QString cbLangStr = langLocale.nativeLanguageName() % " (" % langLocale.nativeCountryName() % ") [" % lang % "]"; + const QString langIconPath = AppEnv::getImagesFolder() % "/flag-" % TranslationClass::getCountryCode(langLocale) % ".png"; - ui->cbLanguage->addItem(QIcon::fromTheme(langIconStr), cbLangStr, lang); - if (currentLanguage == lang) - { + if (QFile::exists(langIconPath)) { + ui->cbLanguage->addItem(QIcon(langIconPath), cbLangStr, lang); + } + else { + ui->cbLanguage->addItem(cbLangStr, lang); + } + if (currentLanguage == lang) { #if QT_VERSION >= 0x050000 ui->cbLanguage->setCurrentText(cbLangStr); #else @@ -187,7 +223,8 @@ void OptionsDialog::setupLanguageBox() } QString aCurrentLanguage = QString("en_GB"); - if (Translator->isLanguageLoaded()) { aCurrentLanguage = Translator->getCurrentLanguage(); } + if (Translator->isLanguageLoaded()) + aCurrentLanguage = Translator->getCurrentLanguage(); QLocale currentLocale = QLocale(aCurrentLanguage); ui->labCurrentLanguage->setText(tr("Current: %1").arg(currentLocale.nativeLanguageName() % " (" % currentLocale.nativeCountryName() % ") [" % aCurrentLanguage % "]")); @@ -196,27 +233,21 @@ void OptionsDialog::setupLanguageBox() availableLanguages.removeDuplicates(); availableLanguages.sort(); - for (QString lang : availableLanguages) - { + for (const QString &lang : qAsConst(availableLanguages)) { // correcting Language Location if possible QString aLang = lang; - if (QFile::exists(":/global/global." % lang % ".loc")) - { + if (QFile::exists(":/global/global." % lang % ".loc")) { QFile locFile(":/global/global." % lang % ".loc"); - if (locFile.open(QFile::ReadOnly)) - { + if (locFile.open(QFile::ReadOnly)) { aLang = QString::fromUtf8(locFile.readLine()).trimmed(); locFile.close(); } } QLocale langLocale(aLang); - QString cbLangStr = langLocale.nativeLanguageName() % " (" % langLocale.nativeCountryName() % ") [" % aLang % "]"; - QString langIconStr = "flag-" % TranslationClass::getCountryCode(langLocale); - - ui->cbAreaLanguage->addItem(QIcon::fromTheme(langIconStr), cbLangStr, lang); - if (currentAreaLanguage == lang) - { + const QString cbLangStr = langLocale.nativeLanguageName() % " (" % langLocale.nativeCountryName() % ") [" % aLang % "]"; + ui->cbAreaLanguage->addItem(cbLangStr, lang); + if (currentAreaLanguage == lang) { #if QT_VERSION >= 0x050000 ui->cbAreaLanguage->setCurrentText(cbLangStr); #else @@ -227,12 +258,10 @@ void OptionsDialog::setupLanguageBox() } QString aCurrentAreaLanguage = Translator->getCurrentAreaLanguage(); - if (QFile::exists(":/global/global." % aCurrentAreaLanguage % ".loc")) - { + if (QFile::exists(":/global/global." % aCurrentAreaLanguage % ".loc")) { qDebug() << "locFile found"; QFile locFile(":/global/global." % aCurrentAreaLanguage % ".loc"); - if (locFile.open(QFile::ReadOnly)) - { + if (locFile.open(QFile::ReadOnly)) { aCurrentAreaLanguage = QString::fromUtf8(locFile.readLine()).trimmed(); locFile.close(); } @@ -248,18 +277,26 @@ void OptionsDialog::setupRadioButtons() contentMode = settings->value("ContentMode", 0).toInt(&contentModeOk); settings->endGroup(); - if (contentModeOk) - { - switch (contentMode) - { + if (contentModeOk) { + switch (contentMode) { case 0: - ui->rbOpenWithSC->setChecked(true); + case 20: + ui->rbModern->setChecked(true); + ui->cbDoubleclick->setChecked(false); break; case 1: - ui->rbOpenWithDC->setChecked(true); - break; case 2: - ui->rbSelectWithSC->setChecked(true); + case 21: + ui->rbModern->setChecked(true); + ui->cbDoubleclick->setChecked(true); + break; + case 10: + ui->rbClassic->setChecked(true); + ui->cbDoubleclick->setChecked(false); + break; + case 11: + ui->rbClassic->setChecked(true); + ui->cbDoubleclick->setChecked(true); break; } } @@ -268,55 +305,40 @@ void OptionsDialog::setupRadioButtons() void OptionsDialog::setupInterfaceSettings() { settings->beginGroup("Startup"); - bool alwaysUseMessageFont = settings->value("AlwaysUseMessageFont", false).toBool(); - ui->cbAlwaysUseMessageFont->setChecked(alwaysUseMessageFont); -#ifdef GTA5SYNC_WIN - if (QSysInfo::windowsVersion() >= 0x0080) - { - ui->gbFont->setVisible(false); - ui->cbAlwaysUseMessageFont->setVisible(false); - } -#else - ui->gbFont->setVisible(false); - ui->cbAlwaysUseMessageFont->setVisible(false); -#endif - QString currentStyle = QApplication::style()->objectName(); - QString appStyle = settings->value("AppStyle", currentStyle).toString(); + const QString currentStyle = QApplication::style()->objectName(); + const QString appStyle = settings->value("AppStyle", currentStyle).toString(); bool customStyle = settings->value("CustomStyle", false).toBool(); const QStringList availableStyles = QStyleFactory::keys(); ui->cbStyleList->addItems(availableStyles); - if (availableStyles.contains(appStyle, Qt::CaseInsensitive)) - { + if (availableStyles.contains(appStyle, Qt::CaseInsensitive)) { // use 'for' for select to be sure it's case insensitive int currentIndex = 0; - for (QString currentStyleFF : availableStyles) - { - if (currentStyleFF.toLower() == appStyle.toLower()) - { + for (const QString ¤tStyleFF : availableStyles) { + if (currentStyleFF.toLower() == appStyle.toLower()) { ui->cbStyleList->setCurrentIndex(currentIndex); } currentIndex++; } } - else - { - if (availableStyles.contains(currentStyle, Qt::CaseInsensitive)) - { + else { + if (availableStyles.contains(currentStyle, Qt::CaseInsensitive)) { int currentIndex = 0; - for (QString currentStyleFF : availableStyles) - { - if (currentStyleFF.toLower() == currentStyle.toLower()) - { + for (const QString ¤tStyleFF : availableStyles) { + if (currentStyleFF.toLower() == currentStyle.toLower()) { ui->cbStyleList->setCurrentIndex(currentIndex); } currentIndex++; } } } - if (customStyle) - { - ui->cbDefaultStyle->setChecked(false); - } + ui->cbDefaultStyle->setChecked(!customStyle); + ui->cbStyleList->setEnabled(customStyle); + const QFont currentFont = QApplication::font(); + const QFont appFont = qvariant_cast(settings->value("AppFont", currentFont)); + bool customFont = settings->value("CustomFont", false).toBool(); + ui->cbDefaultFont->setChecked(!customFont); + ui->cbFont->setEnabled(customFont); + ui->cbFont->setCurrentFont(appFont); settings->endGroup(); } @@ -336,26 +358,25 @@ void OptionsDialog::applySettings() settings->setValue("Language", ui->cbLanguage->itemData(ui->cbLanguage->currentIndex())); settings->setValue("AreaLanguage", ui->cbAreaLanguage->itemData(ui->cbAreaLanguage->currentIndex())); #endif -#ifdef GTA5SYNC_WIN +#ifdef Q_OS_WIN #if QT_VERSION >= 0x050200 settings->setValue("NavigationBar", ui->cbSnapmaticNavigationBar->isChecked()); #endif +#else + settings->setValue("NavigationBar", ui->cbSnapmaticNavigationBar->isChecked()); #endif settings->endGroup(); settings->beginGroup("Profile"); - int newContentMode = 0; - if (ui->rbOpenWithSC->isChecked()) - { - newContentMode = 0; + int newContentMode = 20; + if (ui->rbModern->isChecked()) { + newContentMode = 20; } - else if (ui->rbOpenWithDC->isChecked()) - { - newContentMode = 1; + else if (ui->rbClassic->isChecked()) { + newContentMode = 10; } - else if (ui->rbSelectWithSC->isChecked()) - { - newContentMode = 2; + if (ui->cbDoubleclick->isChecked()) { + newContentMode++; } settings->setValue("ContentMode", newContentMode); #if QT_VERSION >= 0x050000 @@ -366,18 +387,15 @@ void OptionsDialog::applySettings() settings->endGroup(); settings->beginGroup("Pictures"); - if (ui->cbPicCustomQuality->isChecked()) - { + if (ui->cbPicCustomQuality->isChecked()) { settings->setValue("CustomQuality", ui->hsPicQuality->value()); } settings->setValue("CustomQualityEnabled", ui->cbPicCustomQuality->isChecked()); QString sizeMode = "Default"; - if (ui->rbPicDesktopRes->isChecked()) - { + if (ui->rbPicDesktopRes->isChecked()) { sizeMode = "Desktop"; } - else if (ui->rbPicCustomRes->isChecked()) - { + else if (ui->rbPicCustomRes->isChecked()) { sizeMode = "Custom"; settings->setValue("CustomSize", QSize(ui->sbPicExportWidth->value(), ui->sbPicExportHeight->value())); } @@ -393,27 +411,47 @@ void OptionsDialog::applySettings() bool defaultStyle = ui->cbDefaultStyle->isChecked(); settings->beginGroup("Startup"); - if (!defaultStyle) - { + if (!defaultStyle) { QString newStyle = ui->cbStyleList->currentText(); settings->setValue("CustomStyle", true); settings->setValue("AppStyle", newStyle); QApplication::setStyle(QStyleFactory::create(newStyle)); } - else - { + else { settings->setValue("CustomStyle", false); } - settings->setValue("AlwaysUseMessageFont", ui->cbAlwaysUseMessageFont->isChecked()); + bool defaultFont = ui->cbDefaultFont->isChecked(); + if (!defaultFont) { + QFont newFont = ui->cbFont->currentFont(); + settings->setValue("CustomFont", true); + settings->setValue("AppFont", newFont); + QApplication::setFont(newFont); + } + else { + settings->setValue("CustomFont", false); + } settings->endGroup(); #ifdef GTA5SYNC_TELEMETRY settings->beginGroup("Telemetry"); settings->setValue("PushAppConf", ui->cbAppConfigStats->isChecked()); + settings->setValue("PushUsageData", ui->cbUsageData->isChecked()); if (!Telemetry->isStateForced()) { settings->setValue("IsEnabled", ui->cbParticipateStats->isChecked()); } settings->endGroup(); Telemetry->refresh(); Telemetry->work(); + if (ui->cbUsageData->isChecked() && Telemetry->canPush()) { + QJsonDocument jsonDocument; + QJsonObject jsonObject; + jsonObject["Type"] = "SettingsUpdated"; +#if QT_VERSION >= 0x060000 + jsonObject["UpdateTime"] = QString::number(QDateTime::currentDateTimeUtc().toSecsSinceEpoch()); +#else + jsonObject["UpdateTime"] = QString::number(QDateTime::currentDateTimeUtc().toTime_t()); +#endif + jsonDocument.setObject(jsonObject); + Telemetry->push(TelemetryCategory::PersonalData, jsonDocument); + } #endif #if QT_VERSION >= 0x050000 @@ -423,21 +461,19 @@ void OptionsDialog::applySettings() bool languageChanged = ui->cbLanguage->itemData(ui->cbLanguage->currentIndex()).toString() != currentLanguage; bool languageAreaChanged = ui->cbAreaLanguage->itemData(ui->cbLanguage->currentIndex()).toString() != currentAreaLanguage; #endif - if (languageChanged) - { + if (languageChanged) { Translator->unloadTranslation(qApp); Translator->initUserLanguage(); Translator->loadTranslation(qApp); } - else if (languageAreaChanged) - { + else if (languageAreaChanged) { Translator->initUserLanguage(); } + settings->sync(); emit settingsApplied(newContentMode, languageChanged); - if ((forceCustomFolder && ui->txtFolder->text() != currentCFolder) || (forceCustomFolder != currentFFolder && forceCustomFolder)) - { + if ((forceCustomFolder && ui->txtFolder->text() != currentCFolder) || (forceCustomFolder != currentFFolder && forceCustomFolder)) { QMessageBox::information(this, tr("%1", "%1").arg(GTA5SYNC_APPSTR), tr("The new Custom Folder will initialise after you restart %1.").arg(GTA5SYNC_APPSTR)); } } @@ -454,11 +490,9 @@ void OptionsDialog::setupDefaultProfile() void OptionsDialog::commitProfiles(const QStringList &profiles) { - for (QString profile : profiles) - { + for (const QString &profile : profiles) { ui->cbProfiles->addItem(tr("Profile: %1").arg(profile), profile); - if (defaultProfile == profile) - { + if (defaultProfile == profile) { #if QT_VERSION >= 0x050000 ui->cbProfiles->setCurrentText(tr("Profile: %1").arg(profile)); #else @@ -496,8 +530,7 @@ void OptionsDialog::setupPictureSettings() // Quality Settings customQuality = settings->value("CustomQuality", defaultQuality).toInt(); - if (customQuality < 1 || customQuality > 100) - { + if (customQuality < 1 || customQuality > 100) { customQuality = 100; } ui->hsPicQuality->setValue(customQuality); @@ -505,42 +538,34 @@ void OptionsDialog::setupPictureSettings() // Size Settings cusExportSize = settings->value("CustomSize", defExportSize).toSize(); - if (cusExportSize.width() > 3840) - { + if (cusExportSize.width() > 3840) { cusExportSize.setWidth(3840); } - else if (cusExportSize.height() > 2160) - { + else if (cusExportSize.height() > 2160) { cusExportSize.setHeight(2160); } - if (cusExportSize.width() < 1) - { + if (cusExportSize.width() < 1) { cusExportSize.setWidth(1); } - else if (cusExportSize.height() < 1) - { + else if (cusExportSize.height() < 1) { cusExportSize.setHeight(1); } ui->sbPicExportWidth->setValue(cusExportSize.width()); ui->sbPicExportHeight->setValue(cusExportSize.height()); QString sizeMode = settings->value("ExportSizeMode", "Default").toString(); - if (sizeMode == "Desktop") - { + if (sizeMode == "Desktop") { ui->rbPicDesktopRes->setChecked(true); } - else if (sizeMode == "Custom") - { + else if (sizeMode == "Custom") { ui->rbPicCustomRes->setChecked(true); } - else - { + else { ui->rbPicDefaultRes->setChecked(true); } aspectRatio = (Qt::AspectRatioMode)settings->value("AspectRatio", Qt::KeepAspectRatio).toInt(); - if (aspectRatio == Qt::IgnoreAspectRatio) - { + if (aspectRatio == Qt::IgnoreAspectRatio) { ui->cbIgnoreAspectRatio->setChecked(true); } @@ -553,30 +578,20 @@ void OptionsDialog::setupStatisticsSettings() ui->cbParticipateStats->setText(tr("Participate in %1 User Statistics").arg(GTA5SYNC_APPSTR)); ui->labUserStats->setText(QString("%1").arg(tr("View %1 User Statistics Online").arg(GTA5SYNC_APPSTR), TelemetryClass::getWebURL().toString())); - ui->gbUserFeedback->setVisible(false); - // settings->beginGroup("Startup"); - // if (settings->value("IsFirstStart", true).toBool() == true) - // { - // ui->gbUserFeedback->setVisible(false); - // } - // settings->endGroup(); - settings->beginGroup("Telemetry"); ui->cbParticipateStats->setChecked(Telemetry->isEnabled()); ui->cbAppConfigStats->setChecked(settings->value("PushAppConf", false).toBool()); + ui->cbUsageData->setChecked(settings->value("PushUsageData", false).toBool()); settings->endGroup(); - if (Telemetry->isStateForced()) - { + if (Telemetry->isStateForced()) { ui->cbParticipateStats->setEnabled(false); } - if (Telemetry->isRegistered()) - { + if (Telemetry->isRegistered()) { ui->labParticipationID->setText(tr("Participation ID: %1").arg(Telemetry->getRegisteredID())); } - else - { + else { ui->labParticipationID->setText(tr("Participation ID: %1").arg(tr("Not registered"))); ui->cmdCopyStatsID->setVisible(false); } @@ -585,14 +600,70 @@ void OptionsDialog::setupStatisticsSettings() #endif } +void OptionsDialog::setupWindowsGameSettings() +{ +#ifdef GTA5SYNC_GAME + GameVersion gameVersion = AppEnv::getGameVersion(); +#ifdef Q_OS_WIN + if (gameVersion != GameVersion::NoVersion) { + if (gameVersion == GameVersion::SocialClubVersion) { + ui->gbSteam->setDisabled(true); + ui->labSocialClubFound->setText(tr("Found: %1").arg(QString("%1").arg(tr("Yes")))); + ui->labSteamFound->setText(tr("Found: %1").arg(QString("%1").arg(tr("No")))); + if (AppEnv::getGameLanguage(GameVersion::SocialClubVersion) != GameLanguage::Undefined) { + ui->labSocialClubLanguage->setText(tr("Language: %1").arg(QLocale(AppEnv::gameLanguageToString(AppEnv::getGameLanguage(GameVersion::SocialClubVersion))).nativeLanguageName())); + } + else { + ui->labSocialClubLanguage->setText(tr("Language: %1").arg(tr("OS defined"))); + } + ui->labSteamLanguage->setVisible(false); + } + else if (gameVersion == GameVersion::SteamVersion) { + ui->gbSocialClub->setDisabled(true); + ui->labSocialClubFound->setText(tr("Found: %1").arg(QString("%1").arg(tr("No")))); + ui->labSteamFound->setText(tr("Found: %1").arg(QString("%1").arg(tr("Yes")))); + ui->labSocialClubLanguage->setVisible(false); + if (AppEnv::getGameLanguage(GameVersion::SteamVersion) != GameLanguage::Undefined) { + ui->labSteamLanguage->setText(tr("Language: %1").arg(QLocale(AppEnv::gameLanguageToString(AppEnv::getGameLanguage(GameVersion::SteamVersion))).nativeLanguageName())); + } + else { + ui->labSteamLanguage->setText(tr("Language: %1").arg(tr("Steam defined"))); + } + } + else { + ui->labSocialClubFound->setText(tr("Found: %1").arg(QString("%1").arg(tr("Yes")))); + ui->labSteamFound->setText(tr("Found: %1").arg(QString("%1").arg(tr("Yes")))); + if (AppEnv::getGameLanguage(GameVersion::SocialClubVersion) != GameLanguage::Undefined) { + ui->labSocialClubLanguage->setText(tr("Language: %1").arg(QLocale(AppEnv::gameLanguageToString(AppEnv::getGameLanguage(GameVersion::SocialClubVersion))).nativeLanguageName())); + } + else { + ui->labSocialClubLanguage->setText(tr("Language: %1").arg(tr("OS defined"))); + } + if (AppEnv::getGameLanguage(GameVersion::SteamVersion) != GameLanguage::Undefined) { + ui->labSteamLanguage->setText(tr("Language: %1").arg(QLocale(AppEnv::gameLanguageToString(AppEnv::getGameLanguage(GameVersion::SteamVersion))).nativeLanguageName())); + } + else { + ui->labSteamLanguage->setText(tr("Language: %1").arg(tr("Steam defined"))); + } + } + } + else { + ui->tabWidget->removeTab(ui->tabWidget->indexOf(ui->tabGame)); + } +#else + ui->tabWidget->removeTab(ui->tabWidget->indexOf(ui->tabGame)); +#endif +#else + ui->tabWidget->removeTab(ui->tabWidget->indexOf(ui->tabGame)); +#endif +} + void OptionsDialog::on_cbIgnoreAspectRatio_toggled(bool checked) { - if (checked) - { + if (checked) { aspectRatio = Qt::IgnoreAspectRatio; } - else - { + else { aspectRatio = Qt::KeepAspectRatio; } } @@ -604,8 +675,7 @@ void OptionsDialog::setupCustomGTAFolder() settings->beginGroup("dir"); currentCFolder = settings->value("dir", "").toString(); currentFFolder = settings->value("force", false).toBool(); - if (currentCFolder == "" && ok) - { + if (currentCFolder == "" && ok) { currentCFolder = defaultGameFolder; } ui->txtFolder->setText(currentCFolder); @@ -615,26 +685,26 @@ void OptionsDialog::setupCustomGTAFolder() void OptionsDialog::setupSnapmaticPictureViewer() { -#ifdef GTA5SYNC_WIN +#ifdef Q_OS_WIN #if QT_VERSION >= 0x050200 settings->beginGroup("Interface"); - ui->cbSnapmaticNavigationBar->setChecked(settings->value("NavigationBar", false).toBool()); + ui->cbSnapmaticNavigationBar->setChecked(settings->value("NavigationBar", true).toBool()); settings->endGroup(); #else ui->cbSnapmaticNavigationBar->setVisible(false); ui->gbSnapmaticPictureViewer->setVisible(false); #endif #else - ui->cbSnapmaticNavigationBar->setVisible(false); - ui->gbSnapmaticPictureViewer->setVisible(false); + settings->beginGroup("Interface"); + ui->cbSnapmaticNavigationBar->setChecked(settings->value("NavigationBar", true).toBool()); + settings->endGroup(); #endif } void OptionsDialog::on_cmdExploreFolder_clicked() { - QString GTAV_Folder = QFileDialog::getExistingDirectory(this, UserInterface::tr("Select GTA V Folder..."), StandardPaths::documentsLocation(), QFileDialog::ShowDirsOnly); - if (QFileInfo(GTAV_Folder).exists()) - { + const QString GTAV_Folder = QFileDialog::getExistingDirectory(this, UserInterface::tr("Select GTA V Folder..."), StandardPaths::documentsLocation(), QFileDialog::ShowDirsOnly); + if (QDir(GTAV_Folder).exists()) { ui->txtFolder->setText(GTAV_Folder); } } @@ -642,25 +712,14 @@ void OptionsDialog::on_cmdExploreFolder_clicked() void OptionsDialog::on_cbDefaultStyle_toggled(bool checked) { ui->cbStyleList->setDisabled(checked); + ui->labStyle->setDisabled(checked); } -void OptionsDialog::on_cmdUserFeedbackSend_clicked() +void OptionsDialog::on_cbDefaultFont_toggled(bool checked) { -#ifdef GTA5SYNC_TELEMETRY - if (ui->txtUserFeedback->toPlainText().length() < 1024 && ui->txtUserFeedback->toPlainText().length() >= 3) - { - QJsonDocument feedback; - QJsonObject feedbackObject; - feedbackObject["Message"] = ui->txtUserFeedback->toPlainText(); - feedback.setObject(feedbackObject); - Telemetry->push(TelemetryCategory::UserFeedback, feedback); - ui->txtUserFeedback->setPlainText(QString()); - } - else - { - QMessageBox::information(this, tr("User Feedback"), tr("A feedback message have to between 3-1024 characters long")); - } -#endif + ui->cbFont->setDisabled(checked); + ui->cmdFont->setDisabled(checked); + ui->labFont->setDisabled(checked); } void OptionsDialog::on_cmdCopyStatsID_clicked() @@ -669,3 +728,18 @@ void OptionsDialog::on_cmdCopyStatsID_clicked() QApplication::clipboard()->setText(Telemetry->getRegisteredID()); #endif } + +void OptionsDialog::on_cbFont_currentFontChanged(const QFont &font) +{ + ui->cbFont->setFont(font); +} + +void OptionsDialog::on_cmdFont_clicked() +{ + bool ok; + const QFont font = QFontDialog::getFont(&ok, ui->cbFont->currentFont(), this); + if (ok) { + ui->cbFont->setCurrentFont(font); + ui->cbFont->setFont(font); + } +} diff --git a/OptionsDialog.h b/OptionsDialog.h index ae086ba..f53854e 100644 --- a/OptionsDialog.h +++ b/OptionsDialog.h @@ -1,5 +1,5 @@ /****************************************************************************** -* gta5sync GRAND THEFT AUTO V SYNC +* gta5view Grand Theft Auto V Profile Viewer * Copyright (C) 2016-2017 Syping * * This program is free software: you can redistribute it and/or modify @@ -47,8 +47,10 @@ private slots: void on_cbIgnoreAspectRatio_toggled(bool checked); void on_cmdExploreFolder_clicked(); void on_cbDefaultStyle_toggled(bool checked); - void on_cmdUserFeedbackSend_clicked(); + void on_cbDefaultFont_toggled(bool checked); void on_cmdCopyStatsID_clicked(); + void on_cbFont_currentFontChanged(const QFont &font); + void on_cmdFont_clicked(); signals: void settingsApplied(int contentMode, bool languageChanged); @@ -80,6 +82,7 @@ private: void setupInterfaceSettings(); void setupStatisticsSettings(); void setupSnapmaticPictureViewer(); + void setupWindowsGameSettings(); void applySettings(); }; diff --git a/OptionsDialog.ui b/OptionsDialog.ui index 40d8caa..6c5e80b 100644 --- a/OptionsDialog.ui +++ b/OptionsDialog.ui @@ -7,7 +7,7 @@ 0 0 435 - 474 + 524 @@ -20,7 +20,7 @@ - 3 + 0 @@ -34,9 +34,9 @@ - + - Open with Singleclick + %1 1.9+ true @@ -44,16 +44,16 @@ - + - Open with Doubleclick + %1 1.0-1.8 - + - Select with Singleclick + Open with Doubleclick @@ -382,6 +382,72 @@
+ + + Game + + + + + + Social Club Version + + + + + + Found: %1 + + + + + + + Language: %1 + + + + + + + + + + Steam Version + + + + + + Found: %1 + + + + + + + Language: %1 + + + + + + + + + + Qt::Vertical + + + + 0 + 0 + + + + + + Feedback @@ -420,11 +486,11 @@
- + Categories - + @@ -458,6 +524,13 @@ + + + + Personal Usage Data + + + @@ -466,74 +539,32 @@ Other - + - - - - 0 - 0 - - - - Participation ID: %1 - - - true - - - Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse - - - - - - - &Copy - - - false - - - - - -
- - - - User Feedback - - - - - - - + - + + + + 0 + 0 + + - Limit: 1 message/day + Participation ID: %1 + + + true + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse - - - Qt::Horizontal - - - - 0 - 0 - - - - - - + - &Send + &Copy false @@ -629,6 +660,9 @@ + + false + Style: @@ -659,12 +693,52 @@ - + - Always use Message Font (Windows 2003 and earlier) + Use Default Font (Restart) + + + true + + + + + + false + + + Font: + + + + + + + false + + + + 0 + 0 + + + + + + + + false + + + ... + + + + + @@ -683,26 +757,6 @@ - - - Sync - - - - - - Sync is not implemented at current time - - - Qt::AlignCenter - - - true - - - - - diff --git a/PictureDialog.cpp b/PictureDialog.cpp index 0ba79e9..99850f8 100644 --- a/PictureDialog.cpp +++ b/PictureDialog.cpp @@ -1,6 +1,6 @@ /***************************************************************************** -* gta5sync GRAND THEFT AUTO V SYNC -* Copyright (C) 2016-2018 Syping +* gta5view Grand Theft Auto V Profile Viewer +* Copyright (C) 2016-2021 Syping * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -22,28 +22,36 @@ #include "ui_PictureDialog.h" #include "SidebarGenerator.h" #include "MapLocationDialog.h" -#include "ImageEditorDialog.h" #include "JsonEditorDialog.h" #include "SnapmaticEditor.h" #include "StandardPaths.h" #include "PictureExport.h" +#include "ImportDialog.h" #include "StringParser.h" #include "GlobalString.h" #include "UiModLabel.h" #include "AppEnv.h" +#include "config.h" -#ifdef GTA5SYNC_WIN -#if QT_VERSION >= 0x050200 -#include -#include +#if QT_VERSION < 0x060000 +#include #endif + +#ifdef Q_OS_WIN +#if QT_VERSION >= 0x050000 +#include "dwmapi.h" +#endif +#endif + +#ifdef Q_OS_MAC +#include #endif #include -#include #include #include #include +#include #include #include #include @@ -58,6 +66,7 @@ #include #include #include +#include #include #include #include @@ -66,6 +75,10 @@ #include #include +#ifdef GTA5SYNC_TELEMETRY +#include "TelemetryClass.h" +#endif + // Macros for better Overview + RAM #define locX QString::number(picture->getSnapmaticProperties().location.x) #define locY QString::number(picture->getSnapmaticProperties().location.y) @@ -75,7 +88,11 @@ #define picPath picture->getPictureFilePath() #define picTitl StringParser::escapeString(picture->getPictureTitle()) #define plyrsList picture->getSnapmaticProperties().playersList +#if QT_VERSION >= 0x060000 +#define created QLocale().toString(picture->getSnapmaticProperties().createdDateTime, QLocale::ShortFormat) +#else #define created picture->getSnapmaticProperties().createdDateTime.toString(Qt::DefaultLocaleShortDate) +#endif PictureDialog::PictureDialog(ProfileDatabase *profileDB, CrewDatabase *crewDB, QWidget *parent) : QDialog(parent), profileDB(profileDB), crewDB(crewDB), @@ -110,10 +127,20 @@ PictureDialog::PictureDialog(bool primaryWindow, ProfileDatabase *profileDB, Cre void PictureDialog::setupPictureDialog() { // Set Window Flags +#if QT_VERSION >= 0x050900 + setWindowFlag(Qt::WindowContextHelpButtonHint, false); + setWindowFlag(Qt::CustomizeWindowHint, true); +#else setWindowFlags(windowFlags()^Qt::WindowContextHelpButtonHint^Qt::CustomizeWindowHint); +#endif #ifdef Q_OS_LINUX - // for stupid Window Manager (GNOME 3 should feel triggered) + // for stupid Window Manager like GNOME +#if QT_VERSION >= 0x050900 + setWindowFlag(Qt::Dialog, false); + setWindowFlag(Qt::Window, true); +#else setWindowFlags(windowFlags()^Qt::Dialog^Qt::Window); +#endif #endif // Setup User Interface @@ -129,20 +156,27 @@ void PictureDialog::setupPictureDialog() smpic = nullptr; crewStr = ""; + // Get Snapmatic Resolution + const QSize snapmaticResolution = SnapmaticPicture::getSnapmaticResolution(); + // Avatar area qreal screenRatio = AppEnv::screenRatio(); - if (screenRatio != 1) - { - avatarAreaPicture = QImage(":/img/avatararea.png").scaledToHeight(536 * screenRatio, Qt::FastTransformation); + qreal screenRatioPR = AppEnv::screenRatioPR(); + if (screenRatio != 1 || screenRatioPR != 1) { + avatarAreaPicture = QImage(AppEnv::getImagesFolder() % "/avatararea.png").scaledToHeight(snapmaticResolution.height() * screenRatio * screenRatioPR, Qt::FastTransformation); } - else - { - avatarAreaPicture = QImage(":/img/avatararea.png"); + else { + avatarAreaPicture = QImage(AppEnv::getImagesFolder() % "/avatararea.png"); } avatarLocX = 145; avatarLocY = 66; avatarSize = 470; + // DPI calculation (picture) + ui->labPicture->setFixedSize(snapmaticResolution.width() * screenRatio, snapmaticResolution.height() * screenRatio); + ui->labPicture->setFocusPolicy(Qt::StrongFocus); + ui->labPicture->setScaledContents(true); + // Overlay area renderOverlayPicture(); overlayEnabled = true; @@ -163,48 +197,30 @@ void PictureDialog::setupPictureDialog() // Global map globalMap = GlobalString::getGlobalMap(); - // Event connects - connect(ui->labJSON, SIGNAL(resized(QSize)), this, SLOT(adaptNewDialogSize(QSize))); - // Set Icon for Close Button - if (QIcon::hasThemeIcon("dialog-close")) - { + if (QIcon::hasThemeIcon("dialog-close")) { ui->cmdClose->setIcon(QIcon::fromTheme("dialog-close")); } - else if (QIcon::hasThemeIcon("gtk-close")) - { + else if (QIcon::hasThemeIcon("gtk-close")) { ui->cmdClose->setIcon(QIcon::fromTheme("gtk-close")); } installEventFilter(this); - installEventFilter(ui->labPicture); - ui->labPicture->setFixedSize(960 * screenRatio, 536 * screenRatio); - ui->labPicture->setFocusPolicy(Qt::StrongFocus); + + // DPI calculation + ui->hlButtons->setSpacing(6 * screenRatio); + ui->vlButtons->setSpacing(6 * screenRatio); + ui->vlButtons->setContentsMargins(0, 0, 5 * screenRatio, 5 * screenRatio); + ui->jsonLayout->setContentsMargins(4 * screenRatio, 10 * screenRatio, 4 * screenRatio, 4 * screenRatio); // Pre-adapt window for DPI - setFixedWidth(960 * screenRatio); - setFixedHeight(536 * screenRatio); + const QSize windowSize(snapmaticResolution.width() * screenRatio, snapmaticResolution.height() * screenRatio); + setMinimumSize(windowSize); + setMaximumSize(windowSize); } PictureDialog::~PictureDialog() { -#ifdef GTA5SYNC_WIN -#if QT_VERSION >= 0x050200 - if (naviEnabled) - { - for (QObject *obj : layout()->menuBar()->children()) - { - delete obj; - } - delete layout()->menuBar(); - } -#endif -#endif - for (QObject *obj : manageMenu->children()) - { - delete obj; - } - delete manageMenu; delete ui; } @@ -212,194 +228,99 @@ void PictureDialog::closeEvent(QCloseEvent *ev) { Q_UNUSED(ev) if (primaryWindow) - { emit endDatabaseThread(); - } } void PictureDialog::addPreviousNextButtons() { - // Windows Vista additions -#ifdef GTA5SYNC_WIN -#if QT_VERSION >= 0x050200 QToolBar *uiToolbar = new QToolBar("Picture Toolbar", this); +#if QT_VERSION < 0x050600 + qreal screenRatio = AppEnv::screenRatio(); + if (screenRatio != 1) { + QSize iconSize = uiToolbar->iconSize(); + uiToolbar->setIconSize(QSize(iconSize.width() * screenRatio, iconSize.height() * screenRatio)); + } +#endif uiToolbar->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); - uiToolbar->setObjectName("uiToolbar"); - uiToolbar->addAction(QIcon(":/img/back.png"), "", this, SLOT(previousPictureRequestedSlot())); - uiToolbar->addAction(QIcon(":/img/next.png"), "", this, SLOT(nextPictureRequestedSlot())); + uiToolbar->setObjectName("UiToolbar"); + uiToolbar->addAction(QIcon(AppEnv::getImagesFolder() % "/back.svgz"), "", this, SLOT(previousPictureRequestedSlot())); + uiToolbar->addAction(QIcon(AppEnv::getImagesFolder() % "/next.svgz"), "", this, SLOT(nextPictureRequestedSlot())); +#ifdef Q_OS_MAC +#if QT_VERSION >= 0x050000 + uiToolbar->setStyle(QStyleFactory::create("Fusion")); +#endif +#endif layout()->setMenuBar(uiToolbar); - naviEnabled = true; -#endif -#endif } -#ifdef GTA5SYNC_WIN -#if QT_VERSION >= 0x050200 -#ifdef GTA5SYNC_APV -bool PictureDialog::nativeEvent(const QByteArray &eventType, void *message, long *result) +void PictureDialog::adaptDialogSize() { - *result = 0; - MSG *msg = static_cast(message); - LRESULT lRet = 0; - - if (naviEnabled && QtWin::isCompositionEnabled()) - { - if (msg->message == WM_NCCALCSIZE && msg->wParam == TRUE) - { - NCCALCSIZE_PARAMS *pncsp = reinterpret_cast(msg->lParam); - - int sideBorderSize = ((frameSize().width() - size().width()) / 2); -#ifdef GTA5SYNC_APV_SIDE - int buttomBorderSize = sideBorderSize; -#else - int buttomBorderSize = (frameSize().height() - size().height()); -#endif - pncsp->rgrc[0].left += sideBorderSize; - pncsp->rgrc[0].right -= sideBorderSize; - pncsp->rgrc[0].bottom -= buttomBorderSize; - } - else if (msg->message == WM_NCHITTEST) - { - int CLOSE_BUTTON_ID = 20; - lRet = HitTestNCA(msg->hwnd, msg->lParam); - DwmDefWindowProc(msg->hwnd, msg->message, msg->wParam, msg->lParam, &lRet); - *result = lRet; - if (lRet != CLOSE_BUTTON_ID) { return QWidget::nativeEvent(eventType, message, result); } - } - else - { - return QWidget::nativeEvent(eventType, message, result); - } - } - else - { - return QWidget::nativeEvent(eventType, message, result); - } - return true; -} - -LRESULT PictureDialog::HitTestNCA(HWND hWnd, LPARAM lParam) -{ - int LEFTEXTENDWIDTH = 0; - int RIGHTEXTENDWIDTH = 0; - int BOTTOMEXTENDWIDTH = 0; - int TOPEXTENDWIDTH = layout()->menuBar()->height(); - - POINT ptMouse = {(int)(short)LOWORD(lParam), (int)(short)HIWORD(lParam)}; - - RECT rcWindow; - GetWindowRect(hWnd, &rcWindow); - - RECT rcFrame = {}; - AdjustWindowRectEx(&rcFrame, WS_OVERLAPPEDWINDOW & ~WS_CAPTION, FALSE, NULL); - - USHORT uRow = 1; - USHORT uCol = 1; - bool fOnResizeBorder = false; - - if (ptMouse.y >= rcWindow.top && ptMouse.y < rcWindow.top + TOPEXTENDWIDTH) - { - fOnResizeBorder = (ptMouse.y < (rcWindow.top - rcFrame.top)); - uRow = 0; - } - else if (ptMouse.y < rcWindow.bottom && ptMouse.y >= rcWindow.bottom - BOTTOMEXTENDWIDTH) - { - uRow = 2; - } - - if (ptMouse.x >= rcWindow.left && ptMouse.x < rcWindow.left + LEFTEXTENDWIDTH) - { - uCol = 0; - } - else if (ptMouse.x < rcWindow.right && ptMouse.x >= rcWindow.right - RIGHTEXTENDWIDTH) - { - uCol = 2; - } - - LRESULT hitTests[3][3] = - { - { HTTOPLEFT, fOnResizeBorder ? HTTOP : HTCAPTION, HTTOPRIGHT }, - { HTLEFT, HTNOWHERE, HTRIGHT }, - { HTBOTTOMLEFT, HTBOTTOM, HTBOTTOMRIGHT }, - }; - - return hitTests[uRow][uCol]; -} - -void PictureDialog::resizeEvent(QResizeEvent *event) -{ - Q_UNUSED(event) - // int newDialogHeight = ui->labPicture->pixmap()->height(); - // newDialogHeight = newDialogHeight + ui->jsonFrame->height(); - // if (naviEnabled) newDialogHeight = newDialogHeight + layout()->menuBar()->height(); - // int buttomBorderSize = (frameSize().height() - size().height()); - // int sideBorderSize = ((frameSize().width() - size().width()) / 2); - // int brokenDialogHeight = newDialogHeight + (buttomBorderSize - sideBorderSize); - // if (event->size().height() == brokenDialogHeight) - // { - // qDebug() << "BROKEN 1"; - // setGeometry(geometry().x(), geometry().y(), width(), newDialogHeight); - // qDebug() << "BROKEN 2"; - // event->ignore(); - // } -} -#endif -#endif -#endif - -void PictureDialog::adaptNewDialogSize(QSize newLabelSize) -{ - Q_UNUSED(newLabelSize) - int newDialogHeight = ui->labPicture->pixmap()->height(); - newDialogHeight = newDialogHeight + ui->jsonFrame->height(); - if (naviEnabled) newDialogHeight = newDialogHeight + layout()->menuBar()->height(); - setMaximumSize(width(), newDialogHeight); - setMinimumSize(width(), newDialogHeight); - setFixedHeight(newDialogHeight); - ui->labPicture->updateGeometry(); - ui->jsonFrame->updateGeometry(); - updateGeometry(); -} - -void PictureDialog::stylizeDialog() -{ -#ifdef GTA5SYNC_WIN -#if QT_VERSION >= 0x050200 - if (QtWin::isCompositionEnabled()) - { - QPalette palette; - QtWin::extendFrameIntoClientArea(this, 0, this->layout()->menuBar()->height(), 0, 0); - ui->jsonFrame->setStyleSheet(QString("QFrame { background: %1; }").arg(palette.window().color().name())); - setStyleSheet("PictureDialog { background: transparent; }"); - } - else - { - QPalette palette; - QtWin::resetExtendedFrame(this); - ui->jsonFrame->setStyleSheet(QString("QFrame { background: %1; }").arg(palette.window().color().name())); - setStyleSheet(QString("PictureDialog { background: %1; }").arg(QtWin::realColorizationColor().name())); - } -#endif -#endif -} - -bool PictureDialog::event(QEvent *event) -{ -#ifdef GTA5SYNC_WIN -#if QT_VERSION >= 0x050200 + int newDialogHeight = (SnapmaticPicture::getSnapmaticResolution().height() * AppEnv::screenRatio()) + ui->jsonFrame->heightForWidth(width()); if (naviEnabled) - { - if (event->type() == QWinEvent::CompositionChange || event->type() == QWinEvent::ColorizationChange) - { - stylizeDialog(); + newDialogHeight = newDialogHeight + layout()->menuBar()->height(); + const QSize windowSize(width(), newDialogHeight); + setMinimumSize(windowSize); + setMaximumSize(windowSize); +} + +void PictureDialog::styliseDialog() +{ +#ifdef Q_OS_WIN + BOOL isEnabled; + DwmIsCompositionEnabled(&isEnabled); + if (isEnabled == TRUE) { + MARGINS margins = {0, 0, qRound(layout()->menuBar()->height() * AppEnv::screenRatioPR()), 0}; + HRESULT hr = S_OK; + hr = DwmExtendFrameIntoClientArea(reinterpret_cast(winId()), &margins); + if (SUCCEEDED(hr)) { + setStyleSheet("PictureDialog{background:transparent}"); } } + else { + MARGINS margins = {0, 0, 0, 0}; + DwmExtendFrameIntoClientArea(reinterpret_cast(winId()), &margins); + bool colorOk = false; + QSettings dwmRegistry("HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\DWM", QSettings::NativeFormat); + QRgb color = dwmRegistry.value("ColorizationColor").toUInt(&colorOk); + if (colorOk) { + setStyleSheet(QString("PictureDialog{background:%1}").arg(QColor::fromRgba(color).name())); + } + else { + HRESULT hr = S_OK; + BOOL isOpaqueBlend; + DWORD colorization; + hr = DwmGetColorizationColor(&colorization, &isOpaqueBlend); + if (SUCCEEDED(hr) && isOpaqueBlend == FALSE) { + color = colorization; + setStyleSheet(QString("PictureDialog{background:%1}").arg(QColor::fromRgba(color).name())); + } + else { + setStyleSheet("PictureDialog{background:palette(window)}"); + } + } + } + ui->jsonFrame->setStyleSheet("QFrame{background:palette(window)}"); #endif -#endif - return QDialog::event(event); } +#ifdef Q_OS_WIN +#if QT_VERSION >= 0x050000 +#if QT_VERSION >= 0x060000 +bool PictureDialog::nativeEvent(const QByteArray &eventType, void *message, qintptr *result) +#else +bool PictureDialog::nativeEvent(const QByteArray &eventType, void *message, long *result) +#endif +{ + MSG *msg = reinterpret_cast(message); + if (msg->message == 0x031e || msg->message == 0x0320) { + styliseDialog(); + } + return QWidget::nativeEvent(eventType, message, result); +} +#endif +#endif + void PictureDialog::nextPictureRequestedSlot() { emit nextPictureRequested(); @@ -413,12 +334,10 @@ void PictureDialog::previousPictureRequestedSlot() bool PictureDialog::eventFilter(QObject *obj, QEvent *ev) { bool returnValue = false; - if (obj == this || obj == ui->labPicture) - { - if (ev->type() == QEvent::KeyPress) - { + if (obj == this || obj == ui->labPicture) { + if (ev->type() == QEvent::KeyPress) { QKeyEvent *keyEvent = dynamic_cast(ev); - switch (keyEvent->key()){ + switch (keyEvent->key()) { case Qt::Key_Left: emit previousPictureRequested(); returnValue = true; @@ -428,25 +347,21 @@ bool PictureDialog::eventFilter(QObject *obj, QEvent *ev) returnValue = true; break; case Qt::Key_1: - if (previewMode) - { + if (previewMode) { previewMode = false; renderPicture(); } - else - { + else { previewMode = true; renderPicture(); } break; case Qt::Key_2: - if (overlayEnabled) - { + if (overlayEnabled) { overlayEnabled = false; if (!previewMode) renderPicture(); } - else - { + else { overlayEnabled = true; if (!previewMode) renderPicture(); } @@ -471,41 +386,31 @@ bool PictureDialog::eventFilter(QObject *obj, QEvent *ev) break; } } -#ifdef GTA5SYNC_WIN -#if QT_VERSION >= 0x050200 - if (obj != ui->labPicture && naviEnabled) - { - if (ev->type() == QEvent::MouseButtonPress) - { +#ifdef Q_OS_WIN +#if QT_VERSION >= 0x050000 + if (obj != ui->labPicture && naviEnabled) { + if (ev->type() == QEvent::MouseButtonPress) { QMouseEvent *mouseEvent = dynamic_cast(ev); - if (mouseEvent->pos().y() <= layout()->menuBar()->height()) - { - if (mouseEvent->button() == Qt::LeftButton) - { + if (mouseEvent->pos().y() <= layout()->menuBar()->height()) { + if (mouseEvent->button() == Qt::LeftButton) { dragPosition = mouseEvent->pos(); dragStart = true; } } } - if (ev->type() == QEvent::MouseButtonRelease) - { + if (ev->type() == QEvent::MouseButtonRelease) { QMouseEvent *mouseEvent = dynamic_cast(ev); - if (mouseEvent->pos().y() <= layout()->menuBar()->height()) - { - if (mouseEvent->button() == Qt::LeftButton) - { + if (mouseEvent->pos().y() <= layout()->menuBar()->height()) { + if (mouseEvent->button() == Qt::LeftButton) { dragStart = false; } } } - if (ev->type() == QEvent::MouseMove && dragStart) - { + if (dragStart && ev->type() == QEvent::MouseMove) { QMouseEvent *mouseEvent = dynamic_cast(ev); - if (mouseEvent->pos().y() <= layout()->menuBar()->height()) - { - if (mouseEvent->buttons() & Qt::LeftButton) - { - QPoint diff = mouseEvent->pos() - dragPosition; + if (mouseEvent->pos().y() <= layout()->menuBar()->height()) { + if (mouseEvent->buttons() & Qt::LeftButton) { + const QPoint diff = mouseEvent->pos() - dragPosition; move(QPoint(pos() + diff)); updateGeometry(); } @@ -553,24 +458,23 @@ void PictureDialog::renderOverlayPicture() { // Generating Overlay Preview qreal screenRatio = AppEnv::screenRatio(); - QRect preferedRect = QRect(0, 0, 200 * screenRatio, 160 * screenRatio); + qreal screenRatioPR = AppEnv::screenRatioPR(); + QRect preferedRect = QRect(0, 0, 200 * screenRatio * screenRatioPR, 160 * screenRatio * screenRatioPR); QString overlayText = tr("Key 1 - Avatar Preview Mode\nKey 2 - Toggle Overlay\nArrow Keys - Navigate"); QFont overlayPainterFont; - overlayPainterFont.setPixelSize(12 * screenRatio); + overlayPainterFont.setPixelSize(12 * screenRatio * screenRatioPR); QFontMetrics fontMetrics(overlayPainterFont); QRect overlaySpace = fontMetrics.boundingRect(preferedRect, Qt::AlignLeft | Qt::AlignTop | Qt::TextDontClip | Qt::TextWordWrap, overlayText); int hOverlay = Qt::AlignTop; - if (overlaySpace.height() < 74 * screenRatio) - { + if (overlaySpace.height() < 74 * screenRatio * screenRatioPR) { hOverlay = Qt::AlignVCenter; - preferedRect.setHeight(71 * screenRatio); - overlaySpace.setHeight(80 * screenRatio); + preferedRect.setHeight(71 * screenRatio * screenRatioPR); + overlaySpace.setHeight(80 * screenRatio * screenRatioPR); } - else - { - overlaySpace.setHeight(overlaySpace.height() + 6 * screenRatio); + else { + overlaySpace.setHeight(overlaySpace.height() + 6 * screenRatio * screenRatioPR); } QImage overlayImage(overlaySpace.size(), QImage::Format_ARGB32_Premultiplied); @@ -582,13 +486,11 @@ void PictureDialog::renderOverlayPicture() overlayPainter.drawText(preferedRect, Qt::AlignLeft | hOverlay | Qt::TextDontClip | Qt::TextWordWrap, overlayText); overlayPainter.end(); - if (overlaySpace.width() < 194 * screenRatio) - { - overlaySpace.setWidth(200 * screenRatio); + if (overlaySpace.width() < 194 * screenRatio * screenRatioPR) { + overlaySpace.setWidth(200 * screenRatio * screenRatioPR); } - else - { - overlaySpace.setWidth(overlaySpace.width() + 6 * screenRatio); + else { + overlaySpace.setWidth(overlaySpace.width() + 6 * screenRatio * screenRatioPR); } QImage overlayBorderImage(overlaySpace.width(), overlaySpace.height(), QImage::Format_ARGB6666_Premultiplied); @@ -598,14 +500,13 @@ void PictureDialog::renderOverlayPicture() overlayTempImage.fill(Qt::transparent); QPainter overlayTempPainter(&overlayTempImage); overlayTempPainter.drawImage(0, 0, overlayBorderImage); - overlayTempPainter.drawImage(3 * screenRatio, 3 * screenRatio, overlayImage); + overlayTempPainter.drawImage(3 * screenRatio * screenRatioPR, 3 * screenRatio * screenRatioPR, overlayImage); overlayTempPainter.end(); } void PictureDialog::setSnapmaticPicture(SnapmaticPicture *picture, bool readOk, bool _indexed, int _index) { - if (smpic != nullptr) - { + if (smpic != nullptr) { QObject::disconnect(smpic, SIGNAL(updated()), this, SLOT(updated())); QObject::disconnect(smpic, SIGNAL(customSignal(QString)), this, SLOT(customSignal(QString))); } @@ -613,35 +514,30 @@ void PictureDialog::setSnapmaticPicture(SnapmaticPicture *picture, bool readOk, indexed = _indexed; index = _index; smpic = picture; - if (!readOk) - { + if (!readOk) { QMessageBox::warning(this, tr("Snapmatic Picture Viewer"), tr("Failed at %1").arg(picture->getLastStep())); return; } - if (picture->isPicOk()) - { + if (picture->isPicOk()) { snapmaticPicture = picture->getImage(); renderPicture(); ui->cmdManage->setEnabled(true); } - if (picture->isJsonOk()) - { + if (picture->isJsonOk()) { crewStr = crewDB->getCrewName(crewID); - if (globalMap.contains(picArea)) - { - picAreaStr = globalMap[picArea]; + if (globalMap.contains(picArea)) { + picAreaStr = globalMap.value(picArea); } - else - { + else { picAreaStr = picArea; } setWindowTitle(windowTitleStr.arg(picTitl)); ui->labJSON->setText(jsonDrawString.arg(locX, locY, locZ, generatePlayersString(), generateCrewString(), picTitl, picAreaStr, created)); + QTimer::singleShot(0, this, SLOT(adaptDialogSize())); } - else - { + else { ui->labJSON->setText(jsonDrawString.arg("0", "0", "0", tr("No Players"), tr("No Crew"), tr("Unknown Location"))); - QMessageBox::warning(this,tr("Snapmatic Picture Viewer"),tr("Failed at %1").arg(picture->getLastStep())); + QTimer::singleShot(0, this, SLOT(adaptDialogSize())); } QObject::connect(smpic, SIGNAL(updated()), this, SLOT(updated())); QObject::connect(smpic, SIGNAL(customSignal(QString)), this, SLOT(customSignal(QString))); @@ -670,95 +566,72 @@ void PictureDialog::setSnapmaticPicture(SnapmaticPicture *picture) void PictureDialog::renderPicture() { - qreal screenRatio = AppEnv::screenRatio(); - if (!previewMode) - { - if (overlayEnabled) - { - QPixmap shownImagePixmap(960 * screenRatio, 536 * screenRatio); - shownImagePixmap.fill(Qt::transparent); - QPainter shownImagePainter(&shownImagePixmap); - if (screenRatio == 1) - { - shownImagePainter.drawImage(0, 0, snapmaticPicture); - shownImagePainter.drawImage(3 * screenRatio, 3 * screenRatio, overlayTempImage); - } - else - { - shownImagePainter.drawImage(0, 0, snapmaticPicture.scaledToHeight(536 * screenRatio, Qt::SmoothTransformation)); - shownImagePainter.drawImage(3 * screenRatio, 3 * screenRatio, overlayTempImage); - } - shownImagePainter.end(); - ui->labPicture->setPixmap(shownImagePixmap); - } - else - { - if (screenRatio != 1) - { - QPixmap shownImagePixmap(960 * screenRatio, 536 * screenRatio); - shownImagePixmap.fill(Qt::transparent); - QPainter shownImagePainter(&shownImagePixmap); - shownImagePainter.drawImage(0, 0, snapmaticPicture.scaledToHeight(536 * screenRatio, Qt::SmoothTransformation)); - shownImagePainter.end(); - ui->labPicture->setPixmap(shownImagePixmap); - } - else - { - ui->labPicture->setPixmap(QPixmap::fromImage(snapmaticPicture)); - } - } + const qreal screenRatio = AppEnv::screenRatio(); + const qreal screenRatioPR = AppEnv::screenRatioPR(); + const QSize snapmaticResolution(SnapmaticPicture::getSnapmaticResolution()); + const QSize renderResolution(snapmaticResolution.width() * screenRatio * screenRatioPR, snapmaticResolution.height() * screenRatio * screenRatioPR); + QPixmap shownImagePixmap(renderResolution); + shownImagePixmap.fill(Qt::black); + QPainter shownImagePainter(&shownImagePixmap); + const QImage renderImage = snapmaticPicture.scaled(renderResolution, Qt::KeepAspectRatio, Qt::SmoothTransformation); + if (renderImage.width() < renderResolution.width()) { + shownImagePainter.drawImage((renderResolution.width() - renderImage.width()) / 2, 0, renderImage, Qt::AutoColor); } - else - { - // Generating Avatar Preview - QPixmap avatarPixmap(960 * screenRatio, 536 * screenRatio); - QPainter snapPainter(&avatarPixmap); - QFont snapPainterFont; - snapPainterFont.setPixelSize(12 * screenRatio); - if (screenRatio == 1) - { - snapPainter.drawImage(0, 0, snapmaticPicture); - } - else - { - snapPainter.drawImage(0, 0, snapmaticPicture.scaledToHeight(536 * screenRatio, Qt::SmoothTransformation)); - } - snapPainter.drawImage(0, 0, avatarAreaPicture); - snapPainter.setPen(QColor::fromRgb(255, 255, 255, 255)); - snapPainter.setFont(snapPainterFont); - snapPainter.drawText(QRect(3 * screenRatio, 3 * screenRatio, 140 * screenRatio, 536 * screenRatio), Qt::AlignLeft | Qt::TextWordWrap, tr("Avatar Preview Mode\nPress 1 for Default View")); - snapPainter.end(); - ui->labPicture->setPixmap(avatarPixmap); + else if (renderImage.height() < renderResolution.height()) { + shownImagePainter.drawImage(0, (renderResolution.height() - renderImage.height()) / 2, renderImage, Qt::AutoColor); } + else { + shownImagePainter.drawImage(0, 0, renderImage, Qt::AutoColor); + } + if (previewMode) { + QFont shownImagePainterFont; + shownImagePainterFont.setPixelSize(12 * screenRatio * screenRatioPR); + shownImagePainter.drawImage(0, 0, avatarAreaPicture); + shownImagePainter.setPen(QColor::fromRgb(255, 255, 255, 255)); + shownImagePainter.setFont(shownImagePainterFont); + shownImagePainter.drawText(QRect(3 * screenRatio * screenRatioPR, 3 * screenRatio * screenRatioPR, 140 * screenRatio * screenRatioPR, snapmaticResolution.height() * screenRatio * screenRatioPR), Qt::AlignLeft | Qt::TextWordWrap, tr("Avatar Preview Mode\nPress 1 for Default View")); + } + else if (overlayEnabled) { + shownImagePainter.drawImage(3 * screenRatio * screenRatioPR, 3 * screenRatio * screenRatioPR, overlayTempImage, Qt::AutoColor); + } + shownImagePainter.end(); +#if QT_VERSION >= 0x050600 + shownImagePixmap.setDevicePixelRatio(screenRatioPR); +#endif + ui->labPicture->setPixmap(shownImagePixmap); } void PictureDialog::crewNameUpdated() { SnapmaticPicture *picture = smpic; // used by macro QString crewIDStr = crewID; - if (crewIDStr == crewStr) - { + if (crewIDStr == crewStr) { crewStr = crewDB->getCrewName(crewIDStr); ui->labJSON->setText(jsonDrawString.arg(locX, locY, locZ, generatePlayersString(), generateCrewString(), picTitl, picAreaStr, created)); + QTimer::singleShot(0, this, SLOT(adaptDialogSize())); } } void PictureDialog::playerNameUpdated() { SnapmaticPicture *picture = smpic; // used by macro - if (plyrsList.count() >= 1) - { + if (plyrsList.count() >= 1) { ui->labJSON->setText(jsonDrawString.arg(locX, locY, locZ, generatePlayersString(), generateCrewString(), picTitl, picAreaStr, created)); + QTimer::singleShot(0, this, SLOT(adaptDialogSize())); } } QString PictureDialog::generateCrewString() { SnapmaticPicture *picture = smpic; // used by macro - QString crewIDStr = crewID; // save operation time - if (crewIDStr != "0" && !crewIDStr.isEmpty()) - { - return QString("" % crewStr % ""); + const QString crewIDStr = crewID; // save operation time + if (crewIDStr != "0" && !crewIDStr.isEmpty()) { + if (crewIDStr != crewStr) { + return QString("" % crewStr % ""); + } + else { + return QString(crewIDStr); + } } return tr("No Crew"); } @@ -768,18 +641,19 @@ QString PictureDialog::generatePlayersString() SnapmaticPicture *picture = smpic; // used by macro const QStringList playersList = plyrsList; // save operation time QString plyrsStr; - if (playersList.length() >= 1) - { - for (QString player : playersList) - { - QString playerName; - playerName = profileDB->getPlayerName(player); - plyrsStr += ", " % playerName % ""; + if (playersList.length() >= 1) { + for (const QString &player : playersList) { + const QString playerName = profileDB->getPlayerName(player); + if (player != playerName) { + plyrsStr += ", " % playerName % ""; + } + else { + plyrsStr += ", " % player; + } } plyrsStr.remove(0, 2); } - else - { + else { plyrsStr = tr("No Players"); } return plyrsStr; @@ -787,43 +661,45 @@ QString PictureDialog::generatePlayersString() void PictureDialog::exportSnapmaticPicture() { - if (rqFullscreen && fullscreenWidget != nullptr) - { + if (rqFullscreen && fullscreenWidget != nullptr) { PictureExport::exportAsPicture(fullscreenWidget, smpic); } - else - { + else { PictureExport::exportAsPicture(this, smpic); } } void PictureDialog::copySnapmaticPicture() { - if (rqFullscreen && fullscreenWidget != nullptr) - { + if (rqFullscreen && fullscreenWidget != nullptr) { PictureExport::exportAsSnapmatic(fullscreenWidget, smpic); } - else - { + else { PictureExport::exportAsSnapmatic(this, smpic); } } void PictureDialog::on_labPicture_mouseDoubleClicked(Qt::MouseButton button) { - if (button == Qt::LeftButton) - { + if (button == Qt::LeftButton) { +#if QT_VERSION >= 0x060000 + QRect desktopRect = QApplication::screenAt(pos())->geometry(); +#else QRect desktopRect = QApplication::desktop()->screenGeometry(this); +#endif PictureWidget *pictureWidget = new PictureWidget(this); // Work! pictureWidget->setObjectName("PictureWidget"); -#if QT_VERSION >= 0x050600 - pictureWidget->setWindowFlags(pictureWidget->windowFlags()^Qt::FramelessWindowHint^Qt::WindowStaysOnTopHint^Qt::MaximizeUsingFullscreenGeometryHint); +#if QT_VERSION >= 0x050900 + pictureWidget->setWindowFlag(Qt::FramelessWindowHint, true); + pictureWidget->setWindowFlag(Qt::MaximizeUsingFullscreenGeometryHint, true); +#elif QT_VERSION >= 0x050600 + pictureWidget->setWindowFlags(pictureWidget->windowFlags()^Qt::FramelessWindowHint^Qt::MaximizeUsingFullscreenGeometryHint); #else - pictureWidget->setWindowFlags(pictureWidget->windowFlags()^Qt::FramelessWindowHint^Qt::WindowStaysOnTopHint); + pictureWidget->setWindowFlags(pictureWidget->windowFlags()^Qt::FramelessWindowHint); #endif pictureWidget->setWindowTitle(windowTitle()); - pictureWidget->setStyleSheet("QLabel#pictureLabel{background-color: black;}"); - pictureWidget->setImage(snapmaticPicture, desktopRect); + pictureWidget->setStyleSheet("QLabel#pictureLabel{background-color:black;}"); + pictureWidget->setImage(smpic->getImage(), desktopRect); pictureWidget->setModal(true); fullscreenWidget = pictureWidget; @@ -833,11 +709,6 @@ void PictureDialog::on_labPicture_mouseDoubleClicked(Qt::MouseButton button) pictureWidget->move(desktopRect.x(), desktopRect.y()); pictureWidget->resize(desktopRect.width(), desktopRect.height()); -#ifdef GTA5SYNC_WIN -#if QT_VERSION >= 0x050200 - QtWin::markFullscreenWindow(pictureWidget, true); -#endif -#endif pictureWidget->showFullScreen(); pictureWidget->setFocus(); pictureWidget->raise(); @@ -866,15 +737,15 @@ int PictureDialog::getIndex() void PictureDialog::openPreviewMap() { SnapmaticPicture *picture = smpic; + SnapmaticProperties currentProperties = picture->getSnapmaticProperties(); MapLocationDialog *mapLocDialog; - if (rqFullscreen && fullscreenWidget != nullptr) - { - mapLocDialog = new MapLocationDialog(picture->getSnapmaticProperties().location.x, picture->getSnapmaticProperties().location.y, fullscreenWidget); + if (rqFullscreen && fullscreenWidget != nullptr) { + mapLocDialog = new MapLocationDialog(currentProperties.location.x, currentProperties.location.y, fullscreenWidget); } - else - { - mapLocDialog = new MapLocationDialog(picture->getSnapmaticProperties().location.x, picture->getSnapmaticProperties().location.y, this); + else { + mapLocDialog = new MapLocationDialog(currentProperties.location.x, currentProperties.location.y, this); } + mapLocDialog->setCayoPerico(currentProperties.location.isCayoPerico); mapLocDialog->setWindowIcon(windowIcon()); mapLocDialog->setModal(true); #ifndef Q_OS_ANDROID @@ -883,32 +754,47 @@ void PictureDialog::openPreviewMap() mapLocDialog->showMaximized(); #endif mapLocDialog->exec(); - if (mapLocDialog->propUpdated()) - { + if (mapLocDialog->propUpdated()) { // Update Snapmatic Properties - SnapmaticProperties localSpJson = picture->getSnapmaticProperties(); - localSpJson.location.x = mapLocDialog->getXpos(); - localSpJson.location.y = mapLocDialog->getYpos(); - localSpJson.location.z = 0; + currentProperties.location.x = mapLocDialog->getXpos(); + currentProperties.location.y = mapLocDialog->getYpos(); + currentProperties.location.z = 0; // Update Snapmatic Picture QString currentFilePath = picture->getPictureFilePath(); QString originalFilePath = picture->getOriginalPictureFilePath(); QString backupFileName = originalFilePath % ".bak"; - if (!QFile::exists(backupFileName)) - { + if (!QFile::exists(backupFileName)) { QFile::copy(currentFilePath, backupFileName); } SnapmaticProperties fallbackProperties = picture->getSnapmaticProperties(); - picture->setSnapmaticProperties(localSpJson); - if (!picture->exportPicture(currentFilePath)) - { + picture->setSnapmaticProperties(currentProperties); + if (!picture->exportPicture(currentFilePath)) { QMessageBox::warning(this, SnapmaticEditor::tr("Snapmatic Properties"), SnapmaticEditor::tr("Patching of Snapmatic Properties failed because of I/O Error")); picture->setSnapmaticProperties(fallbackProperties); } - else - { + else { updated(); +#ifdef GTA5SYNC_TELEMETRY + QSettings telemetrySettings(GTA5SYNC_APPVENDOR, GTA5SYNC_APPSTR); + telemetrySettings.beginGroup("Telemetry"); + bool pushUsageData = telemetrySettings.value("PushUsageData", false).toBool(); + telemetrySettings.endGroup(); + if (pushUsageData && Telemetry->canPush()) { + QJsonDocument jsonDocument; + QJsonObject jsonObject; + jsonObject["Type"] = "LocationEdited"; + jsonObject["ExtraFlags"] = "Viewer"; + jsonObject["EditedSize"] = QString::number(picture->getContentMaxLength()); +#if QT_VERSION >= 0x060000 + jsonObject["EditedTime"] = QString::number(QDateTime::currentDateTimeUtc().toSecsSinceEpoch()); +#else + jsonObject["EditedTime"] = QString::number(QDateTime::currentDateTimeUtc().toTime_t()); +#endif + jsonDocument.setObject(jsonObject); + Telemetry->push(TelemetryCategory::PersonalData, jsonDocument); + } +#endif } } delete mapLocDialog; @@ -918,12 +804,10 @@ void PictureDialog::editSnapmaticProperties() { SnapmaticPicture *picture = smpic; SnapmaticEditor *snapmaticEditor; - if (rqFullscreen && fullscreenWidget != nullptr) - { + if (rqFullscreen && fullscreenWidget != nullptr) { snapmaticEditor = new SnapmaticEditor(crewDB, profileDB, fullscreenWidget); } - else - { + else { snapmaticEditor = new SnapmaticEditor(crewDB, profileDB, this); } snapmaticEditor->setWindowIcon(windowIcon()); @@ -940,37 +824,72 @@ void PictureDialog::editSnapmaticProperties() void PictureDialog::editSnapmaticImage() { - SnapmaticPicture *picture = smpic; - ImageEditorDialog *imageEditor; - if (rqFullscreen && fullscreenWidget != nullptr) - { - imageEditor = new ImageEditorDialog(picture, profileName, fullscreenWidget); + QImage *currentImage = new QImage(smpic->getImage()); + ImportDialog *importDialog; + if (rqFullscreen && fullscreenWidget != nullptr) { + importDialog = new ImportDialog(profileName, fullscreenWidget); } - else - { - imageEditor = new ImageEditorDialog(picture, profileName, this); + else { + importDialog = new ImportDialog(profileName, this); } - imageEditor->setWindowIcon(windowIcon()); - imageEditor->setModal(true); -#ifndef Q_OS_ANDROID - imageEditor->show(); + importDialog->setWindowIcon(windowIcon()); + importDialog->setImage(currentImage); + importDialog->enableOverwriteMode(); + importDialog->setModal(true); + importDialog->exec(); + if (importDialog->isImportAgreed()) { + const QByteArray previousPicture = smpic->getPictureStream(); + bool success = smpic->setImage(importDialog->image(), importDialog->isUnlimitedBuffer()); + if (success) { + QString currentFilePath = smpic->getPictureFilePath(); + QString originalFilePath = smpic->getOriginalPictureFilePath(); + QString backupFileName = originalFilePath % ".bak"; + if (!QFile::exists(backupFileName)) { + QFile::copy(currentFilePath, backupFileName); + } + if (!smpic->exportPicture(currentFilePath)) { + smpic->setPictureStream(previousPicture); + QMessageBox::warning(this, QApplication::translate("ImageEditorDialog", "Snapmatic Image Editor"), QApplication::translate("ImageEditorDialog", "Patching of Snapmatic Image failed because of I/O Error")); + return; + } + smpic->emitCustomSignal("PictureUpdated"); +#ifdef GTA5SYNC_TELEMETRY + QSettings telemetrySettings(GTA5SYNC_APPVENDOR, GTA5SYNC_APPSTR); + telemetrySettings.beginGroup("Telemetry"); + bool pushUsageData = telemetrySettings.value("PushUsageData", false).toBool(); + telemetrySettings.endGroup(); + if (pushUsageData && Telemetry->canPush()) { + QJsonDocument jsonDocument; + QJsonObject jsonObject; + jsonObject["Type"] = "ImageEdited"; + jsonObject["ExtraFlags"] = "Viewer"; + jsonObject["EditedSize"] = QString::number(smpic->getContentMaxLength()); +#if QT_VERSION >= 0x060000 + jsonObject["EditedTime"] = QString::number(QDateTime::currentDateTimeUtc().toSecsSinceEpoch()); #else - snapmaticEditor->showMaximized(); + jsonObject["EditedTime"] = QString::number(QDateTime::currentDateTimeUtc().toTime_t()); #endif - imageEditor->exec(); - delete imageEditor; + jsonDocument.setObject(jsonObject); + Telemetry->push(TelemetryCategory::PersonalData, jsonDocument); + } +#endif + } + else { + QMessageBox::warning(this, QApplication::translate("ImageEditorDialog", "Snapmatic Image Editor"), QApplication::translate("ImageEditorDialog", "Patching of Snapmatic Image failed because of Image Error")); + return; + } + } + delete importDialog; } void PictureDialog::editSnapmaticRawJson() { SnapmaticPicture *picture = smpic; JsonEditorDialog *jsonEditor; - if (rqFullscreen && fullscreenWidget != nullptr) - { + if (rqFullscreen && fullscreenWidget != nullptr) { jsonEditor = new JsonEditorDialog(picture, fullscreenWidget); } - else - { + else { jsonEditor = new JsonEditorDialog(picture, this); } jsonEditor->setWindowIcon(windowIcon()); @@ -988,23 +907,21 @@ void PictureDialog::updated() { SnapmaticPicture *picture = smpic; // used by macro crewStr = crewDB->getCrewName(crewID); - if (globalMap.contains(picArea)) - { + if (globalMap.contains(picArea)) { picAreaStr = globalMap[picArea]; } - else - { + else { picAreaStr = picArea; } setWindowTitle(windowTitleStr.arg(picTitl)); ui->labJSON->setText(jsonDrawString.arg(locX, locY, locZ, generatePlayersString(), generateCrewString(), picTitl, picAreaStr, created)); + QTimer::singleShot(0, this, SLOT(adaptDialogSize())); } void PictureDialog::customSignal(QString signal) { SnapmaticPicture *picture = smpic; // used by macro - if (signal == "PictureUpdated") - { + if (signal == "PictureUpdated") { snapmaticPicture = picture->getImage(); renderPicture(); } diff --git a/PictureDialog.h b/PictureDialog.h index 0c3155c..a35ebb4 100644 --- a/PictureDialog.h +++ b/PictureDialog.h @@ -1,6 +1,6 @@ /***************************************************************************** -* gta5sync GRAND THEFT AUTO V SYNC -* Copyright (C) 2016-2017 Syping +* gta5view Grand Theft Auto V Profile Viewer +* Copyright (C) 2016-2021 Syping * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -29,14 +29,6 @@ #include #include -#ifdef GTA5SYNC_WIN -#if QT_VERSION >= 0x050200 -#ifdef GTA5SYNC_APV -#include -#endif -#endif -#endif - namespace Ui { class PictureDialog; } @@ -56,17 +48,17 @@ public: void setSnapmaticPicture(SnapmaticPicture *picture, int index); void setSnapmaticPicture(SnapmaticPicture *picture); void addPreviousNextButtons(); - void stylizeDialog(); + void styliseDialog(); bool isIndexed(); int getIndex(); ~PictureDialog(); public slots: + void adaptDialogSize(); void crewNameUpdated(); void playerNameUpdated(); void dialogNextPictureRequested(); void dialogPreviousPictureRequested(); - void adaptNewDialogSize(QSize newLabelSize); void exportCustomContextMenuRequested(const QPoint &pos); private slots: @@ -97,14 +89,11 @@ protected: void closeEvent(QCloseEvent *ev); bool eventFilter(QObject *obj, QEvent *ev); void mousePressEvent(QMouseEvent *ev); - bool event(QEvent *event); -#ifdef GTA5SYNC_WIN -#if QT_VERSION >= 0x050200 -#ifdef GTA5SYNC_APV +#ifdef Q_OS_WIN +#if QT_VERSION >= 0x060000 + bool nativeEvent(const QByteArray &eventType, void *message, qintptr *result); +#elif QT_VERSION >= 0x050000 bool nativeEvent(const QByteArray &eventType, void *message, long *result); - LRESULT HitTestNCA(HWND hWnd, LPARAM lParam); - void resizeEvent(QResizeEvent *event); -#endif #endif #endif @@ -136,8 +125,8 @@ private: int avatarLocY; int avatarSize; QMenu *manageMenu; -#ifdef GTA5SYNC_WIN -#if QT_VERSION >= 0x050200 +#ifdef Q_OS_WIN +#if QT_VERSION >= 0x050000 QPoint dragPosition; bool dragStart; #endif diff --git a/PictureDialog.ui b/PictureDialog.ui index 982c0eb..f324d84 100644 --- a/PictureDialog.ui +++ b/PictureDialog.ui @@ -43,27 +43,11 @@ - - :/img/960x536.png - Qt::AlignCenter - - - - Qt::Vertical - - - - 0 - 0 - - - - @@ -114,10 +98,10 @@ - <span style=" font-weight:600;">Title: </span>%6<br/> -<span style=" font-weight:600;">Location: </span>%7 (%1, %2, %3)<br/> -<span style=" font-weight:600;">Players: </span>%4 (Crew %5)<br/> -<span style=" font-weight:600;">Created: </span>%8 + <span style="font-weight:600">Title: </span>%6<br/> +<span style="font-weight:600">Location: </span>%7 (%1, %2, %3)<br/> +<span style="font-weight:600">Players: </span>%4 (Crew %5)<br/> +<span style="font-weight:600">Created: </span>%8 true @@ -230,9 +214,7 @@ - - - + cmdClose diff --git a/PictureExport.cpp b/PictureExport.cpp index 41d0f0c..8101ddd 100644 --- a/PictureExport.cpp +++ b/PictureExport.cpp @@ -1,6 +1,6 @@ /***************************************************************************** -* gta5sync GRAND THEFT AUTO V SYNC -* Copyright (C) 2016-2017 Syping +* gta5view Grand Theft Auto V Profile Viewer +* Copyright (C) 2016-2020 Syping * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -17,26 +17,29 @@ *****************************************************************************/ #include "config.h" +#include "AppEnv.h" #include "PictureExport.h" #include "PictureDialog.h" #include "StandardPaths.h" #include "SidebarGenerator.h" #include -#include #include #include #include #include -#include #include +#if QT_VERSION < 0x050000 +#include +#endif + #if QT_VERSION >= 0x050000 #include +#include #endif PictureExport::PictureExport() { - } void PictureExport::exportAsPicture(QWidget *parent, SnapmaticPicture *picture) @@ -47,30 +50,25 @@ void PictureExport::exportAsPicture(QWidget *parent, SnapmaticPicture *picture) // Quality Settings settings.beginGroup("Pictures"); int defaultQuality = 100; - QSize defExportSize = QSize(960, 536); + QSize defExportSize = SnapmaticPicture::getSnapmaticResolution(); int customQuality = settings.value("CustomQuality", defaultQuality).toInt(); - if (customQuality < 1 || customQuality > 100) - { + if (customQuality < 1 || customQuality > 100) { customQuality = 100; } bool useCustomQuality = settings.value("CustomQualityEnabled", false).toBool(); // Size Settings QSize cusExportSize = settings.value("CustomSize", defExportSize).toSize(); - if (cusExportSize.width() > 3840) - { + if (cusExportSize.width() > 3840) { cusExportSize.setWidth(3840); } - else if (cusExportSize.height() > 2160) - { + else if (cusExportSize.height() > 2160) { cusExportSize.setHeight(2160); } - if (cusExportSize.width() < 1) - { + if (cusExportSize.width() < 1) { cusExportSize.setWidth(1); } - else if (cusExportSize.height() < 1) - { + else if (cusExportSize.height() < 1) { cusExportSize.setHeight(1); } QString sizeMode = settings.value("ExportSizeMode", "Default").toString(); @@ -109,59 +107,55 @@ fileDialogPreSave: //Work? QString newPictureFileName = getPictureFileName(picture) % defaultExportFormat; fileDialog.selectFile(newPictureFileName); - if (fileDialog.exec()) - { + if (fileDialog.exec()) { QStringList selectedFiles = fileDialog.selectedFiles(); - if (selectedFiles.length() == 1) - { + if (selectedFiles.length() == 1) { QString saveFileFormat; QString selectedFile = selectedFiles.at(0); - if (selectedFile.right(4) == ".jpg") - { + if (selectedFile.right(4) == ".jpg") { saveFileFormat = "JPEG"; } - else if (selectedFile.right(4) == ".jpeg") - { + else if (selectedFile.right(4) == ".jpeg") { saveFileFormat = "JPEG"; } - else if (selectedFile.right(4) == ".png") - { + else if (selectedFile.right(4) == ".png") { saveFileFormat = "PNG"; } - else if (selectedFile.right(7) == ".suffix") - { - if (fileDialog.selectedNameFilter() == "JPEG picture (*.jpg)") - { + else if (selectedFile.right(7) == ".suffix") { + if (fileDialog.selectedNameFilter() == "JPEG picture (*.jpg)") { selectedFile.replace(".suffix", ".jpg"); } - else if (fileDialog.selectedNameFilter() == "Portable Network Graphics (*.png)") - { + else if (fileDialog.selectedNameFilter() == "Portable Network Graphics (*.png)") { selectedFile.replace(".suffix", ".png"); } - else - { + else { selectedFile.replace(".suffix", ".jpg"); } } - if (QFile::exists(selectedFile)) - { - if (QMessageBox::No == QMessageBox::warning(parent, PictureDialog::tr("Export as Picture"), PictureDialog::tr("Overwrite %1 with current Snapmatic picture?").arg("\""+selectedFile+"\""), QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes)) - { + if (QFile::exists(selectedFile)) { + if (QMessageBox::No == QMessageBox::warning(parent, PictureDialog::tr("Export as Picture"), PictureDialog::tr("Overwrite %1 with current Snapmatic picture?").arg("\""+selectedFile+"\""), QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes)) { goto fileDialogPreSave; //Work? } } // Scale Picture QImage exportPicture = picture->getImage(); - if (sizeMode == "Desktop") - { + if (sizeMode == "Desktop") { +#if QT_VERSION >= 0x050000 + qreal screenRatioPR = AppEnv::screenRatioPR(); + QRect desktopResolution = QApplication::primaryScreen()->geometry(); + int desktopSizeWidth = qRound((double)desktopResolution.width() * screenRatioPR); + int desktopSizeHeight = qRound((double)desktopResolution.height() * screenRatioPR); +#else QRect desktopResolution = QApplication::desktop()->screenGeometry(); - exportPicture = exportPicture.scaled(desktopResolution.width(), desktopResolution.height(), aspectRatio, Qt::SmoothTransformation); + int desktopSizeWidth = desktopResolution.width(); + int desktopSizeHeight = desktopResolution.height(); +#endif + exportPicture = exportPicture.scaled(desktopSizeWidth, desktopSizeHeight, aspectRatio, Qt::SmoothTransformation); } - else if (sizeMode == "Custom") - { + else if (sizeMode == "Custom") { exportPicture = exportPicture.scaled(cusExportSize, aspectRatio, Qt::SmoothTransformation); } @@ -172,32 +166,26 @@ fileDialogPreSave: //Work? #else QFile *picFile = new QFile(selectedFile); #endif - if (picFile->open(QIODevice::WriteOnly)) - { + if (picFile->open(QIODevice::WriteOnly)) { isSaved = exportPicture.save(picFile, saveFileFormat.toStdString().c_str(), useCustomQuality ? customQuality : defaultQuality); #if QT_VERSION >= 0x050000 - if (isSaved) - { + if (isSaved) { isSaved = picFile->commit(); } - else - { + else { errorId = 1; } #else picFile->close(); #endif } - else - { + else { errorId = 2; } delete picFile; - if (!isSaved) - { - switch (errorId) - { + if (!isSaved) { + switch (errorId) { case 0: QMessageBox::warning(parent, PictureDialog::tr("Export as Picture"), PictureDialog::tr("Failed to export the picture because the system occurred a write failure")); break; @@ -213,8 +201,7 @@ fileDialogPreSave: //Work? goto fileDialogPreSave; //Work? } } - else - { + else { QMessageBox::warning(parent, PictureDialog::tr("Export as Picture"), PictureDialog::tr("No valid file is selected")); goto fileDialogPreSave; //Work? } @@ -250,7 +237,9 @@ fileDialogPreSave: //Work? QStringList filters; filters << PictureDialog::tr("GTA V Export (*.g5e)"); +#ifndef GTA5SYNC_FLATPAK filters << PictureDialog::tr("GTA V Raw Export (*.auto)"); +#endif filters << PictureDialog::tr("Snapmatic pictures (PGTA*)"); fileDialog.setNameFilters(filters); @@ -261,58 +250,48 @@ fileDialogPreSave: //Work? fileDialog.restoreGeometry(settings.value(parent->objectName() % "+Geometry", "").toByteArray()); fileDialog.selectFile(QString(picture->getExportPictureFileName() % ".g5e")); - if (fileDialog.exec()) - { + if (fileDialog.exec()) { QStringList selectedFiles = fileDialog.selectedFiles(); - if (selectedFiles.length() == 1) - { + if (selectedFiles.length() == 1) { QString selectedFile = selectedFiles.at(0); bool isAutoExt = false; - if (selectedFile.right(5) == ".auto") - { +#ifndef GTA5SYNC_FLATPAK + if (selectedFile.right(5) == ".auto") { isAutoExt = true; QString dirPath = QFileInfo(selectedFile).dir().path(); QString stockFileName = sgdFileInfo.fileName(); selectedFile = dirPath % "/" % stockFileName; } - else if (selectedFile.right(4) == ".rem") - { +#endif + if (selectedFile.right(4) == ".rem") { selectedFile.remove(selectedFile.length() - 4, 4); } - if (QFile::exists(selectedFile)) - { - if (QMessageBox::No == QMessageBox::warning(parent, PictureDialog::tr("Export as Snapmatic"), PictureDialog::tr("Overwrite %1 with current Snapmatic picture?").arg("\""+selectedFile+"\""), QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes)) - { + if (QFile::exists(selectedFile)) { + if (QMessageBox::No == QMessageBox::warning(parent, PictureDialog::tr("Export as Snapmatic"), PictureDialog::tr("Overwrite %1 with current Snapmatic picture?").arg("\""+selectedFile+"\""), QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes)) { goto fileDialogPreSave; //Work? } } - if (selectedFile.right(4) == ".g5e") - { + if (selectedFile.right(4) == ".g5e") { bool isExported = picture->exportPicture(selectedFile, SnapmaticFormat::G5E_Format); - if (!isExported) - { + if (!isExported) { QMessageBox::warning(parent, PictureDialog::tr("Export as Snapmatic"), PictureDialog::tr("Failed to export current Snapmatic picture")); goto fileDialogPreSave; //Work? } } - else - { + else { bool isCopied = picture->exportPicture(selectedFile, SnapmaticFormat::PGTA_Format); - if (!isCopied) - { + if (!isCopied) { QMessageBox::warning(parent, PictureDialog::tr("Export as Snapmatic"), PictureDialog::tr("Failed to export current Snapmatic picture")); goto fileDialogPreSave; //Work? } - else - { + else { if (isAutoExt) QMessageBox::information(parent, PictureDialog::tr("Export as Snapmatic"), PictureDialog::tr("Exported Snapmatic to \"%1\" because of using the .auto extension.").arg(selectedFile)); } } } - else - { + else { QMessageBox::warning(parent, PictureDialog::tr("Export as Snapmatic"), PictureDialog::tr("No valid file is selected")); goto fileDialogPreSave; //Work? } diff --git a/PictureExport.h b/PictureExport.h index 6ee84a0..623f093 100644 --- a/PictureExport.h +++ b/PictureExport.h @@ -1,5 +1,5 @@ /***************************************************************************** -* gta5sync GRAND THEFT AUTO V SYNC +* gta5view Grand Theft Auto V Profile Viewer * Copyright (C) 2016-2017 Syping * * This program is free software: you can redistribute it and/or modify diff --git a/PictureWidget.cpp b/PictureWidget.cpp index 95d51a1..49bdb8b 100644 --- a/PictureWidget.cpp +++ b/PictureWidget.cpp @@ -1,6 +1,6 @@ /***************************************************************************** -* gta5sync GRAND THEFT AUTO V SYNC -* Copyright (C) 2016-2017 Syping +* gta5view Grand Theft Auto V Profile Viewer +* Copyright (C) 2016-2020 Syping * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -19,13 +19,12 @@ #include "PictureDialog.h" #include "PictureWidget.h" #include "UiModLabel.h" -#include +#include "AppEnv.h" #include #include #include #include #include -#include PictureWidget::PictureWidget(QWidget *parent) : QDialog(parent) { @@ -44,7 +43,6 @@ PictureWidget::PictureWidget(QWidget *parent) : QDialog(parent) QObject::connect(pictureLabel, SIGNAL(mouseDoubleClicked(Qt::MouseButton)), this, SLOT(pictureDoubleClicked(Qt::MouseButton))); QObject::connect(pictureLabel, SIGNAL(customContextMenuRequested(QPoint)), parent, SLOT(exportCustomContextMenuRequested(QPoint))); - QObject::connect(QApplication::desktop(), SIGNAL(resized(int)), this, SLOT(updateWindowSize(int))); setLayout(widgetLayout); } @@ -58,12 +56,10 @@ PictureWidget::~PictureWidget() bool PictureWidget::eventFilter(QObject *obj, QEvent *ev) { - if (obj == this) - { - if (ev->type() == QEvent::KeyPress) - { + if (obj == this) { + if (ev->type() == QEvent::KeyPress) { QKeyEvent *keyEvent = (QKeyEvent*)ev; - switch (keyEvent->key()){ + switch (keyEvent->key()) { case Qt::Key_Left: emit previousPictureRequested(); break; @@ -78,32 +74,29 @@ bool PictureWidget::eventFilter(QObject *obj, QEvent *ev) void PictureWidget::pictureDoubleClicked(Qt::MouseButton button) { - if (button == Qt::LeftButton) - { + if (button == Qt::LeftButton) { close(); } } void PictureWidget::setImage(QImage image_, QRect rec) { + const qreal screenRatioPR = AppEnv::screenRatioPR(); image = image_; - pictureLabel->setPixmap(QPixmap::fromImage(image.scaled(rec.width(), rec.height(), Qt::KeepAspectRatio, Qt::SmoothTransformation))); + QPixmap pixmap = QPixmap::fromImage(image.scaled(rec.width() * screenRatioPR, rec.height() * screenRatioPR, Qt::KeepAspectRatio, Qt::SmoothTransformation)); +#if QT_VERSION >= 0x050600 + pixmap.setDevicePixelRatio(AppEnv::screenRatioPR()); +#endif + pictureLabel->setPixmap(pixmap); } void PictureWidget::setImage(QImage image_) { + const qreal screenRatioPR = AppEnv::screenRatioPR(); image = image_; - pictureLabel->setPixmap(QPixmap::fromImage(image.scaled(geometry().width(), geometry().height(), Qt::KeepAspectRatio, Qt::SmoothTransformation))); -} - -void PictureWidget::updateWindowSize(int screenID) -{ - if (screenID == QApplication::desktop()->screenNumber(this)) - { - QRect desktopRect = QApplication::desktop()->screenGeometry(this); - this->move(desktopRect.x(), desktopRect.y()); - this->resize(desktopRect.width(), desktopRect.height()); - this->showFullScreen(); - pictureLabel->setPixmap(QPixmap::fromImage(image.scaled(desktopRect.width(), desktopRect.height(), Qt::KeepAspectRatio, Qt::SmoothTransformation))); - } + QPixmap pixmap = QPixmap::fromImage(image.scaled(geometry().width() * screenRatioPR, geometry().height() * screenRatioPR, Qt::KeepAspectRatio, Qt::SmoothTransformation)); +#if QT_VERSION >= 0x050600 + pixmap.setDevicePixelRatio(screenRatioPR); +#endif + pictureLabel->setPixmap(pixmap); } diff --git a/PictureWidget.h b/PictureWidget.h index 2ebd21c..d2e64f3 100644 --- a/PictureWidget.h +++ b/PictureWidget.h @@ -1,5 +1,5 @@ /***************************************************************************** -* gta5sync GRAND THEFT AUTO V SYNC +* gta5view Grand Theft Auto V Profile Viewer * Copyright (C) 2016-2017 Syping * * This program is free software: you can redistribute it and/or modify @@ -46,7 +46,6 @@ private: private slots: void pictureDoubleClicked(Qt::MouseButton button); - void updateWindowSize(int screenID); signals: void nextPictureRequested(); diff --git a/PlayerListDialog.cpp b/PlayerListDialog.cpp index f828715..7c1b9a3 100644 --- a/PlayerListDialog.cpp +++ b/PlayerListDialog.cpp @@ -1,6 +1,6 @@ /***************************************************************************** -* gta5sync GRAND THEFT AUTO V SYNC -* Copyright (C) 2016-2018 Syping +* gta5view Grand Theft Auto V Profile Viewer +* Copyright (C) 2016-2021 Syping * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -18,7 +18,9 @@ #include "PlayerListDialog.h" #include "ui_PlayerListDialog.h" +#include "wrapper.h" #include "AppEnv.h" +#include #include #include #include @@ -31,54 +33,72 @@ PlayerListDialog::PlayerListDialog(QStringList players, ProfileDatabase *profile ui(new Ui::PlayerListDialog) { // Set Window Flags +#if QT_VERSION >= 0x050900 + setWindowFlag(Qt::WindowContextHelpButtonHint, false); +#else setWindowFlags(windowFlags()^Qt::WindowContextHelpButtonHint); +#endif listUpdated = false; ui->setupUi(this); + ui->cmdCancel->setDefault(true); ui->cmdCancel->setFocus(); // Set Icon for Apply Button - if (QIcon::hasThemeIcon("dialog-ok-apply")) - { + if (QIcon::hasThemeIcon("dialog-ok-apply")) { ui->cmdApply->setIcon(QIcon::fromTheme("dialog-ok-apply")); } - else if (QIcon::hasThemeIcon("dialog-apply")) - { + else if (QIcon::hasThemeIcon("dialog-apply")) { ui->cmdApply->setIcon(QIcon::fromTheme("dialog-apply")); } - else if (QIcon::hasThemeIcon("gtk-apply")) - { + else if (QIcon::hasThemeIcon("gtk-apply")) { ui->cmdApply->setIcon(QIcon::fromTheme("gtk-apply")); } - else if (QIcon::hasThemeIcon("dialog-ok")) - { + else if (QIcon::hasThemeIcon("dialog-ok")) { ui->cmdApply->setIcon(QIcon::fromTheme("dialog-ok")); } - else if (QIcon::hasThemeIcon("gtk-ok")) - { + else if (QIcon::hasThemeIcon("gtk-ok")) { ui->cmdApply->setIcon(QIcon::fromTheme("dialog-ok")); } // Set Icon for Cancel Button - if (QIcon::hasThemeIcon("dialog-cancel")) - { + if (QIcon::hasThemeIcon("dialog-cancel")) { ui->cmdCancel->setIcon(QIcon::fromTheme("dialog-cancel")); } - else if (QIcon::hasThemeIcon("gtk-cancel")) - { + else if (QIcon::hasThemeIcon("gtk-cancel")) { ui->cmdCancel->setIcon(QIcon::fromTheme("gtk-cancel")); } // Set Icon for Manage Buttons - if (QIcon::hasThemeIcon("go-previous") && QIcon::hasThemeIcon("go-next") && QIcon::hasThemeIcon("list-add")) - { + if (QIcon::hasThemeIcon("go-previous") && QIcon::hasThemeIcon("go-next") && QIcon::hasThemeIcon("list-add")) { +#if QT_VERSION < 0x050600 + qreal screenRatio = AppEnv::screenRatio(); + if (screenRatio != 1) { + QSize iconSize = ui->cmdMakeAv->iconSize(); + iconSize = QSize(iconSize.width() * screenRatio, iconSize.height() * screenRatio); + ui->cmdMakeAv->setIconSize(iconSize); + ui->cmdMakeSe->setIconSize(iconSize); + ui->cmdMakeAd->setIconSize(iconSize); + } +#endif ui->cmdMakeAv->setIcon(QIcon::fromTheme("go-previous")); ui->cmdMakeSe->setIcon(QIcon::fromTheme("go-next")); ui->cmdMakeAd->setIcon(QIcon::fromTheme("list-add")); } - else - { - drawSwitchButtons(); + else { +#if QT_VERSION < 0x050600 + qreal screenRatio = AppEnv::screenRatio(); + if (screenRatio != 1) { + QSize iconSize = ui->cmdMakeAv->iconSize(); + iconSize = QSize(iconSize.width() * screenRatio, iconSize.height() * screenRatio); + ui->cmdMakeAv->setIconSize(iconSize); + ui->cmdMakeSe->setIconSize(iconSize); + ui->cmdMakeAd->setIconSize(iconSize); + } +#endif + ui->cmdMakeAv->setIcon(QIcon(AppEnv::getImagesFolder() % "/back.svgz")); + ui->cmdMakeSe->setIcon(QIcon(AppEnv::getImagesFolder() % "/next.svgz")); + ui->cmdMakeAd->setIcon(QIcon(AppEnv::getImagesFolder() % "/add.svgz")); } buildInterface(); @@ -89,90 +109,15 @@ PlayerListDialog::PlayerListDialog(QStringList players, ProfileDatabase *profile PlayerListDialog::~PlayerListDialog() { - for (QObject *object : ui->listAvPlayers->children()) - { + for (QObject *object : ui->listAvPlayers->children()) { delete object; } - for (QObject *object : ui->listSePlayers->children()) - { + for (QObject *object : ui->listSePlayers->children()) { delete object; } delete ui; } -void PlayerListDialog::drawSwitchButtons() -{ - QFont painterFont = ui->cmdApply->font(); - QPalette palette; - - QFontMetrics fontMetrics(painterFont); - QRect makeAvRect = fontMetrics.boundingRect(QRect(0, 0, 0, 0), Qt::AlignCenter | Qt::TextDontClip, "<"); - QRect makeSeRect = fontMetrics.boundingRect(QRect(0, 0, 0, 0), Qt::AlignCenter | Qt::TextDontClip, ">"); - QRect makeAdRect = fontMetrics.boundingRect(QRect(0, 0, 0, 0), Qt::AlignCenter | Qt::TextDontClip, "+"); - - int makeAvSize; - if (makeAvRect.height() > makeAvRect.width()) - { - makeAvSize = makeAvRect.height(); - } - else - { - makeAvSize = makeAvRect.width(); - } - int makeSeSize; - if (makeSeRect.height() > makeSeRect.width()) - { - makeSeSize = makeSeRect.height(); - } - else - { - makeSeSize = makeSeRect.width(); - } - int makeAdSize; - if (makeAdRect.height() > makeAdRect.width()) - { - makeAdSize = makeAdRect.height(); - } - else - { - makeAdSize = makeAdRect.width(); - } - - QImage avImage(makeAvSize, makeAvSize, QImage::Format_ARGB32_Premultiplied); - avImage.fill(Qt::transparent); - QImage seImage(makeSeSize, makeSeSize, QImage::Format_ARGB32_Premultiplied); - seImage.fill(Qt::transparent); - QImage adImage(makeAdSize, makeAdSize, QImage::Format_ARGB32_Premultiplied); - adImage.fill(Qt::transparent); - - QPainter avPainter(&avImage); - avPainter.setFont(painterFont); - avPainter.setBrush(palette.buttonText()); - avPainter.setPen(palette.buttonText().color()); - avPainter.drawText(0, 0, makeAvSize, makeAvSize, Qt::AlignCenter | Qt::TextDontClip, "<"); - avPainter.end(); - QPainter sePainter(&seImage); - sePainter.setFont(painterFont); - sePainter.setBrush(palette.buttonText()); - sePainter.setPen(palette.buttonText().color()); - sePainter.drawText(0, 0, makeSeSize, makeSeSize, Qt::AlignCenter | Qt::TextDontClip, ">"); - sePainter.end(); - QPainter adPainter(&adImage); - adPainter.setFont(painterFont); - adPainter.setBrush(palette.buttonText()); - adPainter.setPen(palette.buttonText().color()); - adPainter.drawText(0, 0, makeAdSize, makeAdSize, Qt::AlignCenter | Qt::TextDontClip, "+"); - adPainter.end(); - - ui->cmdMakeAv->setIconSize(avImage.size()); - ui->cmdMakeSe->setIconSize(seImage.size()); - ui->cmdMakeAd->setIconSize(adImage.size()); - - ui->cmdMakeAv->setIcon(QIcon(QPixmap::fromImage(avImage))); - ui->cmdMakeSe->setIcon(QIcon(QPixmap::fromImage(seImage))); - ui->cmdMakeAd->setIcon(QIcon(QPixmap::fromImage(adImage))); -} - void PlayerListDialog::on_cmdCancel_clicked() { close(); @@ -181,16 +126,13 @@ void PlayerListDialog::on_cmdCancel_clicked() void PlayerListDialog::buildInterface() { const QStringList dbPlayers = profileDB->getPlayers(); - for (QString sePlayer : players) - { + for (const QString &sePlayer : qAsConst(players)) { QListWidgetItem *playerItem = new QListWidgetItem(profileDB->getPlayerName(sePlayer)); playerItem->setData(Qt::UserRole, sePlayer); ui->listSePlayers->addItem(playerItem); } - for (QString dbPlayer : dbPlayers) - { - if (!players.contains(dbPlayer)) - { + for (const QString &dbPlayer : dbPlayers) { + if (!players.contains(dbPlayer)) { QListWidgetItem *playerItem = new QListWidgetItem(profileDB->getPlayerName(dbPlayer)); playerItem->setData(Qt::UserRole, dbPlayer); ui->listAvPlayers->addItem(playerItem); @@ -201,8 +143,7 @@ void PlayerListDialog::buildInterface() void PlayerListDialog::on_cmdMakeAv_clicked() { - for (QListWidgetItem *item : ui->listSePlayers->selectedItems()) - { + for (QListWidgetItem *item : ui->listSePlayers->selectedItems()) { QString playerName = item->text(); int playerID = item->data(Qt::UserRole).toInt(); delete item; @@ -216,13 +157,11 @@ void PlayerListDialog::on_cmdMakeAv_clicked() void PlayerListDialog::on_cmdMakeSe_clicked() { int maxPlayers = 30; - if (maxPlayers < ui->listSePlayers->count() + ui->listAvPlayers->selectedItems().count()) - { + if (maxPlayers < ui->listSePlayers->count() + ui->listAvPlayers->selectedItems().count()) { QMessageBox::warning(this, tr("Add Players..."), tr("Failed to add more Players because the limit of Players are %1!").arg(QString::number(maxPlayers))); return; } - for (QListWidgetItem *item : ui->listAvPlayers->selectedItems()) - { + for (QListWidgetItem *item : ui->listAvPlayers->selectedItems()) { QString playerName = item->text(); int playerID = item->data(Qt::UserRole).toInt(); delete item; @@ -236,15 +175,12 @@ void PlayerListDialog::on_cmdMakeAd_clicked() { bool playerOk; int playerID = QInputDialog::getInt(this, tr("Add Player..."), tr("Enter Social Club Player ID"), 1, 1, 214783647, 1, &playerOk, windowFlags()); - if (playerOk) - { - for (int i = 0; i < ui->listAvPlayers->count(); ++i) - { + if (playerOk) { + for (int i = 0; i < ui->listAvPlayers->count(); ++i) { QListWidgetItem *item = ui->listAvPlayers->item(i); QString itemPlayerName = item->text(); int itemPlayerID = item->data(Qt::UserRole).toInt(); - if (itemPlayerID == playerID) - { + if (itemPlayerID == playerID) { delete item; QListWidgetItem *playerItem = new QListWidgetItem(itemPlayerName); playerItem->setData(Qt::UserRole, playerID); @@ -252,8 +188,7 @@ void PlayerListDialog::on_cmdMakeAd_clicked() return; } } - for (int i = 0; i < ui->listSePlayers->count(); ++i) - { + for (int i = 0; i < ui->listSePlayers->count(); ++i) { QListWidgetItem *item = ui->listSePlayers->item(i); int itemPlayerID = item->data(Qt::UserRole).toInt(); if (itemPlayerID == playerID) @@ -271,8 +206,7 @@ void PlayerListDialog::on_cmdMakeAd_clicked() void PlayerListDialog::on_cmdApply_clicked() { players.clear(); - for (int i = 0; i < ui->listSePlayers->count(); ++i) - { + for (int i = 0; i < ui->listSePlayers->count(); ++i) { players += ui->listSePlayers->item(i)->data(Qt::UserRole).toString(); } emit playerListUpdated(players); diff --git a/PlayerListDialog.h b/PlayerListDialog.h index fb779d7..c341a22 100644 --- a/PlayerListDialog.h +++ b/PlayerListDialog.h @@ -1,5 +1,5 @@ /***************************************************************************** -* gta5sync GRAND THEFT AUTO V SYNC +* gta5view Grand Theft Auto V Profile Viewer * Copyright (C) 2016-2017 Syping * * This program is free software: you can redistribute it and/or modify @@ -48,7 +48,6 @@ private: ProfileDatabase *profileDB; Ui::PlayerListDialog *ui; bool listUpdated; - void drawSwitchButtons(); void buildInterface(); signals: diff --git a/PlayerListDialog.ui b/PlayerListDialog.ui index b4b8ee1..3bdda4c 100644 --- a/PlayerListDialog.ui +++ b/PlayerListDialog.ui @@ -51,8 +51,8 @@ - - + + Qt::NoFocus false @@ -61,6 +61,9 @@ + + Qt::NoFocus + @@ -71,6 +74,9 @@ + + Qt::NoFocus + diff --git a/ProfileDatabase.cpp b/ProfileDatabase.cpp index b04a4fd..09ff581 100644 --- a/ProfileDatabase.cpp +++ b/ProfileDatabase.cpp @@ -1,5 +1,5 @@ /***************************************************************************** -* gta5sync GRAND THEFT AUTO V SYNC +* gta5view Grand Theft Auto V Profile Viewer * Copyright (C) 2016-2017 Syping * * This program is free software: you can redistribute it and/or modify diff --git a/ProfileDatabase.h b/ProfileDatabase.h index 89ae497..99bfc80 100644 --- a/ProfileDatabase.h +++ b/ProfileDatabase.h @@ -1,5 +1,5 @@ /***************************************************************************** -* gta5sync GRAND THEFT AUTO V SYNC +* gta5view Grand Theft Auto V Profile Viewer * Copyright (C) 2016-2017 Syping * * This program is free software: you can redistribute it and/or modify diff --git a/ProfileInterface.cpp b/ProfileInterface.cpp index 4f39530..8eca40a 100644 --- a/ProfileInterface.cpp +++ b/ProfileInterface.cpp @@ -1,6 +1,6 @@ /***************************************************************************** -* gta5sync GRAND THEFT AUTO V SYNC -* Copyright (C) 2016-2018 Syping +* gta5view Grand Theft Auto V Profile Viewer +* Copyright (C) 2016-2022 Syping * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -29,10 +29,16 @@ #include "ProfileLoader.h" #include "ExportThread.h" #include "ImportDialog.h" +#include "UiModLabel.h" +#include "pcg_basic.h" +#include "wrapper.h" #include "AppEnv.h" #include "config.h" +#include #include +#include #include +#include #include #include #include @@ -41,20 +47,31 @@ #include #include #include +#include #include #include +#include #include -#include #include -#include #include #include #include #include +#include #include #include #include +#include +#include +#include + +#ifdef GTA5SYNC_TELEMETRY +#include "TelemetryClass.h" +#include +#include +#endif + #define importTimeFormat "HHmmss" #define findRetryLimit 500 @@ -76,26 +93,29 @@ ProfileInterface::ProfileInterface(ProfileDatabase *profileDB, CrewDatabase *cre saSpacerItem = nullptr; updatePalette(); - ui->labVersion->setText(QString("%1 %2").arg(GTA5SYNC_APPSTR, GTA5SYNC_APPVER)); - ui->saProfileContent->setFilesMode(true); + QString appVersion = QApplication::applicationVersion(); + const char* literalBuildType = GTA5SYNC_BUILDTYPE; +#ifdef GTA5SYNC_COMMIT + if ((strcmp(literalBuildType, REL_BUILDTYPE) != 0) && !appVersion.contains("-")) + appVersion = appVersion % "-" % GTA5SYNC_COMMIT; +#endif + ui->labVersion->setText(QString("%1 %2").arg(GTA5SYNC_APPSTR, appVersion)); + ui->saProfileContent->setFilesDropEnabled(true); + ui->saProfileContent->setImageDropEnabled(true); // Set Icon for Close Button - if (QIcon::hasThemeIcon("dialog-close")) - { + if (QIcon::hasThemeIcon("dialog-close")) { ui->cmdCloseProfile->setIcon(QIcon::fromTheme("dialog-close")); } - else if (QIcon::hasThemeIcon("gtk-close")) - { + else if (QIcon::hasThemeIcon("gtk-close")) { ui->cmdCloseProfile->setIcon(QIcon::fromTheme("gtk-close")); } // Set Icon for Import Button - if (QIcon::hasThemeIcon("document-import")) - { + if (QIcon::hasThemeIcon("document-import")) { ui->cmdImport->setIcon(QIcon::fromTheme("document-import")); } - else if (QIcon::hasThemeIcon("document-open")) - { + else if (QIcon::hasThemeIcon("document-open")) { ui->cmdImport->setIcon(QIcon::fromTheme("document-open")); } @@ -105,40 +125,50 @@ ProfileInterface::ProfileInterface(ProfileDatabase *profileDB, CrewDatabase *cre ui->hlButtons->setSpacing(6 * screenRatio); ui->hlButtons->setContentsMargins(9 * screenRatio, 9 * screenRatio, 9 * screenRatio, 9 * screenRatio); #else - if (QApplication::style()->objectName() == "macintosh") - { +#if QT_VERSION >= 0x060000 + if (QApplication::style()->objectName() == "macos") { +#else + if (QApplication::style()->objectName() == "macintosh") { +#endif ui->hlButtons->setSpacing(6 * screenRatio); ui->hlButtons->setContentsMargins(9 * screenRatio, 15 * screenRatio, 15 * screenRatio, 17 * screenRatio); } - else - { + else { ui->hlButtons->setSpacing(6 * screenRatio); ui->hlButtons->setContentsMargins(9 * screenRatio, 9 * screenRatio, 9 * screenRatio, 9 * screenRatio); } #endif + // Seed RNG + pcg32_srandom_r(&rng, time(NULL), (intptr_t)&rng); + +#if QT_VERSION >= 0x050000 + // Register Metatypes + qRegisterMetaType>(); +#endif + setMouseTracking(true); installEventFilter(this); } ProfileInterface::~ProfileInterface() { - for (ProfileWidget *widget : widgets.keys()) - { - widget->removeEventFilter(this); - widget->disconnect(); - delete widget; + for (const QString &widgetStr : qAsConst(widgets)) { + ProfileWidget *widget = widgets.key(widgetStr, nullptr); + if (widget != nullptr) { + widget->removeEventFilter(this); + widget->disconnect(); + delete widget; + } } widgets.clear(); - for (SavegameData *savegame : savegames) - { + for (SavegameData *savegame : qAsConst(savegames)) { delete savegame; } savegames.clear(); - for (SnapmaticPicture *picture : pictures) - { + for (SnapmaticPicture *picture : qAsConst(pictures)) { delete picture; } pictures.clear(); @@ -158,6 +188,9 @@ void ProfileInterface::setupProfileInterface() fixedPictures.clear(); ui->labProfileLoading->setText(tr("Loading...")); profileLoader = new ProfileLoader(profileFolder, crewDB); +#if QT_VERSION >= 0x050000 + QObject::connect(profileLoader, SIGNAL(directoryScanned(QVector,QVector)), this, SLOT(directoryScanned(QVector,QVector))); +#endif QObject::connect(profileLoader, SIGNAL(savegameLoaded(SavegameData*, QString)), this, SLOT(savegameLoaded_event(SavegameData*, QString))); QObject::connect(profileLoader, SIGNAL(pictureLoaded(SnapmaticPicture*)), this, SLOT(pictureLoaded_event(SnapmaticPicture*))); QObject::connect(profileLoader, SIGNAL(pictureFixed(SnapmaticPicture*)), this, SLOT(pictureFixed_event(SnapmaticPicture*))); @@ -180,14 +213,16 @@ void ProfileInterface::savegameLoaded(SavegameData *savegame, QString savegamePa sgdWidget->installEventFilter(this); widgets[sgdWidget] = "SGD" % QFileInfo(savegamePath).fileName(); savegames += savegame; - if (selectedWidgts != 0 || contentMode == 2) { sgdWidget->setSelectionMode(true); } + if (selectedWidgts != 0 || contentMode == 2) + sgdWidget->setSelectionMode(true); QObject::connect(sgdWidget, SIGNAL(savegameDeleted()), this, SLOT(savegameDeleted_event())); QObject::connect(sgdWidget, SIGNAL(widgetSelected()), this, SLOT(profileWidgetSelected())); QObject::connect(sgdWidget, SIGNAL(widgetDeselected()), this, SLOT(profileWidgetDeselected())); QObject::connect(sgdWidget, SIGNAL(allWidgetsSelected()), this, SLOT(selectAllWidgets())); QObject::connect(sgdWidget, SIGNAL(allWidgetsDeselected()), this, SLOT(deselectAllWidgets())); QObject::connect(sgdWidget, SIGNAL(contextMenuTriggered(QContextMenuEvent*)), this, SLOT(contextMenuTriggeredSGD(QContextMenuEvent*))); - if (inserted) { insertSavegameIPI(sgdWidget); } + if (inserted) + insertSavegameIPI(sgdWidget); } void ProfileInterface::pictureLoaded_event(SnapmaticPicture *picture) @@ -210,7 +245,8 @@ void ProfileInterface::pictureLoaded(SnapmaticPicture *picture, bool inserted) picWidget->installEventFilter(this); widgets[picWidget] = "PIC" % picture->getPictureSortStr(); pictures += picture; - if (selectedWidgts != 0 || contentMode == 2) { picWidget->setSelectionMode(true); } + if (selectedWidgts != 0 || contentMode == 2) + picWidget->setSelectionMode(true); QObject::connect(picWidget, SIGNAL(pictureDeleted()), this, SLOT(pictureDeleted_event())); QObject::connect(picWidget, SIGNAL(widgetSelected()), this, SLOT(profileWidgetSelected())); QObject::connect(picWidget, SIGNAL(widgetDeselected()), this, SLOT(profileWidgetDeselected())); @@ -219,7 +255,8 @@ void ProfileInterface::pictureLoaded(SnapmaticPicture *picture, bool inserted) QObject::connect(picWidget, SIGNAL(nextPictureRequested(QWidget*)), this, SLOT(dialogNextPictureRequested(QWidget*))); QObject::connect(picWidget, SIGNAL(previousPictureRequested(QWidget*)), this, SLOT(dialogPreviousPictureRequested(QWidget*))); QObject::connect(picWidget, SIGNAL(contextMenuTriggered(QContextMenuEvent*)), this, SLOT(contextMenuTriggeredPIC(QContextMenuEvent*))); - if (inserted) { insertSnapmaticIPI(picWidget); } + if (inserted) + insertSnapmaticIPI(picWidget); } void ProfileInterface::loadingProgress(int value, int maximum) @@ -229,23 +266,90 @@ void ProfileInterface::loadingProgress(int value, int maximum) ui->labProfileLoading->setText(loadingStr.arg(QString::number(value), QString::number(maximum))); } +#if QT_VERSION >= 0x050000 +void ProfileInterface::directoryChanged(const QString &path) +{ + Q_UNUSED(path) + + QDir dir(profileFolder); + QVector t_savegameFiles; + QVector t_snapmaticPics; + QVector n_savegameFiles; + QVector n_snapmaticPics; + + const QStringList files = dir.entryList(QDir::Files); + for (const QString &fileName : files) { + if (fileName.startsWith("SGTA5") && !fileName.endsWith(".bak")) { + t_savegameFiles << fileName; + if (!savegameFiles.contains(fileName)) { + n_savegameFiles << fileName; + } + } + if (fileName.startsWith("PGTA5") && !fileName.endsWith(".bak")) { + t_snapmaticPics << fileName; + if (fileName.endsWith(".hidden")) { + const QString originalFileName = fileName.left(fileName.length() - 7); + if (!snapmaticPics.contains(fileName) && !snapmaticPics.contains(originalFileName)) { + n_snapmaticPics << fileName; + } + } + else { + if (!snapmaticPics.contains(fileName) && !snapmaticPics.contains(fileName % ".hidden")) { + n_snapmaticPics << fileName; + } + } + } + } + savegameFiles = t_savegameFiles; + snapmaticPics = t_snapmaticPics; + + if (!n_savegameFiles.isEmpty() || !n_snapmaticPics.isEmpty()) { + QTimer::singleShot(1000, this, [=](){ + for (const QString &fileName : qAsConst(n_savegameFiles)) { + const QString filePath = profileFolder % "/" % fileName; + SavegameData *savegame = new SavegameData(filePath); + if (savegame->readingSavegame()) + savegameLoaded(savegame, filePath, true); + else + delete savegame; + } + for (const QString &fileName : qAsConst(n_snapmaticPics)) { + const QString filePath = profileFolder % "/" % fileName; + SnapmaticPicture *picture = new SnapmaticPicture(filePath); + if (picture->readingPicture(true)) + pictureLoaded(picture, true); + else + delete picture; + } + }); + } +} + +void ProfileInterface::directoryScanned(QVector savegameFiles_s, QVector snapmaticPics_s) +{ + savegameFiles = savegameFiles_s; + snapmaticPics = snapmaticPics_s; + fileSystemWatcher.addPath(profileFolder); + QObject::connect(&fileSystemWatcher, &QFileSystemWatcher::directoryChanged, this, &ProfileInterface::directoryChanged); +} +#endif + void ProfileInterface::insertSnapmaticIPI(QWidget *widget) { ProfileWidget *proWidget = qobject_cast(widget); - if (widgets.contains(proWidget)) - { - QString widgetKey = widgets[proWidget]; + const QString widgetKey = widgets.value(proWidget, QString()); + if (!widgetKey.isNull()) { QStringList widgetsKeyList = widgets.values(); QStringList pictureKeyList = widgetsKeyList.filter("PIC", Qt::CaseSensitive); #if QT_VERSION >= 0x050600 - qSort(pictureKeyList.rbegin(), pictureKeyList.rend()); + std::sort(pictureKeyList.rbegin(), pictureKeyList.rend()); #else qSort(pictureKeyList.begin(), pictureKeyList.end(), qGreater()); #endif - int picIndex = pictureKeyList.indexOf(QRegExp(widgetKey)); + int picIndex = pictureKeyList.indexOf(widgetKey); ui->vlSnapmatic->insertWidget(picIndex, proWidget); - qApp->processEvents(); + QApplication::processEvents(); ui->saProfile->ensureWidgetVisible(proWidget, 0, 0); } } @@ -253,16 +357,19 @@ void ProfileInterface::insertSnapmaticIPI(QWidget *widget) void ProfileInterface::insertSavegameIPI(QWidget *widget) { ProfileWidget *proWidget = qobject_cast(widget); - if (widgets.contains(proWidget)) - { - QString widgetKey = widgets[proWidget]; + const QString widgetKey = widgets.value(proWidget, QString()); + if (!widgetKey.isNull()) { QStringList widgetsKeyList = widgets.values(); QStringList savegameKeyList = widgetsKeyList.filter("SGD", Qt::CaseSensitive); +#if QT_VERSION >= 0x050600 + std::sort(savegameKeyList.begin(), savegameKeyList.end()); +#else qSort(savegameKeyList.begin(), savegameKeyList.end()); - int sgdIndex = savegameKeyList.indexOf(QRegExp(widgetKey)); +#endif + int sgdIndex = savegameKeyList.indexOf(widgetKey); ui->vlSavegame->insertWidget(sgdIndex, proWidget); - qApp->processEvents(); + QApplication::processEvents(); ui->saProfile->ensureWidgetVisible(proWidget, 0, 0); } } @@ -271,33 +378,27 @@ void ProfileInterface::dialogNextPictureRequested(QWidget *dialog) { PictureDialog *picDialog = qobject_cast(dialog); ProfileWidget *proWidget = qobject_cast(sender()); - if (widgets.contains(proWidget)) - { - QString widgetKey = widgets[proWidget]; + const QString widgetKey = widgets.value(proWidget, QString()); + if (!widgetKey.isNull()) { QStringList widgetsKeyList = widgets.values(); QStringList pictureKeyList = widgetsKeyList.filter("PIC", Qt::CaseSensitive); #if QT_VERSION >= 0x050600 - qSort(pictureKeyList.rbegin(), pictureKeyList.rend()); + std::sort(pictureKeyList.rbegin(), pictureKeyList.rend()); #else qSort(pictureKeyList.begin(), pictureKeyList.end(), qGreater()); #endif int picIndex; - if (picDialog->isIndexed()) - { + if (picDialog->isIndexed()) { picIndex = picDialog->getIndex(); } - else - { - picIndex = pictureKeyList.indexOf(QRegExp(widgetKey)); + else { + picIndex = pictureKeyList.indexOf(widgetKey); } picIndex++; - if (pictureKeyList.length() > picIndex) - { - QString newWidgetKey = pictureKeyList.at(picIndex); - SnapmaticWidget *picWidget = (SnapmaticWidget*)widgets.key(newWidgetKey); - //picDialog->setMaximumHeight(QWIDGETSIZE_MAX); + if (pictureKeyList.length() > picIndex) { + const QString newWidgetKey = pictureKeyList.at(picIndex); + SnapmaticWidget *picWidget = static_cast(widgets.key(newWidgetKey)); picDialog->setSnapmaticPicture(picWidget->getPicture(), picIndex); - //picDialog->setMaximumHeight(picDialog->height()); } } } @@ -306,33 +407,27 @@ void ProfileInterface::dialogPreviousPictureRequested(QWidget *dialog) { PictureDialog *picDialog = qobject_cast(dialog); ProfileWidget *proWidget = qobject_cast(sender()); - if (widgets.contains(proWidget)) - { - QString widgetKey = widgets[proWidget]; + const QString widgetKey = widgets.value(proWidget, QString()); + if (!widgetKey.isNull()) { QStringList widgetsKeyList = widgets.values(); QStringList pictureKeyList = widgetsKeyList.filter("PIC", Qt::CaseSensitive); #if QT_VERSION >= 0x050600 - qSort(pictureKeyList.rbegin(), pictureKeyList.rend()); + std::sort(pictureKeyList.rbegin(), pictureKeyList.rend()); #else qSort(pictureKeyList.begin(), pictureKeyList.end(), qGreater()); #endif int picIndex; - if (picDialog->isIndexed()) - { + if (picDialog->isIndexed()) { picIndex = picDialog->getIndex(); } - else - { - picIndex = pictureKeyList.indexOf(QRegExp(widgetKey)); + else { + picIndex = pictureKeyList.indexOf(widgetKey); } - if (picIndex > 0) - { + if (picIndex > 0) { picIndex--; - QString newWidgetKey = pictureKeyList.at(picIndex ); - SnapmaticWidget *picWidget = (SnapmaticWidget*)widgets.key(newWidgetKey); - //picDialog->setMaximumHeight(QWIDGETSIZE_MAX); + const QString newWidgetKey = pictureKeyList.at(picIndex); + SnapmaticWidget *picWidget = static_cast(widgets.key(newWidgetKey)); picDialog->setSnapmaticPicture(picWidget->getPicture(), picIndex); - //picDialog->setMaximumHeight(picDialog->height()); } } } @@ -343,17 +438,19 @@ void ProfileInterface::sortingProfileInterface() ui->vlSnapmatic->setEnabled(false); QStringList widgetsKeyList = widgets.values(); - qSort(widgetsKeyList.begin(), widgetsKeyList.end()); - for (QString widgetKey : widgetsKeyList) - { +#if QT_VERSION >= 0x050600 + std::sort(widgetsKeyList.begin(), widgetsKeyList.end()); +#else + qSort(widgetsKeyList.begin(), widgetsKeyList.end()); +#endif + + for (const QString &widgetKey : qAsConst(widgetsKeyList)) { ProfileWidget *widget = widgets.key(widgetKey); - if (widget->getWidgetType() == "SnapmaticWidget") - { + if (widget->getWidgetType() == "SnapmaticWidget") { ui->vlSnapmatic->insertWidget(0, widget); } - else if (widget->getWidgetType() == "SavegameWidget") - { + else if (widget->getWidgetType() == "SavegameWidget") { ui->vlSavegame->addWidget(widget); } } @@ -361,7 +458,7 @@ void ProfileInterface::sortingProfileInterface() ui->vlSavegame->setEnabled(true); ui->vlSnapmatic->setEnabled(true); - qApp->processEvents(); + QApplication::processEvents(); } void ProfileInterface::profileLoaded_p() @@ -375,12 +472,10 @@ void ProfileInterface::profileLoaded_p() isProfileLoaded = true; emit profileLoaded(); - if (!fixedPictures.isEmpty()) - { + if (!fixedPictures.isEmpty()) { int fixedInt = 0; QString fixedStr; - for (QString fixedPicture : fixedPictures) - { + for (const QString &fixedPicture : qAsConst(fixedPictures)) { if (fixedInt != 0) { fixedStr += "
"; } fixedStr += fixedPicture; fixedInt++; @@ -397,13 +492,13 @@ void ProfileInterface::savegameDeleted_event() void ProfileInterface::savegameDeleted(SavegameWidget *sgdWidget, bool isRemoteEmited) { SavegameData *savegame = sgdWidget->getSavegame(); - if (sgdWidget->isSelected()) { sgdWidget->setSelected(false); } + if (sgdWidget->isSelected()) + sgdWidget->setSelected(false); widgets.remove(sgdWidget); sgdWidget->disconnect(); sgdWidget->removeEventFilter(this); - if (sgdWidget == previousWidget) - { + if (sgdWidget == previousWidget) { previousWidget = nullptr; } @@ -422,13 +517,13 @@ void ProfileInterface::pictureDeleted_event() void ProfileInterface::pictureDeleted(SnapmaticWidget *picWidget, bool isRemoteEmited) { SnapmaticPicture *picture = picWidget->getPicture(); - if (picWidget->isSelected()) { picWidget->setSelected(false); } + if (picWidget->isSelected()) + picWidget->setSelected(false); widgets.remove(picWidget); picWidget->disconnect(); picWidget->removeEventFilter(this); - if (picWidget == previousWidget) - { + if (picWidget == previousWidget) { previousWidget = nullptr; } @@ -459,17 +554,15 @@ fileDialogPreOpen: //Work? fileDialog.setOption(QFileDialog::DontUseNativeDialog, dontUseNativeDialog); fileDialog.setWindowFlags(fileDialog.windowFlags()^Qt::WindowContextHelpButtonHint); fileDialog.setWindowTitle(tr("Import...")); - fileDialog.setLabelText(QFileDialog::Accept, tr("Import")); + fileDialog.setLabelText(QFileDialog::Accept, tr("Import...")); // Getting readable Image formats QString imageFormatsStr = " "; - for (QByteArray imageFormat : QImageReader::supportedImageFormats()) - { + for (const QByteArray &imageFormat : QImageReader::supportedImageFormats()) { imageFormatsStr += QString("*.") % QString::fromUtf8(imageFormat).toLower() % " "; } QString importableFormatsStr = QString("*.g5e SGTA* PGTA*"); - if (!imageFormatsStr.trimmed().isEmpty()) - { + if (!imageFormatsStr.trimmed().isEmpty()) { importableFormatsStr = QString("*.g5e%1SGTA* PGTA*").arg(imageFormatsStr); } @@ -488,23 +581,18 @@ fileDialogPreOpen: //Work? fileDialog.setDirectory(settings.value(profileName % "+Directory", StandardPaths::documentsLocation()).toString()); fileDialog.restoreGeometry(settings.value(profileName % "+Geometry", "").toByteArray()); - if (fileDialog.exec()) - { + if (fileDialog.exec()) { QStringList selectedFiles = fileDialog.selectedFiles(); - if (selectedFiles.length() == 1) - { + if (selectedFiles.length() == 1) { QString selectedFile = selectedFiles.at(0); QDateTime importDateTime = QDateTime::currentDateTime(); - int currentTime = importDateTime.toString(importTimeFormat).toInt(); - if (!importFile(selectedFile, importDateTime, ¤tTime, true)) goto fileDialogPreOpen; //Work? + if (!importFile(selectedFile, importDateTime, true)) goto fileDialogPreOpen; //Work? } - else if (selectedFiles.length() > 1) - { + else if (selectedFiles.length() > 1) { importFilesProgress(selectedFiles); } - else - { - QMessageBox::warning(this, tr("Import"), tr("No valid file is selected")); + else { + QMessageBox::warning(this, tr("Import..."), tr("No valid file is selected")); goto fileDialogPreOpen; //Work? } } @@ -515,7 +603,7 @@ fileDialogPreOpen: //Work? settings.endGroup(); } -void ProfileInterface::importFilesProgress(QStringList selectedFiles) +bool ProfileInterface::importFilesProgress(QStringList selectedFiles) { int maximumId = selectedFiles.length(); int overallId = 0; @@ -539,78 +627,110 @@ void ProfileInterface::importFilesProgress(QStringList selectedFiles) // THREADING HERE PLEASE QDateTime importDateTime = QDateTime::currentDateTime(); - int currentTime = importDateTime.time().toString(importTimeFormat).toInt(); - for (QString selectedFile : selectedFiles) - { + for (const QString &selectedFile : selectedFiles) { overallId++; pbDialog.setValue(overallId); pbDialog.setLabelText(tr("Import file %1 of %2 files").arg(QString::number(overallId), QString::number(maximumId))); importDateTime = QDateTime::currentDateTime(); - if (!importFile(selectedFile, importDateTime, ¤tTime, false)) - { + if (!importFile(selectedFile, importDateTime, false)) { failed << QFileInfo(selectedFile).fileName(); } } pbDialog.close(); - for (QString curErrorStr : failed) - { + for (const QString &curErrorStr : qAsConst(failed)) { errorStr += ", " % curErrorStr; } - if (errorStr != "") - { + if (errorStr != "") { errorStr.remove(0, 2); - QMessageBox::warning(this, tr("Import"), tr("Import failed with...\n\n%1").arg(errorStr)); + QMessageBox::warning(this, tr("Import..."), tr("Import failed with...\n\n%1").arg(errorStr)); + return false; } + return true; } -bool ProfileInterface::importFile(QString selectedFile, QDateTime importDateTime, int *currentTime, bool notMultiple) +bool ProfileInterface::importFile(QString selectedFile, QDateTime importDateTime, bool notMultiple) { QString selectedFileName = QFileInfo(selectedFile).fileName(); - if (QFile::exists(selectedFile)) - { - if (selectedFileName.left(4) == "PGTA" || selectedFileName.right(4) == ".g5e") - { + if (QFile::exists(selectedFile)) { + if ((selectedFileName.left(4) == "PGTA" && !selectedFileName.contains('.')) || selectedFileName.right(4) == ".g5e") { SnapmaticPicture *picture = new SnapmaticPicture(selectedFile); - if (picture->readingPicture(true, true, true)) - { + if (picture->readingPicture(true)) { bool success = importSnapmaticPicture(picture, notMultiple); - if (!success) delete picture; + if (!success) + delete picture; +#ifdef GTA5SYNC_TELEMETRY + if (success && notMultiple) { + QSettings telemetrySettings(GTA5SYNC_APPVENDOR, GTA5SYNC_APPSTR); + telemetrySettings.beginGroup("Telemetry"); + bool pushUsageData = telemetrySettings.value("PushUsageData", false).toBool(); + telemetrySettings.endGroup(); + if (pushUsageData && Telemetry->canPush()) { + QJsonDocument jsonDocument; + QJsonObject jsonObject; + jsonObject["Type"] = "ImportSuccess"; + jsonObject["ImportSize"] = QString::number(picture->getContentMaxLength()); +#if QT_VERSION >= 0x060000 + jsonObject["ImportTime"] = QString::number(QDateTime::currentDateTimeUtc().toSecsSinceEpoch()); +#else + jsonObject["ImportTime"] = QString::number(QDateTime::currentDateTimeUtc().toTime_t()); +#endif + jsonObject["ImportType"] = "Snapmatic"; + jsonDocument.setObject(jsonObject); + Telemetry->push(TelemetryCategory::PersonalData, jsonDocument); + } + } +#endif return success; } - else - { - if (notMultiple) QMessageBox::warning(this, tr("Import"), tr("Failed to read Snapmatic picture")); + else { + if (notMultiple) + QMessageBox::warning(this, tr("Import..."), tr("Failed to read Snapmatic picture")); delete picture; return false; } } - else if (selectedFileName.left(4) == "SGTA") - { + else if (selectedFileName.left(4) == "SGTA") { SavegameData *savegame = new SavegameData(selectedFile); - if (savegame->readingSavegame()) - { + if (savegame->readingSavegame()) { bool success = importSavegameData(savegame, selectedFile, notMultiple); - if (!success) delete savegame; + if (!success) + delete savegame; +#ifdef GTA5SYNC_TELEMETRY + if (success && notMultiple) { + QSettings telemetrySettings(GTA5SYNC_APPVENDOR, GTA5SYNC_APPSTR); + telemetrySettings.beginGroup("Telemetry"); + bool pushUsageData = telemetrySettings.value("PushUsageData", false).toBool(); + telemetrySettings.endGroup(); + if (pushUsageData && Telemetry->canPush()) { + QJsonDocument jsonDocument; + QJsonObject jsonObject; + jsonObject["Type"] = "ImportSuccess"; +#if QT_VERSION >= 0x060000 + jsonObject["ImportTime"] = QString::number(QDateTime::currentDateTimeUtc().toSecsSinceEpoch()); +#else + jsonObject["ImportTime"] = QString::number(QDateTime::currentDateTimeUtc().toTime_t()); +#endif + jsonObject["ImportType"] = "Savegame"; + jsonDocument.setObject(jsonObject); + Telemetry->push(TelemetryCategory::PersonalData, jsonDocument); + } + } +#endif return success; } - else - { - if (notMultiple) QMessageBox::warning(this, tr("Import"), tr("Failed to read Savegame file")); + else { + if (notMultiple) QMessageBox::warning(this, tr("Import..."), tr("Failed to read Savegame file")); delete savegame; return false; } } - else if(isSupportedImageFile(selectedFileName)) - { + else if (isSupportedImageFile(selectedFileName)) { SnapmaticPicture *picture = new SnapmaticPicture(":/template/template.g5e"); - if (picture->readingPicture(true, false, true, false)) - { - if (!notMultiple) - { + if (picture->readingPicture(false)) { + if (!notMultiple) { QFile snapmaticFile(selectedFile); - if (!snapmaticFile.open(QFile::ReadOnly)) - { + if (!snapmaticFile.open(QFile::ReadOnly)) { delete picture; return false; } @@ -618,8 +738,7 @@ bool ProfileInterface::importFile(QString selectedFile, QDateTime importDateTime QImageReader snapmaticImageReader; snapmaticImageReader.setDecideFormatFromContent(true); snapmaticImageReader.setDevice(&snapmaticFile); - if (!snapmaticImageReader.read(&snapmaticImage)) - { + if (!snapmaticImageReader.read(&snapmaticImage)) { delete picture; return false; } @@ -627,38 +746,32 @@ bool ProfileInterface::importFile(QString selectedFile, QDateTime importDateTime QPixmap snapmaticPixmap(960, 536); snapmaticPixmap.fill(Qt::black); QPainter snapmaticPainter(&snapmaticPixmap); - if (snapmaticImage.height() == snapmaticImage.width()) - { + if (snapmaticImage.height() == snapmaticImage.width()) { // Avatar mode int diffWidth = 0; int diffHeight = 0; snapmaticImage = snapmaticImage.scaled(470, 470, Qt::KeepAspectRatio, Qt::SmoothTransformation); - if (snapmaticImage.width() > snapmaticImage.height()) - { + if (snapmaticImage.width() > snapmaticImage.height()) { diffHeight = 470 - snapmaticImage.height(); diffHeight = diffHeight / 2; } - else if (snapmaticImage.width() < snapmaticImage.height()) - { + else if (snapmaticImage.width() < snapmaticImage.height()) { diffWidth = 470 - snapmaticImage.width(); diffWidth = diffWidth / 2; } snapmaticPainter.drawImage(145 + diffWidth, 66 + diffHeight, snapmaticImage); customImageTitle = ImportDialog::tr("Custom Avatar", "Custom Avatar Description in SC, don't use Special Character!"); } - else - { + else { // Picture mode int diffWidth = 0; int diffHeight = 0; snapmaticImage = snapmaticImage.scaled(960, 536, Qt::KeepAspectRatio, Qt::SmoothTransformation); - if (snapmaticImage.width() != 960) - { + if (snapmaticImage.width() != 960) { diffWidth = 960 - snapmaticImage.width(); diffWidth = diffWidth / 2; } - else if (snapmaticImage.height() != 536) - { + else if (snapmaticImage.height() != 536) { diffHeight = 536 - snapmaticImage.height(); diffHeight = diffHeight / 2; } @@ -666,166 +779,461 @@ bool ProfileInterface::importFile(QString selectedFile, QDateTime importDateTime customImageTitle = ImportDialog::tr("Custom Picture", "Custom Picture Description in SC, don't use Special Character!"); } snapmaticPainter.end(); - if (!picture->setImage(snapmaticPixmap.toImage())) - { + if (!picture->setImage(snapmaticPixmap.toImage())) { delete picture; return false; } SnapmaticProperties spJson = picture->getSnapmaticProperties(); - spJson.uid = QString(QString::number(*currentTime) % - QString::number(importDateTime.date().dayOfYear())).toInt(); + spJson.uid = getRandomUid(); bool fExists = QFile::exists(profileFolder % "/PGTA5" % QString::number(spJson.uid)); + bool fExistsBackup = QFile::exists(profileFolder % "/PGTA5" % QString::number(spJson.uid) % ".bak"); bool fExistsHidden = QFile::exists(profileFolder % "/PGTA5" % QString::number(spJson.uid) % ".hidden"); int cEnough = 0; - while ((fExists || fExistsHidden) && cEnough < findRetryLimit) - { - *currentTime = *currentTime - 1; - spJson.uid = QString(QString::number(*currentTime) % - QString::number(importDateTime.date().dayOfYear())).toInt(); + while ((fExists || fExistsBackup || fExistsHidden) && cEnough < findRetryLimit) { + spJson.uid = getRandomUid(); fExists = QFile::exists(profileFolder % "/PGTA5" % QString::number(spJson.uid)); + fExistsBackup = QFile::exists(profileFolder % "/PGTA5" % QString::number(spJson.uid) % ".bak"); fExistsHidden = QFile::exists(profileFolder % "/PGTA5" % QString::number(spJson.uid) % ".hidden"); cEnough++; } spJson.createdDateTime = importDateTime; +#if QT_VERSION >= 0x060000 + quint64 timestamp = spJson.createdDateTime.toSecsSinceEpoch(); + if (timestamp > UINT32_MAX) { + timestamp = UINT32_MAX; + } + spJson.createdTimestamp = (quint32)timestamp; +#else spJson.createdTimestamp = spJson.createdDateTime.toTime_t(); +#endif picture->setSnapmaticProperties(spJson); - picture->setPicFileName(QString("PGTA5%1").arg(QString::number(spJson.uid))); + const QString picFileName = QString("PGTA5%1").arg(QString::number(spJson.uid)); + picture->setPicFileName(picFileName); picture->setPictureTitle(customImageTitle); picture->updateStrings(); bool success = importSnapmaticPicture(picture, notMultiple); - if (!success) delete picture; + if (!success) + delete picture; return success; } - else - { + else { bool success = false; QFile snapmaticFile(selectedFile); - if (!snapmaticFile.open(QFile::ReadOnly)) - { - QMessageBox::warning(this, tr("Import"), tr("Can't import %1 because file can't be open").arg("\""+selectedFileName+"\"")); + if (!snapmaticFile.open(QFile::ReadOnly)) { + QMessageBox::warning(this, tr("Import..."), tr("Can't import %1 because file can't be open").arg("\""+selectedFileName+"\"")); delete picture; return false; } - QImage *importImage = new QImage(); + QImage *snapmaticImage = new QImage(); QImageReader snapmaticImageReader; snapmaticImageReader.setDecideFormatFromContent(true); snapmaticImageReader.setDevice(&snapmaticFile); - if (!snapmaticImageReader.read(importImage)) - { - QMessageBox::warning(this, tr("Import"), tr("Can't import %1 because file can't be parsed properly").arg("\""+selectedFileName+"\"")); - delete importImage; + if (!snapmaticImageReader.read(snapmaticImage)) { + QMessageBox::warning(this, tr("Import..."), tr("Can't import %1 because file can't be parsed properly").arg("\""+selectedFileName+"\"")); + delete snapmaticImage; delete picture; return false; } - ImportDialog *importDialog = new ImportDialog(this); - importDialog->setImage(importImage); + ImportDialog *importDialog = new ImportDialog(profileName, this); + importDialog->setImage(snapmaticImage); importDialog->setModal(true); importDialog->show(); importDialog->exec(); - if (importDialog->isImportAgreed()) - { - if (picture->setImage(importDialog->image())) - { + if (importDialog->isImportAgreed()) { + if (picture->setImage(importDialog->image(), importDialog->isUnlimitedBuffer())) { SnapmaticProperties spJson = picture->getSnapmaticProperties(); - spJson.uid = QString(QString::number(*currentTime) % - QString::number(importDateTime.date().dayOfYear())).toInt(); + spJson.uid = getRandomUid(); bool fExists = QFile::exists(profileFolder % "/PGTA5" % QString::number(spJson.uid)); + bool fExistsBackup = QFile::exists(profileFolder % "/PGTA5" % QString::number(spJson.uid) % ".bak"); bool fExistsHidden = QFile::exists(profileFolder % "/PGTA5" % QString::number(spJson.uid) % ".hidden"); int cEnough = 0; - while ((fExists || fExistsHidden) && cEnough < findRetryLimit) - { - *currentTime = *currentTime - 1; - spJson.uid = QString(QString::number(*currentTime) % - QString::number(importDateTime.date().dayOfYear())).toInt(); + while ((fExists || fExistsBackup || fExistsHidden) && cEnough < findRetryLimit) { + spJson.uid = getRandomUid(); fExists = QFile::exists(profileFolder % "/PGTA5" % QString::number(spJson.uid)); + fExistsBackup = QFile::exists(profileFolder % "/PGTA5" % QString::number(spJson.uid) % ".bak"); fExistsHidden = QFile::exists(profileFolder % "/PGTA5" % QString::number(spJson.uid) % ".hidden"); cEnough++; } spJson.createdDateTime = importDateTime; +#if QT_VERSION >= 0x060000 + quint64 timestamp = spJson.createdDateTime.toSecsSinceEpoch(); + if (timestamp > UINT32_MAX) { + timestamp = UINT32_MAX; + } + spJson.createdTimestamp = (quint32)timestamp; +#else spJson.createdTimestamp = spJson.createdDateTime.toTime_t(); +#endif picture->setSnapmaticProperties(spJson); - picture->setPicFileName(QString("PGTA5%1").arg(QString::number(spJson.uid))); + const QString picFileName = QString("PGTA5%1").arg(QString::number(spJson.uid)); + picture->setPicFileName(picFileName); picture->setPictureTitle(importDialog->getImageTitle()); picture->updateStrings(); success = importSnapmaticPicture(picture, notMultiple); +#ifdef GTA5SYNC_TELEMETRY + if (success) { + QSettings telemetrySettings(GTA5SYNC_APPVENDOR, GTA5SYNC_APPSTR); + telemetrySettings.beginGroup("Telemetry"); + bool pushUsageData = telemetrySettings.value("PushUsageData", false).toBool(); + telemetrySettings.endGroup(); + if (pushUsageData && Telemetry->canPush()) { + QJsonDocument jsonDocument; + QJsonObject jsonObject; + jsonObject["Type"] = "ImportSuccess"; + jsonObject["ExtraFlag"] = "Dialog"; + jsonObject["ImportSize"] = QString::number(picture->getContentMaxLength()); +#if QT_VERSION >= 0x060000 + jsonObject["ImportTime"] = QString::number(QDateTime::currentDateTimeUtc().toSecsSinceEpoch()); +#else + jsonObject["ImportTime"] = QString::number(QDateTime::currentDateTimeUtc().toTime_t()); +#endif + jsonObject["ImportType"] = "Image"; + jsonDocument.setObject(jsonObject); + Telemetry->push(TelemetryCategory::PersonalData, jsonDocument); + } + } +#endif } } - else - { + else { delete picture; success = true; } delete importDialog; - if (!success) delete picture; + if (!success) + delete picture; return success; } } - else - { + else { delete picture; return false; } } - else - { + else { SnapmaticPicture *picture = new SnapmaticPicture(selectedFile); SavegameData *savegame = new SavegameData(selectedFile); - if (picture->readingPicture()) - { + if (picture->readingPicture()) { bool success = importSnapmaticPicture(picture, notMultiple); delete savegame; - if (!success) delete picture; + if (!success) + delete picture; +#ifdef GTA5SYNC_TELEMETRY + if (success && notMultiple) { + QSettings telemetrySettings(GTA5SYNC_APPVENDOR, GTA5SYNC_APPSTR); + telemetrySettings.beginGroup("Telemetry"); + bool pushUsageData = telemetrySettings.value("PushUsageData", false).toBool(); + telemetrySettings.endGroup(); + if (pushUsageData && Telemetry->canPush()) { + QJsonDocument jsonDocument; + QJsonObject jsonObject; + jsonObject["Type"] = "ImportSuccess"; + jsonObject["ImportSize"] = QString::number(picture->getContentMaxLength()); +#if QT_VERSION >= 0x060000 + jsonObject["ImportTime"] = QString::number(QDateTime::currentDateTimeUtc().toSecsSinceEpoch()); +#else + jsonObject["ImportTime"] = QString::number(QDateTime::currentDateTimeUtc().toTime_t()); +#endif + jsonObject["ImportType"] = "Snapmatic"; + jsonDocument.setObject(jsonObject); + Telemetry->push(TelemetryCategory::PersonalData, jsonDocument); + } + } +#endif return success; } - else if (savegame->readingSavegame()) - { + else if (savegame->readingSavegame()) { bool success = importSavegameData(savegame, selectedFile, notMultiple); delete picture; - if (!success) delete savegame; + if (!success) + delete savegame; +#ifdef GTA5SYNC_TELEMETRY + if (success && notMultiple) { + QSettings telemetrySettings(GTA5SYNC_APPVENDOR, GTA5SYNC_APPSTR); + telemetrySettings.beginGroup("Telemetry"); + bool pushUsageData = telemetrySettings.value("PushUsageData", false).toBool(); + telemetrySettings.endGroup(); + if (pushUsageData && Telemetry->canPush()) { + QJsonDocument jsonDocument; + QJsonObject jsonObject; + jsonObject["Type"] = "ImportSuccess"; +#if QT_VERSION >= 0x060000 + jsonObject["ImportTime"] = QString::number(QDateTime::currentDateTimeUtc().toSecsSinceEpoch()); +#else + jsonObject["ImportTime"] = QString::number(QDateTime::currentDateTimeUtc().toTime_t()); +#endif + jsonObject["ImportType"] = "Savegame"; + jsonDocument.setObject(jsonObject); + Telemetry->push(TelemetryCategory::PersonalData, jsonDocument); + } + } +#endif return success; } - else - { + else { #ifdef GTA5SYNC_DEBUG qDebug() << "ImportError SnapmaticPicture" << picture->getLastStep(); qDebug() << "ImportError SavegameData" << savegame->getLastStep(); #endif delete picture; delete savegame; - if (notMultiple) QMessageBox::warning(this, tr("Import"), tr("Can't import %1 because file format can't be detected").arg("\""+selectedFileName+"\"")); + if (notMultiple) QMessageBox::warning(this, tr("Import..."), tr("Can't import %1 because file format can't be detected").arg("\""+selectedFileName+"\"")); return false; } } } - if (notMultiple) QMessageBox::warning(this, tr("Import"), tr("No valid file is selected")); + if (notMultiple) + QMessageBox::warning(this, tr("Import..."), tr("No valid file is selected")); return false; } +bool ProfileInterface::importUrls(const QMimeData *mimeData) +{ + QStringList pathList; + + for (const QUrl ¤tUrl : mimeData->urls()) { + if (currentUrl.isLocalFile()) + pathList += currentUrl.toLocalFile(); + } + + if (pathList.length() == 1) { + QString selectedFile = pathList.at(0); + return importFile(selectedFile, QDateTime::currentDateTime(), true); + } + else if (pathList.length() > 1) { + return importFilesProgress(pathList); + } + return false; +} + +bool ProfileInterface::importRemote(QUrl remoteUrl) +{ + bool retValue = false; + QDialog urlPasteDialog(this); +#if QT_VERSION >= 0x050000 + urlPasteDialog.setObjectName(QStringLiteral("UrlPasteDialog")); +#else + urlPasteDialog.setObjectName(QString::fromUtf8("UrlPasteDialog")); +#endif + urlPasteDialog.setWindowFlags(urlPasteDialog.windowFlags()^Qt::WindowContextHelpButtonHint^Qt::WindowCloseButtonHint); + urlPasteDialog.setWindowTitle(tr("Import...")); + urlPasteDialog.setModal(true); + QVBoxLayout urlPasteLayout(&urlPasteDialog); +#if QT_VERSION >= 0x050000 + urlPasteLayout.setObjectName(QStringLiteral("UrlPasteLayout")); +#else + urlPasteLayout.setObjectName(QString::fromUtf8("UrlPasteLayout")); +#endif + urlPasteDialog.setLayout(&urlPasteLayout); + UiModLabel urlPasteLabel(&urlPasteDialog); +#if QT_VERSION >= 0x050000 + urlPasteLabel.setObjectName(QStringLiteral("UrlPasteLabel")); +#else + urlPasteLabel.setObjectName(QString::fromUtf8("UrlPasteLabel")); +#endif + + urlPasteLabel.setText(tr("Prepare Content for Import...")); + urlPasteLayout.addWidget(&urlPasteLabel); + urlPasteDialog.setFixedSize(urlPasteDialog.sizeHint()); + urlPasteDialog.show(); + + QNetworkAccessManager *netManager = new QNetworkAccessManager(); + QNetworkRequest netRequest(remoteUrl); + netRequest.setRawHeader("User-Agent", AppEnv::getUserAgent()); + netRequest.setRawHeader("Accept", "text/html"); + netRequest.setRawHeader("Accept-Charset", "utf-8"); + netRequest.setRawHeader("Accept-Language", "en-US,en;q=0.9"); + netRequest.setRawHeader("Connection", "keep-alive"); + QNetworkReply *netReply = netManager->get(netRequest); + QEventLoop *downloadLoop = new QEventLoop(); + QObject::connect(netReply, SIGNAL(finished()), downloadLoop, SLOT(quit())); + QTimer::singleShot(30000, downloadLoop, SLOT(quit())); + downloadLoop->exec(); + downloadLoop->disconnect(); + delete downloadLoop; + + urlPasteDialog.close(); + + if (netReply->isFinished()) { + QImage *snapmaticImage = new QImage(); + QImageReader snapmaticImageReader; + snapmaticImageReader.setDecideFormatFromContent(true); + snapmaticImageReader.setDevice(netReply); + if (snapmaticImageReader.read(snapmaticImage)) { + retValue = importImage(snapmaticImage, QDateTime::currentDateTime()); + } + else { + delete snapmaticImage; + } + } + else { + netReply->abort(); + } + delete netReply; + delete netManager; + return retValue; +} + +bool ProfileInterface::importImage(QImage *snapmaticImage, QDateTime importDateTime) +{ + SnapmaticPicture *picture = new SnapmaticPicture(":/template/template.g5e"); + if (picture->readingPicture(false)) { + bool success = false; + ImportDialog *importDialog = new ImportDialog(profileName, this); + importDialog->setImage(snapmaticImage); + importDialog->setModal(true); + importDialog->show(); + importDialog->exec(); + if (importDialog->isImportAgreed()) { + if (picture->setImage(importDialog->image(), importDialog->isUnlimitedBuffer())) { + SnapmaticProperties spJson = picture->getSnapmaticProperties(); + spJson.uid = getRandomUid(); + bool fExists = QFile::exists(profileFolder % "/PGTA5" % QString::number(spJson.uid)); + bool fExistsBackup = QFile::exists(profileFolder % "/PGTA5" % QString::number(spJson.uid) % ".bak"); + bool fExistsHidden = QFile::exists(profileFolder % "/PGTA5" % QString::number(spJson.uid) % ".hidden"); + int cEnough = 0; + while ((fExists || fExistsBackup || fExistsHidden) && cEnough < findRetryLimit) { + spJson.uid = getRandomUid(); + fExists = QFile::exists(profileFolder % "/PGTA5" % QString::number(spJson.uid)); + fExistsBackup = QFile::exists(profileFolder % "/PGTA5" % QString::number(spJson.uid) % ".bak"); + fExistsHidden = QFile::exists(profileFolder % "/PGTA5" % QString::number(spJson.uid) % ".hidden"); + cEnough++; + } + spJson.createdDateTime = importDateTime; +#if QT_VERSION >= 0x060000 + quint64 timestamp = spJson.createdDateTime.toSecsSinceEpoch(); + if (timestamp > UINT32_MAX) { + timestamp = UINT32_MAX; + } + spJson.createdTimestamp = (quint32)timestamp; +#else + spJson.createdTimestamp = spJson.createdDateTime.toTime_t(); +#endif + picture->setSnapmaticProperties(spJson); + const QString picFileName = QString("PGTA5%1").arg(QString::number(spJson.uid)); + picture->setPicFileName(picFileName); + picture->setPictureTitle(importDialog->getImageTitle()); + picture->updateStrings(); + success = importSnapmaticPicture(picture, true); + } + } + else { + delete picture; + success = true; + } + delete importDialog; + if (!success) + delete picture; + return success; + } + else { + delete picture; + return false; + } +} + bool ProfileInterface::importSnapmaticPicture(SnapmaticPicture *picture, bool warn) { QString picFileName = picture->getPictureFileName(); - qDebug() << picFileName; QString adjustedFileName = picture->getOriginalPictureFileName(); - if (picFileName.left(4) != "PGTA") - { - if (warn) QMessageBox::warning(this, tr("Import"), tr("Failed to import the Snapmatic picture, file not begin with PGTA or end with .g5e")); + if (!picFileName.startsWith("PGTA5")) { + if (warn) + QMessageBox::warning(this, tr("Import..."), tr("Failed to import the Snapmatic picture, file not begin with PGTA or end with .g5e")); return false; } - else if (QFile::exists(profileFolder % "/" % adjustedFileName) || QFile::exists(profileFolder % "/" % adjustedFileName % ".hidden")) - { - if (warn) QMessageBox::warning(this, tr("Import"), tr("Failed to import the Snapmatic picture, the picture is already in the game")); - return false; + else if (QFile::exists(profileFolder % "/" % adjustedFileName) || QFile::exists(profileFolder % "/" % adjustedFileName % ".hidden")) { + SnapmaticProperties snapmaticProperties = picture->getSnapmaticProperties(); + if (warn) { + int uchoice = QMessageBox::question(this, tr("Import..."), tr("A Snapmatic picture already exists with the uid %1, you want assign your import a new uid and timestamp?").arg(QString::number(snapmaticProperties.uid)), QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes); + if (uchoice == QMessageBox::Yes) { + // Update Snapmatic uid + snapmaticProperties.uid = getRandomUid(); + snapmaticProperties.createdDateTime = QDateTime::currentDateTime(); +#if QT_VERSION >= 0x060000 + quint64 timestamp = snapmaticProperties.createdDateTime.toSecsSinceEpoch(); + if (timestamp > UINT32_MAX) { + timestamp = UINT32_MAX; + } + snapmaticProperties.createdTimestamp = (quint32)timestamp; +#else + snapmaticProperties.createdTimestamp = snapmaticProperties.createdDateTime.toTime_t(); +#endif + bool fExists = QFile::exists(profileFolder % "/PGTA5" % QString::number(snapmaticProperties.uid)); + bool fExistsBackup = QFile::exists(profileFolder % "/PGTA5" % QString::number(snapmaticProperties.uid) % ".bak"); + bool fExistsHidden = QFile::exists(profileFolder % "/PGTA5" % QString::number(snapmaticProperties.uid) % ".hidden"); + int cEnough = 0; + while ((fExists || fExistsBackup || fExistsHidden) && cEnough < findRetryLimit) { + snapmaticProperties.uid = getRandomUid(); + fExists = QFile::exists(profileFolder % "/PGTA5" % QString::number(snapmaticProperties.uid)); + fExistsBackup = QFile::exists(profileFolder % "/PGTA5" % QString::number(snapmaticProperties.uid) % ".bak"); + fExistsHidden = QFile::exists(profileFolder % "/PGTA5" % QString::number(snapmaticProperties.uid) % ".hidden"); + cEnough++; + } + if (fExists || fExistsBackup || fExistsHidden) { + // That should never happen + return false; + } + if (!picture->setSnapmaticProperties(snapmaticProperties)) { + // That should never happen + return false; + } + picture->updateStrings(); + picFileName = picture->getPictureFileName(); + adjustedFileName = picture->getOriginalPictureFileName(); + } + else { + return false; + } + } + else { + // Update Snapmatic uid + snapmaticProperties.uid = getRandomUid(); + snapmaticProperties.createdDateTime = QDateTime::currentDateTime(); +#if QT_VERSION >= 0x060000 + quint64 timestamp = snapmaticProperties.createdDateTime.toSecsSinceEpoch(); + if (timestamp > UINT32_MAX) { + timestamp = UINT32_MAX; + } + snapmaticProperties.createdTimestamp = (quint32)timestamp; +#else + snapmaticProperties.createdTimestamp = snapmaticProperties.createdDateTime.toTime_t(); +#endif + bool fExists = QFile::exists(profileFolder % "/PGTA5" % QString::number(snapmaticProperties.uid)); + bool fExistsBackup = QFile::exists(profileFolder % "/PGTA5" % QString::number(snapmaticProperties.uid) % ".bak"); + bool fExistsHidden = QFile::exists(profileFolder % "/PGTA5" % QString::number(snapmaticProperties.uid) % ".hidden"); + int cEnough = 0; + while ((fExists || fExistsBackup || fExistsHidden) && cEnough < findRetryLimit) { + snapmaticProperties.uid = getRandomUid(); + fExists = QFile::exists(profileFolder % "/PGTA5" % QString::number(snapmaticProperties.uid)); + fExistsBackup = QFile::exists(profileFolder % "/PGTA5" % QString::number(snapmaticProperties.uid) % ".bak"); + fExistsHidden = QFile::exists(profileFolder % "/PGTA5" % QString::number(snapmaticProperties.uid) % ".hidden"); + cEnough++; + } + if (fExists || fExistsBackup || fExistsHidden) { + // That should never happen + return false; + } + if (!picture->setSnapmaticProperties(snapmaticProperties)) { + // That should never happen + return false; + } + picture->updateStrings(); + picFileName = picture->getPictureFileName(); + adjustedFileName = picture->getOriginalPictureFileName(); + } } - else if (picture->exportPicture(profileFolder % "/" % adjustedFileName, SnapmaticFormat::PGTA_Format)) - { + if (picture->exportPicture(profileFolder % "/" % adjustedFileName, SnapmaticFormat::PGTA_Format)) { picture->setSnapmaticFormat(SnapmaticFormat::PGTA_Format); picture->setPicFilePath(profileFolder % "/" % adjustedFileName); +#if QT_VERSION >= 0x050000 + snapmaticPics << picture->getPictureFileName(); +#endif pictureLoaded(picture, true); return true; } - else - { - if (warn) QMessageBox::warning(this, tr("Import"), tr("Failed to import the Snapmatic picture, can't copy the file into profile")); + else { + if (warn) + QMessageBox::warning(this, tr("Import..."), tr("Failed to import the Snapmatic picture, can't copy the file into profile")); return false; } } @@ -836,50 +1244,49 @@ bool ProfileInterface::importSavegameData(SavegameData *savegame, QString sgdPat bool foundFree = 0; int currentSgd = 0; - while (currentSgd < 15 && !foundFree) - { + while (currentSgd < 15 && !foundFree) { QString sgdNumber = QString::number(currentSgd); - if (sgdNumber.length() == 1) - { + if (sgdNumber.length() == 1) { sgdNumber.insert(0, "0"); } sgdFileName = "SGTA500" % sgdNumber; - if (!QFile::exists(profileFolder % "/" % sgdFileName)) - { + if (!QFile::exists(profileFolder % "/" % sgdFileName)) { foundFree = true; } currentSgd++; } - if (foundFree) - { - if (QFile::copy(sgdPath, profileFolder % "/" % sgdFileName)) - { - savegame->setSavegameFileName(profileFolder % "/" % sgdFileName); - savegameLoaded(savegame, profileFolder % "/" % sgdFileName, true); + if (foundFree) { + const QString newSgdPath = profileFolder % "/" % sgdFileName; + if (QFile::copy(sgdPath, newSgdPath)) { + savegame->setSavegameFileName(newSgdPath); +#if QT_VERSION >= 0x050000 + savegameFiles << newSgdPath; +#endif + savegameLoaded(savegame, newSgdPath, true); return true; } - else - { - if (warn) QMessageBox::warning(this, tr("Import"), tr("Failed to import the Savegame, can't copy the file into profile")); + else { + if (warn) + QMessageBox::warning(this, tr("Import..."), tr("Failed to import the Savegame, can't copy the file into profile")); return false; } } - else - { - if (warn) QMessageBox::warning(this, tr("Import"), tr("Failed to import the Savegame, no Savegame slot is left")); + else { + if (warn) + QMessageBox::warning(this, tr("Import..."), tr("Failed to import the Savegame, no Savegame slot is left")); return false; } } void ProfileInterface::profileWidgetSelected() { - if (selectedWidgts == 0) - { - for (ProfileWidget *widget : widgets.keys()) - { - widget->setSelectionMode(true); + if (selectedWidgts == 0) { + for (const QString &widgetStr : qAsConst(widgets)) { + ProfileWidget *widget = widgets.key(widgetStr, nullptr); + if (widget != nullptr) + widget->setSelectionMode(true); } } selectedWidgts++; @@ -887,13 +1294,11 @@ void ProfileInterface::profileWidgetSelected() void ProfileInterface::profileWidgetDeselected() { - if (selectedWidgts == 1) - { + if (selectedWidgts == 1) { int scrollBarValue = ui->saProfile->verticalScrollBar()->value(); - for (ProfileWidget *widget : widgets.keys()) - { - if (contentMode != 2) - { + for (const QString &widgetStr : qAsConst(widgets)) { + ProfileWidget *widget = widgets.key(widgetStr, nullptr); + if (widget != nullptr && contentMode != 2) { widget->setSelectionMode(false); } } @@ -904,24 +1309,25 @@ void ProfileInterface::profileWidgetDeselected() void ProfileInterface::selectAllWidgets() { - for (ProfileWidget *widget : widgets.keys()) - { - widget->setSelected(true); + for (const QString &widgetStr : qAsConst(widgets)) { + ProfileWidget *widget = widgets.key(widgetStr, nullptr); + if (widget != nullptr) + widget->setSelected(true); } } void ProfileInterface::deselectAllWidgets() { - for (ProfileWidget *widget : widgets.keys()) - { - widget->setSelected(false); + for (const QString &widgetStr : qAsConst(widgets)) { + ProfileWidget *widget = widgets.key(widgetStr, nullptr); + if (widget != nullptr) + widget->setSelected(false); } } void ProfileInterface::exportSelected() { - if (selectedWidgts != 0) - { + if (selectedWidgts != 0) { int exportCount = 0; int exportPictures = 0; int exportSavegames = 0; @@ -933,26 +1339,23 @@ void ProfileInterface::exportSelected() //bool dontUseNativeDialog = settings.value("DontUseNativeDialog", false).toBool(); settings.beginGroup("ExportDirectory"); QString exportDirectory = QFileDialog::getExistingDirectory(this, tr("Export selected..."), settings.value(profileName, profileFolder).toString()); - if (exportDirectory != "") - { + if (exportDirectory != "") { settings.setValue(profileName, exportDirectory); - for (ProfileWidget *widget : widgets.keys()) - { - if (widget->isSelected()) - { - if (widget->getWidgetType() == "SnapmaticWidget") - { - exportPictures++; - } - else if (widget->getWidgetType() == "SavegameWidget") - { - exportSavegames++; + for (const QString &widgetStr : qAsConst(widgets)) { + ProfileWidget *widget = widgets.key(widgetStr, nullptr); + if (widget != nullptr) { + if (widget->isSelected()) { + if (widget->getWidgetType() == "SnapmaticWidget") { + exportPictures++; + } + else if (widget->getWidgetType() == "SavegameWidget") { + exportSavegames++; + } } } } - if (exportPictures != 0) - { + if (exportPictures != 0) { QInputDialog inputDialog; QStringList inputDialogItems; inputDialogItems << tr("JPG pictures and GTA Snapmatic"); @@ -961,39 +1364,33 @@ void ProfileInterface::exportSelected() QString ExportPreSpan; QString ExportPostSpan; -#ifdef GTA5SYNC_WIN - ExportPreSpan = ""; +#ifdef Q_OS_WIN + ExportPreSpan = ""; ExportPostSpan = ""; #else - ExportPreSpan = ""; + ExportPreSpan = ""; ExportPostSpan = ""; #endif bool itemSelected = false; QString selectedItem = inputDialog.getItem(this, tr("Export selected..."), tr("%1Export Snapmatic pictures%2

JPG pictures make it possible to open the picture with a Image Viewer
GTA Snapmatic make it possible to import the picture into the game

Export as:").arg(ExportPreSpan, ExportPostSpan), inputDialogItems, 0, false, &itemSelected, inputDialog.windowFlags()^Qt::WindowContextHelpButtonHint); - if (itemSelected) - { - if (selectedItem == tr("JPG pictures and GTA Snapmatic")) - { + if (itemSelected) { + if (selectedItem == tr("JPG pictures and GTA Snapmatic")) { pictureExportEnabled = true; pictureCopyEnabled = true; } - else if (selectedItem == tr("JPG pictures only")) - { + else if (selectedItem == tr("JPG pictures only")) { pictureExportEnabled = true; } - else if (selectedItem == tr("GTA Snapmatic only")) - { + else if (selectedItem == tr("GTA Snapmatic only")) { pictureCopyEnabled = true; } - else - { + else { pictureExportEnabled = true; pictureCopyEnabled = true; } } - else - { + else { // Don't export anymore when any Cancel button got clicked settings.endGroup(); settings.endGroup(); @@ -1003,13 +1400,11 @@ void ProfileInterface::exportSelected() // Counting the exports together exportCount = exportCount + exportSavegames; - if (pictureExportEnabled && pictureCopyEnabled) - { + if (pictureExportEnabled && pictureCopyEnabled) { int exportPictures2 = exportPictures * 2; exportCount = exportCount + exportPictures2; } - else - { + else { exportCount = exportCount + exportPictures; } @@ -1043,22 +1438,18 @@ void ProfileInterface::exportSelected() errorList << getFailedCopyPictures; errorList << getFailedSavegames; - for (QString curErrorStr : errorList) - { + for (const QString &curErrorStr : qAsConst(errorList)) { errorStr += ", " % curErrorStr; } - if (errorStr != "") - { + if (errorStr != "") { errorStr.remove(0, 2); QMessageBox::warning(this, tr("Export selected..."), tr("Export failed with...\n\n%1").arg(errorStr)); } - if (exportThread->isFinished()) - { + if (exportThread->isFinished()) { delete exportThread; } - else - { + else { QEventLoop threadFinishLoop; QObject::connect(exportThread, SIGNAL(finished()), &threadFinishLoop, SLOT(quit())); threadFinishLoop.exec(); @@ -1068,52 +1459,74 @@ void ProfileInterface::exportSelected() settings.endGroup(); settings.endGroup(); } - else - { + else { QMessageBox::information(this, tr("Export selected..."), tr("No Snapmatic pictures or Savegames files are selected")); } } +void ProfileInterface::deleteSelectedL(bool isRemoteEmited) +{ + if (selectedWidgts != 0) { + if (QMessageBox::Yes == QMessageBox::warning(this, tr("Remove selected"), tr("You really want remove the selected Snapmatic picutres and Savegame files?"), QMessageBox::Yes | QMessageBox::No, QMessageBox::No)) { + for (const QString &widgetStr : qAsConst(widgets)) { + ProfileWidget *widget = widgets.key(widgetStr, nullptr); + if (widget != nullptr) { + if (widget->isSelected()) { + if (widget->getWidgetType() == "SnapmaticWidget") { + SnapmaticWidget *picWidget = qobject_cast(widget); + if (picWidget->getPicture()->deletePictureFile()) { + pictureDeleted(picWidget, isRemoteEmited); + } + } + else if (widget->getWidgetType() == "SavegameWidget") { + SavegameWidget *sgdWidget = qobject_cast(widget); + SavegameData *savegame = sgdWidget->getSavegame(); + QString fileName = savegame->getSavegameFileName(); + if (!QFile::exists(fileName) || QFile::remove(fileName)) { + savegameDeleted(sgdWidget, isRemoteEmited); + } + } + } + } + } + if (selectedWidgts != 0) { + QMessageBox::warning(this, tr("Remove selected"), tr("Failed to remove all selected Snapmatic pictures and/or Savegame files")); + } + } + } + else { + QMessageBox::information(this, tr("Remove selected"), tr("No Snapmatic pictures or Savegames files are selected")); + } +} + void ProfileInterface::deleteSelected() { - if (selectedWidgts != 0) - { - if (QMessageBox::Yes == QMessageBox::warning(this, tr("Remove selected"), tr("You really want remove the selected Snapmatic picutres and Savegame files?"), QMessageBox::Yes | QMessageBox::No, QMessageBox::No)) - { - for (ProfileWidget *widget : widgets.keys()) - { - if (widget->isSelected()) - { - if (widget->getWidgetType() == "SnapmaticWidget") - { - SnapmaticWidget *picWidget = qobject_cast(widget); - if (picWidget->getPicture()->deletePicFile()) - { - pictureDeleted(picWidget); - } - } - else if (widget->getWidgetType() == "SavegameWidget") - { - SavegameWidget *sgdWidget = qobject_cast(widget); - SavegameData *savegame = sgdWidget->getSavegame(); - QString fileName = savegame->getSavegameFileName(); - if (!QFile::exists(fileName) || QFile::remove(fileName)) - { - savegameDeleted(sgdWidget); - } - } - } - } - if (selectedWidgts != 0) - { - QMessageBox::warning(this, tr("Remove selected"), tr("Failed to remove all selected Snapmatic pictures and/or Savegame files")); - } - } - } - else - { - QMessageBox::information(this, tr("Remove selected"), tr("No Snapmatic pictures or Savegames files are selected")); - } + deleteSelectedL(false); +} + +void ProfileInterface::deleteSelectedR() +{ + deleteSelectedL(true); +} + +void ProfileInterface::massToolQualify() +{ + massTool(MassTool::Qualify); +} + +void ProfileInterface::massToolPlayers() +{ + massTool(MassTool::Players); +} + +void ProfileInterface::massToolCrew() +{ + massTool(MassTool::Crew); +} + +void ProfileInterface::massToolTitle() +{ + massTool(MassTool::Title); } void ProfileInterface::importFiles() @@ -1123,39 +1536,45 @@ void ProfileInterface::importFiles() void ProfileInterface::settingsApplied(int _contentMode, bool languageChanged) { - if (languageChanged) retranslateUi(); + if (languageChanged) + retranslateUi(); contentMode = _contentMode; - if (contentMode == 2) - { - for (ProfileWidget *widget : widgets.keys()) - { - widget->setSelectionMode(true); - widget->setContentMode(contentMode); - if (languageChanged) widget->retranslate(); + if (contentMode == 2) { + for (const QString &widgetStr : qAsConst(widgets)) { + ProfileWidget *widget = widgets.key(widgetStr, nullptr); + if (widget != nullptr) { + widget->setSelectionMode(true); + widget->setContentMode(contentMode); + if (languageChanged) + widget->retranslate(); + } } } - else - { - for (ProfileWidget *widget : widgets.keys()) - { - if (selectedWidgts == 0) - { - widget->setSelectionMode(false); + else { + for (const QString &widgetStr : qAsConst(widgets)) { + ProfileWidget *widget = widgets.key(widgetStr, nullptr); + if (widget != nullptr) { + if (selectedWidgts == 0) { + widget->setSelectionMode(false); + } + widget->setContentMode(contentMode); + if (languageChanged) + widget->retranslate(); } - widget->setContentMode(contentMode); - if (languageChanged) widget->retranslate(); } } #ifdef Q_OS_MAC // DPI calculation qreal screenRatio = AppEnv::screenRatio(); - if (QApplication::style()->objectName() == "macintosh") - { +#if QT_VERSION >= 0x060000 + if (QApplication::style()->objectName() == "macos") { +#else + if (QApplication::style()->objectName() == "macintosh") { +#endif ui->hlButtons->setSpacing(6 * screenRatio); ui->hlButtons->setContentsMargins(9 * screenRatio, 15 * screenRatio, 15 * screenRatio, 17 * screenRatio); } - else - { + else { ui->hlButtons->setSpacing(6 * screenRatio); ui->hlButtons->setContentsMargins(9 * screenRatio, 9 * screenRatio, 9 * screenRatio, 9 * screenRatio); } @@ -1165,33 +1584,29 @@ void ProfileInterface::settingsApplied(int _contentMode, bool languageChanged) void ProfileInterface::enableSelected() { QList snapmaticWidgets; - for (ProfileWidget *widget : widgets.keys()) - { - if (widget->isSelected()) - { - if (widget->getWidgetType() == "SnapmaticWidget") - { - SnapmaticWidget *snapmaticWidget = qobject_cast(widget); - snapmaticWidgets += snapmaticWidget; + for (const QString &widgetStr : qAsConst(widgets)) { + ProfileWidget *widget = widgets.key(widgetStr, nullptr); + if (widget != nullptr) { + if (widget->isSelected()) { + if (widget->getWidgetType() == "SnapmaticWidget") { + SnapmaticWidget *snapmaticWidget = qobject_cast(widget); + snapmaticWidgets += snapmaticWidget; + } } } } - if (snapmaticWidgets.isEmpty()) - { + if (snapmaticWidgets.isEmpty()) { QMessageBox::information(this, QApplication::translate("UserInterface", "Show In-game"), QApplication::translate("ProfileInterface", "No Snapmatic pictures are selected")); return; } QStringList fails; - for (SnapmaticWidget *widget : snapmaticWidgets) - { + for (SnapmaticWidget *widget : qAsConst(snapmaticWidgets)) { SnapmaticPicture *picture = widget->getPicture(); - if (!widget->makePictureVisible()) - { + if (!widget->makePictureVisible()) { fails << QString("%1 [%2]").arg(picture->getPictureTitle(), picture->getPictureString()); } } - if (!fails.isEmpty()) - { + if (!fails.isEmpty()) { QMessageBox::warning(this, QApplication::translate("UserInterface", "Show In-game"), QApplication::translate("ProfileInterface", "%1 failed with...\n\n%2", "Action failed with...").arg(QApplication::translate("UserInterface", "Show In-game"), fails.join(", "))); } } @@ -1199,33 +1614,29 @@ void ProfileInterface::enableSelected() void ProfileInterface::disableSelected() { QList snapmaticWidgets; - for (ProfileWidget *widget : widgets.keys()) - { - if (widget->isSelected()) - { - if (widget->getWidgetType() == "SnapmaticWidget") - { - SnapmaticWidget *snapmaticWidget = qobject_cast(widget); - snapmaticWidgets += snapmaticWidget; + for (const QString &widgetStr : qAsConst(widgets)) { + ProfileWidget *widget = widgets.key(widgetStr, nullptr); + if (widget != nullptr) { + if (widget->isSelected()) { + if (widget->getWidgetType() == "SnapmaticWidget") { + SnapmaticWidget *snapmaticWidget = qobject_cast(widget); + snapmaticWidgets += snapmaticWidget; + } } } } - if (snapmaticWidgets.isEmpty()) - { + if (snapmaticWidgets.isEmpty()) { QMessageBox::information(this, QApplication::translate("UserInterface", "Hide In-game"), QApplication::translate("ProfileInterface", "No Snapmatic pictures are selected")); return; } QStringList fails; - for (SnapmaticWidget *widget : snapmaticWidgets) - { + for (SnapmaticWidget *widget : qAsConst(snapmaticWidgets)) { SnapmaticPicture *picture = widget->getPicture(); - if (!widget->makePictureHidden()) - { + if (!widget->makePictureHidden()) { fails << QString("%1 [%2]").arg(picture->getPictureTitle(), picture->getPictureString()); } } - if (!fails.isEmpty()) - { + if (!fails.isEmpty()) { QMessageBox::warning(this, QApplication::translate("UserInterface", "Hide In-game"), QApplication::translate("ProfileInterface", "%1 failed with...\n\n%2", "Action failed with...").arg(QApplication::translate("UserInterface", "Hide In-game"), fails.join(", "))); } } @@ -1238,149 +1649,235 @@ int ProfileInterface::selectedWidgets() void ProfileInterface::contextMenuTriggeredPIC(QContextMenuEvent *ev) { SnapmaticWidget *picWidget = qobject_cast(sender()); - if (picWidget != previousWidget) - { - if (previousWidget != nullptr) - { + if (picWidget != previousWidget) { + if (previousWidget != nullptr) { previousWidget->setStyleSheet(QLatin1String("")); } - picWidget->setStyleSheet(QString("QFrame#SnapmaticFrame{background-color: rgb(%1, %2, %3)}QLabel#labPicStr{color: rgb(%4, %5, %6)}").arg(QString::number(highlightBackColor.red()), QString::number(highlightBackColor.green()), QString::number(highlightBackColor.blue()), QString::number(highlightTextColor.red()), QString::number(highlightTextColor.green()), QString::number(highlightTextColor.blue()))); + picWidget->setStyleSheet(QString("QFrame#SnapmaticFrame{background-color:palette(highlight)}QLabel#labPicStr{color:palette(highlighted-text)}")); previousWidget = picWidget; } QMenu contextMenu(picWidget); - QMenu editMenu(SnapmaticWidget::tr("Edi&t"), picWidget); - if (picWidget->isHidden()) - { - editMenu.addAction(SnapmaticWidget::tr("Show &In-game"), picWidget, SLOT(makePictureVisibleSlot())); + const int selectedCount = selectedWidgets(); + if (contentMode < 20 || selectedCount == 0) { + QMenu editMenu(SnapmaticWidget::tr("Edi&t"), picWidget); + if (picWidget->isHidden()) { + editMenu.addAction(SnapmaticWidget::tr("Show &In-game"), picWidget, SLOT(makePictureVisibleSlot())); + } + else { + editMenu.addAction(SnapmaticWidget::tr("Hide &In-game"), picWidget, SLOT(makePictureHiddenSlot())); + } + editMenu.addAction(PictureDialog::tr("&Edit Properties..."), picWidget, SLOT(editSnapmaticProperties())); + editMenu.addAction(PictureDialog::tr("&Overwrite Image..."), picWidget, SLOT(editSnapmaticImage())); + editMenu.addSeparator(); + editMenu.addAction(PictureDialog::tr("Open &Map Viewer..."), picWidget, SLOT(openMapViewer())); + editMenu.addAction(PictureDialog::tr("Open &JSON Editor..."), picWidget, SLOT(editSnapmaticRawJson())); + QMenu exportMenu(SnapmaticWidget::tr("&Export"), this); + exportMenu.addAction(PictureDialog::tr("Export as &Picture..."), picWidget, SLOT(on_cmdExport_clicked())); + exportMenu.addAction(PictureDialog::tr("Export as &Snapmatic..."), picWidget, SLOT(on_cmdCopy_clicked())); + contextMenu.addAction(SnapmaticWidget::tr("&View"), picWidget, SLOT(on_cmdView_clicked())); + contextMenu.addMenu(&editMenu); + contextMenu.addMenu(&exportMenu); + contextMenu.addAction(SnapmaticWidget::tr("&Remove"), picWidget, SLOT(on_cmdDelete_clicked())); + contextMenu.addSeparator(); + if (!picWidget->isSelected()) + contextMenu.addAction(SnapmaticWidget::tr("&Select"), picWidget, SLOT(pictureSelected())); + else { + contextMenu.addAction(SnapmaticWidget::tr("&Deselect"), picWidget, SLOT(pictureSelected())); + } + if (selectedCount != widgets.count()) { + contextMenu.addAction(SnapmaticWidget::tr("Select &All"), picWidget, SLOT(selectAllWidgets()), QKeySequence::fromString("Ctrl+A")); + } + if (selectedCount != 0) { + contextMenu.addAction(SnapmaticWidget::tr("&Deselect All"), picWidget, SLOT(deselectAllWidgets()), QKeySequence::fromString("Ctrl+D")); + } + contextMenuOpened = true; + contextMenu.exec(ev->globalPos()); + contextMenuOpened = false; + QTimer::singleShot(0, this, SLOT(hoverProfileWidgetCheck())); } - else - { - editMenu.addAction(SnapmaticWidget::tr("Hide &In-game"), picWidget, SLOT(makePictureHiddenSlot())); + else { + QMenu editMenu(SnapmaticWidget::tr("Edi&t"), picWidget); + editMenu.addAction(QApplication::translate("UserInterface", "&Qualify as Avatar"), this, SLOT(massToolQualify()), QKeySequence::fromString("Shift+Q")); + editMenu.addAction(QApplication::translate("UserInterface", "Change &Players..."), this, SLOT(massToolPlayers()), QKeySequence::fromString("Shift+P")); + editMenu.addAction(QApplication::translate("UserInterface", "Change &Crew..."), this, SLOT(massToolCrew()), QKeySequence::fromString("Shift+C")); + editMenu.addAction(QApplication::translate("UserInterface", "Change &Title..."), this, SLOT(massToolTitle()), QKeySequence::fromString("Shift+T")); + editMenu.addSeparator(); + editMenu.addAction(SnapmaticWidget::tr("Show &In-game"), this, SLOT(enableSelected()), QKeySequence::fromString("Shift+E")); + editMenu.addAction(SnapmaticWidget::tr("Hide &In-game"), this, SLOT(disableSelected()), QKeySequence::fromString("Shift+D")); + contextMenu.addMenu(&editMenu); + contextMenu.addAction(SavegameWidget::tr("&Export"), this, SLOT(exportSelected()), QKeySequence::fromString("Ctrl+E")); + contextMenu.addAction(SavegameWidget::tr("&Remove"), this, SLOT(deleteSelectedR()), QKeySequence::fromString("Ctrl+Del")); + contextMenu.addSeparator(); + if (!picWidget->isSelected()) { + contextMenu.addAction(SnapmaticWidget::tr("&Select"), picWidget, SLOT(pictureSelected())); + } + else { + contextMenu.addAction(SnapmaticWidget::tr("&Deselect"), picWidget, SLOT(pictureSelected())); + } + if (selectedCount != widgets.count()) { + contextMenu.addAction(SnapmaticWidget::tr("Select &All"), picWidget, SLOT(selectAllWidgets()), QKeySequence::fromString("Ctrl+A")); + } + if (selectedCount != 0) { + contextMenu.addAction(SnapmaticWidget::tr("&Deselect All"), picWidget, SLOT(deselectAllWidgets()), QKeySequence::fromString("Ctrl+D")); + } + contextMenuOpened = true; + contextMenu.exec(ev->globalPos()); + contextMenuOpened = false; + QTimer::singleShot(0, this, SLOT(hoverProfileWidgetCheck())); } - editMenu.addAction(PictureDialog::tr("&Edit Properties..."), picWidget, SLOT(editSnapmaticProperties())); - editMenu.addAction(PictureDialog::tr("&Overwrite Image..."), picWidget, SLOT(editSnapmaticImage())); - editMenu.addSeparator(); - editMenu.addAction(PictureDialog::tr("Open &Map Viewer..."), picWidget, SLOT(openMapViewer())); - editMenu.addAction(PictureDialog::tr("Open &JSON Editor..."), picWidget, SLOT(editSnapmaticRawJson())); - QMenu exportMenu(SnapmaticWidget::tr("&Export"), this); - exportMenu.addAction(PictureDialog::tr("Export as &Picture..."), picWidget, SLOT(on_cmdExport_clicked())); - exportMenu.addAction(PictureDialog::tr("Export as &Snapmatic..."), picWidget, SLOT(on_cmdCopy_clicked())); - contextMenu.addAction(SnapmaticWidget::tr("&View"), picWidget, SLOT(on_cmdView_clicked())); - contextMenu.addMenu(&editMenu); - contextMenu.addMenu(&exportMenu); - contextMenu.addAction(SnapmaticWidget::tr("&Remove"), picWidget, SLOT(on_cmdDelete_clicked())); - contextMenu.addSeparator(); - if (!picWidget->isSelected()) { contextMenu.addAction(SnapmaticWidget::tr("&Select"), picWidget, SLOT(pictureSelected())); } - if (picWidget->isSelected()) { contextMenu.addAction(SnapmaticWidget::tr("&Deselect"), picWidget, SLOT(pictureSelected())); } - if (selectedWidgets() != widgets.count()) - { - contextMenu.addAction(SnapmaticWidget::tr("Select &All"), picWidget, SLOT(selectAllWidgets()), QKeySequence::fromString("Ctrl+A")); - } - if (selectedWidgets() != 0) - { - contextMenu.addAction(SnapmaticWidget::tr("&Deselect All"), picWidget, SLOT(deselectAllWidgets()), QKeySequence::fromString("Ctrl+D")); - } - contextMenuOpened = true; - contextMenu.exec(ev->globalPos()); - contextMenuOpened = false; - hoverProfileWidgetCheck(); } void ProfileInterface::contextMenuTriggeredSGD(QContextMenuEvent *ev) { SavegameWidget *sgdWidget = qobject_cast(sender()); - if (sgdWidget != previousWidget) - { - if (previousWidget != nullptr) - { + if (sgdWidget != previousWidget) { + if (previousWidget != nullptr) { previousWidget->setStyleSheet(QLatin1String("")); } - sgdWidget->setStyleSheet(QString("QFrame#SavegameFrame{background-color: rgb(%1, %2, %3)}QLabel#labSavegameStr{color: rgb(%4, %5, %6)}").arg(QString::number(highlightBackColor.red()), QString::number(highlightBackColor.green()), QString::number(highlightBackColor.blue()), QString::number(highlightTextColor.red()), QString::number(highlightTextColor.green()), QString::number(highlightTextColor.blue()))); + sgdWidget->setStyleSheet(QString("QFrame#SavegameFrame{background-color:palette(highlight)}QLabel#labSavegameStr{color:palette(highlighted-text)}")); previousWidget = sgdWidget; } QMenu contextMenu(sgdWidget); - contextMenu.addAction(SavegameWidget::tr("&View"), sgdWidget, SLOT(on_cmdView_clicked())); - contextMenu.addAction(SavegameWidget::tr("&Export"), sgdWidget, SLOT(on_cmdCopy_clicked())); - contextMenu.addAction(SavegameWidget::tr("&Remove"), sgdWidget, SLOT(on_cmdDelete_clicked())); - contextMenu.addSeparator(); - if (!sgdWidget->isSelected()) { contextMenu.addAction(SavegameWidget::tr("&Select"), sgdWidget, SLOT(savegameSelected())); } - if (sgdWidget->isSelected()) { contextMenu.addAction(SavegameWidget::tr("&Deselect"), sgdWidget, SLOT(savegameSelected())); } - if (selectedWidgets() != widgets.count()) - { - contextMenu.addAction(SavegameWidget::tr("Select &All"), sgdWidget, SLOT(selectAllWidgets()), QKeySequence::fromString("Ctrl+A")); + const int selectedCount = selectedWidgets(); + if (contentMode < 20 || selectedCount == 0) { + contextMenu.addAction(SavegameWidget::tr("&View"), sgdWidget, SLOT(on_cmdView_clicked())); + contextMenu.addAction(SavegameWidget::tr("&Export"), sgdWidget, SLOT(on_cmdCopy_clicked())); + contextMenu.addAction(SavegameWidget::tr("&Remove"), sgdWidget, SLOT(on_cmdDelete_clicked())); + contextMenu.addSeparator(); + if (!sgdWidget->isSelected()) { + contextMenu.addAction(SavegameWidget::tr("&Select"), sgdWidget, SLOT(savegameSelected())); + } + else { + contextMenu.addAction(SavegameWidget::tr("&Deselect"), sgdWidget, SLOT(savegameSelected())); + } + if (selectedCount != widgets.count()) { + contextMenu.addAction(SavegameWidget::tr("Select &All"), sgdWidget, SLOT(selectAllWidgets()), QKeySequence::fromString("Ctrl+A")); + } + if (selectedCount != 0) { + contextMenu.addAction(SavegameWidget::tr("&Deselect All"), sgdWidget, SLOT(deselectAllWidgets()), QKeySequence::fromString("Ctrl+D")); + } + contextMenuOpened = true; + contextMenu.exec(ev->globalPos()); + contextMenuOpened = false; + QTimer::singleShot(0, this, SLOT(hoverProfileWidgetCheck())); } - if (selectedWidgets() != 0) - { - contextMenu.addAction(SavegameWidget::tr("&Deselect All"), sgdWidget, SLOT(deselectAllWidgets()), QKeySequence::fromString("Ctrl+D")); + else { + QMenu editMenu(SnapmaticWidget::tr("Edi&t"), sgdWidget); + editMenu.addAction(QApplication::translate("UserInterface", "&Qualify as Avatar"), this, SLOT(massToolQualify()), QKeySequence::fromString("Shift+Q")); + editMenu.addAction(QApplication::translate("UserInterface", "Change &Players..."), this, SLOT(massToolPlayers()), QKeySequence::fromString("Shift+P")); + editMenu.addAction(QApplication::translate("UserInterface", "Change &Crew..."), this, SLOT(massToolCrew()), QKeySequence::fromString("Shift+C")); + editMenu.addAction(QApplication::translate("UserInterface", "Change &Title..."), this, SLOT(massToolTitle()), QKeySequence::fromString("Shift+T")); + editMenu.addSeparator(); + editMenu.addAction(SnapmaticWidget::tr("Show &In-game"), this, SLOT(enableSelected()), QKeySequence::fromString("Shift+E")); + editMenu.addAction(SnapmaticWidget::tr("Hide &In-game"), this, SLOT(disableSelected()), QKeySequence::fromString("Shift+D")); + contextMenu.addMenu(&editMenu); + contextMenu.addAction(SavegameWidget::tr("&Export"), this, SLOT(exportSelected()), QKeySequence::fromString("Ctrl+E")); + contextMenu.addAction(SavegameWidget::tr("&Remove"), this, SLOT(deleteSelectedR()), QKeySequence::fromString("Ctrl+Del")); + contextMenu.addSeparator(); + if (!sgdWidget->isSelected()) + contextMenu.addAction(SavegameWidget::tr("&Select"), sgdWidget, SLOT(savegameSelected())); + else { + contextMenu.addAction(SavegameWidget::tr("&Deselect"), sgdWidget, SLOT(savegameSelected())); + } + if (selectedCount != widgets.count()) { + contextMenu.addAction(SavegameWidget::tr("Select &All"), sgdWidget, SLOT(selectAllWidgets()), QKeySequence::fromString("Ctrl+A")); + } + if (selectedCount != 0) { + contextMenu.addAction(SavegameWidget::tr("&Deselect All"), sgdWidget, SLOT(deselectAllWidgets()), QKeySequence::fromString("Ctrl+D")); + } + contextMenuOpened = true; + contextMenu.exec(ev->globalPos()); + contextMenuOpened = false; + QTimer::singleShot(0, this, SLOT(hoverProfileWidgetCheck())); } - contextMenuOpened = true; - contextMenu.exec(ev->globalPos()); - contextMenuOpened = false; - hoverProfileWidgetCheck(); } void ProfileInterface::on_saProfileContent_dropped(const QMimeData *mimeData) { - if (!mimeData) return; - QStringList pathList; - - for (QUrl currentUrl : mimeData->urls()) - { - if (currentUrl.isLocalFile()) - { - pathList += currentUrl.toLocalFile(); - } + if (!mimeData) + return; + if (mimeData->hasImage()) { + QImage *snapmaticImage = new QImage(qvariant_cast(mimeData->imageData())); + importImage(snapmaticImage, QDateTime::currentDateTime()); } - - if (pathList.length() == 1) - { - QString selectedFile = pathList.at(0); - QDateTime importDateTime = QDateTime::currentDateTime(); - int currentTime = importDateTime.toString(importTimeFormat).toInt(); - importFile(selectedFile, QDateTime::currentDateTime(), ¤tTime, true); - } - else if (pathList.length() > 1) - { - importFilesProgress(pathList); + else if (mimeData->hasUrls()) { + importUrls(mimeData); } } void ProfileInterface::retranslateUi() { ui->retranslateUi(this); - ui->labVersion->setText(QString("%1 %2").arg(GTA5SYNC_APPSTR, GTA5SYNC_APPVER)); + QString appVersion = QApplication::applicationVersion(); + const char* literalBuildType = GTA5SYNC_BUILDTYPE; +#ifdef GTA5SYNC_COMMIT + if ((strcmp(literalBuildType, REL_BUILDTYPE) != 0) && !appVersion.contains("-")) + appVersion = appVersion % "-" % GTA5SYNC_COMMIT; +#endif + ui->labVersion->setText(QString("%1 %2").arg(GTA5SYNC_APPSTR, appVersion)); } bool ProfileInterface::eventFilter(QObject *watched, QEvent *event) { - if (event->type() == QEvent::MouseMove) - { - if ((watched->objectName() == "SavegameWidget" || watched->objectName() == "SnapmaticWidget") && isProfileLoaded) - { + if (event->type() == QEvent::KeyPress) { + if (isProfileLoaded) { + QKeyEvent *keyEvent = dynamic_cast(event); + switch (keyEvent->key()) { + case Qt::Key_V: + if (QApplication::keyboardModifiers().testFlag(Qt::ControlModifier) && !QApplication::keyboardModifiers().testFlag(Qt::ShiftModifier)) { + const QMimeData *clipboardData = QApplication::clipboard()->mimeData(); + if (clipboardData->hasImage()) { + QImage *snapmaticImage = new QImage(qvariant_cast(clipboardData->imageData())); + importImage(snapmaticImage, QDateTime::currentDateTime()); + } + else if (clipboardData->hasUrls()) { + if (clipboardData->urls().length() >= 2) { + importUrls(clipboardData); + } + else if (clipboardData->urls().length() == 1) { + QUrl clipboardUrl = clipboardData->urls().at(0); + if (clipboardUrl.isLocalFile()) { + importFile(clipboardUrl.toLocalFile(), QDateTime::currentDateTime(), true); + } + else { + importRemote(clipboardUrl); + } + } + } + else if (clipboardData->hasText()) { + QUrl clipboardUrl = QUrl::fromUserInput(clipboardData->text()); + if (clipboardUrl.isValid()) { + if (clipboardUrl.isLocalFile()) { + importFile(clipboardUrl.toLocalFile(), QDateTime::currentDateTime(), true); + } + else { + importRemote(clipboardUrl); + } + } + } + } + } + } + } + else if (event->type() == QEvent::MouseMove) { + if ((watched->objectName() == "SavegameWidget" || watched->objectName() == "SnapmaticWidget") && isProfileLoaded) { ProfileWidget *pWidget = qobject_cast(watched); - if (pWidget->underMouse()) - { + if (pWidget->underMouse()) { bool styleSheetChanged = false; - if (pWidget->getWidgetType() == "SnapmaticWidget") - { - if (pWidget != previousWidget) - { - pWidget->setStyleSheet(QString("QFrame#SnapmaticFrame{background-color: rgb(%1, %2, %3)}QLabel#labPicStr{color: rgb(%4, %5, %6)}").arg(QString::number(highlightBackColor.red()), QString::number(highlightBackColor.green()), QString::number(highlightBackColor.blue()), QString::number(highlightTextColor.red()), QString::number(highlightTextColor.green()), QString::number(highlightTextColor.blue()))); + if (pWidget->getWidgetType() == "SnapmaticWidget") { + if (pWidget != previousWidget) { + pWidget->setStyleSheet(QString("QFrame#SnapmaticFrame{background-color:palette(highlight)}QLabel#labPicStr{color:palette(highlighted-text)}")); styleSheetChanged = true; } } - else if (pWidget->getWidgetType() == "SavegameWidget") - { - if (pWidget != previousWidget) - { - pWidget->setStyleSheet(QString("QFrame#SavegameFrame{background-color: rgb(%1, %2, %3)}QLabel#labSavegameStr{color: rgb(%4, %5, %6)}").arg(QString::number(highlightBackColor.red()), QString::number(highlightBackColor.green()), QString::number(highlightBackColor.blue()), QString::number(highlightTextColor.red()), QString::number(highlightTextColor.green()), QString::number(highlightTextColor.blue()))); + else if (pWidget->getWidgetType() == "SavegameWidget") { + if (pWidget != previousWidget) { + pWidget->setStyleSheet(QString("QFrame#SavegameFrame{background-color:palette(highlight)}QLabel#labSavegameStr{color:palette(highlighted-text)}")); styleSheetChanged = true; } } - if (styleSheetChanged) - { - if (previousWidget != nullptr) - { + if (styleSheetChanged) { + if (previousWidget != nullptr) { previousWidget->setStyleSheet(QLatin1String("")); } previousWidget = pWidget; @@ -1389,43 +1886,33 @@ bool ProfileInterface::eventFilter(QObject *watched, QEvent *event) return true; } } - else if (event->type() == QEvent::MouseButtonPress || event->type() == QEvent::MouseButtonRelease || event->type() == QEvent::WindowActivate) - { - if ((watched->objectName() == "SavegameWidget" || watched->objectName() == "SnapmaticWidget") && isProfileLoaded) - { + else if (event->type() == QEvent::MouseButtonPress || event->type() == QEvent::MouseButtonRelease) { + if ((watched->objectName() == "SavegameWidget" || watched->objectName() == "SnapmaticWidget") && isProfileLoaded) { ProfileWidget *pWidget = nullptr; - for (ProfileWidget *widget : widgets.keys()) - { + for (auto it = widgets.constBegin(); it != widgets.constEnd(); it++) { + ProfileWidget *widget = it.key(); QPoint mousePos = widget->mapFromGlobal(QCursor::pos()); - if (widget->rect().contains(mousePos)) - { + if (widget->rect().contains(mousePos)) { pWidget = widget; break; } } - if (pWidget != nullptr) - { + if (pWidget != nullptr) { bool styleSheetChanged = false; - if (pWidget->getWidgetType() == "SnapmaticWidget") - { - if (pWidget != previousWidget) - { - pWidget->setStyleSheet(QString("QFrame#SnapmaticFrame{background-color: rgb(%1, %2, %3)}QLabel#labPicStr{color: rgb(%4, %5, %6)}").arg(QString::number(highlightBackColor.red()), QString::number(highlightBackColor.green()), QString::number(highlightBackColor.blue()), QString::number(highlightTextColor.red()), QString::number(highlightTextColor.green()), QString::number(highlightTextColor.blue()))); + if (pWidget->getWidgetType() == "SnapmaticWidget") { + if (pWidget != previousWidget) { + pWidget->setStyleSheet(QString("QFrame#SnapmaticFrame{background-color:palette(highlight)}QLabel#labPicStr{color:palette(highlighted-text)}")); styleSheetChanged = true; } } - else if (pWidget->getWidgetType() == "SavegameWidget") - { - if (pWidget != previousWidget) - { - pWidget->setStyleSheet(QString("QFrame#SavegameFrame{background-color: rgb(%1, %2, %3)}QLabel#labSavegameStr{color: rgb(%4, %5, %6)}").arg(QString::number(highlightBackColor.red()), QString::number(highlightBackColor.green()), QString::number(highlightBackColor.blue()), QString::number(highlightTextColor.red()), QString::number(highlightTextColor.green()), QString::number(highlightTextColor.blue()))); + else if (pWidget->getWidgetType() == "SavegameWidget") { + if (pWidget != previousWidget) { + pWidget->setStyleSheet(QString("QFrame#SavegameFrame{background-color:palette(highlight)}QLabel#labSavegameStr{color:palette(highlighted-text)}")); styleSheetChanged = true; } } - if (styleSheetChanged) - { - if (previousWidget != nullptr) - { + if (styleSheetChanged) { + if (previousWidget != nullptr) { previousWidget->setStyleSheet(QLatin1String("")); } previousWidget = pWidget; @@ -1433,29 +1920,29 @@ bool ProfileInterface::eventFilter(QObject *watched, QEvent *event) } } } - else if (event->type() == QEvent::WindowDeactivate && isProfileLoaded) - { - if (previousWidget != nullptr) - { + else if (event->type() == QEvent::WindowDeactivate && isProfileLoaded) { + if (previousWidget != nullptr && watched == previousWidget) { previousWidget->setStyleSheet(QLatin1String("")); previousWidget = nullptr; } } - else if (event->type() == QEvent::Leave && isProfileLoaded && !contextMenuOpened) - { - if (watched->objectName() == "SavegameWidget" || watched->objectName() == "SnapmaticWidget") - { + else if (event->type() == QEvent::Leave && isProfileLoaded && !contextMenuOpened) { + if (watched->objectName() == "SavegameWidget" || watched->objectName() == "SnapmaticWidget") { ProfileWidget *pWidget = qobject_cast(watched); QPoint mousePos = pWidget->mapFromGlobal(QCursor::pos()); - if (!pWidget->geometry().contains(mousePos)) - { - if (previousWidget != nullptr) - { + if (!pWidget->geometry().contains(mousePos)) { + if (previousWidget != nullptr) { previousWidget->setStyleSheet(QLatin1String("")); previousWidget = nullptr; } } } + else if (watched->objectName() == "ProfileInterface") { + if (previousWidget != nullptr) { + previousWidget->setStyleSheet(QLatin1String("")); + previousWidget = nullptr; + } + } } return false; } @@ -1463,46 +1950,36 @@ bool ProfileInterface::eventFilter(QObject *watched, QEvent *event) void ProfileInterface::hoverProfileWidgetCheck() { ProfileWidget *pWidget = nullptr; - for (ProfileWidget *widget : widgets.keys()) - { - if (widget->underMouse()) - { + for (auto it = widgets.constBegin(); it != widgets.constEnd(); it++) { + ProfileWidget *widget = it.key(); + if (widget->underMouse()) { pWidget = widget; break; } } - if (pWidget != nullptr) - { + if (pWidget != nullptr) { bool styleSheetChanged = false; - if (pWidget->getWidgetType() == "SnapmaticWidget") - { - if (pWidget != previousWidget) - { - pWidget->setStyleSheet(QString("QFrame#SnapmaticFrame{background-color: rgb(%1, %2, %3)}QLabel#labPicStr{color: rgb(%4, %5, %6)}").arg(QString::number(highlightBackColor.red()), QString::number(highlightBackColor.green()), QString::number(highlightBackColor.blue()), QString::number(highlightTextColor.red()), QString::number(highlightTextColor.green()), QString::number(highlightTextColor.blue()))); + if (pWidget->getWidgetType() == "SnapmaticWidget") { + if (pWidget != previousWidget) { + pWidget->setStyleSheet(QString("QFrame#SnapmaticFrame{background-color:palette(highlight)}QLabel#labPicStr{color:palette(highlighted-text)}")); styleSheetChanged = true; } } - else if (pWidget->getWidgetType() == "SavegameWidget") - { - if (pWidget != previousWidget) - { - pWidget->setStyleSheet(QString("QFrame#SavegameFrame{background-color: rgb(%1, %2, %3)}QLabel#labSavegameStr{color: rgb(%4, %5, %6)}").arg(QString::number(highlightBackColor.red()), QString::number(highlightBackColor.green()), QString::number(highlightBackColor.blue()), QString::number(highlightTextColor.red()), QString::number(highlightTextColor.green()), QString::number(highlightTextColor.blue()))); + else if (pWidget->getWidgetType() == "SavegameWidget") { + if (pWidget != previousWidget) { + pWidget->setStyleSheet(QString("QFrame#SavegameFrame{background-color:palette(highlight)}QLabel#labSavegameStr{color:palette(highlighted-text)}")); styleSheetChanged = true; } } - if (styleSheetChanged) - { - if (previousWidget != nullptr) - { + if (styleSheetChanged) { + if (previousWidget != nullptr) { previousWidget->setStyleSheet(QLatin1String("")); } previousWidget = pWidget; } } - else - { - if (previousWidget != nullptr) - { + else { + if (previousWidget != nullptr) { previousWidget->setStyleSheet(QLatin1String("")); previousWidget = nullptr; } @@ -1511,31 +1988,22 @@ void ProfileInterface::hoverProfileWidgetCheck() void ProfileInterface::updatePalette() { - QPalette palette; - QColor baseColor = palette.base().color(); - highlightBackColor = palette.highlight().color(); - highlightTextColor = palette.highlightedText().color(); - ui->saProfile->setStyleSheet(QString("QWidget#saProfileContent{background-color: rgb(%1, %2, %3)}").arg(QString::number(baseColor.red()), QString::number(baseColor.green()), QString::number(baseColor.blue()))); - if (previousWidget != nullptr) - { - if (previousWidget->getWidgetType() == "SnapmaticWidget") - { - previousWidget->setStyleSheet(QString("QFrame#SnapmaticFrame{background-color: rgb(%1, %2, %3)}QLabel#labPicStr{color: rgb(%4, %5, %6)}").arg(QString::number(highlightBackColor.red()), QString::number(highlightBackColor.green()), QString::number(highlightBackColor.blue()), QString::number(highlightTextColor.red()), QString::number(highlightTextColor.green()), QString::number(highlightTextColor.blue()))); + ui->saProfile->setStyleSheet(QString("QWidget#saProfileContent{background-color:palette(base)}")); + if (previousWidget != nullptr) { + if (previousWidget->getWidgetType() == "SnapmaticWidget") { + previousWidget->setStyleSheet(QString("QFrame#SnapmaticFrame{background-color:palette(highlight)}QLabel#labPicStr{color:palette(highlighted-text)}")); } - else if (previousWidget->getWidgetType() == "SavegameWidget") - { - previousWidget->setStyleSheet(QString("QFrame#SavegameFrame{background-color: rgb(%1, %2, %3)}QLabel#labSavegameStr{color: rgb(%4, %5, %6)}").arg(QString::number(highlightBackColor.red()), QString::number(highlightBackColor.green()), QString::number(highlightBackColor.blue()), QString::number(highlightTextColor.red()), QString::number(highlightTextColor.green()), QString::number(highlightTextColor.blue()))); + else if (previousWidget->getWidgetType() == "SavegameWidget") { + previousWidget->setStyleSheet(QString("QFrame#SnapmaticFrame{background-color:palette(highlight)}QLabel#labPicStr{color:palette(highlighted-text)}")); } } } bool ProfileInterface::isSupportedImageFile(QString selectedFileName) { - for (QByteArray imageFormat : QImageReader::supportedImageFormats()) - { + for (const QByteArray &imageFormat : QImageReader::supportedImageFormats()) { QString imageFormatStr = QString(".") % QString::fromUtf8(imageFormat).toLower(); - if (selectedFileName.length() >= imageFormatStr.length() && selectedFileName.toLower().right(imageFormatStr.length()) == imageFormatStr) - { + if (selectedFileName.length() >= imageFormatStr.length() && selectedFileName.toLower().right(imageFormatStr.length()) == imageFormatStr) { return true; } } @@ -1544,25 +2012,22 @@ bool ProfileInterface::isSupportedImageFile(QString selectedFileName) void ProfileInterface::massTool(MassTool tool) { - switch(tool) - { - case MassTool::Qualify: - { + switch(tool) { + case MassTool::Qualify: { QList snapmaticWidgets; - for (ProfileWidget *widget : widgets.keys()) - { - if (widget->isSelected()) - { - if (widget->getWidgetType() == "SnapmaticWidget") - { - SnapmaticWidget *snapmaticWidget = qobject_cast(widget); - snapmaticWidgets += snapmaticWidget; + for (const QString &widgetStr : qAsConst(widgets)) { + ProfileWidget *widget = widgets.key(widgetStr, nullptr); + if (widget != nullptr) { + if (widget->isSelected()) { + if (widget->getWidgetType() == "SnapmaticWidget") { + SnapmaticWidget *snapmaticWidget = qobject_cast(widget); + snapmaticWidgets += snapmaticWidget; + } } } } - if (snapmaticWidgets.isEmpty()) - { + if (snapmaticWidgets.isEmpty()) { QMessageBox::information(this, tr("Qualify as Avatar"), tr("No Snapmatic pictures are selected")); return; } @@ -1589,8 +2054,7 @@ void ProfileInterface::massTool(MassTool tool) // Begin Progress QStringList fails; - for (SnapmaticWidget *snapmaticWidget : snapmaticWidgets) - { + for (SnapmaticWidget *snapmaticWidget : qAsConst(snapmaticWidgets)) { // Update Progress overallId++; pbDialog.setValue(overallId); @@ -1608,60 +2072,57 @@ void ProfileInterface::massTool(MassTool tool) QString currentFilePath = picture->getPictureFilePath(); QString originalFilePath = picture->getOriginalPictureFilePath(); QString backupFileName = originalFilePath % ".bak"; - if (!QFile::exists(backupFileName)) - { + if (!QFile::exists(backupFileName)) { QFile::copy(currentFilePath, backupFileName); } SnapmaticProperties fallbackProperties = picture->getSnapmaticProperties(); picture->setSnapmaticProperties(snapmaticProperties); - if (!picture->exportPicture(currentFilePath)) - { + if (!picture->exportPicture(currentFilePath)) { picture->setSnapmaticProperties(fallbackProperties); fails << QString("%1 [%2]").arg(picture->getPictureTitle(), picture->getPictureString()); } - else - { + else { picture->emitUpdate(); - qApp->processEvents(); + QApplication::processEvents(); } } pbDialog.close(); - if (!fails.isEmpty()) - { + if (!fails.isEmpty()) { QMessageBox::warning(this, tr("Qualify as Avatar"), tr("%1 failed with...\n\n%2", "Action failed with...").arg(tr("Qualify", "%1 failed with..."), fails.join(", "))); } } break; - case MassTool::Players: - { + case MassTool::Players: { QList snapmaticWidgets; - for (ProfileWidget *widget : widgets.keys()) - { - if (widget->isSelected()) - { - if (widget->getWidgetType() == "SnapmaticWidget") - { - SnapmaticWidget *snapmaticWidget = qobject_cast(widget); - snapmaticWidgets += snapmaticWidget; + for (const QString &widgetStr : qAsConst(widgets)) { + ProfileWidget *widget = widgets.key(widgetStr, nullptr); + if (widget != nullptr) { + if (widget->isSelected()) { + if (widget->getWidgetType() == "SnapmaticWidget") { + SnapmaticWidget *snapmaticWidget = qobject_cast(widget); + snapmaticWidgets += snapmaticWidget; + } } } } - if (snapmaticWidgets.isEmpty()) - { + if (snapmaticWidgets.isEmpty()) { QMessageBox::information(this, tr("Change Players..."), tr("No Snapmatic pictures are selected")); return; } - PlayerListDialog *playerListDialog = new PlayerListDialog(QStringList(), profileDB, this); + QStringList players; + if (snapmaticWidgets.length() == 1) { + players = snapmaticWidgets.at(0)->getPicture()->getSnapmaticProperties().playersList; + } + + PlayerListDialog *playerListDialog = new PlayerListDialog(players, profileDB, this); playerListDialog->setModal(true); playerListDialog->show(); playerListDialog->exec(); if (!playerListDialog->isListUpdated()) - { return; - } - QStringList players = playerListDialog->getPlayerList(); + players = playerListDialog->getPlayerList(); delete playerListDialog; // Prepare Progress @@ -1686,8 +2147,7 @@ void ProfileInterface::massTool(MassTool tool) // Begin Progress QStringList fails; - for (SnapmaticWidget *snapmaticWidget : snapmaticWidgets) - { + for (SnapmaticWidget *snapmaticWidget : qAsConst(snapmaticWidgets)) { // Update Progress overallId++; pbDialog.setValue(overallId); @@ -1701,83 +2161,81 @@ void ProfileInterface::massTool(MassTool tool) QString currentFilePath = picture->getPictureFilePath(); QString originalFilePath = picture->getOriginalPictureFilePath(); QString backupFileName = originalFilePath % ".bak"; - if (!QFile::exists(backupFileName)) - { + if (!QFile::exists(backupFileName)) { QFile::copy(currentFilePath, backupFileName); } SnapmaticProperties fallbackProperties = picture->getSnapmaticProperties(); picture->setSnapmaticProperties(snapmaticProperties); - if (!picture->exportPicture(currentFilePath)) - { + if (!picture->exportPicture(currentFilePath)) { picture->setSnapmaticProperties(fallbackProperties); fails << QString("%1 [%2]").arg(picture->getPictureTitle(), picture->getPictureString()); } - else - { + else { picture->emitUpdate(); - qApp->processEvents(); + QApplication::processEvents(); } } pbDialog.close(); - if (!fails.isEmpty()) - { + if (!fails.isEmpty()) { QMessageBox::warning(this, tr("Change Players..."), tr("%1 failed with...\n\n%2", "Action failed with...").arg(tr("Change Players", "%1 failed with..."), fails.join(", "))); } } break; - case MassTool::Crew: - { + case MassTool::Crew: { QList snapmaticWidgets; - for (ProfileWidget *widget : widgets.keys()) - { - if (widget->isSelected()) - { - if (widget->getWidgetType() == "SnapmaticWidget") - { - SnapmaticWidget *snapmaticWidget = qobject_cast(widget); - snapmaticWidgets += snapmaticWidget; + for (const QString &widgetStr : qAsConst(widgets)) { + ProfileWidget *widget = widgets.key(widgetStr, nullptr); + if (widget != nullptr) { + if (widget->isSelected()) { + if (widget->getWidgetType() == "SnapmaticWidget") { + SnapmaticWidget *snapmaticWidget = qobject_cast(widget); + snapmaticWidgets += snapmaticWidget; + } } } } - if (snapmaticWidgets.isEmpty()) - { + if (snapmaticWidgets.isEmpty()) { QMessageBox::information(this, tr("Change Crew..."), tr("No Snapmatic pictures are selected")); return; } int crewID = 0; + if (snapmaticWidgets.length() == 1) { + crewID = snapmaticWidgets.at(0)->getPicture()->getSnapmaticProperties().crewID; + } { preSelectionCrewID: bool ok; + int indexNum = 0; QStringList itemList; QStringList crewList = crewDB->getCrews(); - if (!crewList.contains(QLatin1String("0"))) - { + if (!crewList.contains(QLatin1String("0"))) { crewList += QLatin1String("0"); } crewList.sort(); - for (QString crew : crewList) - { + for (QString crew : qAsConst(crewList)) { itemList += QString("%1 (%2)").arg(crew, crewDB->getCrewName(crew.toInt())); } - QString newCrew = QInputDialog::getItem(this, QApplication::translate("SnapmaticEditor", "Snapmatic Crew"), QApplication::translate("SnapmaticEditor", "New Snapmatic crew:"), itemList, 0, true, &ok, windowFlags()^Qt::Dialog^Qt::WindowMinMaxButtonsHint); - if (ok && !newCrew.isEmpty()) - { + if (crewList.contains(QString::number(crewID))) { + indexNum = crewList.indexOf(QString::number(crewID)); + } + QString newCrew = QInputDialog::getItem(this, QApplication::translate("SnapmaticEditor", "Snapmatic Crew"), QApplication::translate("SnapmaticEditor", "New Snapmatic crew:"), itemList, indexNum, true, &ok, windowFlags()^Qt::Dialog^Qt::WindowMinMaxButtonsHint); + if (ok && !newCrew.isEmpty()) { if (newCrew.contains(" ")) newCrew = newCrew.split(" ").at(0); if (newCrew.length() > 10) return; - for (QChar crewChar : newCrew) - { - if (!crewChar.isNumber()) - { + for (const QChar &crewChar : qAsConst(newCrew)) { + if (!crewChar.isNumber()) { QMessageBox::warning(this, tr("Change Crew..."), tr("Failed to enter a valid Snapmatic Crew ID")); goto preSelectionCrewID; } } + if (!crewList.contains(newCrew)) { + crewDB->addCrew(crewID); + } crewID = newCrew.toInt(); } - else - { + else { return; } } @@ -1804,8 +2262,7 @@ preSelectionCrewID: // Begin Progress QStringList fails; - for (SnapmaticWidget *snapmaticWidget : snapmaticWidgets) - { + for (SnapmaticWidget *snapmaticWidget : qAsConst(snapmaticWidgets)) { // Update Progress overallId++; pbDialog.setValue(overallId); @@ -1819,67 +2276,61 @@ preSelectionCrewID: QString currentFilePath = picture->getPictureFilePath(); QString originalFilePath = picture->getOriginalPictureFilePath(); QString backupFileName = originalFilePath % ".bak"; - if (!QFile::exists(backupFileName)) - { + if (!QFile::exists(backupFileName)) { QFile::copy(currentFilePath, backupFileName); } SnapmaticProperties fallbackProperties = picture->getSnapmaticProperties(); picture->setSnapmaticProperties(snapmaticProperties); - if (!picture->exportPicture(currentFilePath)) - { + if (!picture->exportPicture(currentFilePath)) { picture->setSnapmaticProperties(fallbackProperties); fails << QString("%1 [%2]").arg(picture->getPictureTitle(), picture->getPictureString()); } - else - { + else { picture->emitUpdate(); - qApp->processEvents(); + QApplication::processEvents(); } } pbDialog.close(); - if (!fails.isEmpty()) - { + if (!fails.isEmpty()) { QMessageBox::warning(this, tr("Change Crew..."), tr("%1 failed with...\n\n%2", "Action failed with...").arg(tr("Change Crew", "%1 failed with..."), fails.join(", "))); } } break; - case MassTool::Title: - { + case MassTool::Title: { QList snapmaticWidgets; - for (ProfileWidget *widget : widgets.keys()) - { - if (widget->isSelected()) - { - if (widget->getWidgetType() == "SnapmaticWidget") - { - SnapmaticWidget *snapmaticWidget = qobject_cast(widget); - snapmaticWidgets += snapmaticWidget; + for (const QString &widgetStr : qAsConst(widgets)) { + ProfileWidget *widget = widgets.key(widgetStr, nullptr); + if (widget != nullptr) { + if (widget->isSelected()) { + if (widget->getWidgetType() == "SnapmaticWidget") { + SnapmaticWidget *snapmaticWidget = qobject_cast(widget); + snapmaticWidgets += snapmaticWidget; + } } } } - if (snapmaticWidgets.isEmpty()) - { + if (snapmaticWidgets.isEmpty()) { QMessageBox::information(this, tr("Change Title..."), tr("No Snapmatic pictures are selected")); return; } QString snapmaticTitle; + if (snapmaticWidgets.length() == 1) { + snapmaticTitle = snapmaticWidgets.at(0)->getPicture()->getPictureTitle(); + } { preSelectionTitle: bool ok; QString newTitle = QInputDialog::getText(this, QApplication::translate("SnapmaticEditor", "Snapmatic Title"), QApplication::translate("SnapmaticEditor", "New Snapmatic title:"), QLineEdit::Normal, snapmaticTitle, &ok, windowFlags()^Qt::Dialog^Qt::WindowMinMaxButtonsHint); - if (ok && !newTitle.isEmpty()) - { - if (!SnapmaticPicture::verifyTitle(newTitle)) - { + if (ok && !newTitle.isEmpty()) { + if (!SnapmaticPicture::verifyTitle(newTitle)) { QMessageBox::warning(this, tr("Change Title..."), tr("Failed to enter a valid Snapmatic title")); goto preSelectionTitle; } snapmaticTitle = newTitle; } - else - { + else { return; } } @@ -1906,8 +2357,7 @@ preSelectionTitle: // Begin Progress QStringList fails; - for (SnapmaticWidget *snapmaticWidget : snapmaticWidgets) - { + for (SnapmaticWidget *snapmaticWidget : qAsConst(snapmaticWidgets)) { // Update Progress overallId++; pbDialog.setValue(overallId); @@ -1918,29 +2368,31 @@ preSelectionTitle: QString currentFilePath = picture->getPictureFilePath(); QString originalFilePath = picture->getOriginalPictureFilePath(); QString backupFileName = originalFilePath % ".bak"; - if (!QFile::exists(backupFileName)) - { + if (!QFile::exists(backupFileName)) { QFile::copy(currentFilePath, backupFileName); } QString fallbackTitle = picture->getPictureTitle(); picture->setPictureTitle(snapmaticTitle); - if (!picture->exportPicture(currentFilePath)) - { + if (!picture->exportPicture(currentFilePath)) { picture->setPictureTitle(fallbackTitle); fails << QString("%1 [%2]").arg(picture->getPictureTitle(), picture->getPictureString()); } - else - { + else { picture->emitUpdate(); - qApp->processEvents(); + QApplication::processEvents(); } } pbDialog.close(); - if (!fails.isEmpty()) - { + if (!fails.isEmpty()) { QMessageBox::warning(this, tr("Change Title..."), tr("%1 failed with...\n\n%2", "Action failed with...").arg(tr("Change Title", "%1 failed with..."), fails.join(", "))); } } break; } } + +int ProfileInterface::getRandomUid() +{ + int random_int = pcg32_boundedrand_r(&rng, 2147483647); + return random_int; +} diff --git a/ProfileInterface.h b/ProfileInterface.h index cba5d88..a3297ae 100644 --- a/ProfileInterface.h +++ b/ProfileInterface.h @@ -1,6 +1,6 @@ /***************************************************************************** -* gta5sync GRAND THEFT AUTO V SYNC -* Copyright (C) 2016-2017 Syping +* gta5view Grand Theft Auto V Profile Viewer +* Copyright (C) 2016-2021 Syping * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -29,6 +29,8 @@ #include "ExportThread.h" #include "SavegameData.h" #include "CrewDatabase.h" +#include "pcg_basic.h" +#include #include #include #include @@ -51,8 +53,6 @@ public: void settingsApplied(int contentMode, bool languageChanged); void setupProfileInterface(); void massTool(MassTool tool); - void disableSelected(); - void enableSelected(); int selectedWidgets(); void retranslateUi(); ~ProfileInterface(); @@ -63,8 +63,11 @@ public slots: void hoverProfileWidgetCheck(); void selectAllWidgets(); void deselectAllWidgets(); + void disableSelected(); + void enableSelected(); void exportSelected(); void deleteSelected(); + void deleteSelectedR(); void updatePalette(); void importFiles(); @@ -80,9 +83,17 @@ private slots: void profileLoaded_p(); void profileWidgetSelected(); void profileWidgetDeselected(); + void massToolQualify(); + void massToolPlayers(); + void massToolCrew(); + void massToolTitle(); void dialogNextPictureRequested(QWidget *dialog); void dialogPreviousPictureRequested(QWidget *dialog); void on_saProfileContent_dropped(const QMimeData *mimeData); +#if QT_VERSION >= 0x050000 + void directoryChanged(const QString &path); + void directoryScanned(QVector savegameFiles, QVector snapmaticPics); +#endif protected: bool eventFilter(QObject *watched, QEvent *event); @@ -98,32 +109,41 @@ private: QList savegames; QList pictures; QMap widgets; +#if QT_VERSION >= 0x050000 + QFileSystemWatcher fileSystemWatcher; + QVector savegameFiles; + QVector snapmaticPics; +#endif QSpacerItem *saSpacerItem; QStringList fixedPictures; - QColor highlightBackColor; - QColor highlightTextColor; QString enabledPicStr; QString profileFolder; QString profileName; QString loadingStr; QString language; + pcg32_random_t rng; bool contextMenuOpened; bool isProfileLoaded; int selectedWidgts; int contentMode; bool isSupportedImageFile(QString selectedFileName); - bool importFile(QString selectedFile, QDateTime importDateTime, int *currentTime, bool notMultiple); - void importFilesProgress(QStringList selectedFiles); + bool importFile(QString selectedFile, QDateTime importDateTime, bool notMultiple); + bool importUrls(const QMimeData *mimeData); + bool importRemote(QUrl remoteUrl); + bool importImage(QImage *snapmaticImage, QDateTime importDateTime); + bool importFilesProgress(QStringList selectedFiles); bool importSnapmaticPicture(SnapmaticPicture *picture, bool warn = true); bool importSavegameData(SavegameData *savegame, QString sgdPath, bool warn = true); void pictureLoaded(SnapmaticPicture *picture, bool inserted); void savegameLoaded(SavegameData *savegame, QString savegamePath, bool inserted); void savegameDeleted(SavegameWidget *sgdWidget, bool isRemoteEmited = false); void pictureDeleted(SnapmaticWidget *picWidget, bool isRemoteEmited = false); + void deleteSelectedL(bool isRemoteEmited = false); void insertSnapmaticIPI(QWidget *widget); void insertSavegameIPI(QWidget *widget); void sortingProfileInterface(); + int getRandomUid(); signals: void profileLoaded(); diff --git a/ProfileLoader.cpp b/ProfileLoader.cpp index 3874140..a4e4318 100644 --- a/ProfileLoader.cpp +++ b/ProfileLoader.cpp @@ -1,6 +1,6 @@ /***************************************************************************** -* gta5sync GRAND THEFT AUTO V SYNC -* Copyright (C) 2016-2017 Syping +* gta5view Grand Theft Auto V Profile Viewer +* Copyright (C) 2016-2021 Syping * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -16,80 +16,99 @@ * along with this program. If not, see . *****************************************************************************/ -#include "ProfileLoader.h" #include "SnapmaticPicture.h" +#include "ProfileLoader.h" #include "SavegameData.h" #include "CrewDatabase.h" +#include "wrapper.h" #include -#include +#include #include -#include -#include #include +#ifdef Q_OS_WIN #include +#include +#else +#include "sys/types.h" +#include "sys/stat.h" +#include "dirent.h" +#endif ProfileLoader::ProfileLoader(QString profileFolder, CrewDatabase *crewDB, QObject *parent) : QThread(parent), profileFolder(profileFolder), crewDB(crewDB) { - } void ProfileLoader::run() { int curFile = 1; - QDir profileDir; - QList crewList; - profileDir.setPath(profileFolder); + int maximumV = 0; + QVector crewList; + QVector savegameFiles; + QVector snapmaticPics; - // Seek pictures and savegames - profileDir.setNameFilters(QStringList("SGTA*")); - QStringList SavegameFiles = profileDir.entryList(QDir::Files | QDir::NoDot, QDir::NoSort); - QStringList BackupFiles = SavegameFiles.filter(".bak", Qt::CaseInsensitive); - profileDir.setNameFilters(QStringList("PGTA*")); - QStringList SnapmaticPics = profileDir.entryList(QDir::Files | QDir::NoDot, QDir::NoSort); - BackupFiles += SnapmaticPics.filter(".bak", Qt::CaseInsensitive); - - SavegameFiles.removeDuplicates(); - SnapmaticPics.removeDuplicates(); - for (QString BackupFile : BackupFiles) - { - SavegameFiles.removeAll(BackupFile); - SnapmaticPics.removeAll(BackupFile); +#ifdef Q_OS_WIN + QDir dir(profileFolder); + const QStringList files = dir.entryList(QDir::Files); + for (const QString &fileName : files) { + if (fileName.startsWith("SGTA5") && !fileName.endsWith(".bak")) { + savegameFiles << fileName; + maximumV++; + } + if (fileName.startsWith("PGTA5") && !fileName.endsWith(".bak")) { + snapmaticPics << fileName; + maximumV++; + } } +#else + DIR *dirp = opendir(profileFolder.toUtf8().constData()); + struct dirent *dp; + while ((dp = readdir(dirp)) != 0) { + const QString fileName = QString::fromUtf8(dp->d_name); + const QString filePath = profileFolder % "/" % fileName; + struct stat fileStat; + stat(filePath.toUtf8().constData(), &fileStat); + if (S_ISREG(fileStat.st_mode) != 0) { + if (fileName.startsWith("SGTA5") && !fileName.endsWith(".bak")) { + savegameFiles << fileName; + maximumV++; + } + if (fileName.startsWith("PGTA5") && !fileName.endsWith(".bak")) { + snapmaticPics << fileName; + maximumV++; + } + } + } + closedir(dirp); +#endif - int maximumV = SavegameFiles.length() + SnapmaticPics.length(); + // Directory successfully scanned + emit directoryScanned(savegameFiles, snapmaticPics); // Loading pictures and savegames emit loadingProgress(curFile, maximumV); - for (QString SavegameFile : SavegameFiles) - { + for (const QString &SavegameFile : qAsConst(savegameFiles)) { emit loadingProgress(curFile, maximumV); - QString sgdPath = profileFolder % "/" % SavegameFile; + const QString sgdPath = profileFolder % "/" % SavegameFile; SavegameData *savegame = new SavegameData(sgdPath); - if (savegame->readingSavegame()) - { + if (savegame->readingSavegame()) { emit savegameLoaded(savegame, sgdPath); } curFile++; } - for (QString SnapmaticPic : SnapmaticPics) - { + for (const QString &SnapmaticPic : qAsConst(snapmaticPics)) { emit loadingProgress(curFile, maximumV); - QString picturePath = profileFolder % "/" % SnapmaticPic; + const QString picturePath = profileFolder % "/" % SnapmaticPic; SnapmaticPicture *picture = new SnapmaticPicture(picturePath); - if (picture->readingPicture(true, true, true)) - { - if (picture->isFormatSwitched()) - { + if (picture->readingPicture(true)) { + if (picture->isFormatSwitched()) { picture->setSnapmaticFormat(SnapmaticFormat::PGTA_Format); - if (picture->exportPicture(picturePath, SnapmaticFormat::PGTA_Format)) - { + if (picture->exportPicture(picturePath, SnapmaticFormat::PGTA_Format)) { emit pictureFixed(picture); } } emit pictureLoaded(picture); int crewNumber = picture->getSnapmaticProperties().crewID; - if (!crewList.contains(crewNumber)) - { + if (!crewList.contains(crewNumber)) { crewList += crewNumber; } } @@ -98,8 +117,7 @@ void ProfileLoader::run() // adding found crews crewDB->setAddingCrews(true); - for (int crewID : crewList) - { + for (int crewID : qAsConst(crewList)) { crewDB->addCrew(crewID); } crewDB->setAddingCrews(false); @@ -107,10 +125,9 @@ void ProfileLoader::run() void ProfileLoader::preloaded() { - } void ProfileLoader::loaded() { - } + diff --git a/ProfileLoader.h b/ProfileLoader.h index 472c27d..6bde0ed 100644 --- a/ProfileLoader.h +++ b/ProfileLoader.h @@ -1,6 +1,6 @@ /***************************************************************************** -* gta5sync GRAND THEFT AUTO V SYNC -* Copyright (C) 2016-2017 Syping +* gta5view Grand Theft Auto V Profile Viewer +* Copyright (C) 2016-2021 Syping * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -48,6 +48,7 @@ signals: void pictureFixed(SnapmaticPicture *picture); void savegameLoaded(SavegameData *savegame, QString savegamePath); void loadingProgress(int value, int maximum); + void directoryScanned(QVector savegameFiles, QVector snapmaticPics); }; #endif // PROFILELOADER_H diff --git a/ProfileWidget.cpp b/ProfileWidget.cpp index 09b7a0f..a325d92 100644 --- a/ProfileWidget.cpp +++ b/ProfileWidget.cpp @@ -1,5 +1,5 @@ /***************************************************************************** -* gta5sync GRAND THEFT AUTO V SYNC +* gta5view Grand Theft Auto V Profile Viewer * Copyright (C) 2016-2017 Syping * * This program is free software: you can redistribute it and/or modify diff --git a/ProfileWidget.h b/ProfileWidget.h index 8de7cda..b7d2f77 100644 --- a/ProfileWidget.h +++ b/ProfileWidget.h @@ -1,5 +1,5 @@ /***************************************************************************** -* gta5sync GRAND THEFT AUTO V SYNC +* gta5view Grand Theft Auto V Profile Viewer * Copyright (C) 2016-2017 Syping * * This program is free software: you can redistribute it and/or modify diff --git a/README.md b/README.md index 9be8cfd..8831699 100644 --- a/README.md +++ b/README.md @@ -1,42 +1,45 @@ ## gta5view -Grand Theft Auto V Savegame and Snapmatic viewer/editor +Open Source Snapmatic and Savegame viewer/editor for GTA V - View Snapmatics with the ability to disable them in-game - Edit Snapmatic pictures and properties in multiple ways - Import/Export Snapmatics, Savegames and pictures -- Let choose between multiple Social Club accounts as GTA V profiles IDs +- Choose between multiple Social Club accounts as GTA V profiles IDs #### Screenshots -![Snapmatic Picture Viewer](https://i.imgur.com/dQdW3hx.png) -![User Interface](https://i.imgur.com/SRNQdq6.png) -![Snapmatic Properties](https://i.imgur.com/j1Lodiu.png) +![Snapmatic Picture Viewer](res/src/picture.png) +![User Interface](res/src/mainui.png) +![Snapmatic Properties](res/src/prop.png) + +#### Build gta5view for Windows + + # Note: Install Docker Community Edition and Git before continuing + docker pull sypingauto/gta5view-build:1.10-static + git clone https://gitlab.com/Syping/gta5view + docker run --rm -v "$PWD/gta5view:/gta5view" -it sypingauto/gta5view-build:1.10-static + mingw64-qt-cmake -B /gta5view/build /gta5view + cmake --build /gta5view/build #### Build gta5view for Debian/Ubuntu - # Note: You can use 'sudo make install' instead of 'sudo checkinstall' - sudo apt-get install git gcc g++ qtbase5-dev qttranslations5-l10n qt5-qmake make checkinstall - git clone https://github.com/SyDevTeam/gta5view && cd gta5view - mkdir -p build && cd build - qmake -qt=5 GTA5SYNC_PREFIX=/usr ../gta5view.pro # or just qmake GTA5SYNC_PREFIX=/usr ../gta5view.pro - make -j $(nproc --all) - sudo checkinstall --pkgname=gta5view --pkggroup=utility --requires=libqt5core5a,libqt5gui5,libqt5network5,libqt5widgets5,qttranslations5-l10n - -#### Build gta5view for Windows + sudo apt-get install cmake git gcc g++ libqt5svg5-dev make qtbase5-dev qttranslations5-l10n + git clone https://gitlab.com/Syping/gta5view + cmake -B gta5view-build gta5view + cmake --build gta5view-build + sudo cmake --install gta5view-build - # Note: Install Docker Community Edition and Git before continuing - git clone https://github.com/SyDevTeam/gta5view && cd gta5view - docker pull syping/qt5-static-mingw - docker run --rm -v ${PWD}:/gta5view -it syping/qt5-static-mingw - cd /gta5view && mkdir -p build && cd build - qmake-static ../gta5view.pro - make -j $(nproc --all) +#### Build gta5view for Arch/Manjaro -#### Build gta5view for Windows (Beginner) + sudo pacman -S cmake gcc git make qt5-base qt5-svg qt5-tools qt5-translations + git clone https://gitlab.com/Syping/gta5view + cmake -B gta5view-build gta5view + cmake --build gta5view-build + sudo cmake --install gta5view-build -Download the [Qt Framework](https://www.qt.io/) and install the MinGW version. -Download the Source Code over [GitHub](https://github.com/SyDevTeam/gta5view/archive/1.5.x.zip) or with your Git client. -Open the gta5view.pro Project file with Qt Creator and build it over Qt Creator. +#### Build gta5view for Fedora/RHEL -#### Download Binary Releases - -Go to [gta5view release](https://github.com/SyDevTeam/gta5view/releases) and download the .exe file for Windows, .deb file for Debian/Ubuntu and .dmg file for OS X. + sudo dnf install cmake git gcc gcc-c++ make qt5-qtbase-devel qt5-qtsvg-devel qt5-qttranslations + git clone https://gitlab.com/Syping/gta5view + cmake -B gta5view-build gta5view + cmake --build gta5view-build + sudo cmake --install gta5view-build diff --git a/RagePhoto.cpp b/RagePhoto.cpp new file mode 100644 index 0000000..ba03159 --- /dev/null +++ b/RagePhoto.cpp @@ -0,0 +1,893 @@ +/***************************************************************************** +* gta5view Grand Theft Auto V Profile Viewer +* Copyright (C) 2020-2022 Syping +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +*****************************************************************************/ + +#include "RagePhoto.h" +#include +#include +#include +#if QT_VERSION < 0x060000 +#include +#else +#include +#include +#endif +#ifdef RAGEPHOTO_BENCHMARK +#include +#include +#endif + +inline quint32 joaatFromSI(const char *data, size_t size) +{ + quint32 val = 0xE47AB81CUL; + for (size_t i = 0; i != size; i++) { + val += data[i]; + val += (val << 10); + val ^= (val >> 6); + } + val += (val << 3); + val ^= (val >> 11); + val += (val << 15); + return val; +} + +RagePhoto::RagePhoto() +{ + p_photoFormat = PhotoFormat::Undefined; + p_isLoaded = false; + p_inputMode = -1; +} + +RagePhoto::RagePhoto(const QByteArray &data) : p_fileData(data) +{ + p_photoFormat = PhotoFormat::Undefined; + p_isLoaded = false; + p_inputMode = 0; +} + +RagePhoto::RagePhoto(const QString &filePath) : p_filePath(filePath) +{ + p_photoFormat = PhotoFormat::Undefined; + p_isLoaded = false; + p_inputMode = 1; +} + +RagePhoto::RagePhoto(QIODevice *ioDevice) : p_ioDevice(ioDevice) +{ + p_photoFormat = PhotoFormat::Undefined; + p_isLoaded = false; + p_inputMode = 2; +} + +bool RagePhoto::isLoaded() +{ + return p_isLoaded; +} + +bool RagePhoto::load() +{ + if (p_inputMode == -1) + return false; + + if (p_isLoaded) + clear(); + + if (p_inputMode == 1) { + QFile pictureFile(p_filePath); + if (pictureFile.open(QIODevice::ReadOnly)) { + p_fileData = pictureFile.readAll(); + } + pictureFile.close(); + } + else if (p_inputMode == 2) { + if (!p_ioDevice->isOpen()) { + if (!p_ioDevice->open(QIODevice::ReadOnly)) + return false; + } + p_fileData = p_ioDevice->readAll(); + } + + QBuffer dataBuffer(&p_fileData); + dataBuffer.open(QIODevice::ReadOnly); + +#ifdef RAGEPHOTO_BENCHMARK + auto benchmark_parse_start = std::chrono::high_resolution_clock::now(); +#endif + + char uInt32Buffer[4]; + qint64 size = dataBuffer.read(uInt32Buffer, 4); + if (size != 4) + return false; + quint32 format = charToUInt32LE(uInt32Buffer); + + if (format == static_cast(PhotoFormat::GTA5)) { + char photoHeader[256]; + size = dataBuffer.read(photoHeader, 256); + if (size != 256) { + return false; + } + for (const QChar &photoChar : utf16LEToString(photoHeader, 256)) { + if (photoChar.isNull()) + break; + p_photoString += photoChar; + } + + size = dataBuffer.read(uInt32Buffer, 4); + if (size != 4) + return false; + p_headerSum = charToUInt32LE(uInt32Buffer); + + size = dataBuffer.read(uInt32Buffer, 4); + if (size != 4) + return false; + p_endOfFile = charToUInt32LE(uInt32Buffer); + + size = dataBuffer.read(uInt32Buffer, 4); + if (size != 4) + return false; + p_jsonOffset = charToUInt32LE(uInt32Buffer); + + size = dataBuffer.read(uInt32Buffer, 4); + if (size != 4) + return false; + p_titlOffset = charToUInt32LE(uInt32Buffer); + + size = dataBuffer.read(uInt32Buffer, 4); + if (size != 4) + return false; + p_descOffset = charToUInt32LE(uInt32Buffer); + + char markerBuffer[4]; + size = dataBuffer.read(markerBuffer, 4); + if (size != 4) + return false; + if (strncmp(markerBuffer, "JPEG", 4) != 0) + return false; + + size = dataBuffer.read(uInt32Buffer, 4); + if (size != 4) + return false; + p_photoBuffer = charToUInt32LE(uInt32Buffer); + + size = dataBuffer.read(uInt32Buffer, 4); + if (size != 4) + return false; + quint32 t_photoSize = charToUInt32LE(uInt32Buffer); + + char *photoData = static_cast(malloc(t_photoSize)); + if (!photoData) + return false; + size = dataBuffer.read(photoData, t_photoSize); + if (size != t_photoSize) { + free(photoData); + return false; + } + p_photoData = QByteArray(photoData, t_photoSize); + free(photoData); + + dataBuffer.seek(p_jsonOffset + 264); + size = dataBuffer.read(markerBuffer, 4); + if (size != 4) + return false; + if (strncmp(markerBuffer, "JSON", 4) != 0) + return false; + + size = dataBuffer.read(uInt32Buffer, 4); + if (size != 4) + return false; + p_jsonBuffer = charToUInt32LE(uInt32Buffer); + + char *jsonBytes = static_cast(malloc(p_jsonBuffer)); + if (!jsonBytes) + return false; + size = dataBuffer.read(jsonBytes, p_jsonBuffer); + if (size != p_jsonBuffer) { + free(jsonBytes); + return false; + } + quint32 i; + for (i = 0; i != p_jsonBuffer; i++) { + if (jsonBytes[i] == '\x00') + break; + } + p_jsonData = QByteArray(jsonBytes, i); + free(jsonBytes); + QJsonDocument t_jsonDocument = QJsonDocument::fromJson(p_jsonData); + if (t_jsonDocument.isNull()) + return false; + p_jsonObject = t_jsonDocument.object(); + + dataBuffer.seek(p_titlOffset + 264); + size = dataBuffer.read(markerBuffer, 4); + if (size != 4) + return false; + if (strncmp(markerBuffer, "TITL", 4) != 0) + return false; + + size = dataBuffer.read(uInt32Buffer, 4); + if (size != 4) + return false; + p_titlBuffer = charToUInt32LE(uInt32Buffer); + + char *titlBytes = static_cast(malloc(p_titlBuffer)); + if (!titlBytes) + return false; + size = dataBuffer.read(titlBytes, p_titlBuffer); + if (size != p_titlBuffer){ + free(titlBytes); + return false; + } + for (i = 0; i != p_titlBuffer; i++) { + if (titlBytes[i] == '\x00') + break; + } + p_titleString = QString::fromUtf8(titlBytes, i); + free(titlBytes); + + dataBuffer.seek(p_descOffset + 264); + size = dataBuffer.read(markerBuffer, 4); + if (size != 4) + return false; + if (strncmp(markerBuffer, "DESC", 4) != 0) + return false; + + size = dataBuffer.read(uInt32Buffer, 4); + if (size != 4) + return false; + p_descBuffer = charToUInt32LE(uInt32Buffer); + + char *descBytes = static_cast(malloc(p_descBuffer)); + if (!descBytes) + return false; + size = dataBuffer.read(descBytes, p_descBuffer); + if (size != p_descBuffer) { + free(descBytes); + return false; + } + for (i = 0; i != p_descBuffer; i++) { + if (descBytes[i] == '\x00') + break; + } + p_descriptionString = QString::fromUtf8(descBytes, i); + free(descBytes); + + dataBuffer.seek(p_endOfFile + 260); + size = dataBuffer.read(markerBuffer, 4); + if (size != 4) + return false; + if (strncmp(markerBuffer, "JEND", 4) != 0) + return false; + +#ifdef RAGEPHOTO_BENCHMARK + auto benchmark_parse_end = std::chrono::high_resolution_clock::now(); + auto benchmark_ns = std::chrono::duration_cast(benchmark_parse_end - benchmark_parse_start); + if (p_inputMode == 1) { + QTextStream(stdout) << QFileInfo(p_filePath).fileName() << ": " << benchmark_ns.count() << "ns" << Qt::endl; + } + else { + QTextStream(stdout) << "PGTA5" << p_jsonObject.value("uid").toInt() << ": " << benchmark_ns.count() << "ns" << Qt::endl; + } +#endif + + if (p_photoFormat != PhotoFormat::G5EX) + p_photoFormat = PhotoFormat::GTA5; + + p_fileData.clear(); + p_isLoaded = true; + return true; + } + else if (format == static_cast(PhotoFormat::G5EX)) { + size = dataBuffer.read(uInt32Buffer, 4); + if (size != 4) + return false; + format = charToUInt32LE(uInt32Buffer); + if (format == static_cast(ExportFormat::G5E3P)) { + size = dataBuffer.read(uInt32Buffer, 4); + if (size != 4) + return false; + quint32 compressedSize = charToUInt32LE(uInt32Buffer); + + char *compressedPhotoHeader = static_cast(malloc(compressedSize)); + if (!compressedPhotoHeader) + return false; + size = dataBuffer.read(compressedPhotoHeader, compressedSize); + if (size != compressedSize) { + free(compressedPhotoHeader); + return false; + } + QByteArray t_photoHeader = QByteArray::fromRawData(compressedPhotoHeader, compressedSize); + t_photoHeader = qUncompress(t_photoHeader); + free(compressedPhotoHeader); + if (t_photoHeader.isEmpty()) + return false; + p_photoString = QString::fromUtf8(t_photoHeader); + + size = dataBuffer.read(uInt32Buffer, 4); + if (size != 4) + return false; + p_headerSum = charToUInt32LE(uInt32Buffer); + + size = dataBuffer.read(uInt32Buffer, 4); + if (size != 4) + return false; + p_photoBuffer = charToUInt32LE(uInt32Buffer); + + size = dataBuffer.read(uInt32Buffer, 4); + if (size != 4) + return false; + compressedSize = charToUInt32LE(uInt32Buffer); + + char *compressedPhoto = static_cast(malloc(compressedSize)); + if (!compressedPhoto) + return false; + size = dataBuffer.read(compressedPhoto, compressedSize); + if (size != compressedSize) { + free(compressedPhoto); + return false; + } + QByteArray t_photoData = QByteArray::fromRawData(compressedPhoto, compressedSize); + p_photoData = qUncompress(t_photoData); + free(compressedPhoto); + + size = dataBuffer.read(uInt32Buffer, 4); + if (size != 4) + return false; + p_jsonOffset = charToUInt32LE(uInt32Buffer); + + size = dataBuffer.read(uInt32Buffer, 4); + if (size != 4) + return false; + p_jsonBuffer = charToUInt32LE(uInt32Buffer); + + size = dataBuffer.read(uInt32Buffer, 4); + if (size != 4) + return false; + compressedSize = charToUInt32LE(uInt32Buffer); + + char *compressedJson = static_cast(malloc(compressedSize)); + if (!compressedJson) + return false; + size = dataBuffer.read(compressedJson, compressedSize); + if (size != compressedSize) { + free(compressedJson); + return false; + } + QByteArray t_jsonBytes = QByteArray::fromRawData(compressedJson, compressedSize); + p_jsonData = qUncompress(t_jsonBytes); + free(compressedJson); + if (p_jsonData.isEmpty()) + return false; + QJsonDocument t_jsonDocument = QJsonDocument::fromJson(p_jsonData); + if (t_jsonDocument.isNull()) + return false; + p_jsonObject = t_jsonDocument.object(); + + size = dataBuffer.read(uInt32Buffer, 4); + if (size != 4) + return false; + p_titlOffset = charToUInt32LE(uInt32Buffer); + + size = dataBuffer.read(uInt32Buffer, 4); + if (size != 4) + return false; + p_titlBuffer = charToUInt32LE(uInt32Buffer); + + size = dataBuffer.read(uInt32Buffer, 4); + if (size != 4) + return false; + compressedSize = charToUInt32LE(uInt32Buffer); + + char *compressedTitl = static_cast(malloc(compressedSize)); + if (!compressedTitl) + return false; + size = dataBuffer.read(compressedTitl, compressedSize); + if (size != compressedSize) { + free(compressedTitl); + return false; + } + QByteArray t_titlBytes = QByteArray::fromRawData(compressedTitl, compressedSize); + t_titlBytes = qUncompress(t_titlBytes); + free(compressedTitl); + p_titleString = QString::fromUtf8(t_titlBytes); + + size = dataBuffer.read(uInt32Buffer, 4); + if (size != 4) + return false; + p_descOffset = charToUInt32LE(uInt32Buffer); + + size = dataBuffer.read(uInt32Buffer, 4); + if (size != 4) + return false; + p_descBuffer = charToUInt32LE(uInt32Buffer); + + size = dataBuffer.read(uInt32Buffer, 4); + if (size != 4) + return false; + compressedSize = charToUInt32LE(uInt32Buffer); + + char *compressedDesc = static_cast(malloc(compressedSize)); + if (!compressedDesc) + return false; + size = dataBuffer.read(compressedDesc, compressedSize); + if (size != compressedSize) { + free(compressedDesc); + return false; + } + QByteArray t_descBytes = QByteArray::fromRawData(compressedDesc, compressedSize); + t_descBytes = qUncompress(t_descBytes); + free(compressedDesc); + p_descriptionString = QString::fromUtf8(t_descBytes); + + size = dataBuffer.read(uInt32Buffer, 4); + if (size != 4) + return false; + p_endOfFile = charToUInt32LE(uInt32Buffer); + +#ifdef RAGEPHOTO_BENCHMARK + auto benchmark_parse_end = std::chrono::high_resolution_clock::now(); + auto benchmark_ns = std::chrono::duration_cast(benchmark_parse_end - benchmark_parse_start); + if (p_inputMode == 1) { + QTextStream(stdout) << QFileInfo(p_filePath).fileName() << ": " << benchmark_ns.count() << "ns" << Qt::endl; + } + else { + QTextStream(stdout) << "PGTA5" << p_jsonObject.value("uid").toInt() << ": " << benchmark_ns.count() << "ns" << Qt::endl; + } +#endif + + p_photoFormat = PhotoFormat::G5EX; + + p_fileData.clear(); + p_isLoaded = true; + return true; + } + else if (format == static_cast(ExportFormat::G5E2P)) { + p_photoFormat = PhotoFormat::G5EX; + p_fileData = qUncompress(dataBuffer.readAll()); + if (p_fileData.isEmpty()) + return false; + p_inputMode = 0; + return load(); + } + else if (format == static_cast(ExportFormat::G5E1P)) { +#if QT_VERSION >= 0x050A00 + size = dataBuffer.skip(1); + if (size != 1) + return false; +#else + if (!dataBuffer.seek(dataBuffer.pos() + 1)) + return false; +#endif + + char length[1]; + size = dataBuffer.read(length, 1); + if (size != 1) + return false; + int i_length = QByteArray::number(static_cast(length[0]), 16).toInt() + 6; + +#if QT_VERSION >= 0x050A00 + size = dataBuffer.skip(i_length); + if (size != i_length) + return false; +#else + if (!dataBuffer.seek(dataBuffer.pos() + i_length)) + return false; +#endif + + p_photoFormat = PhotoFormat::G5EX; + p_fileData = qUncompress(dataBuffer.readAll()); + if (p_fileData.isEmpty()) + return false; + p_inputMode = 0; + return load(); + } + else { + return false; + } + } + else { + return false; + } +} + +void RagePhoto::clear() +{ + p_photoFormat = PhotoFormat::Undefined; + p_jsonObject = QJsonObject(); + p_descriptionString.clear(); + p_jsonData.clear(); + p_photoData.clear(); + p_photoString.clear(); + p_titleString.clear(); + p_headerSum = 0; + p_isLoaded = false; +} + +void RagePhoto::setDescription(const QString &description) +{ + p_descriptionString = description; +} + +void RagePhoto::setFileData(const QByteArray &data) +{ + p_fileData = data; + p_inputMode = 0; +} + +void RagePhoto::setFilePath(const QString &filePath) +{ + p_filePath = filePath; + p_inputMode = 1; +} + +void RagePhoto::setIODevice(QIODevice *ioDevice) +{ + p_ioDevice = ioDevice; + p_inputMode = 2; +} + +bool RagePhoto::setJsonData(const QByteArray &data) +{ + QJsonDocument t_jsonDocument = QJsonDocument::fromJson(data); + if (t_jsonDocument.isNull()) + return false; + p_jsonObject = t_jsonDocument.object(); + // serializer band-aid + QJsonObject t_jsonObject = p_jsonObject; + t_jsonObject["sign"] = "__gta5view.sign"; + t_jsonDocument.setObject(t_jsonObject); + p_jsonData = t_jsonDocument.toJson(QJsonDocument::Compact); + char sign_char[24]; + sprintf(sign_char, "%llu", (0x100000000000000ULL | joaatFromSI(p_photoData.constData(), p_photoData.size()))); + p_jsonData.replace("\"__gta5view.sign\"", sign_char); + return true; +} + +bool RagePhoto::setPhotoBuffer(quint32 size, bool moveOffsets) +{ + if (size < static_cast(p_photoData.size())) + return false; + p_photoBuffer = size; + if (moveOffsets) { + p_jsonOffset = size + 28; + p_titlOffset = p_jsonOffset + p_jsonBuffer + 8; + p_descOffset = p_titlOffset + p_titlBuffer + 8; + p_endOfFile = p_descOffset + p_descBuffer + 12; + } + return true; +} + +bool RagePhoto::setPhotoData(const QByteArray &data) +{ + quint32 size = data.size(); + if (size > p_photoBuffer) + return false; + p_photoData = data; + // serializer band-aid + setJsonData(p_jsonData); + return true; +} + +bool RagePhoto::setPhotoData(const char *data, int size) +{ + if (static_cast(size) > p_photoBuffer) + return false; + p_photoData = QByteArray(data, size); + // serializer band-aid + setJsonData(p_jsonData); + return true; +} + +void RagePhoto::setPhotoFormat(PhotoFormat photoFormat) +{ + p_photoFormat = photoFormat; +} + +void RagePhoto::setTitle(const QString &title) +{ + p_titleString = title; +} + +const QByteArray RagePhoto::jsonData(JsonFormat jsonFormat) +{ + if (jsonFormat == JsonFormat::Compact) { + return QJsonDocument(p_jsonObject).toJson(QJsonDocument::Compact); + } + else if (jsonFormat == JsonFormat::Indented) { + return QJsonDocument(p_jsonObject).toJson(QJsonDocument::Indented); + } + else { + return p_jsonData; + } +} + +const QJsonObject RagePhoto::jsonObject() +{ + return p_jsonObject; +} + +const QByteArray RagePhoto::photoData() +{ + return p_photoData; +} + +const QString RagePhoto::description() +{ + return p_descriptionString; +} + +const QString RagePhoto::photoString() +{ + return p_photoString; +} + +const QString RagePhoto::title() +{ + return p_titleString; +} + +quint32 RagePhoto::photoBuffer() +{ + return p_photoBuffer; +} + +quint32 RagePhoto::photoSize() +{ + return p_photoData.size(); +} + +RagePhoto::PhotoFormat RagePhoto::photoFormat() +{ + return p_photoFormat; +} + +QByteArray RagePhoto::save(PhotoFormat photoFormat) +{ + QByteArray data; + QBuffer dataBuffer(&data); + dataBuffer.open(QIODevice::WriteOnly); + save(&dataBuffer, photoFormat); + return data; +} + +void RagePhoto::save(QIODevice *ioDevice, PhotoFormat photoFormat) +{ + // serializer band-aid + setJsonData(p_jsonData); + + if (photoFormat == PhotoFormat::G5EX) { + char uInt32Buffer[4]; + quint32 format = static_cast(PhotoFormat::G5EX); + uInt32ToCharLE(format, uInt32Buffer); + ioDevice->write(uInt32Buffer, 4); + format = static_cast(ExportFormat::G5E3P); + uInt32ToCharLE(format, uInt32Buffer); + ioDevice->write(uInt32Buffer, 4); + + QByteArray compressedData = qCompress(p_photoString.toUtf8(), 9); + quint32 compressedSize = compressedData.size(); + uInt32ToCharLE(compressedSize, uInt32Buffer); + ioDevice->write(uInt32Buffer, 4); + ioDevice->write(compressedData); + + uInt32ToCharLE(p_headerSum, uInt32Buffer); + ioDevice->write(uInt32Buffer, 4); + + uInt32ToCharLE(p_photoBuffer, uInt32Buffer); + ioDevice->write(uInt32Buffer, 4); + + compressedData = qCompress(p_photoData, 9); + compressedSize = compressedData.size(); + uInt32ToCharLE(compressedSize, uInt32Buffer); + ioDevice->write(uInt32Buffer, 4); + ioDevice->write(compressedData); + + uInt32ToCharLE(p_jsonOffset, uInt32Buffer); + ioDevice->write(uInt32Buffer, 4); + + uInt32ToCharLE(p_jsonBuffer, uInt32Buffer); + ioDevice->write(uInt32Buffer, 4); + + compressedData = qCompress(p_jsonData, 9); + compressedSize = compressedData.size(); + uInt32ToCharLE(compressedSize, uInt32Buffer); + ioDevice->write(uInt32Buffer, 4); + ioDevice->write(compressedData); + + uInt32ToCharLE(p_titlOffset, uInt32Buffer); + ioDevice->write(uInt32Buffer, 4); + + uInt32ToCharLE(p_titlBuffer, uInt32Buffer); + ioDevice->write(uInt32Buffer, 4); + + compressedData = qCompress(p_titleString.toUtf8(), 9); + compressedSize = compressedData.size(); + uInt32ToCharLE(compressedSize, uInt32Buffer); + ioDevice->write(uInt32Buffer, 4); + ioDevice->write(compressedData); + + uInt32ToCharLE(p_descOffset, uInt32Buffer); + ioDevice->write(uInt32Buffer, 4); + + uInt32ToCharLE(p_descBuffer, uInt32Buffer); + ioDevice->write(uInt32Buffer, 4); + + compressedData = qCompress(p_descriptionString.toUtf8(), 9); + compressedSize = compressedData.size(); + uInt32ToCharLE(compressedSize, uInt32Buffer); + ioDevice->write(uInt32Buffer, 4); + ioDevice->write(compressedData); + + uInt32ToCharLE(p_endOfFile, uInt32Buffer); + ioDevice->write(uInt32Buffer, 4); + } + else if (photoFormat == PhotoFormat::GTA5) { + char uInt32Buffer[4]; + quint32 format = static_cast(PhotoFormat::GTA5); + uInt32ToCharLE(format, uInt32Buffer); + ioDevice->write(uInt32Buffer, 4); + + QByteArray photoHeader = stringToUtf16LE(p_photoString); + if (photoHeader.startsWith("\xFF\xFE")) { + photoHeader.remove(0, 2); + } + qint64 photoHeaderSize = photoHeader.size(); + if (photoHeaderSize > 256) { + photoHeader = photoHeader.left(256); + photoHeaderSize = 256; + } + ioDevice->write(photoHeader); + for (qint64 size = photoHeaderSize; size < 256; size++) { + ioDevice->write("\x00", 1); + } + + uInt32ToCharLE(p_headerSum, uInt32Buffer); + ioDevice->write(uInt32Buffer, 4); + + uInt32ToCharLE(p_endOfFile, uInt32Buffer); + ioDevice->write(uInt32Buffer, 4); + + uInt32ToCharLE(p_jsonOffset, uInt32Buffer); + ioDevice->write(uInt32Buffer, 4); + + uInt32ToCharLE(p_titlOffset, uInt32Buffer); + ioDevice->write(uInt32Buffer, 4); + + uInt32ToCharLE(p_descOffset, uInt32Buffer); + ioDevice->write(uInt32Buffer, 4); + + ioDevice->write("JPEG", 4); + + uInt32ToCharLE(p_photoBuffer, uInt32Buffer); + ioDevice->write(uInt32Buffer, 4); + + quint32 t_photoSize = p_photoData.size(); + uInt32ToCharLE(t_photoSize, uInt32Buffer); + ioDevice->write(uInt32Buffer, 4); + + ioDevice->write(p_photoData); + for (qint64 size = t_photoSize; size < p_photoBuffer; size++) { + ioDevice->write("\x00", 1); + } + + ioDevice->seek(p_jsonOffset + 264); + ioDevice->write("JSON", 4); + + uInt32ToCharLE(p_jsonBuffer, uInt32Buffer); + ioDevice->write(uInt32Buffer, 4); + + qint64 dataSize = p_jsonData.size(); + ioDevice->write(p_jsonData); + for (qint64 size = dataSize; size < p_jsonBuffer; size++) { + ioDevice->write("\x00", 1); + } + + ioDevice->seek(p_titlOffset + 264); + ioDevice->write("TITL", 4); + + uInt32ToCharLE(p_titlBuffer, uInt32Buffer); + ioDevice->write(uInt32Buffer, 4); + + QByteArray data = p_titleString.toUtf8(); + dataSize = data.size(); + ioDevice->write(data); + for (qint64 size = dataSize; size < p_titlBuffer; size++) { + ioDevice->write("\x00", 1); + } + + ioDevice->seek(p_descOffset + 264); + ioDevice->write("DESC", 4); + + uInt32ToCharLE(p_descBuffer, uInt32Buffer); + ioDevice->write(uInt32Buffer, 4); + + data = p_descriptionString.toUtf8(); + dataSize = data.size(); + ioDevice->write(data); + for (qint64 size = dataSize; size < p_descBuffer; size++) { + ioDevice->write("\x00", 1); + } + + ioDevice->seek(p_endOfFile + 260); + ioDevice->write("JEND", 4); + } +} + +RagePhoto* RagePhoto::loadFile(const QString &filePath) +{ + RagePhoto *ragePhoto = new RagePhoto(filePath); + ragePhoto->load(); + return ragePhoto; +} + +quint32 RagePhoto::charToUInt32BE(char *x) +{ + return (static_cast(x[0]) << 24 | + static_cast(x[1]) << 16 | + static_cast(x[2]) << 8 | + static_cast(x[3])); +} + +quint32 RagePhoto::charToUInt32LE(char *x) +{ + return (static_cast(x[3]) << 24 | + static_cast(x[2]) << 16 | + static_cast(x[1]) << 8 | + static_cast(x[0])); +} + +void RagePhoto::uInt32ToCharBE(quint32 x, char *y) +{ + y[0] = x >> 24; + y[1] = x >> 16; + y[2] = x >> 8; + y[3] = x; +} + +void RagePhoto::uInt32ToCharLE(quint32 x, char *y) +{ + y[0] = x; + y[1] = x >> 8; + y[2] = x >> 16; + y[3] = x >> 24; +} + +const QByteArray RagePhoto::stringToUtf16LE(const QString &string) +{ +#if QT_VERSION >= 0x060000 + return QStringEncoder(QStringEncoder::Utf16LE)(string); +#else + return QTextCodec::codecForName("UTF-16LE")->fromUnicode(string); +#endif +} + +const QString RagePhoto::utf16LEToString(const QByteArray &data) +{ +#if QT_VERSION >= 0x060000 + return QStringDecoder(QStringDecoder::Utf16LE)(data); +#else + return QTextCodec::codecForName("UTF-16LE")->toUnicode(data); +#endif +} + +const QString RagePhoto::utf16LEToString(const char *data, int size) +{ +#if QT_VERSION >= 0x060000 + return QStringDecoder(QStringDecoder::Utf16LE)(QByteArray::fromRawData(data, size)); +#else + return QTextCodec::codecForName("UTF-16LE")->toUnicode(data, size); +#endif +} diff --git a/RagePhoto.h b/RagePhoto.h new file mode 100644 index 0000000..4f7a5ba --- /dev/null +++ b/RagePhoto.h @@ -0,0 +1,110 @@ +/***************************************************************************** +* gta5view Grand Theft Auto V Profile Viewer +* Copyright (C) 2020 Syping +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +*****************************************************************************/ + +#ifndef RAGEPHOTO_H +#define RAGEPHOTO_H + +#include +#include +#include + +class RagePhoto : public QObject +{ + Q_OBJECT +public: + enum class JsonFormat : quint8 { + Original = 0, + Compact = 1, + Indented = 2, + }; + enum class ExportFormat : quint32 { + G5E1P = 0x454C0010U, + G5E2P = 0x01000032U, + G5E2S = 0x02000032U, + G5E3P = 0x01000033U, + G5E3S = 0x02000033U, + Undefined = 0, + }; + enum class PhotoFormat : quint32 { + G5EX = 0x45354700U, + GTA5 = 0x01000000U, + RDR2 = 0x04000000U, + Undefined = 0, + }; + explicit RagePhoto(); + explicit RagePhoto(const QByteArray &data); + explicit RagePhoto(const QString &filePath); + explicit RagePhoto(QIODevice *ioDevice); + bool isLoaded(); + bool load(); + void clear(); + void setDescription(const QString &description); + void setFileData(const QByteArray &data); + void setFilePath(const QString &filePath); + void setIODevice(QIODevice *ioDevice); + bool setJsonData(const QByteArray &data); + bool setPhotoBuffer(quint32 size, bool moveOffsets = true); + bool setPhotoData(const QByteArray &data); + bool setPhotoData(const char *data, int size); + void setPhotoFormat(PhotoFormat photoFormat); + void setTitle(const QString &title); + const QJsonObject jsonObject(); + const QByteArray jsonData(JsonFormat jsonFormat = JsonFormat::Original); + const QByteArray photoData(); + const QString description(); + const QString photoString(); + const QString title(); + quint32 photoBuffer(); + quint32 photoSize(); + PhotoFormat photoFormat(); + QByteArray save(PhotoFormat photoFormat); + void save(QIODevice *ioDevice, PhotoFormat photoFormat); + static RagePhoto* loadFile(const QString &filePath); + +private: + inline quint32 charToUInt32BE(char *x); + inline quint32 charToUInt32LE(char *x); + inline void uInt32ToCharBE(quint32 x, char *y); + inline void uInt32ToCharLE(quint32 x, char *y); + inline const QByteArray stringToUtf16LE(const QString &string); + inline const QString utf16LEToString(const QByteArray &data); + inline const QString utf16LEToString(const char *data, int size); + PhotoFormat p_photoFormat; + QJsonObject p_jsonObject; + QByteArray p_fileData; + QByteArray p_jsonData; + QByteArray p_photoData; + QIODevice *p_ioDevice; + QString p_descriptionString; + QString p_filePath; + QString p_photoString; + QString p_titleString; + quint32 p_descBuffer; + quint32 p_descOffset; + quint32 p_endOfFile; + quint32 p_headerSum; + quint32 p_jsonBuffer; + quint32 p_jsonOffset; + quint32 p_photoBuffer; + quint32 p_titlBuffer; + quint32 p_titlOffset; + bool p_isLoaded; + int p_inputMode; +}; + +#endif // RAGEPHOTO_H diff --git a/SavegameCopy.cpp b/SavegameCopy.cpp index b0f8064..9ebbe66 100644 --- a/SavegameCopy.cpp +++ b/SavegameCopy.cpp @@ -1,5 +1,5 @@ /***************************************************************************** -* gta5sync GRAND THEFT AUTO V SYNC +* gta5view Grand Theft Auto V Profile Viewer * Copyright (C) 2016-2017 Syping * * This program is free software: you can redistribute it and/or modify diff --git a/SavegameCopy.h b/SavegameCopy.h index 6447497..f5550ba 100644 --- a/SavegameCopy.h +++ b/SavegameCopy.h @@ -1,5 +1,5 @@ /***************************************************************************** -* gta5sync GRAND THEFT AUTO V SYNC +* gta5view Grand Theft Auto V Profile Viewer * Copyright (C) 2016-2017 Syping * * This program is free software: you can redistribute it and/or modify diff --git a/SavegameData.cpp b/SavegameData.cpp index 826746a..42a9ae3 100644 --- a/SavegameData.cpp +++ b/SavegameData.cpp @@ -1,5 +1,5 @@ /***************************************************************************** -* gta5sync GRAND THEFT AUTO V SYNC +* gta5view Grand Theft Auto V Profile Viewer * Copyright (C) 2016-2017 Syping * * This program is free software: you can redistribute it and/or modify @@ -16,10 +16,10 @@ * along with this program. If not, see . *****************************************************************************/ +#include "SnapmaticPicture.h" #include "StringParser.h" #include "SavegameData.h" #include -#include #include #include #include @@ -42,7 +42,7 @@ bool SavegameData::readingSavegame() QFile *saveFile = new QFile(savegameFileName); if (!saveFile->open(QFile::ReadOnly)) { - lastStep = "1;/1,OpenFile," % StringParser::convertDrawStringForLog(savegameFileName); + lastStep = "1;/1,OpenFile," % SnapmaticPicture::convertDrawStringForLog(savegameFileName); saveFile->deleteLater(); delete saveFile; return false; @@ -51,7 +51,7 @@ bool SavegameData::readingSavegame() // Reading Savegame Header if (!saveFile->isReadable()) { - lastStep = "2;/3,ReadingFile," % StringParser::convertDrawStringForLog(savegameFileName) % ",1,NOHEADER"; + lastStep = "2;/3,ReadingFile," % SnapmaticPicture::convertDrawStringForLog(savegameFileName) % ",1,NOHEADER"; saveFile->close(); saveFile->deleteLater(); delete saveFile; @@ -78,7 +78,7 @@ QString SavegameData::getSavegameDataString(const QByteArray &savegameHeader) QList savegameBytesList = savegameBytes.split(char(0x01)); savegameBytes = savegameBytesList.at(1); savegameBytesList.clear(); - return StringParser::parseTitleString(savegameBytes, savegameBytes.length()); + return SnapmaticPicture::parseTitleString(savegameBytes, savegameBytes.length()); } bool SavegameData::readingSavegameFromFile(const QString &fileName) diff --git a/SavegameData.h b/SavegameData.h index bc5cf2f..bedb57c 100644 --- a/SavegameData.h +++ b/SavegameData.h @@ -1,5 +1,5 @@ /***************************************************************************** -* gta5sync GRAND THEFT AUTO V SYNC +* gta5view Grand Theft Auto V Profile Viewer * Copyright (C) 2016-2017 Syping * * This program is free software: you can redistribute it and/or modify diff --git a/SavegameDialog.cpp b/SavegameDialog.cpp index 5182e3f..27b0229 100644 --- a/SavegameDialog.cpp +++ b/SavegameDialog.cpp @@ -1,5 +1,5 @@ /***************************************************************************** -* gta5sync GRAND THEFT AUTO V SYNC +* gta5view Grand Theft Auto V Profile Viewer * Copyright (C) 2016-2018 Syping * * This program is free software: you can redistribute it and/or modify @@ -28,7 +28,11 @@ SavegameDialog::SavegameDialog(QWidget *parent) : ui(new Ui::SavegameDialog) { // Set Window Flags +#if QT_VERSION >= 0x050900 + setWindowFlag(Qt::WindowContextHelpButtonHint, false); +#else setWindowFlags(windowFlags()^Qt::WindowContextHelpButtonHint); +#endif // Setup User Interface ui->setupUi(this); diff --git a/SavegameDialog.h b/SavegameDialog.h index cce13cc..4abbba4 100644 --- a/SavegameDialog.h +++ b/SavegameDialog.h @@ -1,5 +1,5 @@ /***************************************************************************** -* gta5sync GRAND THEFT AUTO V SYNC +* gta5view Grand Theft Auto V Profile Viewer * Copyright (C) 2016-2018 Syping * * This program is free software: you can redistribute it and/or modify diff --git a/SavegameDialog.ui b/SavegameDialog.ui index 36b3b9b..ac9481e 100644 --- a/SavegameDialog.ui +++ b/SavegameDialog.ui @@ -7,7 +7,7 @@ 0 0 400 - 105 + 112 @@ -20,7 +20,7 @@ - <span style=" font-weight:600;">Savegame</span><br><br>%1 + <span style="font-weight:600">Savegame</span><br><br>%1 Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop diff --git a/SavegameWidget.cpp b/SavegameWidget.cpp index 7d81d92..cede630 100644 --- a/SavegameWidget.cpp +++ b/SavegameWidget.cpp @@ -1,6 +1,6 @@ /***************************************************************************** -* gta5sync GRAND THEFT AUTO V SYNC -* Copyright (C) 2016-2017 Syping +* gta5view Grand Theft Auto V Profile Viewer +* Copyright (C) 2016-2021 Syping * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -24,6 +24,8 @@ #include "SavegameData.h" #include "SavegameCopy.h" #include "AppEnv.h" +#include "config.h" +#include #include #include #include @@ -37,6 +39,13 @@ #include #include +#ifdef GTA5SYNC_TELEMETRY +#include "TelemetryClass.h" +#include +#include +#include +#endif + SavegameWidget::SavegameWidget(QWidget *parent) : ProfileWidget(parent), ui(new Ui::SavegameWidget) @@ -50,9 +59,8 @@ SavegameWidget::SavegameWidget(QWidget *parent) : qreal screenRatio = AppEnv::screenRatio(); ui->labSavegamePic->setFixedSize(48 * screenRatio, 27 * screenRatio); - QPixmap savegamePixmap(":/img/savegame.png"); - if (screenRatio != 1) savegamePixmap = savegamePixmap.scaledToHeight(ui->labSavegamePic->height(), Qt::SmoothTransformation); - ui->labSavegamePic->setPixmap(savegamePixmap); + ui->labSavegamePic->setScaledContents(true); + ui->labSavegamePic->setPixmap(QPixmap(AppEnv::getImagesFolder() % "/savegame.svgz")); QString exportSavegameStr = tr("Export Savegame..."); Q_UNUSED(exportSavegameStr) @@ -134,9 +142,49 @@ void SavegameWidget::on_cmdDelete_clicked() if (!QFile::exists(sgdPath)) { emit savegameDeleted(); +#ifdef GTA5SYNC_TELEMETRY + QSettings telemetrySettings(GTA5SYNC_APPVENDOR, GTA5SYNC_APPSTR); + telemetrySettings.beginGroup("Telemetry"); + bool pushUsageData = telemetrySettings.value("PushUsageData", false).toBool(); + telemetrySettings.endGroup(); + if (pushUsageData && Telemetry->canPush()) + { + QJsonDocument jsonDocument; + QJsonObject jsonObject; + jsonObject["Type"] = "DeleteSuccess"; + jsonObject["ExtraFlags"] = "Savegame"; +#if QT_VERSION >= 0x060000 + jsonObject["DeletedTime"] = QString::number(QDateTime::currentDateTimeUtc().toSecsSinceEpoch()); +#else + jsonObject["DeletedTime"] = QString::number(QDateTime::currentDateTimeUtc().toTime_t()); +#endif + jsonDocument.setObject(jsonObject); + Telemetry->push(TelemetryCategory::PersonalData, jsonDocument); + } +#endif } - else if(QFile::remove(sgdPath)) + else if (QFile::remove(sgdPath)) { +#ifdef GTA5SYNC_TELEMETRY + QSettings telemetrySettings(GTA5SYNC_APPVENDOR, GTA5SYNC_APPSTR); + telemetrySettings.beginGroup("Telemetry"); + bool pushUsageData = telemetrySettings.value("PushUsageData", false).toBool(); + telemetrySettings.endGroup(); + if (pushUsageData && Telemetry->canPush()) + { + QJsonDocument jsonDocument; + QJsonObject jsonObject; + jsonObject["Type"] = "DeleteSuccess"; + jsonObject["ExtraFlags"] = "Savegame"; +#if QT_VERSION >= 0x060000 + jsonObject["DeletedTime"] = QString::number(QDateTime::currentDateTimeUtc().toSecsSinceEpoch()); +#else + jsonObject["DeletedTime"] = QString::number(QDateTime::currentDateTimeUtc().toTime_t()); +#endif + jsonDocument.setObject(jsonObject); + Telemetry->push(TelemetryCategory::PersonalData, jsonDocument); + } +#endif emit savegameDeleted(); } else @@ -178,7 +226,8 @@ void SavegameWidget::mouseReleaseEvent(QMouseEvent *ev) } else { - if (getContentMode() == 0 && rect().contains(ev->pos()) && ev->button() == Qt::LeftButton) + const int contentMode = getContentMode(); + if ((contentMode == 0 || contentMode == 10 || contentMode == 20) && rect().contains(ev->pos()) && ev->button() == Qt::LeftButton) { if (ev->modifiers().testFlag(Qt::ShiftModifier)) { @@ -189,7 +238,7 @@ void SavegameWidget::mouseReleaseEvent(QMouseEvent *ev) on_cmdView_clicked(); } } - else if (!ui->cbSelected->isVisible() && getContentMode() == 1 && ev->button() == Qt::LeftButton && ev->modifiers().testFlag(Qt::ShiftModifier)) + else if (!ui->cbSelected->isVisible() && (contentMode == 1 || contentMode == 11 || contentMode == 21) && ev->button() == Qt::LeftButton && ev->modifiers().testFlag(Qt::ShiftModifier)) { ui->cbSelected->setChecked(!ui->cbSelected->isChecked()); } @@ -200,7 +249,8 @@ void SavegameWidget::mouseDoubleClickEvent(QMouseEvent *ev) { ProfileWidget::mouseDoubleClickEvent(ev); - if (!ui->cbSelected->isVisible() && getContentMode() == 1 && ev->button() == Qt::LeftButton) + const int contentMode = getContentMode(); + if (!ui->cbSelected->isVisible() && (contentMode == 1 || contentMode == 11 || contentMode == 21) && ev->button() == Qt::LeftButton) { on_cmdView_clicked(); } diff --git a/SavegameWidget.h b/SavegameWidget.h index 0db69af..103fa55 100644 --- a/SavegameWidget.h +++ b/SavegameWidget.h @@ -1,5 +1,5 @@ /***************************************************************************** -* gta5sync GRAND THEFT AUTO V SYNC +* gta5view Grand Theft Auto V Profile Viewer * Copyright (C) 2016-2017 Syping * * This program is free software: you can redistribute it and/or modify diff --git a/SidebarGenerator.cpp b/SidebarGenerator.cpp index 1d57e5d..e93474c 100644 --- a/SidebarGenerator.cpp +++ b/SidebarGenerator.cpp @@ -1,5 +1,5 @@ /***************************************************************************** -* gta5sync GRAND THEFT AUTO V SYNC +* gta5view Grand Theft Auto V Profile Viewer * Copyright (C) 2016-2017 Syping * * This program is free software: you can redistribute it and/or modify diff --git a/SidebarGenerator.h b/SidebarGenerator.h index 5e906a3..446f73c 100644 --- a/SidebarGenerator.h +++ b/SidebarGenerator.h @@ -1,5 +1,5 @@ /***************************************************************************** -* gta5sync GRAND THEFT AUTO V SYNC +* gta5view Grand Theft Auto V Profile Viewer * Copyright (C) 2016-2017 Syping * * This program is free software: you can redistribute it and/or modify diff --git a/SnapmaticEditor.cpp b/SnapmaticEditor.cpp index 954571c..f07346e 100644 --- a/SnapmaticEditor.cpp +++ b/SnapmaticEditor.cpp @@ -1,6 +1,6 @@ /***************************************************************************** -* gta5sync GRAND THEFT AUTO V SYNC -* Copyright (C) 2016-2018 Syping +* gta5view Grand Theft Auto V Profile Viewer +* Copyright (C) 2016-2021 Syping * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -21,8 +21,9 @@ #include "SnapmaticPicture.h" #include "PlayerListDialog.h" #include "StringParser.h" +#include "wrapper.h" #include "AppEnv.h" -#include +#include "config.h" #include #include #include @@ -30,45 +31,49 @@ #include #include +#ifdef GTA5SYNC_TELEMETRY +#include "TelemetryClass.h" +#include +#include +#endif + SnapmaticEditor::SnapmaticEditor(CrewDatabase *crewDB, ProfileDatabase *profileDB, QWidget *parent) : QDialog(parent), crewDB(crewDB), profileDB(profileDB), ui(new Ui::SnapmaticEditor) { // Set Window Flags +#if QT_VERSION >= 0x050900 + setWindowFlag(Qt::WindowContextHelpButtonHint, false); +#else setWindowFlags(windowFlags()^Qt::WindowContextHelpButtonHint); +#endif ui->setupUi(this); + ui->cmdCancel->setDefault(true); ui->cmdCancel->setFocus(); // Set Icon for Apply Button - if (QIcon::hasThemeIcon("dialog-ok-apply")) - { + if (QIcon::hasThemeIcon("dialog-ok-apply")) { ui->cmdApply->setIcon(QIcon::fromTheme("dialog-ok-apply")); } - else if (QIcon::hasThemeIcon("dialog-apply")) - { + else if (QIcon::hasThemeIcon("dialog-apply")) { ui->cmdApply->setIcon(QIcon::fromTheme("dialog-apply")); } - else if (QIcon::hasThemeIcon("gtk-apply")) - { + else if (QIcon::hasThemeIcon("gtk-apply")) { ui->cmdApply->setIcon(QIcon::fromTheme("gtk-apply")); } - else if (QIcon::hasThemeIcon("dialog-ok")) - { + else if (QIcon::hasThemeIcon("dialog-ok")) { ui->cmdApply->setIcon(QIcon::fromTheme("dialog-ok")); } - else if (QIcon::hasThemeIcon("gtk-ok")) - { + else if (QIcon::hasThemeIcon("gtk-ok")) { ui->cmdApply->setIcon(QIcon::fromTheme("dialog-ok")); } // Set Icon for Cancel Button - if (QIcon::hasThemeIcon("dialog-cancel")) - { + if (QIcon::hasThemeIcon("dialog-cancel")) { ui->cmdCancel->setIcon(QIcon::fromTheme("dialog-cancel")); } - else if (QIcon::hasThemeIcon("gtk-cancel")) - { + else if (QIcon::hasThemeIcon("gtk-cancel")) { ui->cmdCancel->setIcon(QIcon::fromTheme("gtk-cancel")); } @@ -89,27 +94,18 @@ SnapmaticEditor::~SnapmaticEditor() void SnapmaticEditor::selfie_toggled(bool checked) { - if (checked) - { - isSelfie = true; - } - else - { - isSelfie = false; - } + isSelfie = checked; } void SnapmaticEditor::mugshot_toggled(bool checked) { - if (checked) - { + if (checked) { isMugshot = true; ui->cbDirector->setEnabled(false); ui->cbDirector->setChecked(false); } - else - { + else { isMugshot = false; ui->cbDirector->setEnabled(true); } @@ -117,14 +113,12 @@ void SnapmaticEditor::mugshot_toggled(bool checked) void SnapmaticEditor::editor_toggled(bool checked) { - if (checked) - { + if (checked) { isEditor = true; ui->cbDirector->setEnabled(false); ui->cbDirector->setChecked(false); } - else - { + else { isEditor = false; ui->cbDirector->setEnabled(true); } @@ -132,8 +126,7 @@ void SnapmaticEditor::editor_toggled(bool checked) void SnapmaticEditor::on_rbSelfie_toggled(bool checked) { - if (checked) - { + if (checked) { mugshot_toggled(false); editor_toggled(false); selfie_toggled(true); @@ -142,8 +135,7 @@ void SnapmaticEditor::on_rbSelfie_toggled(bool checked) void SnapmaticEditor::on_rbMugshot_toggled(bool checked) { - if (checked) - { + if (checked) { selfie_toggled(false); editor_toggled(false); mugshot_toggled(true); @@ -152,8 +144,7 @@ void SnapmaticEditor::on_rbMugshot_toggled(bool checked) void SnapmaticEditor::on_rbEditor_toggled(bool checked) { - if (checked) - { + if (checked) { selfie_toggled(false); mugshot_toggled(false); editor_toggled(true); @@ -162,8 +153,7 @@ void SnapmaticEditor::on_rbEditor_toggled(bool checked) void SnapmaticEditor::on_rbCustom_toggled(bool checked) { - if (checked) - { + if (checked) { selfie_toggled(false); mugshot_toggled(false); editor_toggled(false); @@ -182,20 +172,16 @@ void SnapmaticEditor::setSnapmaticPicture(SnapmaticPicture *picture) playersList = snapmaticProperties.playersList; ui->cbDirector->setChecked(snapmaticProperties.isFromDirector); ui->cbMeme->setChecked(snapmaticProperties.isMeme); - if (isSelfie) - { + if (isSelfie) { ui->rbSelfie->setChecked(true); } - else if (isMugshot) - { + else if (isMugshot) { ui->rbMugshot->setChecked(true); } - else if (isEditor) - { + else if (isEditor) { ui->rbEditor->setChecked(true); } - else - { + else { ui->rbCustom->setChecked(true); } setSnapmaticCrew(returnCrewName(crewID)); @@ -205,8 +191,7 @@ void SnapmaticEditor::setSnapmaticPicture(SnapmaticPicture *picture) void SnapmaticEditor::insertPlayerNames(QStringList *players) { - for (int i = 0; i < players->size(); ++i) - { + for (int i = 0; i < players->size(); ++i) { players->replace(i, profileDB->getPlayerName(players->at(i))); } } @@ -222,54 +207,48 @@ void SnapmaticEditor::setSnapmaticPlayers(const QStringList &players) { QString editStr = QString("%1").arg(tr("Edit")); QString playersStr; - if (players.length() != 1) - { + if (players.length() != 1) { playersStr = tr("Players: %1 (%2)", "Multiple Player are inserted here"); } - else - { + else { playersStr = tr("Player: %1 (%2)", "One Player is inserted here"); } - if (players.length() != 0) - { + if (players.length() != 0) { ui->labPlayers->setText(playersStr.arg(players.join(", "), editStr)); } - else - { + else { ui->labPlayers->setText(playersStr.arg(QApplication::translate("PictureDialog", "No Players"), editStr)); } #ifndef Q_OS_ANDROID ui->gbValues->resize(ui->gbValues->width(), ui->gbValues->heightForWidth(ui->gbValues->width())); ui->frameWidget->resize(ui->gbValues->width(), ui->frameWidget->heightForWidth(ui->frameWidget->width())); - if (heightForWidth(width()) > height()) { resize(width(), heightForWidth(width())); } + if (heightForWidth(width()) > height()) + resize(width(), heightForWidth(width())); #endif } void SnapmaticEditor::setSnapmaticTitle(const QString &title) { - if (title.length() > 39) - { + if (title.length() > 39) { snapmaticTitle = title.left(39); } - else - { + else { snapmaticTitle = title; } QString editStr = QString("%1").arg(tr("Edit")); QString titleStr = tr("Title: %1 (%2)").arg(StringParser::escapeString(snapmaticTitle), editStr); ui->labTitle->setText(titleStr); - if (SnapmaticPicture::verifyTitle(snapmaticTitle)) - { - ui->labAppropriate->setText(tr("Appropriate: %1").arg(QString("%1").arg(tr("Yes", "Yes, should work fine")))); + if (SnapmaticPicture::verifyTitle(snapmaticTitle)) { + ui->labAppropriate->setText(tr("Appropriate: %1").arg(QString("%1").arg(tr("Yes", "Yes, should work fine")))); } - else - { - ui->labAppropriate->setText(tr("Appropriate: %1").arg(QString("%1").arg(tr("No", "No, could lead to issues")))); + else { + ui->labAppropriate->setText(tr("Appropriate: %1").arg(QString("%1").arg(tr("No", "No, could lead to issues")))); } #ifndef Q_OS_ANDROID ui->gbValues->resize(ui->gbValues->width(), ui->gbValues->heightForWidth(ui->gbValues->width())); ui->frameWidget->resize(ui->gbValues->width(), ui->frameWidget->heightForWidth(ui->frameWidget->width())); - if (heightForWidth(width()) > height()) { resize(width(), heightForWidth(width())); } + if (heightForWidth(width()) > height()) + resize(width(), heightForWidth(width())); #endif } @@ -281,7 +260,8 @@ void SnapmaticEditor::setSnapmaticCrew(const QString &crew) #ifndef Q_OS_ANDROID ui->gbValues->resize(ui->gbValues->width(), ui->gbValues->heightForWidth(ui->gbValues->width())); ui->frameWidget->resize(ui->gbValues->width(), ui->frameWidget->heightForWidth(ui->frameWidget->width())); - if (heightForWidth(width()) > height()) { resize(width(), heightForWidth(width())); } + if (heightForWidth(width()) > height()) + resize(width(), heightForWidth(width())); #endif } @@ -297,8 +277,7 @@ void SnapmaticEditor::on_cmdCancel_clicked() void SnapmaticEditor::on_cmdApply_clicked() { - if (ui->cbQualify->isChecked()) - { + if (ui->cbQualify->isChecked()) { qualifyAvatar(); } snapmaticProperties.crewID = crewID; @@ -308,28 +287,44 @@ void SnapmaticEditor::on_cmdApply_clicked() snapmaticProperties.isFromDirector = ui->cbDirector->isChecked(); snapmaticProperties.isMeme = ui->cbMeme->isChecked(); snapmaticProperties.playersList = playersList; - if (smpic) - { + if (smpic) { QString currentFilePath = smpic->getPictureFilePath(); QString originalFilePath = smpic->getOriginalPictureFilePath(); QString backupFileName = originalFilePath % ".bak"; - if (!QFile::exists(backupFileName)) - { + if (!QFile::exists(backupFileName)) { QFile::copy(currentFilePath, backupFileName); } SnapmaticProperties fallbackProperties = smpic->getSnapmaticProperties(); QString fallbackTitle = smpic->getPictureTitle(); smpic->setSnapmaticProperties(snapmaticProperties); smpic->setPictureTitle(snapmaticTitle); - if (!smpic->exportPicture(currentFilePath)) - { + if (!smpic->exportPicture(currentFilePath)) { QMessageBox::warning(this, tr("Snapmatic Properties"), tr("Patching of Snapmatic Properties failed because of I/O Error")); smpic->setSnapmaticProperties(fallbackProperties); smpic->setPictureTitle(fallbackTitle); } - else - { + else { + smpic->updateStrings(); smpic->emitUpdate(); +#ifdef GTA5SYNC_TELEMETRY + QSettings telemetrySettings(GTA5SYNC_APPVENDOR, GTA5SYNC_APPSTR); + telemetrySettings.beginGroup("Telemetry"); + bool pushUsageData = telemetrySettings.value("PushUsageData", false).toBool(); + telemetrySettings.endGroup(); + if (pushUsageData && Telemetry->canPush()) { + QJsonDocument jsonDocument; + QJsonObject jsonObject; + jsonObject["Type"] = "PropertyEdited"; + jsonObject["EditedSize"] = QString::number(smpic->getContentMaxLength()); +#if QT_VERSION >= 0x060000 + jsonObject["EditedTime"] = QString::number(QDateTime::currentDateTimeUtc().toSecsSinceEpoch()); +#else + jsonObject["EditedTime"] = QString::number(QDateTime::currentDateTimeUtc().toTime_t()); +#endif + jsonDocument.setObject(jsonObject); + Telemetry->push(TelemetryCategory::PersonalData, jsonDocument); + } +#endif } } close(); @@ -345,8 +340,7 @@ void SnapmaticEditor::qualifyAvatar() void SnapmaticEditor::on_cbQualify_toggled(bool checked) { - if (checked) - { + if (checked) { ui->cbMeme->setEnabled(false); ui->cbDirector->setEnabled(false); ui->rbCustom->setEnabled(false); @@ -354,15 +348,13 @@ void SnapmaticEditor::on_cbQualify_toggled(bool checked) ui->rbEditor->setEnabled(false); ui->rbMugshot->setEnabled(false); } - else - { + else { ui->cbMeme->setEnabled(true); ui->rbCustom->setEnabled(true); ui->rbSelfie->setEnabled(true); ui->rbEditor->setEnabled(true); ui->rbMugshot->setEnabled(true); - if (ui->rbSelfie->isChecked() || ui->rbCustom->isChecked()) - { + if (ui->rbSelfie->isChecked() || ui->rbCustom->isChecked()) { ui->cbDirector->setEnabled(true); } } @@ -370,8 +362,7 @@ void SnapmaticEditor::on_cbQualify_toggled(bool checked) void SnapmaticEditor::on_labPlayers_linkActivated(const QString &link) { - if (link == "g5e://editplayers") - { + if (link == "g5e://editplayers") { PlayerListDialog *playerListDialog = new PlayerListDialog(playersList, profileDB, this); connect(playerListDialog, SIGNAL(playerListUpdated(QStringList)), this, SLOT(playerListUpdated(QStringList))); playerListDialog->setModal(true); @@ -383,12 +374,10 @@ void SnapmaticEditor::on_labPlayers_linkActivated(const QString &link) void SnapmaticEditor::on_labTitle_linkActivated(const QString &link) { - if (link == "g5e://edittitle") - { + if (link == "g5e://edittitle") { bool ok; QString newTitle = QInputDialog::getText(this, tr("Snapmatic Title"), tr("New Snapmatic title:"), QLineEdit::Normal, snapmaticTitle, &ok, windowFlags()); - if (ok && !newTitle.isEmpty()) - { + if (ok && !newTitle.isEmpty()) { setSnapmaticTitle(newTitle); } } @@ -396,37 +385,35 @@ void SnapmaticEditor::on_labTitle_linkActivated(const QString &link) void SnapmaticEditor::on_labCrew_linkActivated(const QString &link) { - if (link == "g5e://editcrew") - { + if (link == "g5e://editcrew") { bool ok; int indexNum = 0; QStringList itemList; QStringList crewList = crewDB->getCrews(); - if (!crewList.contains(QLatin1String("0"))) - { + if (!crewList.contains(QLatin1String("0"))) { crewList += QLatin1String("0"); } crewList.sort(); - for (QString crew : crewList) - { + for (const QString &crew : crewList) { itemList += QString("%1 (%2)").arg(crew, returnCrewName(crew.toInt())); } - if (crewList.contains(QString::number(crewID))) - { - indexNum = crewList.indexOf(QRegExp(QString::number(crewID))); + if (crewList.contains(QString::number(crewID))) { + indexNum = crewList.indexOf(QString::number(crewID)); } QString newCrew = QInputDialog::getItem(this, tr("Snapmatic Crew"), tr("New Snapmatic crew:"), itemList, indexNum, true, &ok, windowFlags()); - if (ok && !newCrew.isEmpty()) - { - if (newCrew.contains(" ")) newCrew = newCrew.split(" ").at(0); - if (newCrew.length() > 10) return; - for (QChar crewChar : newCrew) - { - if (!crewChar.isNumber()) - { + if (ok && !newCrew.isEmpty()) { + if (newCrew.contains(" ")) + newCrew = newCrew.split(" ").at(0); + if (newCrew.length() > 10) + return; + for (const QChar &crewChar : qAsConst(newCrew)) { + if (!crewChar.isNumber()) { return; } } + if (!crewList.contains(newCrew)) { + crewDB->addCrew(crewID); + } crewID = newCrew.toInt(); setSnapmaticCrew(returnCrewName(crewID)); } diff --git a/SnapmaticEditor.h b/SnapmaticEditor.h index 1e57294..98ed2d6 100644 --- a/SnapmaticEditor.h +++ b/SnapmaticEditor.h @@ -1,5 +1,5 @@ /***************************************************************************** -* gta5sync GRAND THEFT AUTO V SYNC +* gta5view Grand Theft Auto V Profile Viewer * Copyright (C) 2016-2017 Syping * * This program is free software: you can redistribute it and/or modify diff --git a/SnapmaticEditor.ui b/SnapmaticEditor.ui index e591fcc..fc9ede9 100644 --- a/SnapmaticEditor.ui +++ b/SnapmaticEditor.ui @@ -236,6 +236,9 @@ 0 + + Apply changes + &Apply @@ -249,6 +252,9 @@ 0 + + Discard changes + &Cancel diff --git a/SnapmaticPicture.cpp b/SnapmaticPicture.cpp index d5414f7..c3664fd 100644 --- a/SnapmaticPicture.cpp +++ b/SnapmaticPicture.cpp @@ -1,6 +1,6 @@ /***************************************************************************** -* gta5sync-spv Grand Theft Auto Snapmatic Picture Viewer -* Copyright (C) 2016-2017 Syping +* gta5spv Grand Theft Auto Snapmatic Picture Viewer +* Copyright (C) 2016-2021 Syping * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -17,7 +17,6 @@ *****************************************************************************/ #include "SnapmaticPicture.h" -#include "StringParser.h" #include #include #include @@ -33,30 +32,18 @@ #include #include +#if QT_VERSION < 0x060000 +#include +#else +#include +#endif + #if QT_VERSION >= 0x050000 #include #else #include "StandardPaths.h" #endif -// PARSER ALLOCATIONS -#define snapmaticHeaderLength 278 -#define snapmaticUsefulLength 260 -#define snapmaticFileMaxSize 528192 -#define jpegHeaderLineDifStr 2 -#define jpegPreHeaderLength 14 -#define jpegPicStreamLength 524288 -#define jsonStreamLength 3076 -#define tideStreamLength 260 - -// EDITOR ALLOCATIONS -#define jpegStreamEditorBegin 292 -#define jsonStreamEditorBegin 524588 -#define jsonStreamEditorLength 3072 -#define titlStreamEditorBegin 527668 -#define titlStreamEditorLength 256 -#define titlStreamCharacterMax 39 - // IMAGES VALUES #define snapmaticResolutionW 960 #define snapmaticResolutionH 536 @@ -74,38 +61,19 @@ SnapmaticPicture::~SnapmaticPicture() void SnapmaticPicture::reset() { // INIT PIC - rawPicContent.clear(); - rawPicContent.squeeze(); + p_ragePhoto.clear(); cachePicture = QImage(); picExportFileName = QString(); - pictureHead = QString(); pictureStr = QString(); lastStep = QString(); sortStr = QString(); - titlStr = QString(); - descStr = QString(); - - // INIT PIC INTS - jpegRawContentSizeE = 0; - jpegRawContentSize = 0; // INIT PIC BOOLS - isCustomFormat = false; isFormatSwitch = false; - isLoadedInRAM = false; - lowRamMode = false; picOk = false; // INIT JSON jsonOk = false; - jsonStr = QString(); - - // SNAPMATIC DEFAULTS -#ifdef GTA5SYNC_CSDF - careSnapDefault = false; -#else - careSnapDefault = true; -#endif // SNAPMATIC PROPERTIES localProperties = {}; @@ -116,315 +84,61 @@ bool SnapmaticPicture::preloadFile() QFile *picFile = new QFile(picFilePath); picFileName = QFileInfo(picFilePath).fileName(); - bool g5eMode = false; isFormatSwitch = false; - if (!picFile->open(QFile::ReadOnly)) - { - lastStep = "1;/1,OpenFile," % StringParser::convertDrawStringForLog(picFilePath); + if (!picFile->open(QFile::ReadOnly)) { + lastStep = "1;/1,OpenFile," % convertDrawStringForLog(picFilePath); delete picFile; return false; } - if (picFilePath.right(4) != QLatin1String(".g5e")) - { - rawPicContent = picFile->read(snapmaticFileMaxSize + 1024); - picFile->close(); - delete picFile; - if (rawPicContent.mid(1, 3) == QByteArray("G5E")) - { + p_ragePhoto.setIODevice(picFile); + bool ok = p_ragePhoto.load(); + picFile->close(); + delete picFile; + if (!ok) + return false; + + if (picFilePath.right(4) != QLatin1String(".g5e")) { + if (p_ragePhoto.photoFormat() == RagePhoto::PhotoFormat::G5EX) isFormatSwitch = true; - } - else - { - isCustomFormat = false; - isLoadedInRAM = true; - } - } - else - { - g5eMode = true; - } - if (g5eMode || isFormatSwitch) - { - QByteArray g5eContent; - if (!isFormatSwitch) - { - g5eContent = picFile->read(snapmaticFileMaxSize + 1024); - picFile->close(); - delete picFile; - } - else - { - g5eContent = rawPicContent; - rawPicContent.clear(); - } - - // Set Custom Format - isCustomFormat = true; - - // Reading g5e Content - g5eContent.remove(0, 1); - if (g5eContent.left(3) == QByteArray("G5E")) - { - g5eContent.remove(0, 3); - if (g5eContent.left(2).toHex() == QByteArray("1000")) - { - g5eContent.remove(0, 2); - if (g5eContent.left(3) == QByteArray("LEN")) - { - g5eContent.remove(0, 3); - int fileNameLength = g5eContent.left(1).toHex().toInt(); - g5eContent.remove(0, 1); - if (g5eContent.left(3) == QByteArray("FIL")) - { - g5eContent.remove(0, 3); - picFileName = g5eContent.left(fileNameLength); - g5eContent.remove(0, fileNameLength); - if (g5eContent.left(3) == QByteArray("COM")) - { - g5eContent.remove(0, 3); - rawPicContent = qUncompress(g5eContent); - - // Setting is values - isLoadedInRAM = true; - } - else - { - lastStep = "2;/3,ReadingFile," % StringParser::convertDrawStringForLog(picFilePath) % ",4,G5E_FORMATERROR"; - return false; - } - } - else - { - lastStep = "2;/3,ReadingFile," % StringParser::convertDrawStringForLog(picFilePath) % ",3,G5E_FORMATERROR"; - return false; - } - } - else - { - lastStep = "2;/3,ReadingFile," % StringParser::convertDrawStringForLog(picFilePath) % ",2,G5E_FORMATERROR"; - return false; - } - } - else - { - lastStep = "2;/3,ReadingFile," % StringParser::convertDrawStringForLog(picFilePath) % ",1,G5E_NOTCOMPATIBLE"; - return false; - } - } - else - { - lastStep = "2;/3,ReadingFile," % StringParser::convertDrawStringForLog(picFilePath) % ",1,G5E_FORMATERROR"; - return false; - } } emit preloaded(); - return true; + return ok; } -bool SnapmaticPicture::readingPicture(bool writeEnabled_, bool cacheEnabled_, bool fastLoad, bool lowRamMode_) +bool SnapmaticPicture::readingPicture(bool cacheEnabled_) { // Start opening file // lastStep is like currentStep // Set boolean values - writeEnabled = writeEnabled_; cacheEnabled = cacheEnabled_; - lowRamMode = lowRamMode_; - if (!writeEnabled) { lowRamMode = false; } // Low RAM Mode only works when writeEnabled is true - QIODevice *picStream; + bool ok = true; + if (!p_ragePhoto.isLoaded()) + ok = preloadFile(); - if (!isLoadedInRAM) { preloadFile(); } - - picStream = new QBuffer(&rawPicContent); - picStream->open(QIODevice::ReadWrite); - - // Reading Snapmatic Header - if (!picStream->isReadable()) - { - lastStep = "2;/3,ReadingFile," % StringParser::convertDrawStringForLog(picFilePath) % ",1,NOHEADER"; - picStream->close(); - delete picStream; + if (!ok) return false; - } - QByteArray snapmaticHeaderLine = picStream->read(snapmaticHeaderLength); - pictureHead = getSnapmaticHeaderString(snapmaticHeaderLine); - if (pictureHead == QLatin1String("MALFORMED")) - { - lastStep = "2;/3,ReadingFile," % StringParser::convertDrawStringForLog(picFilePath) % ",1,MALFORMEDHEADER"; - picStream->close(); - delete picStream; - return false; - } - // Reading JPEG Header Line - if (!picStream->isReadable()) - { - lastStep = "2;/3,ReadingFile," % StringParser::convertDrawStringForLog(picFilePath) % ",2,NOHEADER"; - picStream->close(); - delete picStream; - return false; - } - QByteArray jpegHeaderLine = picStream->read(jpegPreHeaderLength); - - // Checking for JPEG - jpegHeaderLine.remove(0, jpegHeaderLineDifStr); - if (jpegHeaderLine.left(4) != QByteArray("JPEG")) - { - lastStep = "2;/3,ReadingFile," % StringParser::convertDrawStringForLog(picFilePath) % ",2,NOJPEG"; - picStream->close(); - delete picStream; - return false; - } - - // Read JPEG Stream - if (!picStream->isReadable()) - { - lastStep = "2;/3,ReadingFile," % StringParser::convertDrawStringForLog(picFilePath) % ",2,NOPIC"; - picStream->close(); - delete picStream; - return false; - } - QByteArray jpegRawContent = picStream->read(jpegPicStreamLength); - if (jpegRawContent.contains("\xFF\xD9")) - { - int jpegRawContentSizeT = jpegRawContent.indexOf("\xFF\xD9") + 2; - jpegRawContentSizeE = jpegRawContentSizeT; - jpegRawContentSize = jpegRawContentSizeT; - if (jpegRawContent.contains("\xFF\x45\x4F\x49")) - { - jpegRawContentSizeT = jpegRawContent.indexOf("\xFF\x45\x4F\x49"); - } - jpegRawContent = jpegRawContent.left(jpegRawContentSize); - jpegRawContentSize = jpegRawContentSizeT; - } - if (cacheEnabled) picOk = cachePicture.loadFromData(jpegRawContent, "JPEG"); - if (!cacheEnabled) - { + if (cacheEnabled) + picOk = cachePicture.loadFromData(p_ragePhoto.photoData(), "JPEG"); + if (!cacheEnabled) { QImage tempPicture; - picOk = tempPicture.loadFromData(jpegRawContent, "JPEG"); - } - else if (!fastLoad) - { - if (careSnapDefault) - { - QImage tempPicture = QImage(snapmaticResolution, QImage::Format_RGB888); - QPainter tempPainter(&tempPicture); - if (cachePicture.size() != snapmaticResolution) - { - tempPainter.drawImage(0, 0, cachePicture.scaled(snapmaticResolution, Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); - } - else - { - tempPainter.drawImage(0, 0, cachePicture); - } - tempPainter.end(); - cachePicture = tempPicture; - } - else - { - QImage tempPicture = QImage(cachePicture.size(), QImage::Format_RGB888); - QPainter tempPainter(&tempPicture); - tempPainter.drawImage(0, 0, cachePicture); - tempPainter.end(); - cachePicture = tempPicture; - } + picOk = tempPicture.loadFromData(p_ragePhoto.photoData(), "JPEG"); } - // Read JSON Stream - if (!picStream->isReadable()) - { - lastStep = "2;/3,ReadingFile," % StringParser::convertDrawStringForLog(picFilePath) % ",3,NOJSON"; - picStream->close(); - delete picStream; - return false; - } - else if (picStream->read(4) != QByteArray("JSON")) - { - lastStep = "2;/3,ReadingFile," % StringParser::convertDrawStringForLog(picFilePath) % ",3,CTJSON"; - picStream->close(); - delete picStream; - return false; - } - QByteArray jsonRawContent = picStream->read(jsonStreamLength); - jsonStr = getSnapmaticJSONString(jsonRawContent); parseJsonContent(); // JSON parsing is own function - - if (!picStream->isReadable()) - { - lastStep = "2;/3,ReadingFile," % StringParser::convertDrawStringForLog(picFilePath) % ",4,NOTITL"; - picStream->close(); - delete picStream; - return false; - } - else if (picStream->read(4) != QByteArray("TITL")) - { - lastStep = "2;/3,ReadingFile," % StringParser::convertDrawStringForLog(picFilePath) % ",4,CTTITL"; - picStream->close(); - delete picStream; - return false; - } - QByteArray titlRawContent = picStream->read(tideStreamLength); - titlStr = getSnapmaticTIDEString(titlRawContent); - - if (!picStream->isReadable()) - { - lastStep = "2;/3,ReadingFile," % StringParser::convertDrawStringForLog(picFilePath) % ",5,NODESC"; - picStream->close(); - delete picStream; - return picOk; - } - else if (picStream->read(4) != QByteArray("DESC")) - { - lastStep = "2;/3,ReadingFile," % StringParser::convertDrawStringForLog(picFilePath) % ",5,CTDESC"; - picStream->close(); - delete picStream; - return false; - } - QByteArray descRawContent = picStream->read(tideStreamLength); - descStr = getSnapmaticTIDEString(descRawContent); - updateStrings(); - picStream->close(); - delete picStream; - - if (!writeEnabled) { rawPicContent.clear(); } - else if (lowRamMode) { rawPicContent = qCompress(rawPicContent, 9); } - emit loaded(); return picOk; } -QString SnapmaticPicture::getSnapmaticHeaderString(const QByteArray &snapmaticHeader) -{ - QList snapmaticBytesList = snapmaticHeader.left(snapmaticUsefulLength).split('\x01'); - if (snapmaticBytesList.length() < 2) { return QLatin1String("MALFORMED"); } - QByteArray snapmaticBytes = snapmaticBytesList.at(1); - return StringParser::parseTitleString(snapmaticBytes, snapmaticBytes.length()); -} - -QString SnapmaticPicture::getSnapmaticJSONString(const QByteArray &jsonBytes) -{ - QByteArray jsonUsefulBytes = jsonBytes; - jsonUsefulBytes.replace('\x00', QString()); - jsonUsefulBytes.replace('\x0c', QString()); - return QString::fromUtf8(jsonUsefulBytes.trimmed()); -} - -QString SnapmaticPicture::getSnapmaticTIDEString(const QByteArray &tideBytes) -{ - QByteArray tideUsefulBytes = tideBytes; - tideUsefulBytes.remove(0,4); - QList tideUsefulBytesList = tideUsefulBytes.split('\x00'); - return QString::fromUtf8(tideUsefulBytesList.at(0).trimmed()); -} - void SnapmaticPicture::updateStrings() { - QString cmpPicTitl = titlStr; + QString cmpPicTitl = p_ragePhoto.title(); cmpPicTitl.replace('\"', "''"); cmpPicTitl.replace(' ', '_'); cmpPicTitl.replace(':', '-'); @@ -440,134 +154,94 @@ void SnapmaticPicture::updateStrings() pictureStr = tr("PHOTO - %1").arg(localProperties.createdDateTime.toString("MM/dd/yy HH:mm:ss")); sortStr = localProperties.createdDateTime.toString("yyMMddHHmmss") % QString::number(localProperties.uid); QString exportStr = localProperties.createdDateTime.toString("yyyyMMdd") % "-" % QString::number(localProperties.uid); + if (getSnapmaticFormat() == SnapmaticFormat::G5E_Format) + picFileName = "PGTA5" % QString::number(localProperties.uid); picExportFileName = exportStr % "_" % cmpPicTitl; } -bool SnapmaticPicture::readingPictureFromFile(const QString &fileName, bool writeEnabled_, bool cacheEnabled_, bool fastLoad, bool lowRamMode_) +bool SnapmaticPicture::readingPictureFromFile(const QString &fileName, bool cacheEnabled_) { - if (!fileName.isEmpty()) - { + if (!fileName.isEmpty()) { picFilePath = fileName; - return readingPicture(writeEnabled_, cacheEnabled_, fastLoad, lowRamMode_); + return readingPicture(cacheEnabled_); } - else - { + else { return false; } } -bool SnapmaticPicture::setImage(const QImage &picture) +bool SnapmaticPicture::setImage(const QImage &picture, bool eXtendMode) { - if (writeEnabled) - { - QImage altPicture; - bool useAltPicture = false; - if (picture.size() != snapmaticResolution && careSnapDefault) - { - altPicture = picture.scaled(snapmaticResolution, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); - useAltPicture = true; - } - QByteArray picByteArray; - int comLvl = 100; - bool saveSuccess = false; - while (comLvl != 0 && !saveSuccess) - { - QByteArray picByteArrayT; - QBuffer picStreamT(&picByteArrayT); - picStreamT.open(QIODevice::WriteOnly); - if (useAltPicture) { saveSuccess = altPicture.save(&picStreamT, "JPEG", comLvl); } - else { saveSuccess = picture.save(&picStreamT, "JPEG", comLvl); } - picStreamT.close(); - if (saveSuccess) - { - if (picByteArrayT.length() > jpegRawContentSize) - { +#ifdef GTA5SYNC_DYNAMIC_PHOTOBUFFER + quint32 jpegPicStreamLength = p_ragePhoto.photoBuffer(); +#else + quint32 jpegPicStreamLength = 524288U; +#endif + QByteArray picByteArray; + int comLvl = 100; + bool saveSuccess = false; + while (comLvl != 0 && !saveSuccess) { + QByteArray picByteArrayT; + QBuffer picStreamT(&picByteArrayT); + picStreamT.open(QIODevice::WriteOnly); + saveSuccess = picture.save(&picStreamT, "JPEG", comLvl); + picStreamT.close(); + if (saveSuccess) { + quint32 size = picByteArrayT.length(); + if (size > jpegPicStreamLength) { + if (!eXtendMode) { comLvl--; saveSuccess = false; } - else - { + else { + p_ragePhoto.setPhotoBuffer(size, true); picByteArray = picByteArrayT; } } + else { +#ifndef GTA5SYNC_DYNAMIC_PHOTOBUFFER + if (p_ragePhoto.photoBuffer() != jpegPicStreamLength) + p_ragePhoto.setPhotoData(QByteArray()); // avoid buffer set fail + p_ragePhoto.setPhotoBuffer(jpegPicStreamLength, true); +#endif + picByteArray = picByteArrayT; + } } - if (saveSuccess) { return setPictureStream(picByteArray); } } + if (saveSuccess) + return setPictureStream(picByteArray); return false; } bool SnapmaticPicture::setPictureStream(const QByteArray &streamArray) // clean method { - if (writeEnabled) - { - bool customEOI = false; - QByteArray picByteArray = streamArray; - if (lowRamMode) { rawPicContent = qUncompress(rawPicContent); } - QBuffer snapmaticStream(&rawPicContent); - snapmaticStream.open(QIODevice::ReadWrite); - if (!snapmaticStream.seek(jpegStreamEditorBegin)) return false; - if (picByteArray.length() > jpegPicStreamLength) return false; - if (picByteArray.length() < jpegRawContentSize && jpegRawContentSize + 4 < jpegPicStreamLength) - { - customEOI = true; + bool success = p_ragePhoto.setPhotoData(streamArray); + if (success) { + if (cacheEnabled) { + QImage replacedPicture; + replacedPicture.loadFromData(streamArray); + cachePicture = replacedPicture; } - while (picByteArray.length() != jpegPicStreamLength) - { - picByteArray += '\x00'; - } - if (customEOI) - { - picByteArray.replace(jpegRawContentSize, 4, "\xFF\x45\x4F\x49"); - } - int result = snapmaticStream.write(picByteArray); - snapmaticStream.close(); - if (result != 0) - { - if (cacheEnabled) - { - QImage replacedPicture; - replacedPicture.loadFromData(picByteArray); - cachePicture = replacedPicture; - } - if (lowRamMode) { rawPicContent = qCompress(rawPicContent, 9); } - return true; - } - if (lowRamMode) { rawPicContent = qCompress(rawPicContent, 9); } + return true; + } + else { return false; } - return false; } bool SnapmaticPicture::setPictureTitl(const QString &newTitle_) { - if (writeEnabled) - { - QString newTitle = newTitle_; - if (lowRamMode) { rawPicContent = qUncompress(rawPicContent); } - QBuffer snapmaticStream(&rawPicContent); - snapmaticStream.open(QIODevice::ReadWrite); - if (!snapmaticStream.seek(titlStreamEditorBegin)) return false; - if (newTitle.length() > titlStreamCharacterMax) - { - newTitle = newTitle.left(titlStreamCharacterMax); - } - QByteArray newTitleArray = newTitle.toUtf8(); - while (newTitleArray.length() != titlStreamEditorLength) - { - newTitleArray += '\x00'; - } - int result = snapmaticStream.write(newTitleArray); - snapmaticStream.close(); - if (result != 0) - { - titlStr = newTitle; - if (lowRamMode) { rawPicContent = qCompress(rawPicContent, 9); } - return true; - } - if (lowRamMode) { rawPicContent = qCompress(rawPicContent, 9); } - return false; + QString newTitle = newTitle_; + if (newTitle.length() > 39) { + newTitle = newTitle.left(39); } - return false; + p_ragePhoto.setTitle(newTitle); + return true; +} + +int SnapmaticPicture::getContentMaxLength() +{ + return p_ragePhoto.photoBuffer(); } QString SnapmaticPicture::getExportPictureFileName() @@ -578,12 +252,10 @@ QString SnapmaticPicture::getExportPictureFileName() QString SnapmaticPicture::getOriginalPictureFileName() { QString newPicFileName = picFileName; - if (picFileName.right(4) == ".bak") - { + if (picFileName.right(4) == ".bak") { newPicFileName = QString(picFileName).remove(picFileName.length() - 4, 4); } - if (picFileName.right(7) == ".hidden") - { + if (picFileName.right(7) == ".hidden") { newPicFileName = QString(picFileName).remove(picFileName.length() - 7, 7); } return newPicFileName; @@ -592,12 +264,10 @@ QString SnapmaticPicture::getOriginalPictureFileName() QString SnapmaticPicture::getOriginalPictureFilePath() { QString newPicFilePath = picFilePath; - if (picFilePath.right(4) == ".bak") - { + if (picFilePath.right(4) == ".bak") { newPicFilePath = QString(picFilePath).remove(picFilePath.length() - 4, 4); } - if (picFilePath.right(7) == ".hidden") - { + if (picFilePath.right(7) == ".hidden") { newPicFilePath = QString(picFilePath).remove(picFilePath.length() - 7, 7); } return newPicFilePath; @@ -618,19 +288,9 @@ QString SnapmaticPicture::getPictureSortStr() return sortStr; } -QString SnapmaticPicture::getPictureDesc() -{ - return descStr; -} - QString SnapmaticPicture::getPictureTitl() { - return titlStr; -} - -QString SnapmaticPicture::getPictureHead() -{ - return pictureHead; + return p_ragePhoto.title(); } QString SnapmaticPicture::getPictureStr() @@ -640,82 +300,65 @@ QString SnapmaticPicture::getPictureStr() QString SnapmaticPicture::getLastStep(bool readable) { - if (readable) - { + if (readable) { QStringList lastStepList = lastStep.split(";/"); - if (lastStepList.length() < 2) { return lastStep; } + if (lastStepList.length() < 2) + return lastStep; bool intOk; - //int stepNumber = lastStepList.at(0).toInt(&intOk); - //if (!intOk) { return lastStep; } QStringList descStepList = lastStepList.at(1).split(","); - if (descStepList.length() < 1) { return lastStep; } + if (descStepList.length() < 1) + return lastStep; int argsCount = descStepList.at(0).toInt(&intOk); if (!intOk) { return lastStep; } - if (argsCount == 1) - { + if (argsCount == 1) { QString currentAction = descStepList.at(1); QString actionFile = descStepList.at(2); - if (currentAction == "OpenFile") - { + if (currentAction == "OpenFile") { return tr("open file %1").arg(actionFile); } } - else if (argsCount == 3 || argsCount == 4) - { + else if (argsCount == 3 || argsCount == 4) { QString currentAction = descStepList.at(1); QString actionFile = descStepList.at(2); - //QString actionStep = descStepList.at(3); QString actionError = descStepList.at(4); QString actionError2; if (argsCount == 4) { actionError2 = descStepList.at(5); } - if (currentAction == "ReadingFile") - { + if (currentAction == "ReadingFile") { QString readableError = actionError; - if (actionError == "NOHEADER") - { + if (actionError == "NOHEADER") { readableError = tr("header not exists"); } - else if (actionError == "MALFORMEDHEADER") - { + else if (actionError == "MALFORMEDHEADER") { readableError = tr("header is malformed"); } - else if (actionError == "NOJPEG" || actionError == "NOPIC") - { + else if (actionError == "NOJPEG" || actionError == "NOPIC") { readableError = tr("picture not exists (%1)").arg(actionError); } - else if (actionError == "NOJSON" || actionError == "CTJSON") - { + else if (actionError == "NOJSON" || actionError == "CTJSON") { readableError = tr("JSON not exists (%1)").arg(actionError); } - else if (actionError == "NOTITL" || actionError == "CTTITL") - { + else if (actionError == "NOTITL" || actionError == "CTTITL") { readableError = tr("title not exists (%1)").arg(actionError); } - else if (actionError == "NODESC" || actionError == "CTDESC") - { + else if (actionError == "NODESC" || actionError == "CTDESC") { readableError = tr("description not exists (%1)").arg(actionError); } - else if (actionError == "JSONINCOMPLETE" && actionError2 == "JSONERROR") - { + else if (actionError == "JSONINCOMPLETE" && actionError2 == "JSONERROR") { readableError = tr("JSON is incomplete and malformed"); } - else if (actionError == "JSONINCOMPLETE") - { + else if (actionError == "JSONINCOMPLETE") { readableError = tr("JSON is incomplete"); } - else if (actionError == "JSONERROR") - { + else if (actionError == "JSONERROR") { readableError = tr("JSON is malformed"); } return tr("reading file %1 because of %2", "Example for %2: JSON is malformed error").arg(actionFile, readableError); } - else - { + else { return lastStep; } } - else - { + else { return lastStep; } } @@ -723,140 +366,20 @@ QString SnapmaticPicture::getLastStep(bool readable) } -QImage SnapmaticPicture::getImage(bool fastLoad) +QImage SnapmaticPicture::getImage() { - if (cacheEnabled) - { + if (cacheEnabled) { return cachePicture; } - else if (writeEnabled) - { - bool fastLoadU = fastLoad; - if (!careSnapDefault) { fastLoadU = true; } - - bool returnOk = false; - QImage tempPicture; - QImage returnPicture; - if (!fastLoadU) - { - returnPicture = QImage(snapmaticResolution, QImage::Format_RGB888); - } - - if (lowRamMode) { rawPicContent = qUncompress(rawPicContent); } - QBuffer snapmaticStream(&rawPicContent); - snapmaticStream.open(QIODevice::ReadOnly); - if (snapmaticStream.seek(jpegStreamEditorBegin)) - { - QByteArray jpegRawContent = snapmaticStream.read(jpegPicStreamLength); - returnOk = tempPicture.loadFromData(jpegRawContent, "JPEG"); - } - snapmaticStream.close(); - if (lowRamMode) { rawPicContent = qCompress(rawPicContent, 9); } - - if (returnOk) - { - if (!fastLoadU) - { - QPainter returnPainter(&returnPicture); - if (tempPicture.size() != snapmaticResolution) - { - returnPainter.drawImage(0, 0, tempPicture.scaled(snapmaticResolution, Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); - } - else - { - returnPainter.drawImage(0, 0, tempPicture); - } - returnPainter.end(); - return returnPicture; - } - else - { - return tempPicture; - } - } - } - else - { - bool fastLoadU = fastLoad; - if (!careSnapDefault) { fastLoadU = true; } - - bool returnOk = false; - QImage tempPicture; - QImage returnPicture; - if (!fastLoadU) - { - returnPicture = QImage(snapmaticResolution, QImage::Format_RGB888); - } - QIODevice *picStream; - - QFile *picFile = new QFile(picFilePath); - if (!picFile->open(QFile::ReadOnly)) - { - lastStep = "1;/1,OpenFile," % StringParser::convertDrawStringForLog(picFilePath); - delete picFile; - return QImage(); - } - rawPicContent = picFile->read(snapmaticFileMaxSize); - picFile->close(); - delete picFile; - - picStream = new QBuffer(&rawPicContent); - picStream->open(QIODevice::ReadWrite); - if (picStream->seek(jpegStreamEditorBegin)) - { - QByteArray jpegRawContent = picStream->read(jpegPicStreamLength); - returnOk = tempPicture.loadFromData(jpegRawContent, "JPEG"); - } - picStream->close(); - delete picStream; - - rawPicContent.clear(); - rawPicContent.squeeze(); - - if (returnOk) - { - if (!fastLoadU) - { - QPainter returnPainter(&returnPicture); - if (tempPicture.size() != snapmaticResolution) - { - returnPainter.drawImage(0, 0, tempPicture.scaled(snapmaticResolution, Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); - } - else - { - returnPainter.drawImage(0, 0, tempPicture); - } - returnPainter.end(); - return returnPicture; - } - else - { - return tempPicture; - } - } + else { + return QImage::fromData(p_ragePhoto.photoData(), "JPEG"); } return QImage(); } -QByteArray SnapmaticPicture::getPictureStream() // Incomplete because it just work in writeEnabled mode +QByteArray SnapmaticPicture::getPictureStream() { - QByteArray jpegRawContent; - if (writeEnabled) - { - QBuffer *picStream = new QBuffer(&rawPicContent); - picStream->open(QIODevice::ReadWrite); - if (picStream->seek(jpegStreamEditorBegin)) - { - jpegRawContent = picStream->read(jpegPicStreamLength); - } - delete picStream; - } - return jpegRawContent; -} - -int SnapmaticPicture::getContentMaxLength() -{ - return jpegRawContentSize; + return p_ragePhoto.photoData(); } bool SnapmaticPicture::isPicOk() @@ -889,7 +412,7 @@ bool SnapmaticPicture::isJsonOk() QString SnapmaticPicture::getJsonStr() { - return jsonStr; + return QString::fromUtf8(p_ragePhoto.jsonData()); } SnapmaticProperties SnapmaticPicture::getSnapmaticProperties() @@ -899,31 +422,25 @@ SnapmaticProperties SnapmaticPicture::getSnapmaticProperties() void SnapmaticPicture::parseJsonContent() { - QJsonDocument jsonDocument = QJsonDocument::fromJson(jsonStr.toUtf8()); - QJsonObject jsonObject = jsonDocument.object(); + QJsonObject jsonObject = p_ragePhoto.jsonObject(); QVariantMap jsonMap = jsonObject.toVariantMap(); bool jsonIncomplete = false; bool jsonError = false; - if (jsonObject.contains("loc")) - { - if (jsonObject["loc"].isObject()) - { + if (jsonObject.contains("loc")) { + if (jsonObject["loc"].isObject()) { QJsonObject locObject = jsonObject["loc"].toObject(); - if (locObject.contains("x")) - { + if (locObject.contains("x")) { if (locObject["x"].isDouble()) { localProperties.location.x = locObject["x"].toDouble(); } else { jsonError = true; } } else { jsonIncomplete = true; } - if (locObject.contains("y")) - { + if (locObject.contains("y")) { if (locObject["y"].isDouble()) { localProperties.location.y = locObject["y"].toDouble(); } else { jsonError = true; } } else { jsonIncomplete = true; } - if (locObject.contains("z")) - { + if (locObject.contains("z")) { if (locObject["z"].isDouble()) { localProperties.location.z = locObject["z"].toDouble(); } else { jsonError = true; } } @@ -932,97 +449,90 @@ void SnapmaticPicture::parseJsonContent() else { jsonError = true; } } else { jsonIncomplete = true; } - if (jsonObject.contains("uid")) - { + if (jsonObject.contains("uid")) { bool uidOk; localProperties.uid = jsonMap["uid"].toInt(&uidOk); if (!uidOk) { jsonError = true; } } else { jsonIncomplete = true; } - if (jsonObject.contains("area")) - { + if (jsonObject.contains("area")) { if (jsonObject["area"].isString()) { localProperties.location.area = jsonObject["area"].toString(); } else { jsonError = true; } } else { jsonIncomplete = true; } - if (jsonObject.contains("crewid")) - { + if (jsonObject.contains("crewid")) { bool crewIDOk; localProperties.crewID = jsonMap["crewid"].toInt(&crewIDOk); if (!crewIDOk) { jsonError = true; } } else { jsonIncomplete = true; } - if (jsonObject.contains("street")) - { + if (jsonObject.contains("street")) { bool streetIDOk; localProperties.streetID = jsonMap["street"].toInt(&streetIDOk); if (!streetIDOk) { jsonError = true; } } else { jsonIncomplete = true; } - if (jsonObject.contains("creat")) - { + if (jsonObject.contains("creat")) { bool timestampOk; QDateTime createdTimestamp; localProperties.createdTimestamp = jsonMap["creat"].toUInt(×tampOk); +#if QT_VERSION >= 0x060000 + createdTimestamp.setSecsSinceEpoch(localProperties.createdTimestamp); +#else createdTimestamp.setTime_t(localProperties.createdTimestamp); +#endif localProperties.createdDateTime = createdTimestamp; if (!timestampOk) { jsonError = true; } } else { jsonIncomplete = true; } - if (jsonObject.contains("plyrs")) - { + if (jsonObject.contains("plyrs")) { if (jsonObject["plyrs"].isArray()) { localProperties.playersList = jsonMap["plyrs"].toStringList(); } else { jsonError = true; } } // else { jsonIncomplete = true; } // 2016 Snapmatic pictures left out plyrs when none are captured, so don't force exists on that one - if (jsonObject.contains("meme")) - { + if (jsonObject.contains("meme")) { if (jsonObject["meme"].isBool()) { localProperties.isMeme = jsonObject["meme"].toBool(); } else { jsonError = true; } } else { jsonIncomplete = true; } - if (jsonObject.contains("mug")) - { + if (jsonObject.contains("mug")) { if (jsonObject["mug"].isBool()) { localProperties.isMug = jsonObject["mug"].toBool(); } else { jsonError = true; } } else { jsonIncomplete = true; } - if (jsonObject.contains("slf")) - { + if (jsonObject.contains("slf")) { if (jsonObject["slf"].isBool()) { localProperties.isSelfie = jsonObject["slf"].toBool(); } else { jsonError = true; } } else { jsonIncomplete = true; } - if (jsonObject.contains("drctr")) - { + if (jsonObject.contains("drctr")) { if (jsonObject["drctr"].isBool()) { localProperties.isFromDirector = jsonObject["drctr"].toBool(); } else { jsonError = true; } } else { jsonIncomplete = true; } - if (jsonObject.contains("rsedtr")) - { + if (jsonObject.contains("rsedtr")) { if (jsonObject["rsedtr"].isBool()) { localProperties.isFromRSEditor = jsonObject["rsedtr"].toBool(); } else { jsonError = true; } } - else { jsonIncomplete = true; } + else { localProperties.isFromRSEditor = false; } + if (jsonObject.contains("onislandx")) { + if (jsonObject["onislandx"].isBool()) { localProperties.location.isCayoPerico = jsonObject["onislandx"].toBool(); } + else { jsonError = true; } + } + else { localProperties.location.isCayoPerico = false; } - if (!jsonIncomplete && !jsonError) - { + if (!jsonIncomplete && !jsonError) { jsonOk = true; } - else - { - if (jsonIncomplete && jsonError) - { - lastStep = "2;/4,ReadingFile," % StringParser::convertDrawStringForLog(picFilePath) % ",3,JSONINCOMPLETE,JSONERROR"; + else { + if (jsonIncomplete && jsonError) { + lastStep = "2;/4,ReadingFile," % convertDrawStringForLog(picFilePath) % ",3,JSONINCOMPLETE,JSONERROR"; } - else if (jsonIncomplete) - { - lastStep = "2;/3,ReadingFile," % StringParser::convertDrawStringForLog(picFilePath) % ",3,JSONINCOMPLETE"; + else if (jsonIncomplete) { + lastStep = "2;/3,ReadingFile," % convertDrawStringForLog(picFilePath) % ",3,JSONINCOMPLETE"; } - else if (jsonError) - { - lastStep = "2;/3,ReadingFile," % StringParser::convertDrawStringForLog(picFilePath) % ",3,JSONERROR"; + else if (jsonError) { + lastStep = "2;/3,ReadingFile," % convertDrawStringForLog(picFilePath) % ",3,JSONERROR"; } jsonOk = false; } @@ -1030,8 +540,7 @@ void SnapmaticPicture::parseJsonContent() bool SnapmaticPicture::setSnapmaticProperties(SnapmaticProperties properties) { - QJsonDocument jsonDocument = QJsonDocument::fromJson(jsonStr.toUtf8()); - QJsonObject jsonObject = jsonDocument.object(); + QJsonObject jsonObject = p_ragePhoto.jsonObject(); QJsonObject locObject; locObject["x"] = properties.location.x; @@ -1050,11 +559,10 @@ bool SnapmaticPicture::setSnapmaticProperties(SnapmaticProperties properties) jsonObject["slf"] = properties.isSelfie; jsonObject["drctr"] = properties.isFromDirector; jsonObject["rsedtr"] = properties.isFromRSEditor; + jsonObject["onislandx"] = properties.location.isCayoPerico; - jsonDocument.setObject(jsonObject); - - if (setJsonStr(QString::fromUtf8(jsonDocument.toJson(QJsonDocument::Compact)))) - { + QJsonDocument jsonDocument(jsonObject); + if (setJsonStr(QString::fromUtf8(jsonDocument.toJson(QJsonDocument::Compact)))) { localProperties = properties; return true; } @@ -1063,44 +571,14 @@ bool SnapmaticPicture::setSnapmaticProperties(SnapmaticProperties properties) bool SnapmaticPicture::setJsonStr(const QString &newJsonStr, bool updateProperties) { - if (newJsonStr.length() < jsonStreamEditorLength) - { - if (writeEnabled) - { - QByteArray jsonByteArray = newJsonStr.toUtf8(); - while (jsonByteArray.length() != jsonStreamEditorLength) - { - jsonByteArray += '\x00'; - } - if (lowRamMode) { rawPicContent = qUncompress(rawPicContent); } - QBuffer snapmaticStream(&rawPicContent); - snapmaticStream.open(QIODevice::ReadWrite); - if (!snapmaticStream.seek(jsonStreamEditorBegin)) - { - snapmaticStream.close(); - return false; - } - int result = snapmaticStream.write(jsonByteArray); - snapmaticStream.close(); - if (result != 0) - { - jsonStr = newJsonStr; - if (lowRamMode) { rawPicContent = qCompress(rawPicContent, 9); } - if (updateProperties) { parseJsonContent(); } - return true; - } - else - { - if (lowRamMode) { rawPicContent = qCompress(rawPicContent, 9); } - return false; - } - } - else - { - return false; - } + if (p_ragePhoto.setJsonData(newJsonStr.toUtf8())) { + if (updateProperties) + parseJsonContent(); + return true; + } + else { + return false; } - return false; } // FILE MANAGEMENT @@ -1109,137 +587,75 @@ bool SnapmaticPicture::exportPicture(const QString &fileName, SnapmaticFormat fo { // Keep current format when Auto_Format is used SnapmaticFormat format = format_; - if (format_ == SnapmaticFormat::Auto_Format) - { - if (isCustomFormat) - { + if (format_ == SnapmaticFormat::Auto_Format) { + if (p_ragePhoto.photoFormat() == RagePhoto::PhotoFormat::G5EX) { format = SnapmaticFormat::G5E_Format; } - else - { + else { format = SnapmaticFormat::PGTA_Format; } } bool saveSuccess = false; - bool writeFailure = false; #if QT_VERSION >= 0x050000 QSaveFile *picFile = new QSaveFile(fileName); #else QFile *picFile = new QFile(StandardPaths::tempLocation() % "/" % QFileInfo(fileName).fileName() % ".tmp"); #endif - if (picFile->open(QIODevice::WriteOnly)) - { - if (format == SnapmaticFormat::G5E_Format) - { - // Modern compressed export - QByteArray stockFileNameUTF8 = picFileName.toUtf8(); - QByteArray numberLength = QByteArray::number(stockFileNameUTF8.length()); - if (numberLength.length() == 1) - { - numberLength.insert(0, '0'); - } - else if (numberLength.length() != 2) - { - numberLength = "00"; - } - QByteArray g5eHeader; - g5eHeader.reserve(stockFileNameUTF8.length() + 16); - g5eHeader += '\x00'; // First Null Byte - g5eHeader += QByteArray("G5E"); // GTA 5 Export - g5eHeader += '\x10'; g5eHeader += '\x00'; // 2 byte GTA 5 Export Version - g5eHeader += QByteArray("LEN"); // Before Length - g5eHeader += QByteArray::fromHex(numberLength); // Length in HEX before Compressed - g5eHeader += QByteArray("FIL"); // Before File Name - g5eHeader += stockFileNameUTF8; // File Name - g5eHeader += QByteArray("COM"); // Before Compressed - if (picFile->write(g5eHeader) == -1) { writeFailure = true; } - if (!lowRamMode) - { - if (picFile->write(qCompress(rawPicContent, 9)) == -1) { writeFailure = true; } // Compressed Snapmatic - } - else - { - if (picFile->write(rawPicContent) == -1) { writeFailure = true; } - } + if (picFile->open(QIODevice::WriteOnly)) { + if (format == SnapmaticFormat::G5E_Format) { + p_ragePhoto.save(picFile, RagePhoto::PhotoFormat::G5EX); #if QT_VERSION >= 0x050000 - if (writeFailure) { picFile->cancelWriting(); } - else { saveSuccess = picFile->commit(); } + saveSuccess = picFile->commit(); #else - if (!writeFailure) { saveSuccess = true; } + saveSuccess = true; picFile->close(); #endif delete picFile; } - else if (format == SnapmaticFormat::JPEG_Format) - { - // JPEG export - QBuffer snapmaticStream(&rawPicContent); - snapmaticStream.open(QIODevice::ReadOnly); - if (snapmaticStream.seek(jpegStreamEditorBegin)) - { - QByteArray jpegRawContent = snapmaticStream.read(jpegPicStreamLength); - if (jpegRawContentSizeE != 0) - { - jpegRawContent = jpegRawContent.left(jpegRawContentSizeE); - } - if (picFile->write(jpegRawContent) == -1) { writeFailure = true; } + else if (format == SnapmaticFormat::JPEG_Format) { + picFile->write(p_ragePhoto.photoData()); #if QT_VERSION >= 0x050000 - if (writeFailure) { picFile->cancelWriting(); } - else { saveSuccess = picFile->commit(); } + saveSuccess = picFile->commit(); #else - if (!writeFailure) { saveSuccess = true; } - picFile->close(); + saveSuccess = true; + picFile->close(); #endif - } delete picFile; } - else - { - // Classic straight export - if (!lowRamMode) - { - if (picFile->write(rawPicContent) == -1) { writeFailure = true; } - } - else - { - if (picFile->write(qUncompress(rawPicContent)) == -1) { writeFailure = true; } - } + else { + p_ragePhoto.save(picFile, RagePhoto::PhotoFormat::GTA5); #if QT_VERSION >= 0x050000 - if (writeFailure) { picFile->cancelWriting(); } - else { saveSuccess = picFile->commit(); } + saveSuccess = picFile->commit(); #else - if (!writeFailure) { saveSuccess = true; } + saveSuccess = true; picFile->close(); #endif delete picFile; } #if QT_VERSION <= 0x050000 - if (saveSuccess) - { + if (saveSuccess) { bool tempBakCreated = false; - if (QFile::exists(fileName)) - { - if (!QFile::rename(fileName, fileName % ".tmp")) - { + if (QFile::exists(fileName)) { + if (!QFile::rename(fileName, fileName % ".tmp")) { QFile::remove(StandardPaths::tempLocation() % "/" % QFileInfo(fileName).fileName() % ".tmp"); return false; } tempBakCreated = true; } - if (!QFile::rename(StandardPaths::tempLocation() % "/" % QFileInfo(fileName).fileName() % ".tmp", fileName)) - { + if (!QFile::rename(StandardPaths::tempLocation() % "/" % QFileInfo(fileName).fileName() % ".tmp", fileName)) { QFile::remove(StandardPaths::tempLocation() % "/" % QFileInfo(fileName).fileName() % ".tmp"); - if (tempBakCreated) { QFile::rename(fileName % ".tmp", fileName); } + if (tempBakCreated) + QFile::rename(fileName % ".tmp", fileName); return false; } - if (tempBakCreated) { QFile::remove(fileName % ".tmp"); } + if (tempBakCreated) + QFile::remove(fileName % ".tmp"); } #endif return saveSuccess; } - else - { + else { delete picFile; return saveSuccess; } @@ -1257,17 +673,29 @@ void SnapmaticPicture::setPicFilePath(const QString &picFilePath_) bool SnapmaticPicture::deletePicFile() { - if (!QFile::exists(picFilePath)) return true; - if (QFile::remove(picFilePath)) return true; - return false; + bool success = false; + if (!QFile::exists(picFilePath)) { + success = true; + } + else if (QFile::remove(picFilePath)) { + success = true; + } + if (isHidden()) { + const QString picBakPath = QString(picFilePath).remove(picFilePath.length() - 7, 7) % ".bak"; + if (QFile::exists(picBakPath)) QFile::remove(picBakPath); + } + else { + const QString picBakPath = picFilePath % ".bak"; + if (QFile::exists(picBakPath)) QFile::remove(picBakPath); + } + return success; } // VISIBILITY bool SnapmaticPicture::isHidden() { - if (picFilePath.right(7) == QLatin1String(".hidden")) - { + if (picFilePath.right(7) == QLatin1String(".hidden")) { return true; } return false; @@ -1275,8 +703,7 @@ bool SnapmaticPicture::isHidden() bool SnapmaticPicture::isVisible() { - if (picFilePath.right(7) == QLatin1String(".hidden")) - { + if (picFilePath.right(7) == QLatin1String(".hidden")) { return false; } return true; @@ -1284,15 +711,12 @@ bool SnapmaticPicture::isVisible() bool SnapmaticPicture::setPictureHidden() { - if (isCustomFormat) - { + if (p_ragePhoto.photoFormat() == RagePhoto::PhotoFormat::G5EX) { return false; } - if (!isHidden()) - { + if (!isHidden()) { QString newPicFilePath = QString(picFilePath % ".hidden"); - if (QFile::rename(picFilePath, newPicFilePath)) - { + if (QFile::rename(picFilePath, newPicFilePath)) { picFilePath = newPicFilePath; return true; } @@ -1303,15 +727,12 @@ bool SnapmaticPicture::setPictureHidden() bool SnapmaticPicture::setPictureVisible() { - if (isCustomFormat) - { + if (p_ragePhoto.photoFormat() == RagePhoto::PhotoFormat::G5EX) { return false; } - if (isHidden()) - { + if (isHidden()) { QString newPicFilePath = QString(picFilePath).remove(picFilePath.length() - 7, 7); - if (QFile::rename(picFilePath, newPicFilePath)) - { + if (QFile::rename(picFilePath, newPicFilePath)) { picFilePath = newPicFilePath; return true; } @@ -1327,24 +748,11 @@ QSize SnapmaticPicture::getSnapmaticResolution() return snapmaticResolution; } -// SNAPMATIC DEFAULTS - -bool SnapmaticPicture::isSnapmaticDefaultsEnforced() -{ - return careSnapDefault; -} - -void SnapmaticPicture::setSnapmaticDefaultsEnforced(bool enforced) -{ - careSnapDefault = enforced; -} - // SNAPMATIC FORMAT SnapmaticFormat SnapmaticPicture::getSnapmaticFormat() { - if (isCustomFormat) - { + if (p_ragePhoto.photoFormat() == RagePhoto::PhotoFormat::G5EX) { return SnapmaticFormat::G5E_Format; } return SnapmaticFormat::PGTA_Format; @@ -1352,14 +760,12 @@ SnapmaticFormat SnapmaticPicture::getSnapmaticFormat() void SnapmaticPicture::setSnapmaticFormat(SnapmaticFormat format) { - if (format == SnapmaticFormat::G5E_Format) - { - isCustomFormat = true; + if (format == SnapmaticFormat::G5E_Format) { + p_ragePhoto.setPhotoFormat(RagePhoto::PhotoFormat::G5EX); return; } - else if (format == SnapmaticFormat::PGTA_Format) - { - isCustomFormat = false; + else if (format == SnapmaticFormat::PGTA_Format) { + p_ragePhoto.setPhotoFormat(RagePhoto::PhotoFormat::GTA5); return; } qDebug() << "setSnapmaticFormat: Invalid SnapmaticFormat defined, valid SnapmaticFormats are G5E_Format and PGTA_Format"; @@ -1375,10 +781,8 @@ bool SnapmaticPicture::isFormatSwitched() bool SnapmaticPicture::verifyTitle(const QString &title) { // VERIFY TITLE FOR BE A VALID SNAPMATIC TITLE - if (title.length() <= titlStreamCharacterMax && title.length() > 0) - { - for (QChar titleChar : title) - { + if (title.length() <= 39 && title.length() > 0) { + for (const QChar &titleChar : title) { if (!verifyTitleChar(titleChar)) return false; } return true; @@ -1389,10 +793,44 @@ bool SnapmaticPicture::verifyTitle(const QString &title) bool SnapmaticPicture::verifyTitleChar(const QChar &titleChar) { // VERIFY CHAR FOR BE A VALID SNAPMATIC CHARACTER - if (titleChar.isLetterOrNumber() || titleChar.isPrint()) - { + if (titleChar.isLetterOrNumber() || titleChar.isPrint()) { if (titleChar == '<' || titleChar == '>' || titleChar == '\\') return false; return true; } return false; } + +// STRING OPERATIONS + +QString SnapmaticPicture::parseTitleString(const QByteArray &commitBytes, int maxLength) +{ + Q_UNUSED(maxLength) +#if QT_VERSION >= 0x060000 + QStringDecoder strDecoder = QStringDecoder(QStringDecoder::Utf16LE); + QString retStr = strDecoder(commitBytes); + retStr = retStr.trimmed(); +#else + QString retStr = QTextCodec::codecForName("UTF-16LE")->toUnicode(commitBytes).trimmed(); +#endif + retStr.remove(QChar('\x00')); + return retStr; +} + +QString SnapmaticPicture::convertDrawStringForLog(const QString &inputStr) +{ + QString outputStr = inputStr; + return outputStr.replace("&","&u;").replace(",", "&c;"); +} + +QString SnapmaticPicture::convertLogStringForDraw(const QString &inputStr) +{ + QString outputStr = inputStr; + return outputStr.replace("&c;",",").replace("&u;", "&"); +} + +// RAGEPHOTO + +RagePhoto* SnapmaticPicture::ragePhoto() +{ + return &p_ragePhoto; +} diff --git a/SnapmaticPicture.h b/SnapmaticPicture.h index 71046df..8113cdc 100644 --- a/SnapmaticPicture.h +++ b/SnapmaticPicture.h @@ -1,6 +1,6 @@ /***************************************************************************** -* gta5sync-spv Grand Theft Auto Snapmatic Picture Viewer -* Copyright (C) 2016-2017 Syping +* gta5spv Grand Theft Auto Snapmatic Picture Viewer +* Copyright (C) 2016-2020 Syping * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -19,6 +19,7 @@ #ifndef SNAPMATICPICTURE_H #define SNAPMATICPICTURE_H +#include "RagePhoto.h" #include #include #include @@ -34,6 +35,7 @@ struct SnapmaticProperties { double x; double y; double z; + bool isCayoPerico; }; int uid; int crewID; @@ -57,17 +59,15 @@ public: ~SnapmaticPicture(); void reset(); bool preloadFile(); - bool readingPictureFromFile(const QString &fileName, bool writeEnabled = true, bool cacheEnabled = false, bool fastLoad = true, bool lowRamMode = false); - bool readingPicture(bool writeEnabled = true, bool cacheEnabled = false, bool fastLoad = true, bool lowRamMode = false); + bool readingPictureFromFile(const QString &fileName, bool cacheEnabled = false); + bool readingPicture(bool cacheEnabled = false); bool isPicOk(); // Please use isPictureOk instead void clearCache(); - QImage getImage(bool fastLoad = false); + QImage getImage(); QByteArray getPictureStream(); QString getLastStep(bool readable = true); QString getPictureStr(); - QString getPictureHead(); QString getPictureTitl(); - QString getPictureDesc(); QString getPictureSortStr(); QString getPictureFileName(); QString getPictureFilePath(); @@ -75,7 +75,7 @@ public: QString getOriginalPictureFileName(); QString getOriginalPictureFilePath(); int getContentMaxLength(); - bool setImage(const QImage &picture); + bool setImage(const QImage &picture, bool eXtendMode = false); bool setPictureTitl(const QString &newTitle); // Please use setPictureTitle instead bool setPictureStream(const QByteArray &streamArray); void updateStrings(); @@ -106,7 +106,6 @@ public: QString getPictureJson() { return getJsonStr(); } QString getPictureTitle() { return getPictureTitl(); } QString getPictureString() { return getPictureStr(); } - QString getPictureDescription() { return getPictureDesc(); } bool setJsonString(const QString &jsonString, bool updateProperties = false) { return setJsonStr(jsonString, updateProperties); } // Please use setPictureJson instead bool setPictureJson(const QString &json, bool updateProperties = false) { return setJsonStr(json, updateProperties); } bool setPictureTitle(const QString &title) { return setPictureTitl(title); } @@ -120,11 +119,7 @@ public: bool setVisible() { return setPictureVisible(); } // Please use setPictureVisible instead // PREDEFINED PROPERTIES - QSize getSnapmaticResolution(); - - // SNAPMATIC DEFAULTS - bool isSnapmaticDefaultsEnforced(); - void setSnapmaticDefaultsEnforced(bool enforced); + static QSize getSnapmaticResolution(); // SNAPMATIC FORMAT SnapmaticFormat getSnapmaticFormat(); @@ -134,43 +129,37 @@ public: // VERIFY CONTENT static bool verifyTitle(const QString &title); + // STRING OPERATIONS + static QString parseTitleString(const QByteArray &commitBytes, int maxLength); + static QString convertDrawStringForLog(const QString &inputStr); + static QString convertLogStringForDraw(const QString &inputStr); + + // RAGEPHOTO + RagePhoto* ragePhoto(); + private: - QString getSnapmaticHeaderString(const QByteArray &snapmaticHeader); - QString getSnapmaticJSONString(const QByteArray &jsonBytes); - QString getSnapmaticTIDEString(const QByteArray &tideBytes); QImage cachePicture; QString picExportFileName; QString picFileName; QString picFilePath; - QString pictureHead; QString pictureStr; QString lastStep; QString sortStr; - QString titlStr; - QString descStr; bool picOk; - bool lowRamMode; - bool writeEnabled; bool cacheEnabled; - bool isLoadedInRAM; - bool isCustomFormat; bool isFormatSwitch; - bool careSnapDefault; - int jpegRawContentSize; - int jpegRawContentSizeE; - - // PICTURE STREAM - QByteArray rawPicContent; // JSON void parseJsonContent(); bool jsonOk; - QString jsonStr; SnapmaticProperties localProperties; // VERIFY CONTENT static bool verifyTitleChar(const QChar &titleChar); + // RAGEPHOTO + RagePhoto p_ragePhoto; + signals: void customSignal(QString signal); void preloaded(); diff --git a/SnapmaticWidget.cpp b/SnapmaticWidget.cpp index 16cbc6d..db718a7 100644 --- a/SnapmaticWidget.cpp +++ b/SnapmaticWidget.cpp @@ -1,6 +1,6 @@ /***************************************************************************** -* gta5sync GRAND THEFT AUTO V SYNC -* Copyright (C) 2016-2017 Syping +* gta5view Grand Theft Auto V Profile Viewer +* Copyright (C) 2016-2021 Syping * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -18,7 +18,6 @@ #include "SnapmaticWidget.h" #include "ui_SnapmaticWidget.h" -#include "ImageEditorDialog.h" #include "MapLocationDialog.h" #include "JsonEditorDialog.h" #include "SnapmaticPicture.h" @@ -27,16 +26,23 @@ #include "PictureDialog.h" #include "PictureExport.h" #include "StringParser.h" +#include "ImportDialog.h" #include "AppEnv.h" #include "config.h" #include #include +#include #include #include -#include #include #include +#ifdef GTA5SYNC_TELEMETRY +#include "TelemetryClass.h" +#include +#include +#endif + SnapmaticWidget::SnapmaticWidget(ProfileDatabase *profileDB, CrewDatabase *crewDB, DatabaseThread *threadDB, QString profileName, QWidget *parent) : ProfileWidget(parent), profileDB(profileDB), crewDB(crewDB), threadDB(threadDB), profileName(profileName), ui(new Ui::SnapmaticWidget) @@ -70,12 +76,33 @@ void SnapmaticWidget::setSnapmaticPicture(SnapmaticPicture *picture) QObject::connect(picture, SIGNAL(updated()), this, SLOT(snapmaticUpdated())); QObject::connect(picture, SIGNAL(customSignal(QString)), this, SLOT(customSignal(QString))); - qreal screenRatio = AppEnv::screenRatio(); + const qreal screenRatio = AppEnv::screenRatio(); + const qreal screenRatioPR = AppEnv::screenRatioPR(); + const QSize renderResolution(48 * screenRatio * screenRatioPR, 27 * screenRatio * screenRatioPR); ui->labPicture->setFixedSize(48 * screenRatio, 27 * screenRatio); + ui->labPicture->setScaledContents(true); + + QPixmap renderPixmap(renderResolution); + renderPixmap.fill(Qt::transparent); + QPainter renderPainter(&renderPixmap); + const QImage originalImage = picture->getImage(); + const QImage renderImage = originalImage.scaled(renderResolution, Qt::KeepAspectRatio, Qt::SmoothTransformation); // Stack smash + if (renderImage.width() < renderResolution.width()) { + renderPainter.drawImage((renderResolution.width() - renderImage.width()) / 2, 0, renderImage, Qt::AutoColor); + } + else if (renderImage.height() < renderResolution.height()) { + renderPainter.drawImage(0, (renderResolution.height() - renderImage.height()) / 2, renderImage, Qt::AutoColor); + } + else { + renderPainter.drawImage(0, 0, renderImage, Qt::AutoColor); + } + renderPainter.end(); +#if QT_VERSION >= 0x050600 + renderPixmap.setDevicePixelRatio(screenRatioPR); +#endif - QPixmap SnapmaticPixmap = QPixmap::fromImage(picture->getImage().scaled(ui->labPicture->width(), ui->labPicture->height(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation), Qt::AutoColor); ui->labPicStr->setText(smpic->getPictureStr() % "\n" % smpic->getPictureTitl()); - ui->labPicture->setPixmap(SnapmaticPixmap); + ui->labPicture->setPixmap(renderPixmap); picture->clearCache(); @@ -89,8 +116,7 @@ void SnapmaticWidget::snapmaticUpdated() void SnapmaticWidget::customSignal(QString signal) { - if (signal == "PictureUpdated") - { + if (signal == "PictureUpdated") { QPixmap SnapmaticPixmap = QPixmap::fromImage(smpic->getImage().scaled(ui->labPicture->width(), ui->labPicture->height(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation), Qt::AutoColor); ui->labPicture->setPixmap(SnapmaticPixmap); } @@ -106,7 +132,7 @@ void SnapmaticWidget::on_cmdView_clicked() { QSettings settings(GTA5SYNC_APPVENDOR, GTA5SYNC_APPSTR); settings.beginGroup("Interface"); - bool navigationBar = settings.value("NavigationBar", false).toBool(); + bool navigationBar = settings.value("NavigationBar", true).toBool(); settings.endGroup(); PictureDialog *picDialog = new PictureDialog(profileDB, crewDB, profileName, this); @@ -120,7 +146,8 @@ void SnapmaticWidget::on_cmdView_clicked() QObject::connect(picDialog, SIGNAL(previousPictureRequested()), this, SLOT(dialogPreviousPictureRequested())); // add previous next buttons - if (navigationBar) picDialog->addPreviousNextButtons(); + if (navigationBar) + picDialog->addPreviousNextButtons(); // show picture dialog #ifdef Q_OS_ANDROID @@ -128,7 +155,7 @@ void SnapmaticWidget::on_cmdView_clicked() picDialog->showMaximized(); #else picDialog->show(); - if (navigationBar) picDialog->stylizeDialog(); + if (navigationBar) picDialog->styliseDialog(); //picDialog->adaptNewDialogSize(); picDialog->setMinimumSize(picDialog->size()); picDialog->setMaximumSize(picDialog->size()); @@ -149,20 +176,38 @@ void SnapmaticWidget::on_cmdExport_clicked() void SnapmaticWidget::on_cmdDelete_clicked() { - if (deletePicture()) emit pictureDeleted(); + if (deletePicture()) + emit pictureDeleted(); } bool SnapmaticWidget::deletePicture() { int uchoice = QMessageBox::question(this, tr("Delete picture"), tr("Are you sure to delete %1 from your Snapmatic pictures?").arg("\""+smpic->getPictureTitle()+"\""), QMessageBox::Yes | QMessageBox::No, QMessageBox::No); - if (uchoice == QMessageBox::Yes) - { - if (smpic->deletePicFile()) - { + if (uchoice == QMessageBox::Yes) { + if (smpic->deletePictureFile()) { +#ifdef GTA5SYNC_TELEMETRY + QSettings telemetrySettings(GTA5SYNC_APPVENDOR, GTA5SYNC_APPSTR); + telemetrySettings.beginGroup("Telemetry"); + bool pushUsageData = telemetrySettings.value("PushUsageData", false).toBool(); + telemetrySettings.endGroup(); + if (pushUsageData && Telemetry->canPush()) { + QJsonDocument jsonDocument; + QJsonObject jsonObject; + jsonObject["Type"] = "DeleteSuccess"; + jsonObject["ExtraFlags"] = "Snapmatic"; + jsonObject["DeletedSize"] = QString::number(smpic->getContentMaxLength()); +#if QT_VERSION >= 0x060000 + jsonObject["DeletedTime"] = QString::number(QDateTime::currentDateTimeUtc().toSecsSinceEpoch()); +#else + jsonObject["DeletedTime"] = QString::number(QDateTime::currentDateTimeUtc().toTime_t()); +#endif + jsonDocument.setObject(jsonObject); + Telemetry->push(TelemetryCategory::PersonalData, jsonDocument); + } +#endif return true; } - else - { + else { QMessageBox::warning(this, tr("Delete picture"), tr("Failed at deleting %1 from your Snapmatic pictures").arg("\""+smpic->getPictureTitle()+"\"")); } } @@ -177,28 +222,22 @@ void SnapmaticWidget::mousePressEvent(QMouseEvent *ev) void SnapmaticWidget::mouseReleaseEvent(QMouseEvent *ev) { ProfileWidget::mouseReleaseEvent(ev); - if (ui->cbSelected->isVisible()) - { - if (rect().contains(ev->pos()) && ev->button() == Qt::LeftButton) - { + if (ui->cbSelected->isVisible()) { + if (rect().contains(ev->pos()) && ev->button() == Qt::LeftButton) { ui->cbSelected->setChecked(!ui->cbSelected->isChecked()); } } - else - { - if (getContentMode() == 0 && rect().contains(ev->pos()) && ev->button() == Qt::LeftButton) - { - if (ev->modifiers().testFlag(Qt::ShiftModifier)) - { + else { + const int contentMode = getContentMode(); + if ((contentMode == 0 || contentMode == 10 || contentMode == 20) && rect().contains(ev->pos()) && ev->button() == Qt::LeftButton) { + if (ev->modifiers().testFlag(Qt::ShiftModifier)) { ui->cbSelected->setChecked(!ui->cbSelected->isChecked()); } - else - { + else { on_cmdView_clicked(); } } - else if (!ui->cbSelected->isVisible() && getContentMode() == 1 && ev->button() == Qt::LeftButton && ev->modifiers().testFlag(Qt::ShiftModifier)) - { + else if (!ui->cbSelected->isVisible() && (contentMode == 1 || contentMode == 11 || contentMode == 21) && ev->button() == Qt::LeftButton && ev->modifiers().testFlag(Qt::ShiftModifier)) { ui->cbSelected->setChecked(!ui->cbSelected->isChecked()); } } @@ -208,8 +247,8 @@ void SnapmaticWidget::mouseDoubleClickEvent(QMouseEvent *ev) { ProfileWidget::mouseDoubleClickEvent(ev); - if (!ui->cbSelected->isVisible() && getContentMode() == 1 && ev->button() == Qt::LeftButton) - { + const int contentMode = getContentMode(); + if (!ui->cbSelected->isVisible() && (contentMode == 1 || contentMode == 11 || contentMode == 21) && ev->button() == Qt::LeftButton) { on_cmdView_clicked(); } } @@ -241,32 +280,27 @@ void SnapmaticWidget::dialogPreviousPictureRequested() void SnapmaticWidget::on_cbSelected_stateChanged(int arg1) { - if (arg1 == Qt::Checked) - { + if (arg1 == Qt::Checked) { emit widgetSelected(); } - else if (arg1 == Qt::Unchecked) - { + else if (arg1 == Qt::Unchecked) { emit widgetDeselected(); } } void SnapmaticWidget::adjustTextColor() { - if (isHidden()) - { + if (isHidden()) { ui->labPicStr->setStyleSheet(QString("QLabel{color: rgb(%1, %2, %3);}").arg(QString::number(highlightHiddenColor.red()), QString::number(highlightHiddenColor.green()), QString::number(highlightHiddenColor.blue()))); } - else - { + else { ui->labPicStr->setStyleSheet(""); } } bool SnapmaticWidget::makePictureHidden() { - if (smpic->setPictureHidden()) - { + if (smpic->setPictureHidden()) { adjustTextColor(); return true; } @@ -275,8 +309,7 @@ bool SnapmaticWidget::makePictureHidden() bool SnapmaticWidget::makePictureVisible() { - if (smpic->setPictureVisible()) - { + if (smpic->setPictureVisible()) { adjustTextColor(); return true; } @@ -286,17 +319,13 @@ bool SnapmaticWidget::makePictureVisible() void SnapmaticWidget::makePictureHiddenSlot() { if (!makePictureHidden()) - { QMessageBox::warning(this, QApplication::translate("UserInterface", "Hide In-game"), QApplication::translate("SnapmaticWidget", "Failed to hide %1 In-game from your Snapmatic pictures").arg("\""+smpic->getPictureTitle()+"\"")); - } } void SnapmaticWidget::makePictureVisibleSlot() { if (!makePictureVisible()) - { QMessageBox::warning(this, QApplication::translate("UserInterface", "Show In-game"), QApplication::translate("SnapmaticWidget", "Failed to show %1 In-game from your Snapmatic pictures").arg("\""+smpic->getPictureTitle()+"\"")); - } } void SnapmaticWidget::editSnapmaticProperties() @@ -320,43 +349,107 @@ void SnapmaticWidget::editSnapmaticRawJson() void SnapmaticWidget::editSnapmaticImage() { - ImageEditorDialog *imageEditor = new ImageEditorDialog(smpic, profileName, this); - imageEditor->setModal(true); - imageEditor->show(); - imageEditor->exec(); - delete imageEditor; + QImage *currentImage = new QImage(smpic->getImage()); + ImportDialog *importDialog = new ImportDialog(profileName, this); + importDialog->setImage(currentImage); + importDialog->enableOverwriteMode(); + importDialog->setModal(true); + importDialog->exec(); + if (importDialog->isImportAgreed()) { + const QByteArray previousPicture = smpic->getPictureStream(); + bool success = smpic->setImage(importDialog->image(), importDialog->isUnlimitedBuffer()); + if (success) { + QString currentFilePath = smpic->getPictureFilePath(); + QString originalFilePath = smpic->getOriginalPictureFilePath(); + QString backupFileName = originalFilePath % ".bak"; + if (!QFile::exists(backupFileName)) { + QFile::copy(currentFilePath, backupFileName); + } + if (!smpic->exportPicture(currentFilePath)) { + smpic->setPictureStream(previousPicture); + QMessageBox::warning(this, QApplication::translate("ImageEditorDialog", "Snapmatic Image Editor"), QApplication::translate("ImageEditorDialog", "Patching of Snapmatic Image failed because of I/O Error")); + return; + } + smpic->emitCustomSignal("PictureUpdated"); +#ifdef GTA5SYNC_TELEMETRY + QSettings telemetrySettings(GTA5SYNC_APPVENDOR, GTA5SYNC_APPSTR); + telemetrySettings.beginGroup("Telemetry"); + bool pushUsageData = telemetrySettings.value("PushUsageData", false).toBool(); + telemetrySettings.endGroup(); + if (pushUsageData && Telemetry->canPush()) { + QJsonDocument jsonDocument; + QJsonObject jsonObject; + jsonObject["Type"] = "ImageEdited"; + jsonObject["ExtraFlags"] = "Interface"; + jsonObject["EditedSize"] = QString::number(smpic->getContentMaxLength()); +#if QT_VERSION >= 0x060000 + jsonObject["EditedTime"] = QString::number(QDateTime::currentDateTimeUtc().toSecsSinceEpoch()); +#else + jsonObject["EditedTime"] = QString::number(QDateTime::currentDateTimeUtc().toTime_t()); +#endif + jsonDocument.setObject(jsonObject); + Telemetry->push(TelemetryCategory::PersonalData, jsonDocument); + } +#endif + } + else { + QMessageBox::warning(this, QApplication::translate("ImageEditorDialog", "Snapmatic Image Editor"), QApplication::translate("ImageEditorDialog", "Patching of Snapmatic Image failed because of Image Error")); + return; + } + } + delete importDialog; } void SnapmaticWidget::openMapViewer() { SnapmaticPicture *picture = smpic; - MapLocationDialog *mapLocDialog = new MapLocationDialog(picture->getSnapmaticProperties().location.x, picture->getSnapmaticProperties().location.y, this); + SnapmaticProperties currentProperties = picture->getSnapmaticProperties(); + MapLocationDialog *mapLocDialog = new MapLocationDialog(currentProperties.location.x, currentProperties.location.y, this); + mapLocDialog->setCayoPerico(currentProperties.location.isCayoPerico); mapLocDialog->setModal(true); mapLocDialog->show(); mapLocDialog->exec(); - if (mapLocDialog->propUpdated()) - { + if (mapLocDialog->propUpdated()) { // Update Snapmatic Properties - SnapmaticProperties localSpJson = picture->getSnapmaticProperties(); - localSpJson.location.x = mapLocDialog->getXpos(); - localSpJson.location.y = mapLocDialog->getYpos(); - localSpJson.location.z = 0; + currentProperties.location.x = mapLocDialog->getXpos(); + currentProperties.location.y = mapLocDialog->getYpos(); + currentProperties.location.z = 0; // Update Snapmatic Picture QString currentFilePath = picture->getPictureFilePath(); QString originalFilePath = picture->getOriginalPictureFilePath(); QString backupFileName = originalFilePath % ".bak"; - if (!QFile::exists(backupFileName)) - { + if (!QFile::exists(backupFileName)) { QFile::copy(currentFilePath, backupFileName); } SnapmaticProperties fallbackProperties = picture->getSnapmaticProperties(); - picture->setSnapmaticProperties(localSpJson); - if (!picture->exportPicture(currentFilePath)) - { + picture->setSnapmaticProperties(currentProperties); + if (!picture->exportPicture(currentFilePath)) { QMessageBox::warning(this, SnapmaticEditor::tr("Snapmatic Properties"), SnapmaticEditor::tr("Patching of Snapmatic Properties failed because of I/O Error")); picture->setSnapmaticProperties(fallbackProperties); } +#ifdef GTA5SYNC_TELEMETRY + else { + QSettings telemetrySettings(GTA5SYNC_APPVENDOR, GTA5SYNC_APPSTR); + telemetrySettings.beginGroup("Telemetry"); + bool pushUsageData = telemetrySettings.value("PushUsageData", false).toBool(); + telemetrySettings.endGroup(); + if (pushUsageData && Telemetry->canPush()) { + QJsonDocument jsonDocument; + QJsonObject jsonObject; + jsonObject["Type"] = "LocationEdited"; + jsonObject["ExtraFlags"] = "Interface"; + jsonObject["EditedSize"] = QString::number(picture->getContentMaxLength()); +#if QT_VERSION >= 0x060000 + jsonObject["EditedTime"] = QString::number(QDateTime::currentDateTimeUtc().toSecsSinceEpoch()); +#else + jsonObject["EditedTime"] = QString::number(QDateTime::currentDateTimeUtc().toTime_t()); +#endif + jsonDocument.setObject(jsonObject); + Telemetry->push(TelemetryCategory::PersonalData, jsonDocument); + } + } +#endif } delete mapLocDialog; } diff --git a/SnapmaticWidget.h b/SnapmaticWidget.h index 289dbf6..8c28f12 100644 --- a/SnapmaticWidget.h +++ b/SnapmaticWidget.h @@ -1,5 +1,5 @@ /***************************************************************************** -* gta5sync GRAND THEFT AUTO V SYNC +* gta5view Grand Theft Auto V Profile Viewer * Copyright (C) 2016-2017 Syping * * This program is free software: you can redistribute it and/or modify diff --git a/StandardPaths.cpp b/StandardPaths.cpp index fa5ed24..c2f8111 100644 --- a/StandardPaths.cpp +++ b/StandardPaths.cpp @@ -1,6 +1,6 @@ /***************************************************************************** -* gta5sync GRAND THEFT AUTO V SYNC -* Copyright (C) 2016-2017 Syping +* gta5view Grand Theft Auto V Profile Viewer +* Copyright (C) 2016-2021 Syping * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -23,11 +23,6 @@ #include #endif -StandardPaths::StandardPaths() -{ - -} - QString StandardPaths::applicationsLocation() { #if QT_VERSION >= 0x050000 @@ -48,7 +43,9 @@ QString StandardPaths::cacheLocation() QString StandardPaths::dataLocation() { -#if QT_VERSION >= 0x050000 +#if QT_VERSION >= 0x060000 + return QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation); +#elif QT_VERSION >= 0x050000 return QStandardPaths::writableLocation(QStandardPaths::DataLocation); #else return QDesktopServices::storageLocation(QDesktopServices::DataLocation); diff --git a/StandardPaths.h b/StandardPaths.h index 619bdeb..590e0dc 100644 --- a/StandardPaths.h +++ b/StandardPaths.h @@ -1,6 +1,6 @@ /***************************************************************************** -* gta5sync GRAND THEFT AUTO V SYNC -* Copyright (C) 2016-2017 Syping +* gta5view Grand Theft Auto V Profile Viewer +* Copyright (C) 2016-2021 Syping * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -24,7 +24,6 @@ class StandardPaths { public: - StandardPaths(); static QString applicationsLocation(); static QString cacheLocation(); static QString dataLocation(); diff --git a/StringParser.cpp b/StringParser.cpp index b93fda5..4ac3fba 100644 --- a/StringParser.cpp +++ b/StringParser.cpp @@ -1,6 +1,6 @@ /***************************************************************************** -* gta5sync GRAND THEFT AUTO V SYNC -* Copyright (C) 2016-2017 Syping +* gta5view Grand Theft Auto V Profile Viewer +* Copyright (C) 2016-2021 Syping * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -17,45 +17,16 @@ *****************************************************************************/ #include "StringParser.h" +#include "config.h" #include +#include #include -#include #include #include #include #include #include -#ifdef GTA5SYNC_PROJECT -#include -#include "config.h" -#endif - -StringParser::StringParser() -{ - -} - -QString StringParser::parseTitleString(const QByteArray &commitBytes, int maxLength) -{ - Q_UNUSED(maxLength) - QString retStr = QTextCodec::codecForName("UTF-16LE")->toUnicode(commitBytes).trimmed(); - retStr.remove(QChar('\x00')); - return retStr; -} - -QString StringParser::convertDrawStringForLog(const QString &inputStr) -{ - QString outputStr = inputStr; - return outputStr.replace("&","&u;").replace(",", "&c;"); -} - -QString StringParser::convertLogStringForDraw(const QString &inputStr) -{ - QString outputStr = inputStr; - return outputStr.replace("&c;",",").replace("&u;", "&"); -} - QString StringParser::escapeString(const QString &toEscape) { #if QT_VERSION >= 0x050000 @@ -65,17 +36,19 @@ QString StringParser::escapeString(const QString &toEscape) #endif } -#ifdef GTA5SYNC_PROJECT QString StringParser::convertBuildedString(const QString &buildedStr) { QString outputStr = buildedStr; - QByteArray sharePath = GTA5SYNC_SHARE; - outputStr.replace("APPNAME:", GTA5SYNC_APPSTR); - outputStr.replace("SHAREDDIR:", QString::fromUtf8(sharePath)); - outputStr.replace("RUNDIR:", QFileInfo(qApp->applicationFilePath()).absoluteDir().absolutePath()); + outputStr.replace("APPNAME:", QString::fromUtf8(GTA5SYNC_APPSTR)); + outputStr.replace("SHAREDDIR:", QString::fromUtf8(GTA5SYNC_SHARE)); + outputStr.replace("RUNDIR:", QFileInfo(QApplication::applicationFilePath()).canonicalPath()); +#if QT_VERSION >= 0x060000 + outputStr.replace("QCONFLANG:", QLibraryInfo::path(QLibraryInfo::TranslationsPath)); + outputStr.replace("QCONFPLUG:", QLibraryInfo::path(QLibraryInfo::PluginsPath)); +#else outputStr.replace("QCONFLANG:", QLibraryInfo::location(QLibraryInfo::TranslationsPath)); outputStr.replace("QCONFPLUG:", QLibraryInfo::location(QLibraryInfo::PluginsPath)); +#endif outputStr.replace("SEPARATOR:", QDir::separator()); return outputStr; } -#endif diff --git a/StringParser.h b/StringParser.h index f15b537..7dac436 100644 --- a/StringParser.h +++ b/StringParser.h @@ -1,6 +1,6 @@ /***************************************************************************** -* gta5sync GRAND THEFT AUTO V SYNC -* Copyright (C) 2016-2017 Syping +* gta5view Grand Theft Auto V Profile Viewer +* Copyright (C) 2016-2021 Syping * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -25,14 +25,8 @@ class StringParser { public: - StringParser(); - static QString parseTitleString(const QByteArray &commitBytes, int maxLength); - static QString convertDrawStringForLog(const QString &inputStr); - static QString convertLogStringForDraw(const QString &inputStr); static QString escapeString(const QString &toEscape); -#ifdef GTA5SYNC_PROJECT static QString convertBuildedString(const QString &buildedStr); -#endif }; #endif // STRINGPARSER_H diff --git a/TelemetryClass.cpp b/TelemetryClass.cpp index 6db570c..724e0c3 100644 --- a/TelemetryClass.cpp +++ b/TelemetryClass.cpp @@ -1,5 +1,5 @@ /***************************************************************************** -* gta5sync GRAND THEFT AUTO V SYNC +* gta5view Grand Theft Auto V Profile Viewer * Copyright (C) 2018 Syping * * This program is free software: you can redistribute it and/or modify @@ -41,9 +41,10 @@ #define GTA5SYNC_TELEMETRY_WEBURL "" #endif -#ifdef GTA5SYNC_WIN +#ifdef Q_OS_WIN #include "windows.h" #include "intrin.h" +#include "d3d9.h" #endif TelemetryClass TelemetryClass::telemetryClassInstance; @@ -52,12 +53,8 @@ void TelemetryClass::init() { QSettings settings(GTA5SYNC_APPVENDOR, GTA5SYNC_APPSTR); settings.beginGroup("Telemetry"); -#ifndef GTA5SYNC_BUILDTYPE_DEV - telemetryEnabled = settings.value("IsEnabled", false).toBool(); -#else - telemetryEnabled = true; // Always enable Telemetry for Developer Versions + telemetryEnabled = true; telemetryStateForced = true; -#endif QString telemetryLegacyClientID = settings.value("ClientID", QString()).toString(); if (telemetryLegacyClientID.isEmpty() || telemetryLegacyClientID == "v2+") { @@ -172,6 +169,8 @@ void TelemetryClass::push(TelemetryCategory category) break; case TelemetryCategory::UserFeedback: break; + case TelemetryCategory::PersonalData: + break; case TelemetryCategory::CustomEmitted: break; } @@ -202,6 +201,7 @@ void TelemetryClass::push(TelemetryCategory category, QJsonDocument json) QNetworkAccessManager *netManager = new QNetworkAccessManager(); QNetworkRequest netRequest(TelemetryClassAuthenticator::getTrackingPushURL()); + netRequest.setRawHeader("User-Agent", AppEnv::getUserAgent()); QNetworkReply *netReply = netManager->post(netRequest, httpMultiPart); httpMultiPart->setParent(netReply); @@ -213,6 +213,10 @@ QJsonDocument TelemetryClass::getOperatingSystem() QJsonDocument jsonDocument; QJsonObject jsonObject; #if QT_VERSION >= 0x050400 + jsonObject["KernelType"] = QSysInfo::kernelType(); + jsonObject["KernelVersion"] = QSysInfo::kernelVersion(); + jsonObject["ProductType"] = QSysInfo::productType(); + jsonObject["ProductVersion"] = QSysInfo::productVersion(); jsonObject["OSName"] = QSysInfo::prettyProductName(); jsonObject["OSArch"] = QSysInfo::currentCpuArchitecture(); #endif @@ -224,21 +228,21 @@ QJsonDocument TelemetryClass::getSystemHardware() { QJsonDocument jsonDocument; QJsonObject jsonObject; -#ifdef GTA5SYNC_WIN +#ifdef Q_OS_WIN { int CPUInfo[4] = {-1}; - unsigned nExIds, i = 0; + unsigned nExIds, ic = 0; char CPUBrandString[0x40]; __cpuid(CPUInfo, 0x80000000); nExIds = CPUInfo[0]; - for (i = 0x80000000; i <= nExIds; ++i) + for (ic = 0x80000000; ic <= nExIds; ic++) { - __cpuid(CPUInfo, i); - if (i == 0x80000002) { memcpy(CPUBrandString, CPUInfo, sizeof(CPUInfo)); } - else if (i == 0x80000003) { memcpy(CPUBrandString + 16, CPUInfo, sizeof(CPUInfo)); } - else if (i == 0x80000004) { memcpy(CPUBrandString + 32, CPUInfo, sizeof(CPUInfo)); } + __cpuid(CPUInfo, ic); + if (ic == 0x80000002) { memcpy(CPUBrandString, CPUInfo, sizeof(CPUInfo)); } + else if (ic == 0x80000003) { memcpy(CPUBrandString + 16, CPUInfo, sizeof(CPUInfo)); } + else if (ic == 0x80000004) { memcpy(CPUBrandString + 32, CPUInfo, sizeof(CPUInfo)); } } - jsonObject["CPUName"] = QString(CPUBrandString).trimmed(); + jsonObject["CPUName"] = QString::fromLatin1(CPUBrandString).simplified(); SYSTEM_INFO sysInfo; GetSystemInfo(&sysInfo); jsonObject["CPUThreads"] = QString::number(sysInfo.dwNumberOfProcessors); @@ -246,6 +250,21 @@ QJsonDocument TelemetryClass::getSystemHardware() statex.dwLength = sizeof(statex); GlobalMemoryStatusEx(&statex); jsonObject["SystemRAM"] = QString(QString::number((statex.ullTotalPhys / 1024) / 1024) % "MB"); + QStringList gpusList; + IDirect3D9 *pD3D = Direct3DCreate9(D3D_SDK_VERSION); + int adapters = pD3D->GetAdapterCount(); + for (int ia = 0; ia < adapters; ia++) + { + D3DADAPTER_IDENTIFIER9 d3dIdent; + HRESULT result = pD3D->GetAdapterIdentifier(ia, 0, &d3dIdent); + if (result == D3D_OK) + { + QString gpuAdapter = QString::fromLatin1(d3dIdent.Description); + if (!gpusList.contains(gpuAdapter)) { gpusList << gpuAdapter; } + } + } + pD3D->Release(); + jsonObject["GPUs"] = QJsonValue::fromVariant(gpusList); } #else QDir procDir("/proc"); @@ -264,7 +283,7 @@ QJsonDocument TelemetryClass::getSystemHardware() QByteArray cpuData = cpuInfoBuffer.readLine(); if (cpuData.left(toFind.length()) == toFind) { - jsonObject["CPUName"] = QString::fromUtf8(cpuData).split(':').at(1).trimmed(); + jsonObject["CPUName"] = QString::fromUtf8(cpuData).split(':').at(1).simplified(); break; } } @@ -319,6 +338,9 @@ QJsonDocument TelemetryClass::getApplicationSpec() jsonObject["Arch"] = QSysInfo::buildCpuArchitecture(); #endif jsonObject["Name"] = GTA5SYNC_APPSTR; +#ifdef GTA5SYNC_COMMIT + jsonObject["Commit"] = GTA5SYNC_COMMIT; +#endif jsonObject["Version"] = GTA5SYNC_APPVER; jsonObject["BuildDateTime"] = AppEnv::getBuildDateTime(); jsonObject["BuildType"] = GTA5SYNC_BUILDTYPE; @@ -373,6 +395,7 @@ QJsonDocument TelemetryClass::getApplicationConf() QJsonObject startupObject; startupObject["AppStyle"] = settings.value("AppStyle", "System").toString(); startupObject["CustomStyle"] = settings.value("CustomStyle", false).toBool(); + startupObject["StartCount"] = QString::number(settings.value("StartCount", 0).toUInt()); jsonObject["Startup"] = startupObject; settings.endGroup(); @@ -414,11 +437,14 @@ QString TelemetryClass::categoryToString(TelemetryCategory category) case TelemetryCategory::ApplicationConf: return QString("ApplicationConf"); break; + case TelemetryCategory::ApplicationSpec: + return QString("ApplicationSpec"); + break; case TelemetryCategory::UserFeedback: return QString("UserFeedback"); break; - case TelemetryCategory::ApplicationSpec: - return QString("ApplicationSpec"); + case TelemetryCategory::PersonalData: + return QString("PersonalData"); break; case TelemetryCategory::CustomEmitted: return QString("CustomEmitted"); @@ -438,6 +464,7 @@ void TelemetryClass::registerClient() { QNetworkAccessManager *netManager = new QNetworkAccessManager(); QNetworkRequest netRequest(TelemetryClassAuthenticator::getTrackingRegURL()); + netRequest.setRawHeader("User-Agent", AppEnv::getUserAgent()); netManager->get(netRequest); connect(netManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(registerFinished(QNetworkReply*))); @@ -468,6 +495,10 @@ void TelemetryClass::work_p(bool doWork) { push(TelemetryCategory::ApplicationConf); } + else + { + push(TelemetryCategory::ApplicationConf, QJsonDocument()); + } } } diff --git a/TelemetryClass.h b/TelemetryClass.h index 1e06ff5..834c3ed 100644 --- a/TelemetryClass.h +++ b/TelemetryClass.h @@ -1,5 +1,5 @@ /***************************************************************************** -* gta5sync GRAND THEFT AUTO V SYNC +* gta5view Grand Theft Auto V Profile Viewer * Copyright (C) 2018 Syping * * This program is free software: you can redistribute it and/or modify @@ -25,7 +25,7 @@ #include #include -enum class TelemetryCategory : int { OperatingSystemSpec = 0, HardwareSpec = 1, UserLocaleData = 2, ApplicationConf = 3, UserFeedback = 4, ApplicationSpec = 5, CustomEmitted = 99}; +enum class TelemetryCategory : int { OperatingSystemSpec = 0, HardwareSpec = 1, UserLocaleData = 2, ApplicationConf = 3, UserFeedback = 4, ApplicationSpec = 5, PersonalData = 6, CustomEmitted = 99 }; class TelemetryClass : public QObject { diff --git a/TranslationClass.cpp b/TranslationClass.cpp index 611d1f2..06450d1 100644 --- a/TranslationClass.cpp +++ b/TranslationClass.cpp @@ -1,6 +1,6 @@ /***************************************************************************** -* gta5sync GRAND THEFT AUTO V SYNC -* Copyright (C) 2017 Syping +* gta5view Grand Theft Auto V Profile Viewer +* Copyright (C) 2017-2021 Syping * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -48,22 +48,27 @@ void TranslationClass::initUserLanguage() void TranslationClass::loadTranslation(QApplication *app) { - if (isLangLoaded) { unloadTranslation(app); } - else { currentLangIndex = 0; } - QString exLangPath = AppEnv::getExLangFolder(); - QString inLangPath = AppEnv::getInLangFolder(); - if (userLanguage == "en" || userLanguage == "en_GB") - { + if (isLangLoaded) { + unloadTranslation(app); + } + else { + currentLangIndex = 0; + } + const QString exLangPath = AppEnv::getExLangFolder(); + const QString inLangPath = AppEnv::getInLangFolder(); + if (userLanguage == "en" || userLanguage == "en_GB") { currentLanguage = "en_GB"; - if (loadQtTranslation_p(exLangPath, &exQtTranslator)) - { + if (loadQtTranslation_p(exLangPath, &exQtTranslator)) { app->installTranslator(&exQtTranslator); } - else if (loadQtTranslation_p(inLangPath, &inQtTranslator)) - { + else if (loadQtTranslation_p(inLangPath, &inQtTranslator)) { app->installTranslator(&inQtTranslator); } +#if QT_VERSION >= 0x060000 + QLocale::setDefault(QLocale(currentLanguage)); +#else QLocale::setDefault(currentLanguage); +#endif isLangLoaded = true; return; } @@ -73,33 +78,28 @@ void TranslationClass::loadTranslation(QApplication *app) bool externalEnglishMode = false; bool loadInternalLang = false; bool trLoadSuccess = false; - if (isUserLanguageSystem_p()) - { + if (isUserLanguageSystem_p()) { #ifdef GTA5SYNC_DEBUG qDebug() << "loadExSystemLanguage"; #endif trLoadSuccess = loadSystemTranslation_p(exLangPath, &exAppTranslator); } - else - { + else { #ifdef GTA5SYNC_DEBUG qDebug() << "loadExUserLanguage"; #endif trLoadSuccess = loadUserTranslation_p(exLangPath, &exAppTranslator); - if (!trLoadSuccess) - { + if (!trLoadSuccess) { #ifdef GTA5SYNC_DEBUG qDebug() << "loadInUserLanguage"; #endif trLoadSuccess = loadUserTranslation_p(inLangPath, &inAppTranslator); - if (!trLoadSuccess) - { + if (!trLoadSuccess) { #ifdef GTA5SYNC_DEBUG qDebug() << "loadUserLanguageFailed"; #endif } - else - { + else { #ifdef GTA5SYNC_DEBUG qDebug() << "loadUserLanguageSuccess"; #endif @@ -107,18 +107,16 @@ void TranslationClass::loadTranslation(QApplication *app) isLangLoaded = true; } } - else - { + else { #ifdef GTA5SYNC_DEBUG qDebug() << "loadUserLanguageSuccess"; #endif isLangLoaded = true; } } - if (trLoadSuccess) - { - if (currentLangIndex != 0 || isEnglishMode) // Don't install the language until we know we not have a better language for the user - { + if (trLoadSuccess) { + // Don't install the language until we know we not have a better language for the user + if (currentLangIndex != 0 || isEnglishMode) { #ifdef GTA5SYNC_DEBUG qDebug() << "externalLanguageReady" << currentLanguage; #endif @@ -126,33 +124,31 @@ void TranslationClass::loadTranslation(QApplication *app) externalLanguageStr = currentLanguage; externalLanguageReady = true; } - else - { + else { #ifdef GTA5SYNC_DEBUG qDebug() << "installTranslation"; #endif - if (loadInternalLang) - { + if (loadInternalLang) { app->installTranslator(&inAppTranslator); } - else - { + else { app->installTranslator(&exAppTranslator); } - if (loadQtTranslation_p(exLangPath, &exQtTranslator)) - { + if (loadQtTranslation_p(exLangPath, &exQtTranslator)) { app->installTranslator(&exQtTranslator); } - else if (loadQtTranslation_p(inLangPath, &inQtTranslator)) - { + else if (loadQtTranslation_p(inLangPath, &inQtTranslator)) { app->installTranslator(&inQtTranslator); } +#if QT_VERSION >= 0x060000 + QLocale::setDefault(QLocale(currentLanguage)); +#else QLocale::setDefault(currentLanguage); +#endif isLangLoaded = true; } } - if (externalLanguageReady) - { + if (externalLanguageReady) { #ifdef GTA5SYNC_DEBUG qDebug() << "loadInSystemLanguage"; #endif @@ -162,113 +158,114 @@ void TranslationClass::loadTranslation(QApplication *app) qDebug() << "externalLangIndex" << externalLangIndex << "internalLangIndex" << currentLangIndex; qDebug() << "externalEnglishMode" << externalEnglishMode << "internalEnglishMode" << isEnglishMode; #endif - if ((trLoadSuccess && externalLangIndex > currentLangIndex) || (trLoadSuccess && externalEnglishMode && !isEnglishMode)) - { + if ((trLoadSuccess && externalLangIndex > currentLangIndex) || (trLoadSuccess && externalEnglishMode && !isEnglishMode)) { #ifdef GTA5SYNC_DEBUG qDebug() << "installInternalTranslation"; #endif app->installTranslator(&inAppTranslator); - if (loadQtTranslation_p(exLangPath, &exQtTranslator)) - { + if (loadQtTranslation_p(exLangPath, &exQtTranslator)) { app->installTranslator(&exQtTranslator); } - else if (loadQtTranslation_p(inLangPath, &inQtTranslator)) - { + else if (loadQtTranslation_p(inLangPath, &inQtTranslator)) { app->installTranslator(&inQtTranslator); } +#if QT_VERSION >= 0x060000 + QLocale::setDefault(QLocale(currentLanguage)); +#else QLocale::setDefault(currentLanguage); +#endif isLangLoaded = true; } - else - { + else { #ifdef GTA5SYNC_DEBUG qDebug() << "installExternalTranslation"; #endif isEnglishMode = externalEnglishMode; currentLanguage = externalLanguageStr; app->installTranslator(&exAppTranslator); - if (loadQtTranslation_p(exLangPath, &exQtTranslator)) - { + if (loadQtTranslation_p(exLangPath, &exQtTranslator)) { app->installTranslator(&exQtTranslator); } - else if (loadQtTranslation_p(inLangPath, &inQtTranslator)) - { + else if (loadQtTranslation_p(inLangPath, &inQtTranslator)) { app->installTranslator(&inQtTranslator); } +#if QT_VERSION >= 0x060000 + QLocale::setDefault(QLocale(currentLanguage)); +#else QLocale::setDefault(currentLanguage); +#endif isLangLoaded = true; } } - else if (!isLangLoaded) - { + else if (!isLangLoaded) { #ifdef GTA5SYNC_DEBUG qDebug() << "loadInSystemLanguage"; #endif trLoadSuccess = loadSystemTranslation_p(inLangPath, &inAppTranslator); - if (trLoadSuccess) - { + if (trLoadSuccess) { #ifdef GTA5SYNC_DEBUG qDebug() << "installInternalTranslation"; #endif app->installTranslator(&inAppTranslator); - if (loadQtTranslation_p(exLangPath, &exQtTranslator)) - { + if (loadQtTranslation_p(exLangPath, &exQtTranslator)) { app->installTranslator(&exQtTranslator); } - else if (loadQtTranslation_p(inLangPath, &inQtTranslator)) - { + else if (loadQtTranslation_p(inLangPath, &inQtTranslator)) { app->installTranslator(&inQtTranslator); } +#if QT_VERSION >= 0x060000 + QLocale::setDefault(QLocale(currentLanguage)); +#else QLocale::setDefault(currentLanguage); +#endif isLangLoaded = true; } - else if (!trLoadSuccess) - { + else if (!trLoadSuccess) { #ifdef GTA5SYNC_DEBUG qDebug() << "fallbackToDefaultApplicationLanguage"; #endif currentLanguage = "en_GB"; - if (loadQtTranslation_p(exLangPath, &exQtTranslator)) - { + if (loadQtTranslation_p(exLangPath, &exQtTranslator)) { app->installTranslator(&exQtTranslator); } - else if (loadQtTranslation_p(inLangPath, &inQtTranslator)) - { + else if (loadQtTranslation_p(inLangPath, &inQtTranslator)) { app->installTranslator(&inQtTranslator); } +#if QT_VERSION >= 0x060000 + QLocale::setDefault(QLocale(currentLanguage)); +#else QLocale::setDefault(currentLanguage); +#endif isLangLoaded = true; } } #else // New qconf loading method bool trLoadSuccess; - if (isUserLanguageSystem_p()) - { + if (isUserLanguageSystem_p()) { trLoadSuccess = loadSystemTranslation_p(inLangPath, &inAppTranslator); } - else - { + else { trLoadSuccess = loadUserTranslation_p(inLangPath, &inAppTranslator); } - if (!trLoadSuccess && !isUserLanguageSystem_p()) - { + if (!trLoadSuccess && !isUserLanguageSystem_p()) { trLoadSuccess = loadSystemTranslation_p(inLangPath, &inAppTranslator); } - if (trLoadSuccess) - { + if (trLoadSuccess) { #ifdef GTA5SYNC_DEBUG qDebug() << "installTranslation" << currentLanguage; #endif app->installTranslator(&inAppTranslator); - if (loadQtTranslation_p(exLangPath, &exQtTranslator)) - { + if (loadQtTranslation_p(exLangPath, &exQtTranslator)) { app->installTranslator(&exQtTranslator); } - else if (loadQtTranslation_p(inLangPath, &inQtTranslator)) - { + else if (loadQtTranslation_p(inLangPath, &inQtTranslator)) { app->installTranslator(&inQtTranslator); } +#if QT_VERSION >= 0x060000 + QLocale::setDefault(QLocale(currentLanguage)); +#else QLocale::setDefault(currentLanguage); +#endif isLangLoaded = true; } #endif @@ -280,8 +277,7 @@ QStringList TranslationClass::listTranslations(const QString &langPath) langDir.setNameFilters(QStringList("gta5sync_*.qm")); langDir.setPath(langPath); QStringList availableLanguages; - for (QString lang : langDir.entryList(QDir::Files | QDir::NoDotAndDotDot, QDir::NoSort)) - { + for (const QString &lang : langDir.entryList(QDir::Files | QDir::NoDotAndDotDot, QDir::NoSort)) { availableLanguages << QString(lang).remove("gta5sync_").remove(".qm"); } return availableLanguages; @@ -293,8 +289,7 @@ QStringList TranslationClass::listAreaTranslations() langDir.setNameFilters(QStringList("global.*.ini")); langDir.setPath(":/global"); QStringList availableLanguages; - for (QString lang : langDir.entryList(QDir::Files | QDir::NoDotAndDotDot, QDir::NoSort)) - { + for (const QString &lang : langDir.entryList(QDir::Files | QDir::NoDotAndDotDot, QDir::NoSort)) { availableLanguages << QString(lang).remove("global.").remove(".ini"); } return availableLanguages; @@ -306,21 +301,17 @@ bool TranslationClass::loadSystemTranslation_p(const QString &langPath, QTransla qDebug() << "loadSystemTranslation_p"; #endif int currentLangCounter = 0; - for (QString languageName : QLocale::system().uiLanguages()) - { + for (const QString &languageName : QLocale::system().uiLanguages()) { #ifdef GTA5SYNC_DEBUG qDebug() << "loadLanguage" << languageName; #endif - QStringList langList = QString(languageName).replace("-","_").split("_"); - if (langList.length() == 2) - { + const QStringList langList = QString(languageName).replace("-","_").split("_"); + if (langList.length() == 2) { #ifdef GTA5SYNC_DEBUG qDebug() << "loadLanguageFile" << QString(langPath % "/gta5sync_" % langList.at(0) % "_" % langList.at(1) % ".qm"); #endif - if (QFile::exists(langPath % "/gta5sync_" % langList.at(0) % "_" % langList.at(1) % ".qm")) - { - if (appTranslator->load(langPath % "/gta5sync_" % langList.at(0) % "_" % langList.at(1) % ".qm")) - { + if (QFile::exists(langPath % "/gta5sync_" % langList.at(0) % "_" % langList.at(1) % ".qm")) { + if (appTranslator->load(langPath % "/gta5sync_" % langList.at(0) % "_" % langList.at(1) % ".qm")) { #ifdef GTA5SYNC_DEBUG qDebug() << "loadLanguageFileSuccess" << QString(langPath % "/gta5sync_" % langList.at(0) % "_" % langList.at(1) % ".qm"); #endif @@ -333,10 +324,8 @@ bool TranslationClass::loadSystemTranslation_p(const QString &langPath, QTransla #ifdef GTA5SYNC_DEBUG qDebug() << "loadLanguageFile" << QString(langPath % "/gta5sync_" % langList.at(0) % ".qm"); #endif - if (QFile::exists(langPath % "/gta5sync_" % langList.at(0) % ".qm")) - { - if (appTranslator->load(langPath % "/gta5sync_" % langList.at(0) % ".qm")) - { + if (QFile::exists(langPath % "/gta5sync_" % langList.at(0) % ".qm")) { + if (appTranslator->load(langPath % "/gta5sync_" % langList.at(0) % ".qm")) { #ifdef GTA5SYNC_DEBUG qDebug() << "loadLanguageFileSuccess" << QString(langPath % "/gta5sync_" % langList.at(0) % ".qm"); #endif @@ -345,8 +334,7 @@ bool TranslationClass::loadSystemTranslation_p(const QString &langPath, QTransla currentLangIndex = currentLangCounter; return true; } - else if (langList.at(0) == "en") - { + else if (langList.at(0) == "en") { #ifdef GTA5SYNC_DEBUG qDebug() << "languageEnglishMode index" << currentLangCounter; #endif @@ -356,8 +344,7 @@ bool TranslationClass::loadSystemTranslation_p(const QString &langPath, QTransla return true; } } - else if (langList.at(0) == "en") - { + else if (langList.at(0) == "en") { #ifdef GTA5SYNC_DEBUG qDebug() << "languageEnglishMode index" << currentLangCounter; #endif @@ -367,15 +354,12 @@ bool TranslationClass::loadSystemTranslation_p(const QString &langPath, QTransla return true; } } - else if (langList.length() == 1) - { + else if (langList.length() == 1) { #ifdef GTA5SYNC_DEBUG qDebug() << "loadLanguageFile" << QString(langPath % "/gta5sync_" % langList.at(0) % ".qm"); #endif - if (QFile::exists(langPath % "/gta5sync_" % langList.at(0) % ".qm")) - { - if (appTranslator->load(langPath % "/gta5sync_" % langList.at(0) % ".qm")) - { + if (QFile::exists(langPath % "/gta5sync_" % langList.at(0) % ".qm")) { + if (appTranslator->load(langPath % "/gta5sync_" % langList.at(0) % ".qm")) { #ifdef GTA5SYNC_DEBUG qDebug() << "loadLanguageFileSuccess" << QString(langPath % "/gta5sync_" % langList.at(0) % ".qm"); #endif @@ -399,17 +383,14 @@ bool TranslationClass::loadUserTranslation_p(const QString &langPath, QTranslato #ifdef GTA5SYNC_DEBUG qDebug() << "loadUserTranslation_p"; #endif - QString languageName = userLanguage; - QStringList langList = QString(languageName).replace("-","_").split("_"); - if (langList.length() == 2) - { + const QString languageName = userLanguage; + const QStringList langList = QString(languageName).replace("-","_").split("_"); + if (langList.length() == 2) { #ifdef GTA5SYNC_DEBUG qDebug() << "loadLanguageFile" << QString(langPath % "/gta5sync_" % langList.at(0) % "_" % langList.at(1) % ".qm"); #endif - if (QFile::exists(langPath % "/gta5sync_" % langList.at(0) % "_" % langList.at(1) % ".qm")) - { - if (appTranslator->load(langPath % "/gta5sync_" % langList.at(0) % "_" % langList.at(1) % ".qm")) - { + if (QFile::exists(langPath % "/gta5sync_" % langList.at(0) % "_" % langList.at(1) % ".qm")) { + if (appTranslator->load(langPath % "/gta5sync_" % langList.at(0) % "_" % langList.at(1) % ".qm")) { #ifdef GTA5SYNC_DEBUG qDebug() << "loadLanguageFileSuccess" << QString(langPath % "/gta5sync_" % langList.at(0) % "_" % langList.at(1) % ".qm"); #endif @@ -420,10 +401,8 @@ bool TranslationClass::loadUserTranslation_p(const QString &langPath, QTranslato #ifdef GTA5SYNC_DEBUG qDebug() << "loadLanguageFile" << QString(langPath % "/gta5sync_" % langList.at(0) % ".qm"); #endif - if (QFile::exists(langPath % "/gta5sync_" % langList.at(0) % ".qm")) - { - if (appTranslator->load(langPath % "/gta5sync_" % langList.at(0) % ".qm")) - { + if (QFile::exists(langPath % "/gta5sync_" % langList.at(0) % ".qm")) { + if (appTranslator->load(langPath % "/gta5sync_" % langList.at(0) % ".qm")) { #ifdef GTA5SYNC_DEBUG qDebug() << "loadLanguageFileSuccess" << QString(langPath % "/gta5sync_" % langList.at(0) % ".qm"); #endif @@ -432,15 +411,12 @@ bool TranslationClass::loadUserTranslation_p(const QString &langPath, QTranslato } } } - else if (langList.length() == 1) - { + else if (langList.length() == 1) { #ifdef GTA5SYNC_DEBUG qDebug() << "loadLanguageFile" << QString(langPath % "/gta5sync_" % langList.at(0) % ".qm"); #endif - if (QFile::exists(langPath % "/gta5sync_" % langList.at(0) % ".qm")) - { - if (appTranslator->load(langPath % "/gta5sync_" % langList.at(0) % ".qm")) - { + if (QFile::exists(langPath % "/gta5sync_" % langList.at(0) % ".qm")) { + if (appTranslator->load(langPath % "/gta5sync_" % langList.at(0) % ".qm")) { #ifdef GTA5SYNC_DEBUG qDebug() << "loadLanguageFileSuccess" << QString(langPath % "/gta5sync_" % langList.at(0) % ".qm"); #endif @@ -457,17 +433,14 @@ bool TranslationClass::loadQtTranslation_p(const QString &langPath, QTranslator #ifdef GTA5SYNC_DEBUG qDebug() << "loadQtTranslation_p" << currentLanguage; #endif - QString languageName = currentLanguage; - QStringList langList = QString(languageName).replace("-","_").split("_"); - if (langList.length() == 2) - { + const QString languageName = currentLanguage; + const QStringList langList = QString(languageName).replace("-","_").split("_"); + if (langList.length() == 2) { #ifdef GTA5SYNC_DEBUG qDebug() << "loadLanguageFile" << QString(langPath % QDir::separator() % QtBaseTranslationFormat % langList.at(0) % "_" % langList.at(1) % ".qm"); #endif - if (QFile::exists(langPath % QDir::separator() % QtBaseTranslationFormat % langList.at(0) % "_" % langList.at(1) % ".qm")) - { - if (qtTranslator->load(langPath % QDir::separator() % QtBaseTranslationFormat % langList.at(0) % "_" % langList.at(1) % ".qm")) - { + if (QFile::exists(langPath % QDir::separator() % QtBaseTranslationFormat % langList.at(0) % "_" % langList.at(1) % ".qm")) { + if (qtTranslator->load(langPath % QDir::separator() % QtBaseTranslationFormat % langList.at(0) % "_" % langList.at(1) % ".qm")) { #ifdef GTA5SYNC_DEBUG qDebug() << "loadLanguageFileSuccess" << QString(langPath % QDir::separator() % QtBaseTranslationFormat % langList.at(0) % "_" % langList.at(1) % ".qm"); #endif @@ -477,10 +450,8 @@ bool TranslationClass::loadQtTranslation_p(const QString &langPath, QTranslator #ifdef GTA5SYNC_DEBUG qDebug() << "loadLanguageFile" << QString(langPath % QDir::separator() % QtBaseTranslationFormat % langList.at(0) % ".qm"); #endif - if (QFile::exists(langPath % QDir::separator() % QtBaseTranslationFormat % langList.at(0) % ".qm")) - { - if (qtTranslator->load(langPath % QDir::separator() % QtBaseTranslationFormat % langList.at(0) % ".qm")) - { + if (QFile::exists(langPath % QDir::separator() % QtBaseTranslationFormat % langList.at(0) % ".qm")) { + if (qtTranslator->load(langPath % QDir::separator() % QtBaseTranslationFormat % langList.at(0) % ".qm")) { #ifdef GTA5SYNC_DEBUG qDebug() << "loadLanguageFileSuccess" << QString(langPath % QDir::separator() % QtBaseTranslationFormat % langList.at(0) % ".qm"); #endif @@ -488,15 +459,12 @@ bool TranslationClass::loadQtTranslation_p(const QString &langPath, QTranslator } } } - else if (langList.length() == 1) - { + else if (langList.length() == 1) { #ifdef GTA5SYNC_DEBUG qDebug() << "loadLanguageFile" << QString(langPath % QDir::separator() % QtBaseTranslationFormat % langList.at(0) % ".qm"); #endif - if (QFile::exists(langPath % QDir::separator() % QtBaseTranslationFormat % langList.at(0) % ".qm")) - { - if (qtTranslator->load(langPath % QDir::separator() % QtBaseTranslationFormat % langList.at(0) % ".qm")) - { + if (QFile::exists(langPath % QDir::separator() % QtBaseTranslationFormat % langList.at(0) % ".qm")) { + if (qtTranslator->load(langPath % QDir::separator() % QtBaseTranslationFormat % langList.at(0) % ".qm")) { #ifdef GTA5SYNC_DEBUG qDebug() << "loadLanguageFileSuccess" << QString(langPath % QDir::separator() % QtBaseTranslationFormat % langList.at(0) % ".qm"); #endif @@ -515,40 +483,61 @@ bool TranslationClass::isUserLanguageSystem_p() QString TranslationClass::getCurrentAreaLanguage() { const QStringList areaTranslations = listAreaTranslations(); - if (userAreaLanguage == "Auto" || userAreaLanguage.trimmed().isEmpty()) - { + if (userAreaLanguage == "Auto" || userAreaLanguage.trimmed().isEmpty()) { + const GameLanguage gameLanguage = AppEnv::getGameLanguage(AppEnv::getGameVersion()); + if (gameLanguage == GameLanguage::Undefined) { #ifdef GTA5SYNC_DEBUG - qDebug() << "autoAreaLanguageMode"; + qDebug() << "autoAreaLanguageModeInterface"; #endif - QString langCode = QString(currentLanguage).replace("-", "_"); - if (areaTranslations.contains(langCode)) - { + QString langCode = QString(currentLanguage).replace("-", "_"); + if (areaTranslations.contains(langCode)) { #ifdef GTA5SYNC_DEBUG - qDebug() << "autoAreaLanguageSelected" << langCode; + qDebug() << "autoAreaLanguageSelected" << langCode; #endif - return langCode; + return langCode; + } + else if (langCode.contains("_")) { + langCode = langCode.split("_").at(0); + if (!areaTranslations.contains(langCode)) + goto outputDefaultLanguage; +#ifdef GTA5SYNC_DEBUG + qDebug() << "autoAreaLanguageSelected" << langCode; +#endif + return langCode; + } } - else if (langCode.contains("_")) - { - langCode = langCode.split("_").at(0); - if (!areaTranslations.contains(langCode)) goto outputDefaultLanguage; + else { #ifdef GTA5SYNC_DEBUG - qDebug() << "autoAreaLanguageSelected" << langCode; + qDebug() << "autoAreaLanguageModeGame"; #endif - return langCode; + QString langCode = AppEnv::gameLanguageToString(gameLanguage).replace("-", "_"); + if (areaTranslations.contains(langCode)) { +#ifdef GTA5SYNC_DEBUG + qDebug() << "autoAreaLanguageSelected" << langCode; +#endif + return langCode; + } + else if (langCode.contains("_")) { + langCode = langCode.split("_").at(0); + if (!areaTranslations.contains(langCode)) + goto outputDefaultLanguage; +#ifdef GTA5SYNC_DEBUG + qDebug() << "autoAreaLanguageSelected" << langCode; +#endif + return langCode; + } } } - else if (areaTranslations.contains(userAreaLanguage)) - { + else if (areaTranslations.contains(userAreaLanguage)) { #ifdef GTA5SYNC_DEBUG qDebug() << "userAreaLanguageSelected" << userAreaLanguage; #endif return userAreaLanguage; } - else if (userAreaLanguage.contains("_")) - { - QString langCode = QString(userAreaLanguage).replace("-", "_").split("_").at(0); - if (!areaTranslations.contains(langCode)) goto outputDefaultLanguage; + else if (userAreaLanguage.contains("_")) { + const QString langCode = QString(userAreaLanguage).replace("-", "_").split("_").at(0); + if (!areaTranslations.contains(langCode)) + goto outputDefaultLanguage; #ifdef GTA5SYNC_DEBUG qDebug() << "userAreaLanguageSelected" << langCode; #endif @@ -573,8 +562,7 @@ bool TranslationClass::isLanguageLoaded() void TranslationClass::unloadTranslation(QApplication *app) { - if (isLangLoaded) - { + if (isLangLoaded) { #ifndef GTA5SYNC_QCONF app->removeTranslator(&exAppTranslator); app->removeTranslator(&exQtTranslator); @@ -597,31 +585,21 @@ void TranslationClass::unloadTranslation(QApplication *app) QString TranslationClass::getCountryCode(QLocale::Country country) { - QList locales = QLocale::matchingLocales(QLocale::AnyLanguage, - QLocale::AnyScript, - country); - if (locales.isEmpty()) return QString(); - QStringList localeStrList = locales.at(0).name().split("_"); - if (localeStrList.length() >= 2) - { - return localeStrList.at(1).toLower(); - } - else - { - return QString(); + const QList locales = QLocale::matchingLocales(QLocale::AnyLanguage, QLocale::AnyScript, country); + if (!locales.isEmpty()) { + const QStringList localeStrList = locales.at(0).name().split("_"); + if (localeStrList.length() >= 2) { + return localeStrList.at(1).toLower(); + } } + return QString(); } QString TranslationClass::getCountryCode(QLocale locale) { QStringList localeStrList = locale.name().split("_"); - if (localeStrList.length() >= 2) - { - qDebug() << localeStrList; + if (localeStrList.length() >= 2) { return localeStrList.at(1).toLower(); } - else - { - return QString(); - } + return QString(); } diff --git a/TranslationClass.h b/TranslationClass.h index 138216d..963eaff 100644 --- a/TranslationClass.h +++ b/TranslationClass.h @@ -1,5 +1,5 @@ /***************************************************************************** -* gta5sync GRAND THEFT AUTO V SYNC +* gta5view Grand Theft Auto V Profile Viewer * Copyright (C) 2017 Syping * * This program is free software: you can redistribute it and/or modify diff --git a/UserInterface.cpp b/UserInterface.cpp index 7d63257..a990b51 100644 --- a/UserInterface.cpp +++ b/UserInterface.cpp @@ -1,6 +1,6 @@ /***************************************************************************** -* gta5sync GRAND THEFT AUTO V SYNC -* Copyright (C) 2016-2018 Syping +* gta5view Grand Theft Auto V Profile Viewer +* Copyright (C) 2016-2021 Syping * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -40,14 +40,31 @@ #include #include #include +#include #include #include #include #include +#ifdef GTA5SYNC_DONATE +#ifdef GTA5SYNC_DONATE_ADDRESSES +#include +#include +#include +#include "QrCode.h" +using namespace qrcodegen; +#endif +#endif + +#ifdef GTA5SYNC_MOTD +UserInterface::UserInterface(ProfileDatabase *profileDB, CrewDatabase *crewDB, DatabaseThread *threadDB, MessageThread *threadMessage, QWidget *parent) : + QMainWindow(parent), profileDB(profileDB), crewDB(crewDB), threadDB(threadDB), threadMessage(threadMessage), + ui(new Ui::UserInterface) + #else UserInterface::UserInterface(ProfileDatabase *profileDB, CrewDatabase *crewDB, DatabaseThread *threadDB, QWidget *parent) : QMainWindow(parent), profileDB(profileDB), crewDB(crewDB), threadDB(threadDB), ui(new Ui::UserInterface) + #endif { ui->setupUi(this); contentMode = 0; @@ -56,107 +73,244 @@ UserInterface::UserInterface(ProfileDatabase *profileDB, CrewDatabase *crewDB, D ui->menuProfile->setEnabled(false); ui->actionSelect_profile->setEnabled(false); ui->actionAbout_gta5sync->setIcon(IconLoader::loadingAppIcon()); +#ifdef Q_OS_MAC + ui->actionAbout_gta5sync->setText(QApplication::translate("MAC_APPLICATION_MENU", "About %1").arg(GTA5SYNC_APPSTR)); + ui->actionOptions->setText(QApplication::translate("MAC_APPLICATION_MENU", "Preferences...")); +#else ui->actionAbout_gta5sync->setText(tr("&About %1").arg(GTA5SYNC_APPSTR)); +#endif ui->cmdClose->setToolTip(ui->cmdClose->toolTip().arg(GTA5SYNC_APPSTR)); defaultWindowTitle = tr("%2 - %1").arg("%1", GTA5SYNC_APPSTR); - this->setWindowTitle(defaultWindowTitle.arg(tr("Select Profile"))); - ui->labVersion->setText(QString("%1 %2").arg(GTA5SYNC_APPSTR, GTA5SYNC_APPVER)); + setWindowTitle(defaultWindowTitle.arg(tr("Select Profile"))); + QString appVersion = QApplication::applicationVersion(); + const char* literalBuildType = GTA5SYNC_BUILDTYPE; +#ifdef GTA5SYNC_COMMIT + if ((strcmp(literalBuildType, REL_BUILDTYPE) != 0) && !appVersion.contains("-")) + appVersion = appVersion % "-" % GTA5SYNC_COMMIT; +#endif + ui->labVersion->setText(QString("%1 %2").arg(GTA5SYNC_APPSTR, appVersion)); // Set Icon for Close Button - if (QIcon::hasThemeIcon("dialog-close")) - { + if (QIcon::hasThemeIcon("dialog-close")) { ui->cmdClose->setIcon(QIcon::fromTheme("dialog-close")); } - else if (QIcon::hasThemeIcon("gtk-close")) - { + else if (QIcon::hasThemeIcon("gtk-close")) { ui->cmdClose->setIcon(QIcon::fromTheme("gtk-close")); } // Set Icon for Reload Button - if (QIcon::hasThemeIcon("view-refresh")) - { + if (QIcon::hasThemeIcon("view-refresh")) { ui->cmdReload->setIcon(QIcon::fromTheme("view-refresh")); } - else if (QIcon::hasThemeIcon("reload")) - { + else if (QIcon::hasThemeIcon("reload")) { ui->cmdReload->setIcon(QIcon::fromTheme("reload")); } // Set Icon for Choose GTA V Folder Menu Item - if (QIcon::hasThemeIcon("document-open-folder")) - { + if (QIcon::hasThemeIcon("document-open-folder")) { ui->actionSelect_GTA_Folder->setIcon(QIcon::fromTheme("document-open-folder")); } - else if (QIcon::hasThemeIcon("gtk-directory")) - { + else if (QIcon::hasThemeIcon("gtk-directory")) { ui->actionSelect_GTA_Folder->setIcon(QIcon::fromTheme("gtk-directory")); } // Set Icon for Open File Menu Item - if (QIcon::hasThemeIcon("document-open")) - { + if (QIcon::hasThemeIcon("document-open")) { ui->actionOpen_File->setIcon(QIcon::fromTheme("document-open")); } // Set Icon for Close Profile Menu Item - if (QIcon::hasThemeIcon("dialog-close")) - { + if (QIcon::hasThemeIcon("dialog-close")) { ui->actionSelect_profile->setIcon(QIcon::fromTheme("dialog-close")); } - else if (QIcon::hasThemeIcon("gtk-close")) - { + else if (QIcon::hasThemeIcon("gtk-close")) { ui->actionSelect_profile->setIcon(QIcon::fromTheme("gtk-close")); } // Set Icon for Exit Menu Item - if (QIcon::hasThemeIcon("application-exit")) - { + if (QIcon::hasThemeIcon("application-exit")) { #ifndef Q_OS_MACOS // Setting icon for exit/quit lead to a crash in Mac OS X ui->actionExit->setIcon(QIcon::fromTheme("application-exit")); #endif } // Set Icon for Preferences Menu Item - if (QIcon::hasThemeIcon("preferences-system")) - { + if (QIcon::hasThemeIcon("preferences-system")) { #ifndef Q_OS_MACOS // Setting icon for preferences/settings/options lead to a crash in Mac OS X ui->actionOptions->setIcon(QIcon::fromTheme("preferences-system")); #endif } - else if (QIcon::hasThemeIcon("configure")) - { + else if (QIcon::hasThemeIcon("configure")) { #ifndef Q_OS_MACOS // Setting icon for preferences/settings/options lead to a crash in Mac OS X ui->actionOptions->setIcon(QIcon::fromTheme("configure")); #endif } // Set Icon for Profile Import Menu Item - if (QIcon::hasThemeIcon("document-import")) - { + if (QIcon::hasThemeIcon("document-import")) { ui->action_Import->setIcon(QIcon::fromTheme("document-import")); } - else if (QIcon::hasThemeIcon("document-open")) - { + else if (QIcon::hasThemeIcon("document-open")) { ui->action_Import->setIcon(QIcon::fromTheme("document-open")); } // Set Icon for Profile Export Menu Item - if (QIcon::hasThemeIcon("document-export")) - { + if (QIcon::hasThemeIcon("document-export")) { ui->actionExport_selected->setIcon(QIcon::fromTheme("document-export")); } - else if (QIcon::hasThemeIcon("document-save")) - { + else if (QIcon::hasThemeIcon("document-save")) { ui->actionExport_selected->setIcon(QIcon::fromTheme("document-save")); } // Set Icon for Profile Remove Menu Item - if (QIcon::hasThemeIcon("remove")) - { + if (QIcon::hasThemeIcon("remove")) { ui->actionDelete_selected->setIcon(QIcon::fromTheme("remove")); } +#ifdef GTA5SYNC_DONATE +#ifdef GTA5SYNC_DONATE_ADDRESSES + donateAction = new QAction(tr("&Donate"), this); + if (QIcon::hasThemeIcon("help-donate")) { + donateAction->setIcon(QIcon::fromTheme("help-donate")); + } + else if (QIcon::hasThemeIcon("taxes-finances")) { + donateAction->setIcon(QIcon::fromTheme("taxes-finances")); + } + else { + donateAction->setIcon(QIcon(":/img/donate.svgz")); + } + ui->menuHelp->insertAction(ui->actionAbout_gta5sync, donateAction); + QObject::connect(donateAction, &QAction::triggered, this, [=](){ + QDialog *donateDialog = new QDialog(this); + donateDialog->setWindowTitle(QString("%1 - %2").arg(GTA5SYNC_APPSTR, tr("Donate"))); +#if QT_VERSION >= 0x050900 + donateDialog->setWindowFlag(Qt::WindowContextHelpButtonHint, false); +#else + donateDialog->setWindowFlags(donateDialog->windowFlags()^Qt::WindowContextHelpButtonHint); +#endif + QVBoxLayout *donateLayout = new QVBoxLayout; + donateDialog->setLayout(donateLayout); + QLabel *methodsLabel = new QLabel(QString("%1").arg(tr("Donation methods").toHtmlEscaped()), donateDialog); + methodsLabel->setWordWrap(true); + donateLayout->addWidget(methodsLabel); + QHBoxLayout *currencyLayout = new QHBoxLayout; + donateLayout->addLayout(currencyLayout); + const QStringList addressList = QString::fromUtf8(GTA5SYNC_DONATE_ADDRESSES).split(','); + for (const QString &address : addressList) { + const QStringList addressList = address.split(':'); + if (addressList.length() == 2) { + const QString currency = addressList.at(0); + const QString address = addressList.at(1); + QString currencyStr = currency; + const QString strPath = QString(":/donate/%1.str").arg(currency); + if (QFile::exists(strPath)) { + QFile strFile(strPath); + if (strFile.open(QIODevice::ReadOnly)) { + currencyStr = QString::fromUtf8(strFile.readAll()); + strFile.close(); + } + } + const QString iconPath = QString(":/donate/%1.svgz").arg(currency); + QPushButton *currencyButton = new QPushButton(currencyStr, donateDialog); + currencyButton->setToolTip(currencyStr); + if (QFile::exists(iconPath)) { + currencyButton->setIconSize(QSize(32, 32)); + currencyButton->setIcon(QIcon(iconPath)); + } + currencyLayout->addWidget(currencyButton); + QObject::connect(currencyButton, &QPushButton::pressed, donateDialog, [=](){ + QDialog *addressDialog = new QDialog(donateDialog); + addressDialog->setWindowTitle(currencyStr); +#if QT_VERSION >= 0x050900 + addressDialog->setWindowFlag(Qt::WindowContextHelpButtonHint, false); +#else + addressDialog->setWindowFlags(donateDialog->windowFlags()^Qt::WindowContextHelpButtonHint); +#endif + QVBoxLayout *addressLayout = new QVBoxLayout; + addressDialog->setLayout(addressLayout); + QLabel *addressLabel = new QLabel(address, addressDialog); + addressLabel->setAlignment(Qt::AlignCenter); + addressLabel->setTextFormat(Qt::PlainText); + addressLabel->setTextInteractionFlags(Qt::TextSelectableByMouse); + addressLayout->addWidget(addressLabel); + QHBoxLayout *qrLayout = new QHBoxLayout; + qrLayout->addSpacerItem(new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Minimum)); + QrCode qr = QrCode::encodeText(address.toUtf8().constData(), QrCode::Ecc::MEDIUM); + const std::string svgString = qr.toSvgString(0); + QSvgRenderer svgRenderer(QByteArray::fromRawData(svgString.c_str(), svgString.size())); + qreal screenRatioPR = AppEnv::screenRatioPR(); + const QSize widgetSize = QSize(200, 200); + const QSize pixmapSize = widgetSize * screenRatioPR; + QPixmap qrPixmap(pixmapSize); + qrPixmap.fill(Qt::white); + QPainter qrPainter(&qrPixmap); + svgRenderer.render(&qrPainter, QRectF(QPointF(0, 0), pixmapSize)); + qrPainter.end(); +#if QT_VERSION >= 0x050600 + qrPixmap.setDevicePixelRatio(screenRatioPR); +#endif + QLabel *qrLabel = new QLabel(addressDialog); + qrLabel->setFixedSize(widgetSize); + qrLabel->setPixmap(qrPixmap); + qrLayout->addWidget(qrLabel); + qrLayout->addSpacerItem(new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Minimum)); + addressLayout->addLayout(qrLayout); + QHBoxLayout *buttonLayout = new QHBoxLayout; + buttonLayout->addSpacerItem(new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Minimum)); + QPushButton *copyAddressButton = new QPushButton(tr("&Copy"), addressDialog); + if (QIcon::hasThemeIcon("edit-copy")) { + copyAddressButton->setIcon(QIcon::fromTheme("edit-copy")); + } + QObject::connect(copyAddressButton, &QPushButton::pressed, addressDialog, [=](){ + QApplication::clipboard()->setText(address); + }); + buttonLayout->addWidget(copyAddressButton); + QPushButton *closeButton = new QPushButton(tr("&Close"), addressDialog); + if (QIcon::hasThemeIcon("dialog-close")) { + closeButton->setIcon(QIcon::fromTheme("dialog-close")); + } + else if (QIcon::hasThemeIcon("gtk-close")) { + closeButton->setIcon(QIcon::fromTheme("gtk-close")); + } + closeButton->setDefault(true); + buttonLayout->addWidget(closeButton); + buttonLayout->addSpacerItem(new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Minimum)); + addressLayout->addLayout(buttonLayout); + QObject::connect(closeButton, &QPushButton::clicked, addressDialog, &QDialog::accept); + QObject::connect(addressDialog, &QDialog::finished, addressDialog, &QDialog::deleteLater); + QTimer::singleShot(0, addressDialog, [=](){ + addressDialog->setFocus(); + }); + addressDialog->open(); + addressDialog->setFixedSize(addressDialog->size()); + }); + } + } + QHBoxLayout *buttonLayout = new QHBoxLayout; + buttonLayout->addSpacerItem(new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Minimum)); + QPushButton *closeButton = new QPushButton(donateDialog); + closeButton->setText(tr("&Close")); + if (QIcon::hasThemeIcon("dialog-close")) { + closeButton->setIcon(QIcon::fromTheme("dialog-close")); + } + else if (QIcon::hasThemeIcon("gtk-close")) { + closeButton->setIcon(QIcon::fromTheme("gtk-close")); + } + closeButton->setDefault(true); + buttonLayout->addWidget(closeButton); + donateLayout->addLayout(buttonLayout); + QObject::connect(closeButton, &QPushButton::clicked, donateDialog, &QDialog::accept); + QObject::connect(donateDialog, &QDialog::finished, donateDialog, &QDialog::deleteLater); + QTimer::singleShot(0, donateDialog, [=](){ + donateDialog->setFocus(); + }); + donateDialog->open(); + donateDialog->setFixedSize(donateDialog->size()); + }); +#endif +#endif + // DPI calculation qreal screenRatio = AppEnv::screenRatio(); #ifndef Q_QS_ANDROID @@ -166,30 +320,26 @@ UserInterface::UserInterface(ProfileDatabase *profileDB, CrewDatabase *crewDB, D ui->vlUserInterface->setContentsMargins(9 * screenRatio, 9 * screenRatio, 9 * screenRatio, 9 * screenRatio); } -void UserInterface::setupDirEnv() +void UserInterface::setupDirEnv(bool showFolderDialog) { // settings init QSettings settings(GTA5SYNC_APPVENDOR, GTA5SYNC_APPSTR); bool folderExists; GTAV_Folder = AppEnv::getGameFolder(&folderExists); - if (folderExists) - { + if (folderExists) { QDir::setCurrent(GTAV_Folder); } - else - { + else if (showFolderDialog) { GTAV_Folder = QFileDialog::getExistingDirectory(this, tr("Select GTA V Folder..."), StandardPaths::documentsLocation(), QFileDialog::ShowDirsOnly); - if (QFileInfo(GTAV_Folder).exists()) - { + if (QDir(GTAV_Folder).exists()) { folderExists = true; QDir::setCurrent(GTAV_Folder); AppEnv::setGameFolder(GTAV_Folder); // First time folder selection save settings.beginGroup("dir"); - if (settings.value("dir", "").toString().isEmpty()) - { + if (settings.value("dir", "").toString().isEmpty()) { settings.setValue("dir", GTAV_Folder); } settings.endGroup(); @@ -200,15 +350,15 @@ void UserInterface::setupDirEnv() settings.beginGroup("Profile"); QString defaultProfile = settings.value("Default", "").toString(); - bool contentModeOk; - contentMode = settings.value("ContentMode", 0).toInt(&contentModeOk); - if (contentMode != 0 && contentMode != 1 && contentMode != 2) - { - contentMode = 0; + contentMode = settings.value("ContentMode", 0).toInt(); + if (contentMode == 1) { + contentMode = 21; + } + else if (contentMode != 10 && contentMode != 11 && contentMode != 20 && contentMode != 21) { + contentMode = 20; } - if (folderExists) - { + if (folderExists) { QDir GTAV_ProfilesDir; GTAV_ProfilesFolder = GTAV_Folder % "/Profiles"; GTAV_ProfilesDir.setPath(GTAV_ProfilesFolder); @@ -216,17 +366,14 @@ void UserInterface::setupDirEnv() GTAV_Profiles = GTAV_ProfilesDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot, QDir::NoSort); setupProfileUi(); - if (GTAV_Profiles.length() == 1) - { + if (GTAV_Profiles.length() == 1) { openProfile(GTAV_Profiles.at(0)); } - else if(GTAV_Profiles.contains(defaultProfile)) - { + else if(GTAV_Profiles.contains(defaultProfile)) { openProfile(defaultProfile); } } - else - { + else { GTAV_Profiles = QStringList(); setupProfileUi(); } @@ -236,8 +383,7 @@ void UserInterface::setupDirEnv() void UserInterface::setupProfileUi() { qreal screenRatio = AppEnv::screenRatio(); - if (GTAV_Profiles.isEmpty()) - { + if (GTAV_Profiles.isEmpty()) { QPushButton *changeDirBtn = new QPushButton(tr("Select >A V Folder..."), ui->swSelection); changeDirBtn->setObjectName("cmdChangeDir"); changeDirBtn->setMinimumSize(0, 40 * screenRatio); @@ -247,8 +393,7 @@ void UserInterface::setupProfileUi() QObject::connect(changeDirBtn, SIGNAL(clicked(bool)), this, SLOT(changeFolder_clicked())); } - else for (QString GTAV_Profile : GTAV_Profiles) - { + else for (const QString >AV_Profile : GTAV_Profiles) { QPushButton *profileBtn = new QPushButton(GTAV_Profile, ui->swSelection); profileBtn->setObjectName(GTAV_Profile); profileBtn->setMinimumSize(0, 40 * screenRatio); @@ -268,8 +413,7 @@ void UserInterface::changeFolder_clicked() void UserInterface::on_cmdReload_clicked() { - for (QPushButton *profileBtn : profileBtns) - { + for (QPushButton *profileBtn : profileBtns) { ui->vlButtons->removeWidget(profileBtn); delete profileBtn; } @@ -295,16 +439,15 @@ void UserInterface::openProfile(const QString &profileName_) profileUI->setupProfileInterface(); QObject::connect(profileUI, SIGNAL(profileClosed()), this, SLOT(closeProfile())); QObject::connect(profileUI, SIGNAL(profileLoaded()), this, SLOT(profileLoaded())); - this->setWindowTitle(defaultWindowTitle.arg(profileName)); + setWindowTitle(defaultWindowTitle.arg(profileName)); } void UserInterface::closeProfile() { - if (profileOpen) - { + if (profileOpen) { closeProfile_p(); } - this->setWindowTitle(defaultWindowTitle.arg(tr("Select Profile"))); + setWindowTitle(defaultWindowTitle.arg(tr("Select Profile"))); } void UserInterface::closeProfile_p() @@ -322,17 +465,18 @@ void UserInterface::closeProfile_p() void UserInterface::closeEvent(QCloseEvent *ev) { Q_UNUSED(ev) +#ifdef GTA5SYNC_MOTD + threadMessage->terminateThread(); +#else threadDB->terminateThread(); +#endif } UserInterface::~UserInterface() { if (profileOpen) - { closeProfile_p(); - } - for (QPushButton *profileBtn : profileBtns) - { + for (QPushButton *profileBtn : profileBtns) { delete profileBtn; } profileBtns.clear(); @@ -341,7 +485,7 @@ UserInterface::~UserInterface() void UserInterface::on_actionExit_triggered() { - this->close(); + close(); } void UserInterface::on_actionSelect_profile_triggered() @@ -379,33 +523,25 @@ void UserInterface::profileLoaded() void UserInterface::on_actionSelect_all_triggered() { if (profileOpen) - { profileUI->selectAllWidgets(); - } } void UserInterface::on_actionDeselect_all_triggered() { if (profileOpen) - { profileUI->deselectAllWidgets(); - } } void UserInterface::on_actionExport_selected_triggered() { if (profileOpen) - { profileUI->exportSelected(); - } } void UserInterface::on_actionDelete_selected_triggered() { if (profileOpen) - { profileUI->deleteSelected(); - } } void UserInterface::on_actionOptions_triggered() @@ -430,9 +566,7 @@ void UserInterface::on_actionOptions_triggered() void UserInterface::on_action_Import_triggered() { if (profileOpen) - { profileUI->importFiles(); - } } void UserInterface::on_actionOpen_File_triggered() @@ -446,7 +580,11 @@ fileDialogPreOpen: fileDialog.setViewMode(QFileDialog::Detail); fileDialog.setAcceptMode(QFileDialog::AcceptOpen); fileDialog.setOption(QFileDialog::DontUseNativeDialog, false); +#if QT_VERSION >= 0x050900 + fileDialog.setWindowFlag(Qt::WindowContextHelpButtonHint, false); +#else fileDialog.setWindowFlags(fileDialog.windowFlags()^Qt::WindowContextHelpButtonHint); +#endif fileDialog.setWindowTitle(tr("Open File...")); QStringList filters; @@ -463,11 +601,9 @@ fileDialogPreOpen: fileDialog.setDirectory(settings.value("OpenDialogDirectory", StandardPaths::documentsLocation()).toString()); fileDialog.restoreGeometry(settings.value("OpenDialogGeometry","").toByteArray()); - if (fileDialog.exec()) - { + if (fileDialog.exec()) { QStringList selectedFiles = fileDialog.selectedFiles(); - if (selectedFiles.length() == 1) - { + if (selectedFiles.length() == 1) { QString selectedFile = selectedFiles.at(0); if (!openFile(selectedFile, true)) goto fileDialogPreOpen; } @@ -481,68 +617,61 @@ fileDialogPreOpen: bool UserInterface::openFile(QString selectedFile, bool warn) { QString selectedFileName = QFileInfo(selectedFile).fileName(); - if (QFile::exists(selectedFile)) - { - if (selectedFileName.left(4) == "PGTA" || selectedFileName.right(4) == ".g5e") - { + if (QFile::exists(selectedFile)) { + if (selectedFileName.left(4) == "PGTA" || selectedFileName.right(4) == ".g5e") { SnapmaticPicture *picture = new SnapmaticPicture(selectedFile); - if (picture->readingPicture()) - { + if (picture->readingPicture()) { openSnapmaticFile(picture); delete picture; return true; } - else - { - if (warn) QMessageBox::warning(this, tr("Open File"), ProfileInterface::tr("Failed to read Snapmatic picture")); + else { + if (warn) + QMessageBox::warning(this, tr("Open File"), ProfileInterface::tr("Failed to read Snapmatic picture")); delete picture; return false; } } - else if (selectedFileName.left(4) == "SGTA") - { + else if (selectedFileName.left(4) == "SGTA") { SavegameData *savegame = new SavegameData(selectedFile); - if (savegame->readingSavegame()) - { + if (savegame->readingSavegame()) { openSavegameFile(savegame); delete savegame; return true; } - else - { - if (warn) QMessageBox::warning(this, tr("Open File"), ProfileInterface::tr("Failed to read Savegame file")); + else { + if (warn) + QMessageBox::warning(this, tr("Open File"), ProfileInterface::tr("Failed to read Savegame file")); delete savegame; return false; } } - else - { + else { SnapmaticPicture *picture = new SnapmaticPicture(selectedFile); SavegameData *savegame = new SavegameData(selectedFile); - if (picture->readingPicture()) - { + if (picture->readingPicture()) { delete savegame; openSnapmaticFile(picture); delete picture; return true; } - else if (savegame->readingSavegame()) - { + else if (savegame->readingSavegame()) { delete picture; openSavegameFile(savegame); delete savegame; return true; } - else - { + else { delete savegame; delete picture; - if (warn) QMessageBox::warning(this, tr("Open File"), tr("Can't open %1 because of not valid file format").arg("\""+selectedFileName+"\"")); + if (warn) + QMessageBox::warning(this, tr("Open File"), tr("Can't open %1 because of not valid file format").arg("\""+selectedFileName+"\"")); return false; } } } - if (warn) QMessageBox::warning(this, tr("Open File"), ProfileInterface::tr("No valid file is selected")); + if (warn) + QMessageBox::warning(this, tr("Open File"), ProfileInterface::tr("No valid file is selected")); return false; } @@ -553,7 +682,8 @@ void UserInterface::openSnapmaticFile(SnapmaticPicture *picture) picDialog.setModal(true); int crewID = picture->getSnapmaticProperties().crewID; - if (crewID != 0) { crewDB->addCrew(crewID); } + if (crewID != 0) + crewDB->addCrew(crewID); QObject::connect(threadDB, SIGNAL(crewNameUpdated()), &picDialog, SLOT(crewNameUpdated())); QObject::connect(threadDB, SIGNAL(playerNameUpdated()), &picDialog, SLOT(playerNameUpdated())); @@ -586,24 +716,139 @@ void UserInterface::openSavegameFile(SavegameData *savegame) void UserInterface::settingsApplied(int _contentMode, bool languageChanged) { - if (languageChanged) - { + if (languageChanged) { retranslateUi(); } contentMode = _contentMode; - if (profileOpen) - { + if (profileOpen) { profileUI->settingsApplied(contentMode, languageChanged); } } +#ifdef GTA5SYNC_MOTD +void UserInterface::messagesArrived(const QJsonObject &object) +{ + QSettings settings(GTA5SYNC_APPVENDOR, GTA5SYNC_APPSTR); + settings.beginGroup("Messages"); + QJsonObject::const_iterator it = object.constBegin(); + QJsonObject::const_iterator end = object.constEnd(); + QStringList messages; + while (it != end) { + const QString key = it.key(); + const QJsonValue value = it.value(); + bool uintOk; + uint messageId = key.toUInt(&uintOk); + if (uintOk && value.isString()) { + const QString valueStr = value.toString(); + settings.setValue(QString::number(messageId), valueStr); + messages << valueStr; + } + it++; + } + settings.endGroup(); + if (!messages.isEmpty()) + showMessages(messages); +} + +void UserInterface::showMessages(const QStringList messages) +{ + QDialog *messageDialog = new QDialog(this); + messageDialog->setWindowTitle(tr("%1 - Messages").arg(GTA5SYNC_APPSTR)); +#if QT_VERSION >= 0x050900 + messageDialog->setWindowFlag(Qt::WindowContextHelpButtonHint, false); +#else + messageDialog->setWindowFlags(messageDialog->windowFlags()^Qt::WindowContextHelpButtonHint); +#endif + QVBoxLayout *messageLayout = new QVBoxLayout; + messageDialog->setLayout(messageLayout); + QStackedWidget *stackWidget = new QStackedWidget(messageDialog); + for (const QString message : messages) { + QLabel *messageLabel = new QLabel(messageDialog); + messageLabel->setText(message); + messageLabel->setWordWrap(true); + stackWidget->addWidget(messageLabel); + } + messageLayout->addWidget(stackWidget); + QHBoxLayout *buttonLayout = new QHBoxLayout; + QPushButton *backButton = new QPushButton(messageDialog); + QPushButton *nextButton = new QPushButton(messageDialog); + if (QIcon::hasThemeIcon("go-previous") && QIcon::hasThemeIcon("go-next") && QIcon::hasThemeIcon("list-add")) { + backButton->setIcon(QIcon::fromTheme("go-previous")); + nextButton->setIcon(QIcon::fromTheme("go-next")); + } + else { + backButton->setIcon(QIcon(AppEnv::getImagesFolder() % "/back.svgz")); + nextButton->setIcon(QIcon(AppEnv::getImagesFolder() % "/next.svgz")); + } + backButton->setEnabled(false); + if (stackWidget->count() <= 1) { + nextButton->setEnabled(false); + } + buttonLayout->addWidget(backButton); + buttonLayout->addWidget(nextButton); + buttonLayout->addSpacerItem(new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Minimum)); + QPushButton *closeButton = new QPushButton(messageDialog); + closeButton->setText(tr("&Close")); + if (QIcon::hasThemeIcon("dialog-close")) { + closeButton->setIcon(QIcon::fromTheme("dialog-close")); + } + else if (QIcon::hasThemeIcon("gtk-close")) { + closeButton->setIcon(QIcon::fromTheme("gtk-close")); + } + buttonLayout->addWidget(closeButton); + messageLayout->addLayout(buttonLayout); + QObject::connect(backButton, &QPushButton::clicked, [stackWidget,backButton,nextButton,closeButton]() { + int index = stackWidget->currentIndex(); + if (index > 0) { + index--; + stackWidget->setCurrentIndex(index); + nextButton->setEnabled(true); + if (index > 0) { + backButton->setEnabled(true); + } + else { + backButton->setEnabled(false); + closeButton->setFocus(); + } + } + }); + QObject::connect(nextButton, &QPushButton::clicked, [stackWidget,backButton,nextButton,closeButton]() { + int index = stackWidget->currentIndex(); + if (index < stackWidget->count()-1) { + index++; + stackWidget->setCurrentIndex(index); + backButton->setEnabled(true); + if (index < stackWidget->count()-1) { + nextButton->setEnabled(true); + } + else { + nextButton->setEnabled(false); + closeButton->setFocus(); + } + } + }); + QObject::connect(closeButton, &QPushButton::clicked, messageDialog, &QDialog::accept); + QObject::connect(messageDialog, &QDialog::finished, messageDialog, &QDialog::deleteLater); + QTimer::singleShot(0, closeButton, [=](){ + closeButton->setFocus(); + }); + messageDialog->show(); +} + +void UserInterface::updateCacheId(uint cacheId) +{ + QSettings settings(GTA5SYNC_APPVENDOR, GTA5SYNC_APPSTR); + settings.beginGroup("Messages"); + settings.setValue("CacheId", cacheId); + settings.endGroup(); +} +#endif + void UserInterface::on_actionSelect_GTA_Folder_triggered() { QString GTAV_Folder_Temp = QFileDialog::getExistingDirectory(this, tr("Select GTA V Folder..."), StandardPaths::documentsLocation(), QFileDialog::ShowDirsOnly); - if (QFileInfo(GTAV_Folder_Temp).exists()) - { - if (profileOpen) - { + if (QDir(GTAV_Folder_Temp).exists()) { + if (profileOpen) { closeProfile_p(); } GTAV_Folder = GTAV_Folder_Temp; @@ -616,31 +861,41 @@ void UserInterface::on_actionSelect_GTA_Folder_triggered() void UserInterface::on_action_Enable_In_game_triggered() { if (profileOpen) - { profileUI->enableSelected(); - } } void UserInterface::on_action_Disable_In_game_triggered() { if (profileOpen) - { profileUI->disableSelected(); - } } void UserInterface::retranslateUi() { ui->retranslateUi(this); +#ifdef GTA5SYNC_DONATE +#ifdef GTA5SYNC_DONATE_ADDRESSES + donateAction->setText(tr("&Donate")); +#endif +#endif +#ifdef Q_OS_MAC + ui->actionAbout_gta5sync->setText(QApplication::translate("MAC_APPLICATION_MENU", "About %1").arg(GTA5SYNC_APPSTR)); + ui->actionOptions->setText(QApplication::translate("MAC_APPLICATION_MENU", "Preferences...")); +#else ui->actionAbout_gta5sync->setText(tr("&About %1").arg(GTA5SYNC_APPSTR)); - ui->labVersion->setText(QString("%1 %2").arg(GTA5SYNC_APPSTR, GTA5SYNC_APPVER)); - if (profileOpen) - { - this->setWindowTitle(defaultWindowTitle.arg(profileName)); +#endif + QString appVersion = QApplication::applicationVersion(); + const char* literalBuildType = GTA5SYNC_BUILDTYPE; +#ifdef GTA5SYNC_COMMIT + if ((strcmp(literalBuildType, REL_BUILDTYPE) != 0) && !appVersion.contains("-")) + appVersion = appVersion % "-" % GTA5SYNC_COMMIT; +#endif + ui->labVersion->setText(QString("%1 %2").arg(GTA5SYNC_APPSTR, appVersion)); + if (profileOpen) { + setWindowTitle(defaultWindowTitle.arg(profileName)); } - else - { - this->setWindowTitle(defaultWindowTitle.arg(tr("Select Profile"))); + else { + setWindowTitle(defaultWindowTitle.arg(tr("Select Profile"))); } } diff --git a/UserInterface.h b/UserInterface.h index 05ef444..12abc90 100644 --- a/UserInterface.h +++ b/UserInterface.h @@ -1,5 +1,5 @@ /***************************************************************************** -* gta5sync GRAND THEFT AUTO V SYNC +* gta5view Grand Theft Auto V Profile Viewer * Copyright (C) 2016-2017 Syping * * This program is free software: you can redistribute it and/or modify @@ -31,6 +31,10 @@ #include #include +#ifdef GTA5SYNC_MOTD +#include "MessageThread.h" +#endif + namespace Ui { class UserInterface; } @@ -39,8 +43,12 @@ class UserInterface : public QMainWindow { Q_OBJECT public: +#ifdef GTA5SYNC_MOTD + explicit UserInterface(ProfileDatabase *profileDB, CrewDatabase *crewDB, DatabaseThread *threadDB, MessageThread *messageThread, QWidget *parent = 0); +#else explicit UserInterface(ProfileDatabase *profileDB, CrewDatabase *crewDB, DatabaseThread *threadDB, QWidget *parent = 0); - void setupDirEnv(); +#endif + void setupDirEnv(bool showFolderDialog = true); ~UserInterface(); private slots: @@ -67,6 +75,11 @@ private slots: void on_actionSet_Crew_triggered(); void on_actionSet_Title_triggered(); void settingsApplied(int contentMode, bool languageChanged); +#ifdef GTA5SYNC_MOTD + void messagesArrived(const QJsonObject &object); + void showMessages(const QStringList messages); + void updateCacheId(uint cacheId); +#endif protected: void closeEvent(QCloseEvent *ev); @@ -75,6 +88,14 @@ private: ProfileDatabase *profileDB; CrewDatabase *crewDB; DatabaseThread *threadDB; +#ifdef GTA5SYNC_MOTD + MessageThread *threadMessage; +#endif +#ifdef GTA5SYNC_DONATE +#ifdef GTA5SYNC_DONATE_ADDRESSES + QAction *donateAction; +#endif +#endif Ui::UserInterface *ui; ProfileInterface *profileUI; QList profileBtns; diff --git a/anpro/QrCode.cpp b/anpro/QrCode.cpp new file mode 100644 index 0000000..6d67ee9 --- /dev/null +++ b/anpro/QrCode.cpp @@ -0,0 +1,862 @@ +/* + * QR Code generator library (C++) + * + * Copyright (c) Project Nayuki. (MIT License) + * https://www.nayuki.io/page/qr-code-generator-library + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * - The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * - The Software is provided "as is", without warranty of any kind, express or + * implied, including but not limited to the warranties of merchantability, + * fitness for a particular purpose and noninfringement. In no event shall the + * authors or copyright holders be liable for any claim, damages or other + * liability, whether in an action of contract, tort or otherwise, arising from, + * out of or in connection with the Software or the use or other dealings in the + * Software. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "QrCode.h" + +using std::int8_t; +using std::uint8_t; +using std::size_t; +using std::vector; + + +namespace qrcodegen { + +QrSegment::Mode::Mode(int mode, int cc0, int cc1, int cc2) : + modeBits(mode) { + numBitsCharCount[0] = cc0; + numBitsCharCount[1] = cc1; + numBitsCharCount[2] = cc2; +} + + +int QrSegment::Mode::getModeBits() const { + return modeBits; +} + + +int QrSegment::Mode::numCharCountBits(int ver) const { + return numBitsCharCount[(ver + 7) / 17]; +} + + +const QrSegment::Mode QrSegment::Mode::NUMERIC (0x1, 10, 12, 14); +const QrSegment::Mode QrSegment::Mode::ALPHANUMERIC(0x2, 9, 11, 13); +const QrSegment::Mode QrSegment::Mode::BYTE (0x4, 8, 16, 16); +const QrSegment::Mode QrSegment::Mode::KANJI (0x8, 8, 10, 12); +const QrSegment::Mode QrSegment::Mode::ECI (0x7, 0, 0, 0); + + +QrSegment QrSegment::makeBytes(const vector &data) { + if (data.size() > static_cast(INT_MAX)) + throw std::length_error("Data too long"); + BitBuffer bb; + for (uint8_t b : data) + bb.appendBits(b, 8); + return QrSegment(Mode::BYTE, static_cast(data.size()), std::move(bb)); +} + + +QrSegment QrSegment::makeNumeric(const char *digits) { + BitBuffer bb; + int accumData = 0; + int accumCount = 0; + int charCount = 0; + for (; *digits != '\0'; digits++, charCount++) { + char c = *digits; + if (c < '0' || c > '9') + throw std::domain_error("String contains non-numeric characters"); + accumData = accumData * 10 + (c - '0'); + accumCount++; + if (accumCount == 3) { + bb.appendBits(static_cast(accumData), 10); + accumData = 0; + accumCount = 0; + } + } + if (accumCount > 0) // 1 or 2 digits remaining + bb.appendBits(static_cast(accumData), accumCount * 3 + 1); + return QrSegment(Mode::NUMERIC, charCount, std::move(bb)); +} + + +QrSegment QrSegment::makeAlphanumeric(const char *text) { + BitBuffer bb; + int accumData = 0; + int accumCount = 0; + int charCount = 0; + for (; *text != '\0'; text++, charCount++) { + const char *temp = std::strchr(ALPHANUMERIC_CHARSET, *text); + if (temp == nullptr) + throw std::domain_error("String contains unencodable characters in alphanumeric mode"); + accumData = accumData * 45 + static_cast(temp - ALPHANUMERIC_CHARSET); + accumCount++; + if (accumCount == 2) { + bb.appendBits(static_cast(accumData), 11); + accumData = 0; + accumCount = 0; + } + } + if (accumCount > 0) // 1 character remaining + bb.appendBits(static_cast(accumData), 6); + return QrSegment(Mode::ALPHANUMERIC, charCount, std::move(bb)); +} + + +vector QrSegment::makeSegments(const char *text) { + // Select the most efficient segment encoding automatically + vector result; + if (*text == '\0'); // Leave result empty + else if (isNumeric(text)) + result.push_back(makeNumeric(text)); + else if (isAlphanumeric(text)) + result.push_back(makeAlphanumeric(text)); + else { + vector bytes; + for (; *text != '\0'; text++) + bytes.push_back(static_cast(*text)); + result.push_back(makeBytes(bytes)); + } + return result; +} + + +QrSegment QrSegment::makeEci(long assignVal) { + BitBuffer bb; + if (assignVal < 0) + throw std::domain_error("ECI assignment value out of range"); + else if (assignVal < (1 << 7)) + bb.appendBits(static_cast(assignVal), 8); + else if (assignVal < (1 << 14)) { + bb.appendBits(2, 2); + bb.appendBits(static_cast(assignVal), 14); + } else if (assignVal < 1000000L) { + bb.appendBits(6, 3); + bb.appendBits(static_cast(assignVal), 21); + } else + throw std::domain_error("ECI assignment value out of range"); + return QrSegment(Mode::ECI, 0, std::move(bb)); +} + + +QrSegment::QrSegment(Mode md, int numCh, const std::vector &dt) : + mode(md), + numChars(numCh), + data(dt) { + if (numCh < 0) + throw std::domain_error("Invalid value"); +} + + +QrSegment::QrSegment(Mode md, int numCh, std::vector &&dt) : + mode(md), + numChars(numCh), + data(std::move(dt)) { + if (numCh < 0) + throw std::domain_error("Invalid value"); +} + + +int QrSegment::getTotalBits(const vector &segs, int version) { + int result = 0; + for (const QrSegment &seg : segs) { + int ccbits = seg.mode.numCharCountBits(version); + if (seg.numChars >= (1L << ccbits)) + return -1; // The segment's length doesn't fit the field's bit width + if (4 + ccbits > INT_MAX - result) + return -1; // The sum will overflow an int type + result += 4 + ccbits; + if (seg.data.size() > static_cast(INT_MAX - result)) + return -1; // The sum will overflow an int type + result += static_cast(seg.data.size()); + } + return result; +} + + +bool QrSegment::isAlphanumeric(const char *text) { + for (; *text != '\0'; text++) { + if (std::strchr(ALPHANUMERIC_CHARSET, *text) == nullptr) + return false; + } + return true; +} + + +bool QrSegment::isNumeric(const char *text) { + for (; *text != '\0'; text++) { + char c = *text; + if (c < '0' || c > '9') + return false; + } + return true; +} + + +QrSegment::Mode QrSegment::getMode() const { + return mode; +} + + +int QrSegment::getNumChars() const { + return numChars; +} + + +const std::vector &QrSegment::getData() const { + return data; +} + + +const char *QrSegment::ALPHANUMERIC_CHARSET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:"; + + + +int QrCode::getFormatBits(Ecc ecl) { + switch (ecl) { + case Ecc::LOW : return 1; + case Ecc::MEDIUM : return 0; + case Ecc::QUARTILE: return 3; + case Ecc::HIGH : return 2; + default: throw std::logic_error("Assertion error"); + } +} + + +QrCode QrCode::encodeText(const char *text, Ecc ecl) { + vector segs = QrSegment::makeSegments(text); + return encodeSegments(segs, ecl); +} + + +QrCode QrCode::encodeBinary(const vector &data, Ecc ecl) { + vector segs{QrSegment::makeBytes(data)}; + return encodeSegments(segs, ecl); +} + + +QrCode QrCode::encodeSegments(const vector &segs, Ecc ecl, + int minVersion, int maxVersion, int mask, bool boostEcl) { + if (!(MIN_VERSION <= minVersion && minVersion <= maxVersion && maxVersion <= MAX_VERSION) || mask < -1 || mask > 7) + throw std::invalid_argument("Invalid value"); + + // Find the minimal version number to use + int version, dataUsedBits; + for (version = minVersion; ; version++) { + int dataCapacityBits = getNumDataCodewords(version, ecl) * 8; // Number of data bits available + dataUsedBits = QrSegment::getTotalBits(segs, version); + if (dataUsedBits != -1 && dataUsedBits <= dataCapacityBits) + break; // This version number is found to be suitable + if (version >= maxVersion) { // All versions in the range could not fit the given data + std::ostringstream sb; + if (dataUsedBits == -1) + sb << "Segment too long"; + else { + sb << "Data length = " << dataUsedBits << " bits, "; + sb << "Max capacity = " << dataCapacityBits << " bits"; + } + throw data_too_long(sb.str()); + } + } + if (dataUsedBits == -1) + throw std::logic_error("Assertion error"); + + // Increase the error correction level while the data still fits in the current version number + for (Ecc newEcl : vector{Ecc::MEDIUM, Ecc::QUARTILE, Ecc::HIGH}) { // From low to high + if (boostEcl && dataUsedBits <= getNumDataCodewords(version, newEcl) * 8) + ecl = newEcl; + } + + // Concatenate all segments to create the data bit string + BitBuffer bb; + for (const QrSegment &seg : segs) { + bb.appendBits(static_cast(seg.getMode().getModeBits()), 4); + bb.appendBits(static_cast(seg.getNumChars()), seg.getMode().numCharCountBits(version)); + bb.insert(bb.end(), seg.getData().begin(), seg.getData().end()); + } + if (bb.size() != static_cast(dataUsedBits)) + throw std::logic_error("Assertion error"); + + // Add terminator and pad up to a byte if applicable + size_t dataCapacityBits = static_cast(getNumDataCodewords(version, ecl)) * 8; + if (bb.size() > dataCapacityBits) + throw std::logic_error("Assertion error"); + bb.appendBits(0, std::min(4, static_cast(dataCapacityBits - bb.size()))); + bb.appendBits(0, (8 - static_cast(bb.size() % 8)) % 8); + if (bb.size() % 8 != 0) + throw std::logic_error("Assertion error"); + + // Pad with alternating bytes until data capacity is reached + for (uint8_t padByte = 0xEC; bb.size() < dataCapacityBits; padByte ^= 0xEC ^ 0x11) + bb.appendBits(padByte, 8); + + // Pack bits into bytes in big endian + vector dataCodewords(bb.size() / 8); + for (size_t i = 0; i < bb.size(); i++) + dataCodewords[i >> 3] |= (bb.at(i) ? 1 : 0) << (7 - (i & 7)); + + // Create the QR Code object + return QrCode(version, ecl, dataCodewords, mask); +} + + +QrCode::QrCode(int ver, Ecc ecl, const vector &dataCodewords, int msk) : + // Initialize fields and check arguments + version(ver), + errorCorrectionLevel(ecl) { + if (ver < MIN_VERSION || ver > MAX_VERSION) + throw std::domain_error("Version value out of range"); + if (msk < -1 || msk > 7) + throw std::domain_error("Mask value out of range"); + size = ver * 4 + 17; + size_t sz = static_cast(size); + modules = vector >(sz, vector(sz)); // Initially all white + isFunction = vector >(sz, vector(sz)); + + // Compute ECC, draw modules + drawFunctionPatterns(); + const vector allCodewords = addEccAndInterleave(dataCodewords); + drawCodewords(allCodewords); + + // Do masking + if (msk == -1) { // Automatically choose best mask + long minPenalty = LONG_MAX; + for (int i = 0; i < 8; i++) { + applyMask(i); + drawFormatBits(i); + long penalty = getPenaltyScore(); + if (penalty < minPenalty) { + msk = i; + minPenalty = penalty; + } + applyMask(i); // Undoes the mask due to XOR + } + } + if (msk < 0 || msk > 7) + throw std::logic_error("Assertion error"); + this->mask = msk; + applyMask(msk); // Apply the final choice of mask + drawFormatBits(msk); // Overwrite old format bits + + isFunction.clear(); + isFunction.shrink_to_fit(); +} + + +int QrCode::getVersion() const { + return version; +} + + +int QrCode::getSize() const { + return size; +} + + +QrCode::Ecc QrCode::getErrorCorrectionLevel() const { + return errorCorrectionLevel; +} + + +int QrCode::getMask() const { + return mask; +} + + +bool QrCode::getModule(int x, int y) const { + return 0 <= x && x < size && 0 <= y && y < size && module(x, y); +} + + +std::string QrCode::toSvgString(int border) const { + if (border < 0) + throw std::domain_error("Border must be non-negative"); + if (border > INT_MAX / 2 || border * 2 > INT_MAX - size) + throw std::overflow_error("Border too large"); + + std::ostringstream sb; + sb << "\n"; + sb << "\n"; + sb << "\n"; + sb << "\t\n"; + sb << "\t\n"; + sb << "\n"; + return sb.str(); +} + + +void QrCode::drawFunctionPatterns() { + // Draw horizontal and vertical timing patterns + for (int i = 0; i < size; i++) { + setFunctionModule(6, i, i % 2 == 0); + setFunctionModule(i, 6, i % 2 == 0); + } + + // Draw 3 finder patterns (all corners except bottom right; overwrites some timing modules) + drawFinderPattern(3, 3); + drawFinderPattern(size - 4, 3); + drawFinderPattern(3, size - 4); + + // Draw numerous alignment patterns + const vector alignPatPos = getAlignmentPatternPositions(); + size_t numAlign = alignPatPos.size(); + for (size_t i = 0; i < numAlign; i++) { + for (size_t j = 0; j < numAlign; j++) { + // Don't draw on the three finder corners + if (!((i == 0 && j == 0) || (i == 0 && j == numAlign - 1) || (i == numAlign - 1 && j == 0))) + drawAlignmentPattern(alignPatPos.at(i), alignPatPos.at(j)); + } + } + + // Draw configuration data + drawFormatBits(0); // Dummy mask value; overwritten later in the constructor + drawVersion(); +} + + +void QrCode::drawFormatBits(int msk) { + // Calculate error correction code and pack bits + int data = getFormatBits(errorCorrectionLevel) << 3 | msk; // errCorrLvl is uint2, msk is uint3 + int rem = data; + for (int i = 0; i < 10; i++) + rem = (rem << 1) ^ ((rem >> 9) * 0x537); + int bits = (data << 10 | rem) ^ 0x5412; // uint15 + if (bits >> 15 != 0) + throw std::logic_error("Assertion error"); + + // Draw first copy + for (int i = 0; i <= 5; i++) + setFunctionModule(8, i, getBit(bits, i)); + setFunctionModule(8, 7, getBit(bits, 6)); + setFunctionModule(8, 8, getBit(bits, 7)); + setFunctionModule(7, 8, getBit(bits, 8)); + for (int i = 9; i < 15; i++) + setFunctionModule(14 - i, 8, getBit(bits, i)); + + // Draw second copy + for (int i = 0; i < 8; i++) + setFunctionModule(size - 1 - i, 8, getBit(bits, i)); + for (int i = 8; i < 15; i++) + setFunctionModule(8, size - 15 + i, getBit(bits, i)); + setFunctionModule(8, size - 8, true); // Always black +} + + +void QrCode::drawVersion() { + if (version < 7) + return; + + // Calculate error correction code and pack bits + int rem = version; // version is uint6, in the range [7, 40] + for (int i = 0; i < 12; i++) + rem = (rem << 1) ^ ((rem >> 11) * 0x1F25); + long bits = static_cast(version) << 12 | rem; // uint18 + if (bits >> 18 != 0) + throw std::logic_error("Assertion error"); + + // Draw two copies + for (int i = 0; i < 18; i++) { + bool bit = getBit(bits, i); + int a = size - 11 + i % 3; + int b = i / 3; + setFunctionModule(a, b, bit); + setFunctionModule(b, a, bit); + } +} + + +void QrCode::drawFinderPattern(int x, int y) { + for (int dy = -4; dy <= 4; dy++) { + for (int dx = -4; dx <= 4; dx++) { + int dist = std::max(std::abs(dx), std::abs(dy)); // Chebyshev/infinity norm + int xx = x + dx, yy = y + dy; + if (0 <= xx && xx < size && 0 <= yy && yy < size) + setFunctionModule(xx, yy, dist != 2 && dist != 4); + } + } +} + + +void QrCode::drawAlignmentPattern(int x, int y) { + for (int dy = -2; dy <= 2; dy++) { + for (int dx = -2; dx <= 2; dx++) + setFunctionModule(x + dx, y + dy, std::max(std::abs(dx), std::abs(dy)) != 1); + } +} + + +void QrCode::setFunctionModule(int x, int y, bool isBlack) { + size_t ux = static_cast(x); + size_t uy = static_cast(y); + modules .at(uy).at(ux) = isBlack; + isFunction.at(uy).at(ux) = true; +} + + +bool QrCode::module(int x, int y) const { + return modules.at(static_cast(y)).at(static_cast(x)); +} + + +vector QrCode::addEccAndInterleave(const vector &data) const { + if (data.size() != static_cast(getNumDataCodewords(version, errorCorrectionLevel))) + throw std::invalid_argument("Invalid argument"); + + // Calculate parameter numbers + int numBlocks = NUM_ERROR_CORRECTION_BLOCKS[static_cast(errorCorrectionLevel)][version]; + int blockEccLen = ECC_CODEWORDS_PER_BLOCK [static_cast(errorCorrectionLevel)][version]; + int rawCodewords = getNumRawDataModules(version) / 8; + int numShortBlocks = numBlocks - rawCodewords % numBlocks; + int shortBlockLen = rawCodewords / numBlocks; + + // Split data into blocks and append ECC to each block + vector > blocks; + const vector rsDiv = reedSolomonComputeDivisor(blockEccLen); + for (int i = 0, k = 0; i < numBlocks; i++) { + vector dat(data.cbegin() + k, data.cbegin() + (k + shortBlockLen - blockEccLen + (i < numShortBlocks ? 0 : 1))); + k += static_cast(dat.size()); + const vector ecc = reedSolomonComputeRemainder(dat, rsDiv); + if (i < numShortBlocks) + dat.push_back(0); + dat.insert(dat.end(), ecc.cbegin(), ecc.cend()); + blocks.push_back(std::move(dat)); + } + + // Interleave (not concatenate) the bytes from every block into a single sequence + vector result; + for (size_t i = 0; i < blocks.at(0).size(); i++) { + for (size_t j = 0; j < blocks.size(); j++) { + // Skip the padding byte in short blocks + if (i != static_cast(shortBlockLen - blockEccLen) || j >= static_cast(numShortBlocks)) + result.push_back(blocks.at(j).at(i)); + } + } + if (result.size() != static_cast(rawCodewords)) + throw std::logic_error("Assertion error"); + return result; +} + + +void QrCode::drawCodewords(const vector &data) { + if (data.size() != static_cast(getNumRawDataModules(version) / 8)) + throw std::invalid_argument("Invalid argument"); + + size_t i = 0; // Bit index into the data + // Do the funny zigzag scan + for (int right = size - 1; right >= 1; right -= 2) { // Index of right column in each column pair + if (right == 6) + right = 5; + for (int vert = 0; vert < size; vert++) { // Vertical counter + for (int j = 0; j < 2; j++) { + size_t x = static_cast(right - j); // Actual x coordinate + bool upward = ((right + 1) & 2) == 0; + size_t y = static_cast(upward ? size - 1 - vert : vert); // Actual y coordinate + if (!isFunction.at(y).at(x) && i < data.size() * 8) { + modules.at(y).at(x) = getBit(data.at(i >> 3), 7 - static_cast(i & 7)); + i++; + } + // If this QR Code has any remainder bits (0 to 7), they were assigned as + // 0/false/white by the constructor and are left unchanged by this method + } + } + } + if (i != data.size() * 8) + throw std::logic_error("Assertion error"); +} + + +void QrCode::applyMask(int msk) { + if (msk < 0 || msk > 7) + throw std::domain_error("Mask value out of range"); + size_t sz = static_cast(size); + for (size_t y = 0; y < sz; y++) { + for (size_t x = 0; x < sz; x++) { + bool invert; + switch (msk) { + case 0: invert = (x + y) % 2 == 0; break; + case 1: invert = y % 2 == 0; break; + case 2: invert = x % 3 == 0; break; + case 3: invert = (x + y) % 3 == 0; break; + case 4: invert = (x / 3 + y / 2) % 2 == 0; break; + case 5: invert = x * y % 2 + x * y % 3 == 0; break; + case 6: invert = (x * y % 2 + x * y % 3) % 2 == 0; break; + case 7: invert = ((x + y) % 2 + x * y % 3) % 2 == 0; break; + default: throw std::logic_error("Assertion error"); + } + modules.at(y).at(x) = modules.at(y).at(x) ^ (invert & !isFunction.at(y).at(x)); + } + } +} + + +long QrCode::getPenaltyScore() const { + long result = 0; + + // Adjacent modules in row having same color, and finder-like patterns + for (int y = 0; y < size; y++) { + bool runColor = false; + int runX = 0; + std::array runHistory = {}; + for (int x = 0; x < size; x++) { + if (module(x, y) == runColor) { + runX++; + if (runX == 5) + result += PENALTY_N1; + else if (runX > 5) + result++; + } else { + finderPenaltyAddHistory(runX, runHistory); + if (!runColor) + result += finderPenaltyCountPatterns(runHistory) * PENALTY_N3; + runColor = module(x, y); + runX = 1; + } + } + result += finderPenaltyTerminateAndCount(runColor, runX, runHistory) * PENALTY_N3; + } + // Adjacent modules in column having same color, and finder-like patterns + for (int x = 0; x < size; x++) { + bool runColor = false; + int runY = 0; + std::array runHistory = {}; + for (int y = 0; y < size; y++) { + if (module(x, y) == runColor) { + runY++; + if (runY == 5) + result += PENALTY_N1; + else if (runY > 5) + result++; + } else { + finderPenaltyAddHistory(runY, runHistory); + if (!runColor) + result += finderPenaltyCountPatterns(runHistory) * PENALTY_N3; + runColor = module(x, y); + runY = 1; + } + } + result += finderPenaltyTerminateAndCount(runColor, runY, runHistory) * PENALTY_N3; + } + + // 2*2 blocks of modules having same color + for (int y = 0; y < size - 1; y++) { + for (int x = 0; x < size - 1; x++) { + bool color = module(x, y); + if ( color == module(x + 1, y) && + color == module(x, y + 1) && + color == module(x + 1, y + 1)) + result += PENALTY_N2; + } + } + + // Balance of black and white modules + int black = 0; + for (const vector &row : modules) { + for (bool color : row) { + if (color) + black++; + } + } + int total = size * size; // Note that size is odd, so black/total != 1/2 + // Compute the smallest integer k >= 0 such that (45-5k)% <= black/total <= (55+5k)% + int k = static_cast((std::abs(black * 20L - total * 10L) + total - 1) / total) - 1; + result += k * PENALTY_N4; + return result; +} + + +vector QrCode::getAlignmentPatternPositions() const { + if (version == 1) + return vector(); + else { + int numAlign = version / 7 + 2; + int step = (version == 32) ? 26 : + (version*4 + numAlign*2 + 1) / (numAlign*2 - 2) * 2; + vector result; + for (int i = 0, pos = size - 7; i < numAlign - 1; i++, pos -= step) + result.insert(result.begin(), pos); + result.insert(result.begin(), 6); + return result; + } +} + + +int QrCode::getNumRawDataModules(int ver) { + if (ver < MIN_VERSION || ver > MAX_VERSION) + throw std::domain_error("Version number out of range"); + int result = (16 * ver + 128) * ver + 64; + if (ver >= 2) { + int numAlign = ver / 7 + 2; + result -= (25 * numAlign - 10) * numAlign - 55; + if (ver >= 7) + result -= 36; + } + if (!(208 <= result && result <= 29648)) + throw std::logic_error("Assertion error"); + return result; +} + + +int QrCode::getNumDataCodewords(int ver, Ecc ecl) { + return getNumRawDataModules(ver) / 8 + - ECC_CODEWORDS_PER_BLOCK [static_cast(ecl)][ver] + * NUM_ERROR_CORRECTION_BLOCKS[static_cast(ecl)][ver]; +} + + +vector QrCode::reedSolomonComputeDivisor(int degree) { + if (degree < 1 || degree > 255) + throw std::domain_error("Degree out of range"); + // Polynomial coefficients are stored from highest to lowest power, excluding the leading term which is always 1. + // For example the polynomial x^3 + 255x^2 + 8x + 93 is stored as the uint8 array {255, 8, 93}. + vector result(static_cast(degree)); + result.at(result.size() - 1) = 1; // Start off with the monomial x^0 + + // Compute the product polynomial (x - r^0) * (x - r^1) * (x - r^2) * ... * (x - r^{degree-1}), + // and drop the highest monomial term which is always 1x^degree. + // Note that r = 0x02, which is a generator element of this field GF(2^8/0x11D). + uint8_t root = 1; + for (int i = 0; i < degree; i++) { + // Multiply the current product by (x - r^i) + for (size_t j = 0; j < result.size(); j++) { + result.at(j) = reedSolomonMultiply(result.at(j), root); + if (j + 1 < result.size()) + result.at(j) ^= result.at(j + 1); + } + root = reedSolomonMultiply(root, 0x02); + } + return result; +} + + +vector QrCode::reedSolomonComputeRemainder(const vector &data, const vector &divisor) { + vector result(divisor.size()); + for (uint8_t b : data) { // Polynomial division + uint8_t factor = b ^ result.at(0); + result.erase(result.begin()); + result.push_back(0); + for (size_t i = 0; i < result.size(); i++) + result.at(i) ^= reedSolomonMultiply(divisor.at(i), factor); + } + return result; +} + + +uint8_t QrCode::reedSolomonMultiply(uint8_t x, uint8_t y) { + // Russian peasant multiplication + int z = 0; + for (int i = 7; i >= 0; i--) { + z = (z << 1) ^ ((z >> 7) * 0x11D); + z ^= ((y >> i) & 1) * x; + } + if (z >> 8 != 0) + throw std::logic_error("Assertion error"); + return static_cast(z); +} + + +int QrCode::finderPenaltyCountPatterns(const std::array &runHistory) const { + int n = runHistory.at(1); + if (n > size * 3) + throw std::logic_error("Assertion error"); + bool core = n > 0 && runHistory.at(2) == n && runHistory.at(3) == n * 3 && runHistory.at(4) == n && runHistory.at(5) == n; + return (core && runHistory.at(0) >= n * 4 && runHistory.at(6) >= n ? 1 : 0) + + (core && runHistory.at(6) >= n * 4 && runHistory.at(0) >= n ? 1 : 0); +} + + +int QrCode::finderPenaltyTerminateAndCount(bool currentRunColor, int currentRunLength, std::array &runHistory) const { + if (currentRunColor) { // Terminate black run + finderPenaltyAddHistory(currentRunLength, runHistory); + currentRunLength = 0; + } + currentRunLength += size; // Add white border to final run + finderPenaltyAddHistory(currentRunLength, runHistory); + return finderPenaltyCountPatterns(runHistory); +} + + +void QrCode::finderPenaltyAddHistory(int currentRunLength, std::array &runHistory) const { + if (runHistory.at(0) == 0) + currentRunLength += size; // Add white border to initial run + std::copy_backward(runHistory.cbegin(), runHistory.cend() - 1, runHistory.end()); + runHistory.at(0) = currentRunLength; +} + + +bool QrCode::getBit(long x, int i) { + return ((x >> i) & 1) != 0; +} + + +/*---- Tables of constants ----*/ + +const int QrCode::PENALTY_N1 = 3; +const int QrCode::PENALTY_N2 = 3; +const int QrCode::PENALTY_N3 = 40; +const int QrCode::PENALTY_N4 = 10; + + +const int8_t QrCode::ECC_CODEWORDS_PER_BLOCK[4][41] = { + // Version: (note that index 0 is for padding, and is set to an illegal value) + //0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40 Error correction level + {-1, 7, 10, 15, 20, 26, 18, 20, 24, 30, 18, 20, 24, 26, 30, 22, 24, 28, 30, 28, 28, 28, 28, 30, 30, 26, 28, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30}, // Low + {-1, 10, 16, 26, 18, 24, 16, 18, 22, 22, 26, 30, 22, 22, 24, 24, 28, 28, 26, 26, 26, 26, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28}, // Medium + {-1, 13, 22, 18, 26, 18, 24, 18, 22, 20, 24, 28, 26, 24, 20, 30, 24, 28, 28, 26, 30, 28, 30, 30, 30, 30, 28, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30}, // Quartile + {-1, 17, 28, 22, 16, 22, 28, 26, 26, 24, 28, 24, 28, 22, 24, 24, 30, 28, 28, 26, 28, 30, 24, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30}, // High +}; + +const int8_t QrCode::NUM_ERROR_CORRECTION_BLOCKS[4][41] = { + // Version: (note that index 0 is for padding, and is set to an illegal value) + //0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40 Error correction level + {-1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 4, 4, 4, 4, 4, 6, 6, 6, 6, 7, 8, 8, 9, 9, 10, 12, 12, 12, 13, 14, 15, 16, 17, 18, 19, 19, 20, 21, 22, 24, 25}, // Low + {-1, 1, 1, 1, 2, 2, 4, 4, 4, 5, 5, 5, 8, 9, 9, 10, 10, 11, 13, 14, 16, 17, 17, 18, 20, 21, 23, 25, 26, 28, 29, 31, 33, 35, 37, 38, 40, 43, 45, 47, 49}, // Medium + {-1, 1, 1, 2, 2, 4, 4, 6, 6, 8, 8, 8, 10, 12, 16, 12, 17, 16, 18, 21, 20, 23, 23, 25, 27, 29, 34, 34, 35, 38, 40, 43, 45, 48, 51, 53, 56, 59, 62, 65, 68}, // Quartile + {-1, 1, 1, 2, 4, 4, 4, 5, 6, 8, 8, 11, 11, 16, 16, 18, 16, 19, 21, 25, 25, 25, 34, 30, 32, 35, 37, 40, 42, 45, 48, 51, 54, 57, 60, 63, 66, 70, 74, 77, 81}, // High +}; + + +data_too_long::data_too_long(const std::string &msg) : + std::length_error(msg) {} + + + +BitBuffer::BitBuffer() + : std::vector() {} + + +void BitBuffer::appendBits(std::uint32_t val, int len) { + if (len < 0 || len > 31 || val >> len != 0) + throw std::domain_error("Value out of range"); + for (int i = len - 1; i >= 0; i--) // Append bit by bit + this->push_back(((val >> i) & 1) != 0); +} + +} diff --git a/anpro/QrCode.h b/anpro/QrCode.h new file mode 100644 index 0000000..7341e41 --- /dev/null +++ b/anpro/QrCode.h @@ -0,0 +1,556 @@ +/* + * QR Code generator library (C++) + * + * Copyright (c) Project Nayuki. (MIT License) + * https://www.nayuki.io/page/qr-code-generator-library + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * - The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * - The Software is provided "as is", without warranty of any kind, express or + * implied, including but not limited to the warranties of merchantability, + * fitness for a particular purpose and noninfringement. In no event shall the + * authors or copyright holders be liable for any claim, damages or other + * liability, whether in an action of contract, tort or otherwise, arising from, + * out of or in connection with the Software or the use or other dealings in the + * Software. + */ + +#pragma once + +#include +#include +#include +#include +#include + + +namespace qrcodegen { + +/* + * A segment of character/binary/control data in a QR Code symbol. + * Instances of this class are immutable. + * The mid-level way to create a segment is to take the payload data + * and call a static factory function such as QrSegment::makeNumeric(). + * The low-level way to create a segment is to custom-make the bit buffer + * and call the QrSegment() constructor with appropriate values. + * This segment class imposes no length restrictions, but QR Codes have restrictions. + * Even in the most favorable conditions, a QR Code can only hold 7089 characters of data. + * Any segment longer than this is meaningless for the purpose of generating QR Codes. + */ +class QrSegment final { + + /*---- Public helper enumeration ----*/ + + /* + * Describes how a segment's data bits are interpreted. Immutable. + */ + public: class Mode final { + + /*-- Constants --*/ + + public: static const Mode NUMERIC; + public: static const Mode ALPHANUMERIC; + public: static const Mode BYTE; + public: static const Mode KANJI; + public: static const Mode ECI; + + + /*-- Fields --*/ + + // The mode indicator bits, which is a uint4 value (range 0 to 15). + private: int modeBits; + + // Number of character count bits for three different version ranges. + private: int numBitsCharCount[3]; + + + /*-- Constructor --*/ + + private: Mode(int mode, int cc0, int cc1, int cc2); + + + /*-- Methods --*/ + + /* + * (Package-private) Returns the mode indicator bits, which is an unsigned 4-bit value (range 0 to 15). + */ + public: int getModeBits() const; + + /* + * (Package-private) Returns the bit width of the character count field for a segment in + * this mode in a QR Code at the given version number. The result is in the range [0, 16]. + */ + public: int numCharCountBits(int ver) const; + + }; + + + + /*---- Static factory functions (mid level) ----*/ + + /* + * Returns a segment representing the given binary data encoded in + * byte mode. All input byte vectors are acceptable. Any text string + * can be converted to UTF-8 bytes and encoded as a byte mode segment. + */ + public: static QrSegment makeBytes(const std::vector &data); + + + /* + * Returns a segment representing the given string of decimal digits encoded in numeric mode. + */ + public: static QrSegment makeNumeric(const char *digits); + + + /* + * Returns a segment representing the given text string encoded in alphanumeric mode. + * The characters allowed are: 0 to 9, A to Z (uppercase only), space, + * dollar, percent, asterisk, plus, hyphen, period, slash, colon. + */ + public: static QrSegment makeAlphanumeric(const char *text); + + + /* + * Returns a list of zero or more segments to represent the given text string. The result + * may use various segment modes and switch modes to optimize the length of the bit stream. + */ + public: static std::vector makeSegments(const char *text); + + + /* + * Returns a segment representing an Extended Channel Interpretation + * (ECI) designator with the given assignment value. + */ + public: static QrSegment makeEci(long assignVal); + + + /*---- Public static helper functions ----*/ + + /* + * Tests whether the given string can be encoded as a segment in alphanumeric mode. + * A string is encodable iff each character is in the following set: 0 to 9, A to Z + * (uppercase only), space, dollar, percent, asterisk, plus, hyphen, period, slash, colon. + */ + public: static bool isAlphanumeric(const char *text); + + + /* + * Tests whether the given string can be encoded as a segment in numeric mode. + * A string is encodable iff each character is in the range 0 to 9. + */ + public: static bool isNumeric(const char *text); + + + + /*---- Instance fields ----*/ + + /* The mode indicator of this segment. Accessed through getMode(). */ + private: Mode mode; + + /* The length of this segment's unencoded data. Measured in characters for + * numeric/alphanumeric/kanji mode, bytes for byte mode, and 0 for ECI mode. + * Always zero or positive. Not the same as the data's bit length. + * Accessed through getNumChars(). */ + private: int numChars; + + /* The data bits of this segment. Accessed through getData(). */ + private: std::vector data; + + + /*---- Constructors (low level) ----*/ + + /* + * Creates a new QR Code segment with the given attributes and data. + * The character count (numCh) must agree with the mode and the bit buffer length, + * but the constraint isn't checked. The given bit buffer is copied and stored. + */ + public: QrSegment(Mode md, int numCh, const std::vector &dt); + + + /* + * Creates a new QR Code segment with the given parameters and data. + * The character count (numCh) must agree with the mode and the bit buffer length, + * but the constraint isn't checked. The given bit buffer is moved and stored. + */ + public: QrSegment(Mode md, int numCh, std::vector &&dt); + + + /*---- Methods ----*/ + + /* + * Returns the mode field of this segment. + */ + public: Mode getMode() const; + + + /* + * Returns the character count field of this segment. + */ + public: int getNumChars() const; + + + /* + * Returns the data bits of this segment. + */ + public: const std::vector &getData() const; + + + // (Package-private) Calculates the number of bits needed to encode the given segments at + // the given version. Returns a non-negative number if successful. Otherwise returns -1 if a + // segment has too many characters to fit its length field, or the total bits exceeds INT_MAX. + public: static int getTotalBits(const std::vector &segs, int version); + + + /*---- Private constant ----*/ + + /* The set of all legal characters in alphanumeric mode, where + * each character value maps to the index in the string. */ + private: static const char *ALPHANUMERIC_CHARSET; + +}; + + + +/* + * A QR Code symbol, which is a type of two-dimension barcode. + * Invented by Denso Wave and described in the ISO/IEC 18004 standard. + * Instances of this class represent an immutable square grid of black and white cells. + * The class provides static factory functions to create a QR Code from text or binary data. + * The class covers the QR Code Model 2 specification, supporting all versions (sizes) + * from 1 to 40, all 4 error correction levels, and 4 character encoding modes. + * + * Ways to create a QR Code object: + * - High level: Take the payload data and call QrCode::encodeText() or QrCode::encodeBinary(). + * - Mid level: Custom-make the list of segments and call QrCode::encodeSegments(). + * - Low level: Custom-make the array of data codeword bytes (including + * segment headers and final padding, excluding error correction codewords), + * supply the appropriate version number, and call the QrCode() constructor. + * (Note that all ways require supplying the desired error correction level.) + */ +class QrCode final { + + /*---- Public helper enumeration ----*/ + + /* + * The error correction level in a QR Code symbol. + */ + public: enum class Ecc { + LOW = 0 , // The QR Code can tolerate about 7% erroneous codewords + MEDIUM , // The QR Code can tolerate about 15% erroneous codewords + QUARTILE, // The QR Code can tolerate about 25% erroneous codewords + HIGH , // The QR Code can tolerate about 30% erroneous codewords + }; + + + // Returns a value in the range 0 to 3 (unsigned 2-bit integer). + private: static int getFormatBits(Ecc ecl); + + + + /*---- Static factory functions (high level) ----*/ + + /* + * Returns a QR Code representing the given Unicode text string at the given error correction level. + * As a conservative upper bound, this function is guaranteed to succeed for strings that have 2953 or fewer + * UTF-8 code units (not Unicode code points) if the low error correction level is used. The smallest possible + * QR Code version is automatically chosen for the output. The ECC level of the result may be higher than + * the ecl argument if it can be done without increasing the version. + */ + public: static QrCode encodeText(const char *text, Ecc ecl); + + + /* + * Returns a QR Code representing the given binary data at the given error correction level. + * This function always encodes using the binary segment mode, not any text mode. The maximum number of + * bytes allowed is 2953. The smallest possible QR Code version is automatically chosen for the output. + * The ECC level of the result may be higher than the ecl argument if it can be done without increasing the version. + */ + public: static QrCode encodeBinary(const std::vector &data, Ecc ecl); + + + /*---- Static factory functions (mid level) ----*/ + + /* + * Returns a QR Code representing the given segments with the given encoding parameters. + * The smallest possible QR Code version within the given range is automatically + * chosen for the output. Iff boostEcl is true, then the ECC level of the result + * may be higher than the ecl argument if it can be done without increasing the + * version. The mask number is either between 0 to 7 (inclusive) to force that + * mask, or -1 to automatically choose an appropriate mask (which may be slow). + * This function allows the user to create a custom sequence of segments that switches + * between modes (such as alphanumeric and byte) to encode text in less space. + * This is a mid-level API; the high-level API is encodeText() and encodeBinary(). + */ + public: static QrCode encodeSegments(const std::vector &segs, Ecc ecl, + int minVersion=1, int maxVersion=40, int mask=-1, bool boostEcl=true); // All optional parameters + + + + /*---- Instance fields ----*/ + + // Immutable scalar parameters: + + /* The version number of this QR Code, which is between 1 and 40 (inclusive). + * This determines the size of this barcode. */ + private: int version; + + /* The width and height of this QR Code, measured in modules, between + * 21 and 177 (inclusive). This is equal to version * 4 + 17. */ + private: int size; + + /* The error correction level used in this QR Code. */ + private: Ecc errorCorrectionLevel; + + /* The index of the mask pattern used in this QR Code, which is between 0 and 7 (inclusive). + * Even if a QR Code is created with automatic masking requested (mask = -1), + * the resulting object still has a mask value between 0 and 7. */ + private: int mask; + + // Private grids of modules/pixels, with dimensions of size*size: + + // The modules of this QR Code (false = white, true = black). + // Immutable after constructor finishes. Accessed through getModule(). + private: std::vector > modules; + + // Indicates function modules that are not subjected to masking. Discarded when constructor finishes. + private: std::vector > isFunction; + + + + /*---- Constructor (low level) ----*/ + + /* + * Creates a new QR Code with the given version number, + * error correction level, data codeword bytes, and mask number. + * This is a low-level API that most users should not use directly. + * A mid-level API is the encodeSegments() function. + */ + public: QrCode(int ver, Ecc ecl, const std::vector &dataCodewords, int msk); + + + + /*---- Public instance methods ----*/ + + /* + * Returns this QR Code's version, in the range [1, 40]. + */ + public: int getVersion() const; + + + /* + * Returns this QR Code's size, in the range [21, 177]. + */ + public: int getSize() const; + + + /* + * Returns this QR Code's error correction level. + */ + public: Ecc getErrorCorrectionLevel() const; + + + /* + * Returns this QR Code's mask, in the range [0, 7]. + */ + public: int getMask() const; + + + /* + * Returns the color of the module (pixel) at the given coordinates, which is false + * for white or true for black. The top left corner has the coordinates (x=0, y=0). + * If the given coordinates are out of bounds, then false (white) is returned. + */ + public: bool getModule(int x, int y) const; + + + /* + * Returns a string of SVG code for an image depicting this QR Code, with the given number + * of border modules. The string always uses Unix newlines (\n), regardless of the platform. + */ + public: std::string toSvgString(int border) const; + + + + /*---- Private helper methods for constructor: Drawing function modules ----*/ + + // Reads this object's version field, and draws and marks all function modules. + private: void drawFunctionPatterns(); + + + // Draws two copies of the format bits (with its own error correction code) + // based on the given mask and this object's error correction level field. + private: void drawFormatBits(int msk); + + + // Draws two copies of the version bits (with its own error correction code), + // based on this object's version field, iff 7 <= version <= 40. + private: void drawVersion(); + + + // Draws a 9*9 finder pattern including the border separator, + // with the center module at (x, y). Modules can be out of bounds. + private: void drawFinderPattern(int x, int y); + + + // Draws a 5*5 alignment pattern, with the center module + // at (x, y). All modules must be in bounds. + private: void drawAlignmentPattern(int x, int y); + + + // Sets the color of a module and marks it as a function module. + // Only used by the constructor. Coordinates must be in bounds. + private: void setFunctionModule(int x, int y, bool isBlack); + + + // Returns the color of the module at the given coordinates, which must be in range. + private: bool module(int x, int y) const; + + + /*---- Private helper methods for constructor: Codewords and masking ----*/ + + // Returns a new byte string representing the given data with the appropriate error correction + // codewords appended to it, based on this object's version and error correction level. + private: std::vector addEccAndInterleave(const std::vector &data) const; + + + // Draws the given sequence of 8-bit codewords (data and error correction) onto the entire + // data area of this QR Code. Function modules need to be marked off before this is called. + private: void drawCodewords(const std::vector &data); + + + // XORs the codeword modules in this QR Code with the given mask pattern. + // The function modules must be marked and the codeword bits must be drawn + // before masking. Due to the arithmetic of XOR, calling applyMask() with + // the same mask value a second time will undo the mask. A final well-formed + // QR Code needs exactly one (not zero, two, etc.) mask applied. + private: void applyMask(int msk); + + + // Calculates and returns the penalty score based on state of this QR Code's current modules. + // This is used by the automatic mask choice algorithm to find the mask pattern that yields the lowest score. + private: long getPenaltyScore() const; + + + + /*---- Private helper functions ----*/ + + // Returns an ascending list of positions of alignment patterns for this version number. + // Each position is in the range [0,177), and are used on both the x and y axes. + // This could be implemented as lookup table of 40 variable-length lists of unsigned bytes. + private: std::vector getAlignmentPatternPositions() const; + + + // Returns the number of data bits that can be stored in a QR Code of the given version number, after + // all function modules are excluded. This includes remainder bits, so it might not be a multiple of 8. + // The result is in the range [208, 29648]. This could be implemented as a 40-entry lookup table. + private: static int getNumRawDataModules(int ver); + + + // Returns the number of 8-bit data (i.e. not error correction) codewords contained in any + // QR Code of the given version number and error correction level, with remainder bits discarded. + // This stateless pure function could be implemented as a (40*4)-cell lookup table. + private: static int getNumDataCodewords(int ver, Ecc ecl); + + + // Returns a Reed-Solomon ECC generator polynomial for the given degree. This could be + // implemented as a lookup table over all possible parameter values, instead of as an algorithm. + private: static std::vector reedSolomonComputeDivisor(int degree); + + + // Returns the Reed-Solomon error correction codeword for the given data and divisor polynomials. + private: static std::vector reedSolomonComputeRemainder(const std::vector &data, const std::vector &divisor); + + + // Returns the product of the two given field elements modulo GF(2^8/0x11D). + // All inputs are valid. This could be implemented as a 256*256 lookup table. + private: static std::uint8_t reedSolomonMultiply(std::uint8_t x, std::uint8_t y); + + + // Can only be called immediately after a white run is added, and + // returns either 0, 1, or 2. A helper function for getPenaltyScore(). + private: int finderPenaltyCountPatterns(const std::array &runHistory) const; + + + // Must be called at the end of a line (row or column) of modules. A helper function for getPenaltyScore(). + private: int finderPenaltyTerminateAndCount(bool currentRunColor, int currentRunLength, std::array &runHistory) const; + + + // Pushes the given value to the front and drops the last value. A helper function for getPenaltyScore(). + private: void finderPenaltyAddHistory(int currentRunLength, std::array &runHistory) const; + + + // Returns true iff the i'th bit of x is set to 1. + private: static bool getBit(long x, int i); + + + /*---- Constants and tables ----*/ + + // The minimum version number supported in the QR Code Model 2 standard. + public: static constexpr int MIN_VERSION = 1; + + // The maximum version number supported in the QR Code Model 2 standard. + public: static constexpr int MAX_VERSION = 40; + + + // For use in getPenaltyScore(), when evaluating which mask is best. + private: static const int PENALTY_N1; + private: static const int PENALTY_N2; + private: static const int PENALTY_N3; + private: static const int PENALTY_N4; + + + private: static const std::int8_t ECC_CODEWORDS_PER_BLOCK[4][41]; + private: static const std::int8_t NUM_ERROR_CORRECTION_BLOCKS[4][41]; + +}; + + + +/*---- Public exception class ----*/ + +/* + * Thrown when the supplied data does not fit any QR Code version. Ways to handle this exception include: + * - Decrease the error correction level if it was greater than Ecc::LOW. + * - If the encodeSegments() function was called with a maxVersion argument, then increase + * it if it was less than QrCode::MAX_VERSION. (This advice does not apply to the other + * factory functions because they search all versions up to QrCode::MAX_VERSION.) + * - Split the text data into better or optimal segments in order to reduce the number of bits required. + * - Change the text or binary data to be shorter. + * - Change the text to fit the character set of a particular segment mode (e.g. alphanumeric). + * - Propagate the error upward to the caller/user. + */ +class data_too_long : public std::length_error { + + public: explicit data_too_long(const std::string &msg); + +}; + + + +/* + * An appendable sequence of bits (0s and 1s). Mainly used by QrSegment. + */ +class BitBuffer final : public std::vector { + + /*---- Constructor ----*/ + + // Creates an empty bit buffer (length 0). + public: BitBuffer(); + + + + /*---- Method ----*/ + + // Appends the given number of low-order bits of the given value + // to this buffer. Requires 0 <= len <= 31 and val < 2^len. + public: void appendBits(std::uint32_t val, int len); + +}; + +} diff --git a/anpro/imagecropper.cpp b/anpro/imagecropper.cpp new file mode 100644 index 0000000..3d80f71 --- /dev/null +++ b/anpro/imagecropper.cpp @@ -0,0 +1,537 @@ +/***************************************************************************** +* ImageCropper Qt Widget for cropping images +* Copyright (C) 2013 Dimka Novikov, to@dimkanovikov.pro +* Copyright (C) 2020 Syping +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public +* License as published by the Free Software Foundation; either +* version 3 of the License, or any later version. +* +* This library is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this program. If not, see . +*****************************************************************************/ + +#include "imagecropper.h" +#include "AppEnv.h" + +#include +#include +#include + +namespace { + static const QSize WIDGET_MINIMUM_SIZE(470, 470); +} + +ImageCropper::ImageCropper(QWidget* parent) : + QWidget(parent), + pimpl(new ImageCropperPrivate) +{ + setMinimumSize(WIDGET_MINIMUM_SIZE); + setMouseTracking(true); +} + +ImageCropper::~ImageCropper() +{ + delete pimpl; +} + +void ImageCropper::setImage(const QPixmap& _image) +{ + pimpl->imageForCropping = _image; + update(); +} + +void ImageCropper::setBackgroundColor(const QColor& _backgroundColor) +{ + pimpl->backgroundColor = _backgroundColor; + update(); +} + +void ImageCropper::setCroppingRectBorderColor(const QColor& _borderColor) +{ + pimpl->croppingRectBorderColor = _borderColor; + update(); +} + +void ImageCropper::setProportion(const QSizeF& _proportion) +{ + // Пропорции хранÑÑ‚ÑÑ Ð² коÑффициентах Ð¿Ñ€Ð¸Ñ€Ð°Ñ‰ÐµÐ½Ð¸Ñ Ñторон + // Таким образом, при изменении размера облаÑти выделениÑ, + // размеры Ñторон изменÑÑŽÑ‚ÑÑ Ð½Ð° размер завиÑÑщий от + // коÑффициентов приращениÑ. + + // Сохраним пропорциональную завиÑимоÑть облаÑти Ð²Ñ‹Ð´ÐµÐ»ÐµÐ½Ð¸Ñ Ð² коÑффициентах Ð¿Ñ€Ð¸Ñ€Ð°Ñ‰ÐµÐ½Ð¸Ñ Ñторон + if (pimpl->proportion != _proportion) { + pimpl->proportion = _proportion; + // ... раÑчитаем коÑффициенты + float heightDelta = (float)_proportion.height() / _proportion.width(); + float widthDelta = (float)_proportion.width() / _proportion.height(); + // ... Ñохраним коÑффициенты + pimpl->deltas.setHeight(heightDelta); + pimpl->deltas.setWidth(widthDelta); + } + + // Обновим пропорции облаÑти Ð²Ñ‹Ð´ÐµÐ»ÐµÐ½Ð¸Ñ + if ( pimpl->isProportionFixed ) { + float croppintRectSideRelation = + (float)pimpl->croppingRect.width() / pimpl->croppingRect.height(); + float proportionSideRelation = + (float)pimpl->proportion.width() / pimpl->proportion.height(); + // ЕÑли облаÑть Ð²Ñ‹Ð´ÐµÐ»ÐµÐ½Ð¸Ñ Ð½Ðµ ÑоответÑтвует необходимым пропорциÑм обновим её + if (croppintRectSideRelation != proportionSideRelation) { + bool widthShotrerThenHeight = + pimpl->croppingRect.width() < pimpl->croppingRect.height(); + // ... уÑтановим размер той Ñтороны, что длиннее + if (widthShotrerThenHeight) { + pimpl->croppingRect.setHeight( + pimpl->croppingRect.width() * pimpl->deltas.height()); + } else { + pimpl->croppingRect.setWidth( + pimpl->croppingRect.height() * pimpl->deltas.width()); + } + // ... перериÑуем виджет + update(); + } + } + +} + +void ImageCropper::setProportionFixed(const bool _isFixed) +{ + if (pimpl->isProportionFixed != _isFixed) { + pimpl->isProportionFixed = _isFixed; + setProportion(pimpl->proportion); + } +} + +const QPixmap ImageCropper::cropImage() +{ + // Получим размер отображаемого Ð¸Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð¸Ñ + QSize scaledImageSize = + pimpl->imageForCropping.scaled( + size(), Qt::KeepAspectRatio, Qt::SmoothTransformation + ).size(); + // Определим раÑÑтоÑние от левого и верхнего краёв + float leftDelta = 0; + float topDelta = 0; + const float HALF_COUNT = 2; + if (size().height() == scaledImageSize.height()) { + leftDelta = (width() - scaledImageSize.width()) / HALF_COUNT; + } else { + topDelta = (height() - scaledImageSize.height()) / HALF_COUNT; + } + // Определим пропорцию облаÑти обрезки по отношению к иÑходному изображению + float xScale = (float)pimpl->imageForCropping.width() / scaledImageSize.width(); + float yScale = (float)pimpl->imageForCropping.height() / scaledImageSize.height(); + // РаÑчитаем облаÑть обрезки Ñ ÑƒÑ‡Ñ‘Ñ‚Ð¾Ð¼ коррекции размеров иÑходного Ð¸Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð¸Ñ + QRectF realSizeRect( + QPointF(pimpl->croppingRect.left() - leftDelta, pimpl->croppingRect.top() - topDelta), + pimpl->croppingRect.size()); + // ... корректируем левый и верхний ÐºÑ€Ð°Ñ + realSizeRect.setLeft((pimpl->croppingRect.left() - leftDelta) * xScale); + realSizeRect.setTop ((pimpl->croppingRect.top() - topDelta) * yScale); + // ... корректируем размер + realSizeRect.setWidth(pimpl->croppingRect.width() * xScale); + realSizeRect.setHeight(pimpl->croppingRect.height() * yScale); + // Получаем обрезанное изображение + return pimpl->imageForCropping.copy(realSizeRect.toRect()); +} + +// ******** +// Protected section + +void ImageCropper::paintEvent(QPaintEvent* _event) +{ + QWidget::paintEvent(_event); + // + QPainter widgetPainter(this); + // РиÑуем изображение по центру виджета + { +#if QT_VERSION >= 0x050600 + qreal screenRatioPR = AppEnv::screenRatioPR(); + // ... подгоним изображение Ð´Ð»Ñ Ð¾Ñ‚Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð¸Ñ Ð¿Ð¾ размеру виджета + QPixmap scaledImage = + pimpl->imageForCropping.scaled(qRound((double)width() * screenRatioPR), qRound((double)height() * screenRatioPR), Qt::KeepAspectRatio, Qt::SmoothTransformation); + scaledImage.setDevicePixelRatio(screenRatioPR); +#else + QPixmap scaledImage = + pimpl->imageForCropping.scaled(size(), Qt::KeepAspectRatio, Qt::SmoothTransformation); +#endif + // ... заливаем фон + widgetPainter.fillRect(rect(), pimpl->backgroundColor); + // ... риÑуем изображение по центру виджета +#if QT_VERSION >= 0x050600 + if (qRound((double)height() * screenRatioPR) == scaledImage.height()) { + widgetPainter.drawPixmap( ( qRound((double)width() * screenRatioPR) - scaledImage.width() ) / 2, 0, scaledImage ); + } else { + widgetPainter.drawPixmap( 0, ( qRound((double)height() * screenRatioPR) - scaledImage.height() ) / 2, scaledImage ); + } +#else + if (height() == scaledImage.height()) { + widgetPainter.drawPixmap( ( width()- scaledImage.width() ) / 2, 0, scaledImage ); + } else { + widgetPainter.drawPixmap( 0, ( height() - scaledImage.height() ) / 2, scaledImage ); + } +#endif + } + // РиÑуем облаÑть обрезки + { + // ... еÑли Ñто первое отображение поÑле инициилизации, то центруем облаÑто обрезки + if (pimpl->croppingRect.isNull()) { + const int cwidth = WIDGET_MINIMUM_SIZE.width()/2; + const int cheight = WIDGET_MINIMUM_SIZE.height()/2; + pimpl->croppingRect.setSize(QSize(cwidth, cheight)); + float x = (width() - pimpl->croppingRect.width())/2; + float y = (height() - pimpl->croppingRect.height())/2; + pimpl->croppingRect.moveTo(x, y); + } + + // ... риÑуем затемненную облаÑть + QPainterPath p; + p.addRect(pimpl->croppingRect); + p.addRect(rect()); + widgetPainter.setBrush(QBrush(QColor(0,0,0,120))); + widgetPainter.setPen(Qt::transparent); + widgetPainter.drawPath(p); + // Рамка и контрольные точки + widgetPainter.setPen(pimpl->croppingRectBorderColor); + // ... риÑуем прÑмоугольник облаÑти обрезки + { + widgetPainter.setBrush(QBrush(Qt::transparent)); + widgetPainter.drawRect(pimpl->croppingRect); + } + // ... риÑуем контрольные точки + { + widgetPainter.setBrush(QBrush(pimpl->croppingRectBorderColor)); + // Ð’Ñпомогательные X координаты + int leftXCoord = pimpl->croppingRect.left() - 2; + int centerXCoord = pimpl->croppingRect.center().x() - 3; + int rightXCoord = pimpl->croppingRect.right() - 2; + // Ð’Ñпомогательные Y координаты + int topYCoord = pimpl->croppingRect.top() - 2; + int middleYCoord = pimpl->croppingRect.center().y() - 3; + int bottomYCoord = pimpl->croppingRect.bottom() - 2; + // + const QSize pointSize(6, 6); + // + QVector points; + points + // Ð»ÐµÐ²Ð°Ñ Ñторона + << QRect( QPoint(leftXCoord, topYCoord), pointSize ) + << QRect( QPoint(leftXCoord, middleYCoord), pointSize ) + << QRect( QPoint(leftXCoord, bottomYCoord), pointSize ) + // центр + << QRect( QPoint(centerXCoord, topYCoord), pointSize ) + << QRect( QPoint(centerXCoord, middleYCoord), pointSize ) + << QRect( QPoint(centerXCoord, bottomYCoord), pointSize ) + // Ð¿Ñ€Ð°Ð²Ð°Ñ Ñторона + << QRect( QPoint(rightXCoord, topYCoord), pointSize ) + << QRect( QPoint(rightXCoord, middleYCoord), pointSize ) + << QRect( QPoint(rightXCoord, bottomYCoord), pointSize ); + // + widgetPainter.drawRects( points ); + } + // ... риÑуем пунктирные линии + { + QPen dashPen(pimpl->croppingRectBorderColor); + dashPen.setStyle(Qt::DashLine); + widgetPainter.setPen(dashPen); + // ... Ð²ÐµÑ€Ñ‚Ð¸ÐºÐ°Ð»ÑŒÐ½Ð°Ñ + widgetPainter.drawLine( + QPoint(pimpl->croppingRect.center().x(), pimpl->croppingRect.top()), + QPoint(pimpl->croppingRect.center().x(), pimpl->croppingRect.bottom()) ); + // ... Ð³Ð¾Ñ€Ð¸Ð·Ð¾Ð½Ñ‚Ð°Ð»ÑŒÐ½Ð°Ñ + widgetPainter.drawLine( + QPoint(pimpl->croppingRect.left(), pimpl->croppingRect.center().y()), + QPoint(pimpl->croppingRect.right(), pimpl->croppingRect.center().y()) ); + } + } + // + widgetPainter.end(); +} + +void ImageCropper::mousePressEvent(QMouseEvent* _event) +{ + if (_event->button() == Qt::LeftButton) { + pimpl->isMousePressed = true; + pimpl->startMousePos = _event->pos(); + pimpl->lastStaticCroppingRect = pimpl->croppingRect; + } + // + updateCursorIcon(_event->pos()); +} + +void ImageCropper::mouseMoveEvent(QMouseEvent* _event) +{ + QPointF mousePos = _event->pos(); // отноÑительно ÑÐµÐ±Ñ (виджета) + // + if (!pimpl->isMousePressed) { + // Обработка обычного ÑоÑтоÑниÑ, Ñ‚.е. не изменÑетÑÑ Ñ€Ð°Ð·Ð¼ÐµÑ€ + // облаÑти обрезки, и она не перемещаетÑÑ Ð¿Ð¾ виджету + pimpl->cursorPosition = cursorPosition(pimpl->croppingRect, mousePos); + updateCursorIcon(mousePos); + } else if (pimpl->cursorPosition != CursorPositionUndefined) { + // Обработка дейÑтвий над облаÑтью обрезки + // ... определим Ñмещение курÑора мышки + QPointF mouseDelta; + mouseDelta.setX( mousePos.x() - pimpl->startMousePos.x() ); + mouseDelta.setY( mousePos.y() - pimpl->startMousePos.y() ); + // + if (pimpl->cursorPosition != CursorPositionMiddle) { + // ... изменÑем размер облаÑти обрезки + QRectF newGeometry = + calculateGeometry( + pimpl->lastStaticCroppingRect, + pimpl->cursorPosition, + mouseDelta); + // ... пользователь пытаетÑÑ Ð²Ñ‹Ð²ÐµÑ€Ð½ÑƒÑ‚ÑŒ облаÑть обрезки наизнанку + if (!newGeometry.isNull()) { + pimpl->croppingRect = newGeometry; + } + } else { + // ... перемещаем облаÑть обрезки + pimpl->croppingRect.moveTo( pimpl->lastStaticCroppingRect.topLeft() + mouseDelta ); + } + // ПерериÑуем виджет + update(); + } +} + +void ImageCropper::mouseReleaseEvent(QMouseEvent* _event) +{ + pimpl->isMousePressed = false; + updateCursorIcon(_event->pos()); +} + +// ******** +// Private section + +namespace { + // ÐаходитÑÑ Ð»Ð¸ точка Ñ€Ñдом Ñ ÐºÐ¾Ð¾Ñ€Ð´Ð¸Ð½Ð°Ñ‚Ð¾Ð¹ Ñтороны + static bool isPointNearSide (const int _sideCoordinate, const int _pointCoordinate) + { + static const int indent = 10; + return (_sideCoordinate - indent) < _pointCoordinate && _pointCoordinate < (_sideCoordinate + indent); + } +} + +CursorPosition ImageCropper::cursorPosition(const QRectF& _cropRect, const QPointF& _mousePosition) +{ + CursorPosition cursorPosition = CursorPositionUndefined; + // + if ( _cropRect.contains( _mousePosition ) ) { + // ДвухÑтороннее направление + if (isPointNearSide(_cropRect.top(), _mousePosition.y()) && + isPointNearSide(_cropRect.left(), _mousePosition.x())) { + cursorPosition = CursorPositionTopLeft; + } else if (isPointNearSide(_cropRect.bottom(), _mousePosition.y()) && + isPointNearSide(_cropRect.left(), _mousePosition.x())) { + cursorPosition = CursorPositionBottomLeft; + } else if (isPointNearSide(_cropRect.top(), _mousePosition.y()) && + isPointNearSide(_cropRect.right(), _mousePosition.x())) { + cursorPosition = CursorPositionTopRight; + } else if (isPointNearSide(_cropRect.bottom(), _mousePosition.y()) && + isPointNearSide(_cropRect.right(), _mousePosition.x())) { + cursorPosition = CursorPositionBottomRight; + // ОдноÑтороннее направление + } else if (isPointNearSide(_cropRect.left(), _mousePosition.x())) { + cursorPosition = CursorPositionLeft; + } else if (isPointNearSide(_cropRect.right(), _mousePosition.x())) { + cursorPosition = CursorPositionRight; + } else if (isPointNearSide(_cropRect.top(), _mousePosition.y())) { + cursorPosition = CursorPositionTop; + } else if (isPointNearSide(_cropRect.bottom(), _mousePosition.y())) { + cursorPosition = CursorPositionBottom; + // Без Ð½Ð°Ð¿Ñ€Ð°Ð²Ð»ÐµÐ½Ð¸Ñ + } else { + cursorPosition = CursorPositionMiddle; + } + } + // + return cursorPosition; +} + +void ImageCropper::updateCursorIcon(const QPointF& _mousePosition) +{ + QCursor cursorIcon; + // + switch (cursorPosition(pimpl->croppingRect, _mousePosition)) + { + case CursorPositionTopRight: + case CursorPositionBottomLeft: + cursorIcon = QCursor(Qt::SizeBDiagCursor); + break; + case CursorPositionTopLeft: + case CursorPositionBottomRight: + cursorIcon = QCursor(Qt::SizeFDiagCursor); + break; + case CursorPositionTop: + case CursorPositionBottom: + cursorIcon = QCursor(Qt::SizeVerCursor); + break; + case CursorPositionLeft: + case CursorPositionRight: + cursorIcon = QCursor(Qt::SizeHorCursor); + break; + case CursorPositionMiddle: + cursorIcon = pimpl->isMousePressed ? + QCursor(Qt::ClosedHandCursor) : + QCursor(Qt::OpenHandCursor); + break; + case CursorPositionUndefined: + default: + cursorIcon = QCursor(Qt::ArrowCursor); + break; + } + // + this->setCursor(cursorIcon); +} + +const QRectF ImageCropper::calculateGeometry( + const QRectF& _sourceGeometry, + const CursorPosition _cursorPosition, + const QPointF& _mouseDelta + ) +{ + QRectF resultGeometry; + // + if ( pimpl->isProportionFixed ) { + resultGeometry = + calculateGeometryWithFixedProportions( + _sourceGeometry, _cursorPosition, _mouseDelta, pimpl->deltas); + } else { + resultGeometry = + calculateGeometryWithCustomProportions( + _sourceGeometry, _cursorPosition, _mouseDelta); + } + // ЕÑли пользователь пытаетÑÑ Ð²Ñ‹Ð²ÐµÑ€Ð½ÑƒÑ‚ÑŒ облаÑть обрезки наизнанку, + // возвращаем null-прÑмоугольник + if ((resultGeometry.left() >= resultGeometry.right()) || + (resultGeometry.top() >= resultGeometry.bottom())) { + resultGeometry = QRect(); + } + // + return resultGeometry; +} + +const QRectF ImageCropper::calculateGeometryWithCustomProportions( + const QRectF& _sourceGeometry, + const CursorPosition _cursorPosition, + const QPointF& _mouseDelta + ) +{ + QRectF resultGeometry = _sourceGeometry; + // + switch ( _cursorPosition ) + { + case CursorPositionTopLeft: + resultGeometry.setLeft( _sourceGeometry.left() + _mouseDelta.x() ); + resultGeometry.setTop ( _sourceGeometry.top() + _mouseDelta.y() ); + break; + case CursorPositionTopRight: + resultGeometry.setTop ( _sourceGeometry.top() + _mouseDelta.y() ); + resultGeometry.setRight( _sourceGeometry.right() + _mouseDelta.x() ); + break; + case CursorPositionBottomLeft: + resultGeometry.setBottom( _sourceGeometry.bottom() + _mouseDelta.y() ); + resultGeometry.setLeft ( _sourceGeometry.left() + _mouseDelta.x() ); + break; + case CursorPositionBottomRight: + resultGeometry.setBottom( _sourceGeometry.bottom() + _mouseDelta.y() ); + resultGeometry.setRight ( _sourceGeometry.right() + _mouseDelta.x() ); + break; + case CursorPositionTop: + resultGeometry.setTop( _sourceGeometry.top() + _mouseDelta.y() ); + break; + case CursorPositionBottom: + resultGeometry.setBottom( _sourceGeometry.bottom() + _mouseDelta.y() ); + break; + case CursorPositionLeft: + resultGeometry.setLeft( _sourceGeometry.left() + _mouseDelta.x() ); + break; + case CursorPositionRight: + resultGeometry.setRight( _sourceGeometry.right() + _mouseDelta.x() ); + break; + default: + break; + } + // + return resultGeometry; +} + +const QRectF ImageCropper::calculateGeometryWithFixedProportions( + const QRectF& _sourceGeometry, + const CursorPosition _cursorPosition, + const QPointF& _mouseDelta, + const QSizeF& _deltas + ) +{ + QRectF resultGeometry = _sourceGeometry; + // + switch (_cursorPosition) + { + case CursorPositionLeft: + resultGeometry.setTop(_sourceGeometry.top() + _mouseDelta.x() * _deltas.height()); + resultGeometry.setLeft(_sourceGeometry.left() + _mouseDelta.x()); + break; + case CursorPositionRight: + resultGeometry.setTop(_sourceGeometry.top() - _mouseDelta.x() * _deltas.height()); + resultGeometry.setRight(_sourceGeometry.right() + _mouseDelta.x()); + break; + case CursorPositionTop: + resultGeometry.setTop(_sourceGeometry.top() + _mouseDelta.y()); + resultGeometry.setRight(_sourceGeometry.right() - _mouseDelta.y() * _deltas.width()); + break; + case CursorPositionBottom: + resultGeometry.setBottom(_sourceGeometry.bottom() + _mouseDelta.y()); + resultGeometry.setRight(_sourceGeometry.right() + _mouseDelta.y() * _deltas.width()); + break; + case CursorPositionTopLeft: + if ((_mouseDelta.x() * _deltas.height()) < (_mouseDelta.y())) { + resultGeometry.setTop(_sourceGeometry.top() + _mouseDelta.x() * _deltas.height()); + resultGeometry.setLeft(_sourceGeometry.left() + _mouseDelta.x()); + } else { + resultGeometry.setTop(_sourceGeometry.top() + _mouseDelta.y()); + resultGeometry.setLeft(_sourceGeometry.left() + _mouseDelta.y() * _deltas.width()); + } + break; + case CursorPositionTopRight: + if ((_mouseDelta.x() * _deltas.height() * -1) < (_mouseDelta.y())) { + resultGeometry.setTop(_sourceGeometry.top() - _mouseDelta.x() * _deltas.height()); + resultGeometry.setRight(_sourceGeometry.right() + _mouseDelta.x() ); + } else { + resultGeometry.setTop(_sourceGeometry.top() + _mouseDelta.y()); + resultGeometry.setRight(_sourceGeometry.right() - _mouseDelta.y() * _deltas.width()); + } + break; + case CursorPositionBottomLeft: + if ((_mouseDelta.x() * _deltas.height()) < (_mouseDelta.y() * -1)) { + resultGeometry.setBottom(_sourceGeometry.bottom() - _mouseDelta.x() * _deltas.height()); + resultGeometry.setLeft(_sourceGeometry.left() + _mouseDelta.x()); + } else { + resultGeometry.setBottom(_sourceGeometry.bottom() + _mouseDelta.y()); + resultGeometry.setLeft(_sourceGeometry.left() - _mouseDelta.y() * _deltas.width()); + } + break; + case CursorPositionBottomRight: + if ((_mouseDelta.x() * _deltas.height()) > (_mouseDelta.y())) { + resultGeometry.setBottom(_sourceGeometry.bottom() + _mouseDelta.x() * _deltas.height()); + resultGeometry.setRight(_sourceGeometry.right() + _mouseDelta.x()); + } else { + resultGeometry.setBottom(_sourceGeometry.bottom() + _mouseDelta.y()); + resultGeometry.setRight(_sourceGeometry.right() + _mouseDelta.y() * _deltas.width()); + } + break; + default: + break; + } + // + return resultGeometry; +} + diff --git a/anpro/imagecropper.h b/anpro/imagecropper.h new file mode 100644 index 0000000..a5a19a0 --- /dev/null +++ b/anpro/imagecropper.h @@ -0,0 +1,103 @@ +/***************************************************************************** +* ImageCropper Qt Widget for cropping images +* Copyright (C) 2013 Dimka Novikov, to@dimkanovikov.pro +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public +* License as published by the Free Software Foundation; either +* version 3 of the License, or any later version. +* +* This library is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this program. If not, see . +*****************************************************************************/ + +#ifndef IMAGECROPPER_H +#define IMAGECROPPER_H + +#include "imagecropper_p.h" +#include "imagecropper_e.h" + +#include + +class ImageCropper : public QWidget +{ + Q_OBJECT + +public: + ImageCropper(QWidget *parent = 0); + ~ImageCropper(); + +public slots: + // УÑтановить изображение Ð´Ð»Ñ Ð¾Ð±Ñ€ÐµÐ·ÐºÐ¸ + void setImage(const QPixmap& _image); + // УÑтановить цвет фона виджета обрезки + void setBackgroundColor(const QColor& _backgroundColor); + // УÑтановить цвет рамки облаÑти обрезки + void setCroppingRectBorderColor(const QColor& _borderColor); + // УÑтановить пропорции облаÑти Ð²Ñ‹Ð´ÐµÐ»ÐµÐ½Ð¸Ñ + void setProportion(const QSizeF& _proportion); + // ИÑпользовать фикÑированные пропорции облаÑти Ð²Ð¸Ð´ÐµÐ»ÐµÐ½Ð¸Ñ + void setProportionFixed(const bool _isFixed); + +public: + // Обрезать изображение + const QPixmap cropImage(); + +protected: + virtual void paintEvent(QPaintEvent* _event); + virtual void mousePressEvent(QMouseEvent* _event); + virtual void mouseMoveEvent(QMouseEvent* _event); + virtual void mouseReleaseEvent(QMouseEvent* _event); + +private: + // Определение меÑÑ‚Ð¾Ð¿Ð¾Ð»Ð¾Ð¶ÐµÐ½Ð¸Ñ ÐºÑƒÑ€Ñора над виджетом + CursorPosition cursorPosition(const QRectF& _cropRect, const QPointF& _mousePosition); + // Обновить иконку курÑора ÑоответÑтвующую меÑтоположению мыши + void updateCursorIcon(const QPointF& _mousePosition); + + // Получить размер виджета поÑле его Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð¼Ñ‹ÑˆÑŒÑŽ + // -------- + // Контракты: + // 1. Метод должен вызыватьÑÑ, только при зажатой кнопке мыши + // (Ñ‚.е. при перемещении или изменении размера виджета) + // -------- + // Ð’ Ñлучае неудачи возвращает null-прÑмоугольник + const QRectF calculateGeometry( + const QRectF& _sourceGeometry, + const CursorPosition _cursorPosition, + const QPointF& _mouseDelta + ); + // Получить размер виджета поÑле его Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð¼Ñ‹ÑˆÑŒÑŽ + // Метод изменÑет виджет не ÑохранÑÑ Ð½Ð°Ñ‡Ð°Ð»ÑŒÐ½Ñ‹Ñ… пропорций Ñторон + // ------ + // Контракты: + // 1. Метод должен вызыватьÑÑ, только при зажатой кнопке мыши + // (Ñ‚.е. при перемещении или изменении размера виджета) + const QRectF calculateGeometryWithCustomProportions( + const QRectF& _sourceGeometry, + const CursorPosition _cursorPosition, + const QPointF& _mouseDelta + ); + // Получить размер виджета поÑле его Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð¼Ñ‹ÑˆÑŒÑŽ + // Метод изменÑет виджет ÑохранÑÑ Ð½Ð°Ñ‡Ð°Ð»ÑŒÐ½Ñ‹Ðµ пропорции Ñторон + // ------ + // Контракты: + // 1. Метод должен вызыватьÑÑ, только при зажатой кнопке мыши + // (Ñ‚.е. при перемещении или изменении размера виджета) + const QRectF calculateGeometryWithFixedProportions(const QRectF &_sourceGeometry, + const CursorPosition _cursorPosition, + const QPointF &_mouseDelta, + const QSizeF &_deltas + ); + +private: + // Private data implementation + ImageCropperPrivate* pimpl; +}; + +#endif // IMAGECROPPER_H diff --git a/anpro/imagecropper_e.h b/anpro/imagecropper_e.h new file mode 100644 index 0000000..a9ced6a --- /dev/null +++ b/anpro/imagecropper_e.h @@ -0,0 +1,36 @@ +/***************************************************************************** +* ImageCropper Qt Widget for cropping images +* Copyright (C) 2013 Dimka Novikov, to@dimkanovikov.pro +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public +* License as published by the Free Software Foundation; either +* version 3 of the License, or any later version. +* +* This library is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this program. If not, see . +*****************************************************************************/ + +#ifndef IMAGECROPPER_E_H +#define IMAGECROPPER_E_H + +enum CursorPosition +{ + CursorPositionUndefined, + CursorPositionMiddle, + CursorPositionTop, + CursorPositionBottom, + CursorPositionLeft, + CursorPositionRight, + CursorPositionTopLeft, + CursorPositionTopRight, + CursorPositionBottomLeft, + CursorPositionBottomRight +}; + +#endif // IMAGECROPPER_E_H diff --git a/anpro/imagecropper_p.h b/anpro/imagecropper_p.h new file mode 100644 index 0000000..bd09dbb --- /dev/null +++ b/anpro/imagecropper_p.h @@ -0,0 +1,76 @@ +/***************************************************************************** +* ImageCropper Qt Widget for cropping images +* Copyright (C) 2013 Dimka Novikov, to@dimkanovikov.pro +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public +* License as published by the Free Software Foundation; either +* version 3 of the License, or any later version. +* +* This library is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this program. If not, see . +*****************************************************************************/ + +#ifndef IMAGECROPPER_P_H +#define IMAGECROPPER_P_H + +#include "imagecropper_e.h" + +#include +#include +#include + +namespace { + const QRect INIT_CROPPING_RECT = QRect(); + const QSizeF INIT_PROPORTION = QSizeF(1.0, 1.0); +} + +class ImageCropperPrivate { +public: + ImageCropperPrivate() : + imageForCropping(QPixmap()), + croppingRect(INIT_CROPPING_RECT), + lastStaticCroppingRect(QRect()), + cursorPosition(CursorPositionUndefined), + isMousePressed(false), + isProportionFixed(false), + startMousePos(QPoint()), + proportion(INIT_PROPORTION), + deltas(INIT_PROPORTION), + backgroundColor(Qt::black), + croppingRectBorderColor(Qt::white) + {} + +public: + // Изображение Ð´Ð»Ñ Ð¾Ð±Ñ€ÐµÐ·ÐºÐ¸ + QPixmap imageForCropping; + // ОблаÑть обрезки + QRectF croppingRect; + // ПоÑледнÑÑ Ñ„Ð¸ÐºÑÐ¸Ñ€Ð¾Ð²Ð°Ð½Ð½Ð°Ñ Ð¾Ð±Ð»Ð°Ñть обрезки + QRectF lastStaticCroppingRect; + // ÐŸÐ¾Ð·Ð¸Ñ†Ð¸Ñ ÐºÑƒÑ€Ñора отноÑительно облаÑти обрезки + CursorPosition cursorPosition; + // Зажата ли Ð»ÐµÐ²Ð°Ñ ÐºÐ½Ð¾Ð¿ÐºÐ° мыши + bool isMousePressed; + // ФикÑировать пропорции облаÑти обрезки + bool isProportionFixed; + // ÐÐ°Ñ‡Ð°Ð»ÑŒÐ½Ð°Ñ Ð¿Ð¾Ð·Ð¸Ñ†Ð¸Ñ ÐºÑƒÑ€Ñора при изменении размера облаÑти обрезки + QPointF startMousePos; + // Пропорции + QSizeF proportion; + // ÐŸÑ€Ð¸Ñ€Ð°Ñ‰ÐµÐ½Ð¸Ñ + // width - приращение по x + // height - приращение по y + QSizeF deltas; + // Цвет заливки фона под изображением + QColor backgroundColor; + // Цвет рамки облаÑти обрезки + QColor croppingRectBorderColor; +}; + +#endif // IMAGECROPPER_P_H diff --git a/config.h b/config.h index 1ebab69..6f96056 100644 --- a/config.h +++ b/config.h @@ -1,6 +1,6 @@ /***************************************************************************** -* gta5sync GRAND THEFT AUTO V SYNC -* Copyright (C) 2016-2018 Syping +* gta5view Grand Theft Auto V Profile Viewer +* Copyright (C) 2016-2021 Syping * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -18,76 +18,43 @@ #ifndef CONFIG_H #define CONFIG_H + +#if __cplusplus #include +#define REL_BUILDTYPE QT_TRANSLATE_NOOP("AboutDialog", "Release") +#define RC_BUILDTYPE QT_TRANSLATE_NOOP("AboutDialog", "Release Candidate") +#define BETA_BUILDTYPE QT_TRANSLATE_NOOP("AboutDialog", "Beta") +#define ALPHA_BUILDTYPE QT_TRANSLATE_NOOP("AboutDialog", "Alpha") +#define DEV_BUILDTYPE QT_TRANSLATE_NOOP("AboutDialog", "Developer") +#define DAILY_BUILDTYPE QT_TRANSLATE_NOOP("AboutDialog", "Daily Build") +#define CUSTOM_BUILDTYPE QT_TRANSLATE_NOOP("AboutDialog", "Custom") +#endif #ifndef GTA5SYNC_APPVENDOR #define GTA5SYNC_APPVENDOR "Syping" #endif #ifndef GTA5SYNC_APPVENDORLINK -#define GTA5SYNC_APPVENDORLINK "https://github.com/Syping/" -#endif - -#ifndef GTA5SYNC_DISABLED -#define GTA5SYNC_ENABLED +#define GTA5SYNC_APPVENDORLINK "g5e://about?U3lwaW5n:R2l0TGFiOiA8YSBocmVmPSJodHRwczovL2dpdGxhYi5jb20vU3lwaW5nIj5TeXBpbmc8L2E+PGJyLz5HaXRIdWI6IDxhIGhyZWY9Imh0dHBzOi8vZ2l0aHViLmNvbS9TeXBpbmciPlN5cGluZzwvYT48YnIvPlNvY2lhbCBDbHViOiA8YSBocmVmPSJodHRwczovL3NvY2lhbGNsdWIucm9ja3N0YXJnYW1lcy5jb20vbWVtYmVyL1N5cGluZy80NjMwMzA1NiI+U3lwaW5nPC9hPg" #endif #ifndef GTA5SYNC_APPSTR -#ifdef GTA5SYNC_ENABLED -#define GTA5SYNC_APPSTR "gta5sync" -#else #define GTA5SYNC_APPSTR "gta5view" #endif -#endif #ifndef GTA5SYNC_APPDES #define GTA5SYNC_APPDES "INSERT YOUR APPLICATION DESCRIPTION HERE" #endif #ifndef GTA5SYNC_COPYRIGHT -#define GTA5SYNC_COPYRIGHT "2016-2018" +#define GTA5SYNC_COPYRIGHT "2016-2023" #endif #ifndef GTA5SYNC_APPVER -#define GTA5SYNC_APPVER "1.5.4" -#endif - -#ifdef GTA5SYNC_BUILDTYPE_REL -#ifndef GTA5SYNC_BUILDTYPE -#define GTA5SYNC_BUILDTYPE QT_TRANSLATE_NOOP("AboutDialog", "Release") -#endif -#endif - -#ifdef GTA5SYNC_BUILDTYPE_RC -#ifndef GTA5SYNC_BUILDTYPE -#define GTA5SYNC_BUILDTYPE QT_TRANSLATE_NOOP("AboutDialog", "Release Candidate") -#endif -#endif - -#ifdef GTA5SYNC_BUILDTYPE_DAILY -#ifndef GTA5SYNC_BUILDTYPE -#define GTA5SYNC_BUILDTYPE QT_TRANSLATE_NOOP("AboutDialog", "Daily Build") -#endif -#endif - -#ifdef GTA5SYNC_BUILDTYPE_DEV -#ifndef GTA5SYNC_BUILDTYPE -#define GTA5SYNC_BUILDTYPE QT_TRANSLATE_NOOP("AboutDialog", "Developer") -#endif -#endif - -#ifdef GTA5SYNC_BUILDTYPE_BETA -#ifndef GTA5SYNC_BUILDTYPE -#define GTA5SYNC_BUILDTYPE QT_TRANSLATE_NOOP("AboutDialog", "Beta") -#endif -#endif - -#ifdef GTA5SYNC_BUILDTYPE_ALPHA -#ifndef GTA5SYNC_BUILDTYPE -#define GTA5SYNC_BUILDTYPE QT_TRANSLATE_NOOP("AboutDialog", "Alpha") -#endif +#define GTA5SYNC_APPVER "1.10.2" #endif +#if __cplusplus #ifndef GTA5SYNC_BUILDTYPE #define GTA5SYNC_BUILDTYPE QT_TRANSLATE_NOOP("AboutDialog", "Custom") #endif @@ -98,7 +65,11 @@ #ifdef GTA5SYNC_QCONF #ifndef GTA5SYNC_SHARE -#define GTA5SYNC_SHARE "RUNDIR:SEPARATOR:..SEPARATOR:share" +#ifdef Q_OS_WIN +#define GTA5SYNC_SHARE "RUNDIR:" +#else +#define GTA5SYNC_SHARE "RUNDIR:/../share" +#endif #endif #ifndef GTA5SYNC_LANG #define GTA5SYNC_LANG "QCONFLANG:" @@ -111,22 +82,16 @@ #define GTA5SYNC_INLANG ":/tr" #endif #endif -#endif - +#else #ifndef GTA5SYNC_SHARE #define GTA5SYNC_SHARE "RUNDIR:" #endif - #ifndef GTA5SYNC_LANG -#define GTA5SYNC_LANG "SHAREDDIR:SEPARATOR:lang" +#define GTA5SYNC_LANG "SHAREDDIR:/lang" #endif - #ifndef GTA5SYNC_PLUG -#define GTA5SYNC_PLUG "RUNDIR:SEPARATOR:plugins" +#define GTA5SYNC_PLUG "RUNDIR:/plugins" #endif - -#ifdef GTA5SYNC_WINRT -#undef GTA5SYNC_WIN #endif #ifndef GTA5SYNC_COMPILER @@ -154,5 +119,6 @@ #ifndef GTA5SYNC_BUILDSTRING #define GTA5SYNC_BUILDSTRING QString("%1, %2").arg(QT_VERSION_STR, GTA5SYNC_COMPILER) #endif +#endif #endif // CONFIG_H diff --git a/gta5view.pro b/gta5view.pro index 09a47f8..9326210 100644 --- a/gta5view.pro +++ b/gta5view.pro @@ -1,6 +1,6 @@ #/***************************************************************************** #* gta5view Grand Theft Auto V Profile Viewer -#* Copyright (C) 2015-2018 Syping +#* Copyright (C) 2015-2021 Syping #* #* This program is free software: you can redistribute it and/or modify #* it under the terms of the GNU General Public License as published by @@ -16,16 +16,17 @@ #* along with this program. If not, see . #*****************************************************************************/ -QT += core gui network +QT += core gui network svg greaterThan(QT_MAJOR_VERSION, 4): QT += widgets -greaterThan(QT_MAJOR_VERSION, 4): greaterThan(QT_MINOR_VERSION, 1): win32: QT += winextras +greaterThan(QT_MAJOR_VERSION, 4): win32: LIBS += -ldwmapi DEPLOYMENT.display_name = gta5view TARGET = gta5view TEMPLATE = app -HEADERS += config.h +HEADERS += config.h \ + wrapper.h PRECOMPILED_HEADER += config.h SOURCES += main.cpp \ @@ -37,10 +38,10 @@ SOURCES += main.cpp \ ExportThread.cpp \ GlobalString.cpp \ IconLoader.cpp \ - ImageEditorDialog.cpp \ ImportDialog.cpp \ JsonEditorDialog.cpp \ MapLocationDialog.cpp \ + MessageThread.cpp \ OptionsDialog.cpp \ PictureDialog.cpp \ PictureExport.cpp \ @@ -50,6 +51,7 @@ SOURCES += main.cpp \ ProfileInterface.cpp \ ProfileLoader.cpp \ ProfileWidget.cpp \ + RagePhoto.cpp \ SavegameCopy.cpp \ SavegameData.cpp \ SavegameDialog.cpp \ @@ -63,12 +65,14 @@ SOURCES += main.cpp \ TelemetryClass.cpp \ TranslationClass.cpp \ UserInterface.cpp \ - anpro/JSHighlighter.cpp \ + anpro/imagecropper.cpp \ + pcg/pcg_basic.c \ tmext/TelemetryClassAuthenticator.cpp \ + uimod/JSHighlighter.cpp \ uimod/UiModLabel.cpp \ uimod/UiModWidget.cpp -HEADERS += \ +HEADERS += \ AboutDialog.h \ AppEnv.h \ CrewDatabase.h \ @@ -77,10 +81,10 @@ HEADERS += \ ExportThread.h \ GlobalString.h \ IconLoader.h \ - ImageEditorDialog.h \ ImportDialog.h \ JsonEditorDialog.h \ MapLocationDialog.h \ + MessageThread.h \ OptionsDialog.h \ PictureDialog.h \ PictureExport.h \ @@ -90,6 +94,7 @@ HEADERS += \ ProfileInterface.h \ ProfileLoader.h \ ProfileWidget.h \ + RagePhoto.h \ SavegameCopy.h \ SavegameData.h \ SavegameDialog.h \ @@ -103,15 +108,18 @@ HEADERS += \ TelemetryClass.h \ TranslationClass.h \ UserInterface.h \ - anpro/JSHighlighter.h \ + anpro/imagecropper.h \ + anpro/imagecropper_e.h \ + anpro/imagecropper_p.h \ + pcg/pcg_basic.h \ tmext/TelemetryClassAuthenticator.h \ + uimod/JSHighlighter.h \ uimod/UiModLabel.h \ uimod/UiModWidget.h -FORMS += \ +FORMS += \ AboutDialog.ui \ ExportDialog.ui \ - ImageEditorDialog.ui \ ImportDialog.ui \ JsonEditorDialog.ui \ MapLocationDialog.ui \ @@ -126,48 +134,60 @@ FORMS += \ UserInterface.ui TRANSLATIONS += \ - res/gta5sync_en_US.ts \ + res/gta5sync.ts \ res/gta5sync_de.ts \ + res/gta5sync_en_US.ts \ res/gta5sync_fr.ts \ + res/gta5sync_ko.ts \ res/gta5sync_ru.ts \ res/gta5sync_uk.ts \ - res/gta5sync_zh_TW.ts \ - lang/gta5sync_no.ts + res/gta5sync_zh_TW.ts RESOURCES += \ - res/tr_g5p.qrc \ - res/app.qrc + res/img.qrc \ + res/template.qrc \ + res/tr_g5p.qrc -DISTFILES += res/app.rc \ - res/gta5view.desktop \ +DISTFILES += \ + res/gta5view-16.png \ + res/gta5view-24.png \ + res/gta5view-32.png \ + res/gta5view-40.png \ + res/gta5view-48.png \ + res/gta5view-64.png \ + res/gta5view-96.png \ + res/gta5view-128.png \ + res/gta5view-256.png \ + res/gta5view-512.png \ + res/app.rc \ + res/de.syping.gta5view.desktop \ + res/de.syping.gta5view.png \ res/gta5sync_de.ts \ + res/gta5sync_en_US.ts \ res/gta5sync_fr.ts \ + res/gta5sync_ko.ts \ res/gta5sync_ru.ts \ res/gta5sync_uk.ts \ res/gta5sync_zh_TW.ts \ res/gta5view.exe.manifest \ res/gta5view.png \ - lang/gta5sync_no.ts \ lang/README.txt -INCLUDEPATH += ./anpro ./tmext ./uimod +INCLUDEPATH += ./anpro ./pcg ./tmext ./uimod # GTA5SYNC/GTA5VIEW ONLY -DEFINES += GTA5SYNC_DISABLED +DEFINES += GTA5SYNC_QMAKE # We using qmake do we? DEFINES += GTA5SYNC_PROJECT # Enable exclusive gta5sync/gta5view functions -DEFINES += GTA5SYNC_CSDF # Not assisting at proper usage of SnapmaticPicture class # WINDOWS ONLY -win32: DEFINES += GTA5SYNC_WIN win32: RC_FILE += res/app.rc -win32: LIBS += -luser32 win32: CONFIG -= embed_manifest_exe -contains(DEFINES, GTA5SYNC_APV): greaterThan(QT_MAJOR_VERSION, 4): greaterThan(QT_MINOR_VERSION, 1): win32: LIBS += -ldwmapi +contains(DEFINES, GTA5SYNC_TELEMETRY): win32: LIBS += -ld3d9 # Required for getting information about GPU # MAC OS X ONLY -macx: ICON = res/5sync.icns +macx: ICON = res/gta5view.icns # QT4 ONLY STUFF @@ -189,40 +209,77 @@ isEqual(QT_MAJOR_VERSION, 4): SOURCES += qjson4/QJsonArray.cpp \ qjson4/QJsonValueRef.cpp \ qjson4/QJsonParser.cpp -isEqual(QT_MAJOR_VERSION, 4): RESOURCES += res/tr_qt4.qrc +isEqual(QT_MAJOR_VERSION, 4): RESOURCES += res/qt4/tr_qt.qrc +isEqual(QT_MAJOR_VERSION, 4): GTA5SYNC_RCC = $$[QT_INSTALL_BINS]/rcc # QT5 ONLY STUFF -isEqual(QT_MAJOR_VERSION, 5): RESOURCES += res/tr_qt5.qrc + +isEqual(QT_MAJOR_VERSION, 5): RESOURCES += res/qt5/tr_qt.qrc + +# QT5+ ONLY STUFF + +greaterThan(QT_MAJOR_VERSION, 4): GTA5SYNC_RCC = $$[QT_HOST_BINS]/rcc + +# QT6 ONLY STUFF + +isEqual(QT_MAJOR_VERSION, 6): RESOURCES += res/qt6/tr_qt.qrc + +# RESOURCE COMPILATION + +system($$GTA5SYNC_RCC -threshold 0 -compress 9 $$PWD/res/global.qrc -o $$OUT_PWD/qrc_global.cpp) { + SOURCES += $$OUT_PWD/qrc_global.cpp +} else { + message("Failed to generate qrc_global.cpp") +} # PROJECT INSTALLATION isEmpty(GTA5SYNC_PREFIX): GTA5SYNC_PREFIX = /usr/local appfiles.path = $$GTA5SYNC_PREFIX/share/applications -appfiles.files = $$PWD/res/gta5view.desktop +appfiles.files = $$PWD/res/de.syping.gta5view.desktop pixmaps.path = $$GTA5SYNC_PREFIX/share/pixmaps -pixmaps.files = $$PWD/res/gta5view.png +pixmaps.files = $$PWD/res/de.syping.gta5view.png target.path = $$GTA5SYNC_PREFIX/bin INSTALLS += target pixmaps appfiles # QCONF BASED BUILD STUFF -contains(DEFINES, GTA5SYNC_QCONF){ - isEqual(QT_MAJOR_VERSION, 4): RESOURCES -= res/tr_qt4.qrc - isEqual(QT_MAJOR_VERSION, 5): RESOURCES -= res/tr_qt5.qrc - !contains(DEFINES, GTA5SYNC_QCONF_IN){ +contains(DEFINES, GTA5SYNC_QCONF) { + isEqual(QT_MAJOR_VERSION, 4): RESOURCES -= res/qt4/tr_qt.qrc + isEqual(QT_MAJOR_VERSION, 5): RESOURCES -= res/qt5/tr_qt.qrc + isEqual(QT_MAJOR_VERSION, 6): RESOURCES -= res/qt6/tr_qt.qrc + !contains(DEFINES, GTA5SYNC_QCONF_IN) { RESOURCES -= res/tr_g5p.qrc langfiles.path = $$GTA5SYNC_PREFIX/share/gta5view/translations - langfiles.files = $$PWD/res/gta5sync_en_US.qm $$PWD/res/gta5sync_de.qm $$PWD/res/gta5sync_fr.qm $$PWD/res/gta5sync_ru.qm $$PWD/res/gta5sync_uk.qm $$PWD/res/gta5sync_zh_TW.qm $$PWD/res/qtbase_en_GB.qm $$PWD/res/qtbase_zh_TW.qm + langfiles.files = $$PWD/res/gta5sync_en_US.qm $$PWD/res/gta5sync_de.qm $$PWD/res/gta5sync_fr.qm $$PWD/res/gta5sync_ko.qm $$PWD/res/gta5sync_ru.qm $$PWD/res/gta5sync_uk.qm $$PWD/res/gta5sync_zh_TW.qm $$PWD/res/qtbase_en_GB.qm INSTALLS += langfiles } } # TELEMETRY BASED STUFF -!contains(DEFINES, GTA5SYNC_TELEMETRY){ +!contains(DEFINES, GTA5SYNC_TELEMETRY) { SOURCES -= TelemetryClass.cpp \ tmext/TelemetryClassAuthenticator.cpp HEADERS -= TelemetryClass.h \ tmext/TelemetryClassAuthenticator.h } + +!contains(DEFINES, GTA5SYNC_MOTD) { + SOURCES -= MessageThread.cpp + HEADERS -= MessageThread.h +} else { + lessThan(QT_MAJOR_VERSION, 5) { + SOURCES -= MessageThread.cpp + HEADERS -= MessageThread.h + DEFINES -= GTA5SYNC_MOTD + message("Messages require Qt5 or newer!") + } +} + +# CMAKE BASED STUFF + +greaterThan(QT_MAJOR_VERSION, 4) { + message("Building gta5view with QMake is deprecated, please use CMake instead!") +} diff --git a/lang/README.txt b/lang/README.txt index c21786f..fd72055 100644 --- a/lang/README.txt +++ b/lang/README.txt @@ -2,4 +2,4 @@ Community translation files They get loaded in ApplicationPathExecFileFolder/lang -You can help translate with using Qt Linguist, after you've translated you'll need to send me a pull request on https://github.com/Syping/gta5sync +You can help translate with using Qt Linguist, after you've translated you'll need to send me a pull request on https://github.com/SyDevTeam/gta5view diff --git a/lang/gta5sync_no.qm b/lang/gta5sync_no.qm deleted file mode 100644 index 9dad8df..0000000 Binary files a/lang/gta5sync_no.qm and /dev/null differ diff --git a/main.cpp b/main.cpp index f02fea5..350d430 100644 --- a/main.cpp +++ b/main.cpp @@ -1,6 +1,6 @@ /***************************************************************************** -* gta5sync GRAND THEFT AUTO V SYNC -* Copyright (C) 2016-2018 Syping +* gta5view Grand Theft Auto V Profile Viewer +* Copyright (C) 2016-2021 Syping * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -31,7 +31,6 @@ #include "IconLoader.h" #include "AppEnv.h" #include "config.h" -#include #include #include #include @@ -43,27 +42,41 @@ #include #include #include +#include #include #include #include #include #include #include -#include #include #include -#ifdef GTA5SYNC_WIN +#if QT_VERSION < 0x060000 +#include +#endif + +#ifdef Q_OS_WIN #include "windows.h" #include #endif +#ifdef GTA5SYNC_MOTD +#include "MessageThread.h" +#endif + #ifdef GTA5SYNC_TELEMETRY #include "TelemetryClass.h" #endif int main(int argc, char *argv[]) { +#if QT_VERSION >= 0x050600 +#if QT_VERSION < 0x060000 + QApplication::setAttribute(Qt::AA_EnableHighDpiScaling, true); + QApplication::setAttribute(Qt::AA_UseHighDpiPixmaps, true); +#endif +#endif QApplication a(argc, argv); a.setApplicationName(GTA5SYNC_APPSTR); a.setApplicationVersion(GTA5SYNC_APPVER); @@ -72,41 +85,38 @@ int main(int argc, char *argv[]) QSettings settings(GTA5SYNC_APPVENDOR, GTA5SYNC_APPSTR); settings.beginGroup("Startup"); - bool isFirstStart = settings.value("IsFirstStart", true).toBool(); - bool customStyle = settings.value("CustomStyle", false).toBool(); - QString appStyle = settings.value("AppStyle", "Default").toString(); +#ifdef GTA5SYNC_TELEMETRY + // Increase Start count at every startup + uint startCount = settings.value("StartCount", 0).toUInt(); + startCount++; + settings.setValue("StartCount", startCount); + settings.sync(); +#endif - if (customStyle) - { - if (QStyleFactory::keys().contains(appStyle, Qt::CaseInsensitive)) - { + bool customStyle = settings.value("CustomStyle", false).toBool(); + if (customStyle) { + const QString appStyle = settings.value("AppStyle", "Default").toString(); + if (QStyleFactory::keys().contains(appStyle, Qt::CaseInsensitive)) { a.setStyle(QStyleFactory::create(appStyle)); } } -#ifdef GTA5SYNC_WIN -#if QT_VERSION >= 0x050400 - bool alwaysUseMessageFont = settings.value("AlwaysUseMessageFont", false).toBool(); - if (QSysInfo::windowsVersion() >= 0x0080 || alwaysUseMessageFont) - { - // Get Windows Font - NONCLIENTMETRICS ncm; - ncm.cbSize = sizeof(ncm); - SystemParametersInfo(SPI_GETNONCLIENTMETRICS, ncm.cbSize, &ncm, 0); - LOGFONTW uiFont = ncm.lfMessageFont; - QString uiFontStr(QString::fromStdWString(std::wstring(uiFont.lfFaceName))); - -#ifdef GTA5SYNC_DEBUG - qDebug() << QApplication::tr("Font") << QApplication::tr("Selected Font: %1").arg(uiFontStr); -#endif - - // Set Application Font - QFont appFont(uiFontStr, 9); - a.setFont(appFont); +#ifdef Q_OS_WIN +#if QT_VERSION >= 0x060000 + a.setFont(QApplication::font("QMenu")); +#elif QT_VERSION >= 0x050400 + if (QSysInfo::windowsVersion() >= 0x0080) { + a.setFont(QApplication::font("QMenu")); } #endif #endif + bool customFont = settings.value("CustomFont", false).toBool(); + if (customFont) { + const QFont appFont = qvariant_cast(settings.value("AppFont", a.font())); + a.setFont(appFont); + } + QStringList applicationArgs = a.arguments(); QString selectedAction; QString arg1; @@ -116,41 +126,14 @@ int main(int argc, char *argv[]) Translator->loadTranslation(&a); #ifdef GTA5SYNC_TELEMETRY - if (!applicationArgs.contains("--disable-telemetry")) - { - if (!applicationArgs.contains("--skip-telemetryinit")) - { - Telemetry->init(); - Telemetry->work(); - } - } - else - { - Telemetry->setDisabled(true); - } + Telemetry->init(); + Telemetry->work(); #endif - if (!applicationArgs.contains("--skip-firststart")) - { - if (isFirstStart) - { - QMessageBox::StandardButton button = QMessageBox::information(a.desktop(), QString("%1 %2").arg(GTA5SYNC_APPSTR, GTA5SYNC_APPVER), QApplication::tr("

Welcome to %1!

You want to configure %1 before you start using it?").arg(GTA5SYNC_APPSTR), QMessageBox::Yes | QMessageBox::No, QMessageBox::No); - if (button == QMessageBox::Yes) - { - ProfileDatabase profileDB; - OptionsDialog optionsDialog(&profileDB); - optionsDialog.setWindowIcon(IconLoader::loadingAppIcon()); - optionsDialog.show(); - optionsDialog.exec(); - } - settings.setValue("IsFirstStart", false); - } - } - #ifdef GTA5SYNC_TELEMETRY - bool telemetryWindowLaunched = settings.value("TelemetryWindowLaunched", false).toBool(); - if (!telemetryWindowLaunched && !Telemetry->isEnabled() && !Telemetry->isStateForced()) - { + bool telemetryWindowLaunched = settings.value("PersonalUsageDataWindowLaunched", false).toBool(); + bool pushUsageData = settings.value("PushUsageData", false).toBool(); + if (!telemetryWindowLaunched && !pushUsageData) { QDialog *telemetryDialog = new QDialog(); telemetryDialog->setObjectName(QStringLiteral("TelemetryDialog")); telemetryDialog->setWindowTitle(QString("%1 %2").arg(GTA5SYNC_APPSTR, GTA5SYNC_APPVER)); @@ -161,12 +144,13 @@ int main(int argc, char *argv[]) telemetryDialog->setLayout(telemetryLayout); UiModLabel *telemetryLabel = new UiModLabel(telemetryDialog); telemetryLabel->setObjectName(QStringLiteral("TelemetryLabel")); - telemetryLabel->setText(QString("

%2

%1").arg(QApplication::translate("TelemetryDialog", "You want help %1 to improve in the future by collection of data?").arg(GTA5SYNC_APPSTR), QApplication::translate("TelemetryDialog", "%1 User Statistics").arg(GTA5SYNC_APPSTR))); + telemetryLabel->setText(QString("

%2

%1").arg( + QApplication::translate("TelemetryDialog", "You want help %1 to improve in the future by including personal usage data in your submission?").arg(GTA5SYNC_APPSTR), + QApplication::translate("TelemetryDialog", "%1 User Statistics").arg(GTA5SYNC_APPSTR))); telemetryLayout->addWidget(telemetryLabel); QCheckBox *telemetryCheckBox = new QCheckBox(telemetryDialog); telemetryCheckBox->setObjectName(QStringLiteral("TelemetryCheckBox")); - telemetryCheckBox->setText(QApplication::translate("TelemetryDialog", "Yes, I would like to take part.")); - telemetryCheckBox->setChecked(true); + telemetryCheckBox->setText(QApplication::translate("TelemetryDialog", "Yes, I want include personal usage data.")); telemetryLayout->addWidget(telemetryCheckBox); QHBoxLayout *telemetryButtonLayout = new QHBoxLayout(); telemetryButtonLayout->setObjectName(QStringLiteral("TelemetryButtonLayout")); @@ -181,59 +165,51 @@ int main(int argc, char *argv[]) telemetryDialog->setFixedSize(telemetryDialog->sizeHint()); telemetryDialog->exec(); QObject::disconnect(telemetryButton, SIGNAL(clicked(bool)), telemetryDialog, SLOT(close())); - if (telemetryCheckBox->isChecked()) - { + if (telemetryCheckBox->isChecked()) { QSettings telemetrySettings(GTA5SYNC_APPVENDOR, GTA5SYNC_APPSTR); telemetrySettings.beginGroup("Telemetry"); - telemetrySettings.setValue("IsEnabled", true); + telemetrySettings.setValue("PushUsageData", true); + telemetrySettings.setValue("PushAppConf", true); telemetrySettings.endGroup(); + telemetrySettings.sync(); Telemetry->init(); Telemetry->work(); } - settings.setValue("TelemetryWindowLaunched", true); + settings.setValue("PersonalUsageDataWindowLaunched", true); delete telemetryDialog; } #endif - settings.endGroup(); - for (QString currentArg : applicationArgs) - { + for (const QString ¤tArg : applicationArgs) { QString reworkedArg; - if (currentArg.left(9) == "-showpic=" && selectedAction == "") - { - reworkedArg = currentArg.remove(0,9); + if (currentArg.left(9) == "-showpic=" && selectedAction == "") { + reworkedArg = QString(currentArg).remove(0,9); arg1 = reworkedArg; selectedAction = "showpic"; } - else if (currentArg.left(9) == "-showsgd=" && selectedAction == "") - { - reworkedArg = currentArg.remove(0,9); + else if (currentArg.left(9) == "-showsgd=" && selectedAction == "") { + reworkedArg = QString(currentArg).remove(0,9); arg1 = reworkedArg; selectedAction = "showsgd"; } - else if (selectedAction == "") - { + else if (selectedAction == "") { QFile argumentFile(currentArg); QFileInfo argumentFileInfo(argumentFile); - if (argumentFile.exists()) - { + if (argumentFile.exists()) { QString argumentFileName = argumentFileInfo.fileName(); QString argumentFileType = argumentFileName.left(4); QString argumentFileExt = argumentFileName.right(4); - if (argumentFileType == "PGTA" || argumentFileExt == ".g5e") - { + if (argumentFileType == "PGTA" || argumentFileExt == ".g5e") { arg1 = currentArg; selectedAction = "showpic"; } - else if (argumentFileType == "SGTA") - { + else if (argumentFileType == "SGTA") { arg1 = currentArg; selectedAction = "showsgd"; } - else if (argumentFileType == "MISR") - { + else if (argumentFileType == "MISR") { arg1 = currentArg; selectedAction = "showsgd"; } @@ -241,8 +217,7 @@ int main(int argc, char *argv[]) } } - if (selectedAction == "showpic") - { + if (selectedAction == "showpic") { CrewDatabase crewDB; ProfileDatabase profileDB; DatabaseThread threadDB(&crewDB); @@ -252,13 +227,13 @@ int main(int argc, char *argv[]) bool readOk = picture.readingPictureFromFile(arg1); picDialog.setWindowIcon(IconLoader::loadingAppIcon()); picDialog.setSnapmaticPicture(&picture, readOk); -#ifndef Q_OS_LINUX picDialog.setWindowFlags(picDialog.windowFlags()^Qt::Dialog^Qt::Window); -#endif int crewID = picture.getSnapmaticProperties().crewID; - if (crewID != 0) { crewDB.addCrew(crewID); } - if (!readOk) { return 1; } + if (crewID != 0) + crewDB.addCrew(crewID); + if (!readOk) + return 1; QObject::connect(&threadDB, SIGNAL(crewNameFound(int, QString)), &crewDB, SLOT(setCrewName(int, QString))); QObject::connect(&threadDB, SIGNAL(crewNameUpdated()), &picDialog, SLOT(crewNameUpdated())); @@ -272,8 +247,7 @@ int main(int argc, char *argv[]) return a.exec(); } - else if (selectedAction == "showsgd") - { + else if (selectedAction == "showsgd") { SavegameDialog savegameDialog; SavegameData savegame; @@ -282,7 +256,8 @@ int main(int argc, char *argv[]) savegameDialog.setSavegameData(&savegame, arg1, readOk); savegameDialog.setWindowFlags(savegameDialog.windowFlags()^Qt::Dialog^Qt::Window); - if (!readOk) { return 1; } + if (!readOk) + return 1; a.setQuitOnLastWindowClosed(true); savegameDialog.show(); @@ -299,9 +274,32 @@ int main(int argc, char *argv[]) QObject::connect(&threadDB, SIGNAL(finished()), &a, SLOT(quit())); threadDB.start(); +#ifdef GTA5SYNC_MOTD + uint cacheId; + { + QSettings messageSettings(GTA5SYNC_APPVENDOR, GTA5SYNC_APPSTR); + messageSettings.beginGroup("Messages"); + cacheId = messageSettings.value("CacheId", 0).toUInt(); + messageSettings.endGroup(); + } + MessageThread threadMessage(cacheId); + QObject::connect(&threadMessage, SIGNAL(finished()), &threadDB, SLOT(terminateThread())); + threadMessage.start(); +#endif + +#ifdef GTA5SYNC_MOTD + UserInterface uiWindow(&profileDB, &crewDB, &threadDB, &threadMessage); + QObject::connect(&threadMessage, SIGNAL(messagesArrived(QJsonObject)), &uiWindow, SLOT(messagesArrived(QJsonObject))); + QObject::connect(&threadMessage, SIGNAL(updateCacheId(uint)), &uiWindow, SLOT(updateCacheId(uint))); +#else UserInterface uiWindow(&profileDB, &crewDB, &threadDB); +#endif uiWindow.setWindowIcon(IconLoader::loadingAppIcon()); +#ifdef GTA5SYNC_FLATPAK + uiWindow.setupDirEnv(false); +#else uiWindow.setupDirEnv(); +#endif #ifdef Q_OS_ANDROID uiWindow.showMaximized(); #else diff --git a/pcg/LICENSE.txt b/pcg/LICENSE.txt new file mode 100644 index 0000000..8dada3e --- /dev/null +++ b/pcg/LICENSE.txt @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/pcg/pcg_basic.c b/pcg/pcg_basic.c new file mode 100644 index 0000000..8c2fd0d --- /dev/null +++ b/pcg/pcg_basic.c @@ -0,0 +1,116 @@ +/* + * PCG Random Number Generation for C. + * + * Copyright 2014 Melissa O'Neill + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * For additional information about the PCG random number generation scheme, + * including its license and other licensing options, visit + * + * http://www.pcg-random.org + */ + +/* + * This code is derived from the full C implementation, which is in turn + * derived from the canonical C++ PCG implementation. The C++ version + * has many additional features and is preferable if you can use C++ in + * your project. + */ + +#include "pcg_basic.h" + +// state for global RNGs + +static pcg32_random_t pcg32_global = PCG32_INITIALIZER; + +// pcg32_srandom(initstate, initseq) +// pcg32_srandom_r(rng, initstate, initseq): +// Seed the rng. Specified in two parts, state initializer and a +// sequence selection constant (a.k.a. stream id) + +void pcg32_srandom_r(pcg32_random_t* rng, uint64_t initstate, uint64_t initseq) +{ + rng->state = 0U; + rng->inc = (initseq << 1u) | 1u; + pcg32_random_r(rng); + rng->state += initstate; + pcg32_random_r(rng); +} + +void pcg32_srandom(uint64_t seed, uint64_t seq) +{ + pcg32_srandom_r(&pcg32_global, seed, seq); +} + +// pcg32_random() +// pcg32_random_r(rng) +// Generate a uniformly distributed 32-bit random number + +uint32_t pcg32_random_r(pcg32_random_t* rng) +{ + uint64_t oldstate = rng->state; + rng->state = oldstate * 6364136223846793005ULL + rng->inc; + uint32_t xorshifted = ((oldstate >> 18u) ^ oldstate) >> 27u; + uint32_t rot = oldstate >> 59u; + return (xorshifted >> rot) | (xorshifted << ((-rot) & 31)); +} + +uint32_t pcg32_random() +{ + return pcg32_random_r(&pcg32_global); +} + + +// pcg32_boundedrand(bound): +// pcg32_boundedrand_r(rng, bound): +// Generate a uniformly distributed number, r, where 0 <= r < bound + +uint32_t pcg32_boundedrand_r(pcg32_random_t* rng, uint32_t bound) +{ + // To avoid bias, we need to make the range of the RNG a multiple of + // bound, which we do by dropping output less than a threshold. + // A naive scheme to calculate the threshold would be to do + // + // uint32_t threshold = 0x100000000ull % bound; + // + // but 64-bit div/mod is slower than 32-bit div/mod (especially on + // 32-bit platforms). In essence, we do + // + // uint32_t threshold = (0x100000000ull-bound) % bound; + // + // because this version will calculate the same modulus, but the LHS + // value is less than 2^32. + + uint32_t threshold = -bound % bound; + + // Uniformity guarantees that this loop will terminate. In practice, it + // should usually terminate quickly; on average (assuming all bounds are + // equally likely), 82.25% of the time, we can expect it to require just + // one iteration. In the worst case, someone passes a bound of 2^31 + 1 + // (i.e., 2147483649), which invalidates almost 50% of the range. In + // practice, bounds are typically small and only a tiny amount of the range + // is eliminated. + for (;;) { + uint32_t r = pcg32_random_r(rng); + if (r >= threshold) + return r % bound; + } +} + + +uint32_t pcg32_boundedrand(uint32_t bound) +{ + return pcg32_boundedrand_r(&pcg32_global, bound); +} + diff --git a/pcg/pcg_basic.h b/pcg/pcg_basic.h new file mode 100644 index 0000000..e2b526a --- /dev/null +++ b/pcg/pcg_basic.h @@ -0,0 +1,78 @@ +/* + * PCG Random Number Generation for C. + * + * Copyright 2014 Melissa O'Neill + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * For additional information about the PCG random number generation scheme, + * including its license and other licensing options, visit + * + * http://www.pcg-random.org + */ + +/* + * This code is derived from the full C implementation, which is in turn + * derived from the canonical C++ PCG implementation. The C++ version + * has many additional features and is preferable if you can use C++ in + * your project. + */ + +#ifndef PCG_BASIC_H_INCLUDED +#define PCG_BASIC_H_INCLUDED 1 + +#include + +#if __cplusplus +extern "C" { +#endif + +struct pcg_state_setseq_64 { // Internals are *Private*. + uint64_t state; // RNG state. All values are possible. + uint64_t inc; // Controls which RNG sequence (stream) is + // selected. Must *always* be odd. +}; +typedef struct pcg_state_setseq_64 pcg32_random_t; + +// If you *must* statically initialize it, here's one. + +#define PCG32_INITIALIZER { 0x853c49e6748fea9bULL, 0xda3e39cb94b95bdbULL } + +// pcg32_srandom(initstate, initseq) +// pcg32_srandom_r(rng, initstate, initseq): +// Seed the rng. Specified in two parts, state initializer and a +// sequence selection constant (a.k.a. stream id) + +void pcg32_srandom(uint64_t initstate, uint64_t initseq); +void pcg32_srandom_r(pcg32_random_t* rng, uint64_t initstate, + uint64_t initseq); + +// pcg32_random() +// pcg32_random_r(rng) +// Generate a uniformly distributed 32-bit random number + +uint32_t pcg32_random(void); +uint32_t pcg32_random_r(pcg32_random_t* rng); + +// pcg32_boundedrand(bound): +// pcg32_boundedrand_r(rng, bound): +// Generate a uniformly distributed number, r, where 0 <= r < bound + +uint32_t pcg32_boundedrand(uint32_t bound); +uint32_t pcg32_boundedrand_r(pcg32_random_t* rng, uint32_t bound); + +#if __cplusplus +} +#endif + +#endif // PCG_BASIC_H_INCLUDED diff --git a/qjson4/QJsonArray.cpp b/qjson4/QJsonArray.cpp index 531941f..ad8a82b 100644 --- a/qjson4/QJsonArray.cpp +++ b/qjson4/QJsonArray.cpp @@ -1,5 +1,5 @@ /***************************************************************************** -* gta5sync GRAND THEFT AUTO V SYNC +* gta5view Grand Theft Auto V Profile Viewer * Copyright (C) 2016 Syping * * This program is free software: you can redistribute it and/or modify diff --git a/qjson4/QJsonArray.h b/qjson4/QJsonArray.h index 94aab1b..dc4fc69 100644 --- a/qjson4/QJsonArray.h +++ b/qjson4/QJsonArray.h @@ -1,5 +1,5 @@ /***************************************************************************** -* gta5sync GRAND THEFT AUTO V SYNC +* gta5view Grand Theft Auto V Profile Viewer * Copyright (C) 2016 Syping * * This program is free software: you can redistribute it and/or modify diff --git a/qjson4/QJsonDocument.cpp b/qjson4/QJsonDocument.cpp index 59adf32..7f8ad18 100644 --- a/qjson4/QJsonDocument.cpp +++ b/qjson4/QJsonDocument.cpp @@ -1,5 +1,5 @@ /***************************************************************************** -* gta5sync GRAND THEFT AUTO V SYNC +* gta5view Grand Theft Auto V Profile Viewer * Copyright (C) 2016 Syping * * This program is free software: you can redistribute it and/or modify @@ -39,45 +39,45 @@ QJsonDocument::QJsonDocument() : root_(0) { // Name: QJsonDocument //------------------------------------------------------------------------------ QJsonDocument::QJsonDocument(const QJsonObject &object) : root_(0) { - setObject(object); + setObject(object); } //------------------------------------------------------------------------------ // Name: QJsonDocument //------------------------------------------------------------------------------ QJsonDocument::QJsonDocument(const QJsonArray &array) : root_(0) { - setArray(array); + setArray(array); } //------------------------------------------------------------------------------ // Name: QJsonDocument //------------------------------------------------------------------------------ QJsonDocument::QJsonDocument(const QJsonDocument &other) : root_(0) { - if(other.root_) { - root_ = other.root_->clone(); - } + if(other.root_) { + root_ = other.root_->clone(); + } } //------------------------------------------------------------------------------ // Name: ~QJsonDocument //------------------------------------------------------------------------------ QJsonDocument::~QJsonDocument() { - delete root_; + delete root_; } //------------------------------------------------------------------------------ // Name: operator= //------------------------------------------------------------------------------ QJsonDocument &QJsonDocument::operator=(const QJsonDocument &other) { - QJsonDocument(other).swap(*this); - return *this; + QJsonDocument(other).swap(*this); + return *this; } //------------------------------------------------------------------------------ // Name: operator!= //------------------------------------------------------------------------------ bool QJsonDocument::operator!=(const QJsonDocument &other) const { - return !(*this == other); + return !(*this == other); } //------------------------------------------------------------------------------ @@ -85,30 +85,30 @@ bool QJsonDocument::operator!=(const QJsonDocument &other) const { //------------------------------------------------------------------------------ bool QJsonDocument::operator==(const QJsonDocument &other) const { - if(isArray() && other.isArray()) { - return array() == other.array(); - } + if(isArray() && other.isArray()) { + return array() == other.array(); + } - if(isObject() && other.isObject()) { - return object() == other.object(); - } + if(isObject() && other.isObject()) { + return object() == other.object(); + } - if(isEmpty() && other.isEmpty()) { - return true; - } + if(isEmpty() && other.isEmpty()) { + return true; + } - if(isNull() && other.isNull()) { - return true; - } + if(isNull() && other.isNull()) { + return true; + } - return false; + return false; } //------------------------------------------------------------------------------ // Name: isArray //------------------------------------------------------------------------------ bool QJsonDocument::isArray() const { - return root_ && root_->toArray(); + return root_ && root_->toArray(); } //------------------------------------------------------------------------------ @@ -116,56 +116,56 @@ bool QJsonDocument::isArray() const { //------------------------------------------------------------------------------ bool QJsonDocument::isEmpty() const { - // TODO(eteran): figure out the rules here that Qt5 uses - // it *looks* like they define empty as being NULL - // which is obviously different than this + // TODO(eteran): figure out the rules here that Qt5 uses + // it *looks* like they define empty as being NULL + // which is obviously different than this - return !root_; + return !root_; } //------------------------------------------------------------------------------ // Name: isNull //------------------------------------------------------------------------------ bool QJsonDocument::isNull() const { - return !root_; + return !root_; } //------------------------------------------------------------------------------ // Name: isObject //------------------------------------------------------------------------------ bool QJsonDocument::isObject() const { - return root_ && root_->toObject(); + return root_ && root_->toObject(); } //------------------------------------------------------------------------------ // Name: setArray //------------------------------------------------------------------------------ void QJsonDocument::setArray(const QJsonArray &array) { - setRoot(array); + setRoot(array); } //------------------------------------------------------------------------------ // Name: setObject //------------------------------------------------------------------------------ void QJsonDocument::setObject(const QJsonObject &object) { - setRoot(object); + setRoot(object); } //------------------------------------------------------------------------------ // Name: setRoot //------------------------------------------------------------------------------ void QJsonDocument::setRoot(const QJsonRoot &root) { - delete root_; - root_ = root.clone(); + delete root_; + root_ = root.clone(); } //------------------------------------------------------------------------------ // Name: toBinaryData //------------------------------------------------------------------------------ QByteArray QJsonDocument::toBinaryData() const { - QByteArray r; - // TODO(eteran): implement this - return r; + QByteArray r; + // TODO(eteran): implement this + return r; } //------------------------------------------------------------------------------ @@ -173,100 +173,107 @@ QByteArray QJsonDocument::toBinaryData() const { //------------------------------------------------------------------------------ QString QJsonDocument::escapeString(const QString &s) const { - QString r; + QString r; - Q_FOREACH(QChar ch, s) { - switch(ch.toLatin1()) { - case '\"': r.append("\\\""); break; - case '\\': r.append("\\\\"); break; - #if 0 - case '/': r.append("\\/"); break; - #endif - case '\b': r.append("\\b"); break; - case '\f': r.append("\\f"); break; - case '\n': r.append("\\n"); break; - case '\r': r.append("\\r"); break; - case '\t': r.append("\\t"); break; - default: - r += ch; - break; - } - } + Q_FOREACH(QChar ch, s) { + switch(ch.toLatin1()) { + case '\"': r.append("\\\""); break; + case '\\': r.append("\\\\"); break; +#if 0 + case '/': r.append("\\/"); break; +#endif + case '\b': r.append("\\b"); break; + case '\f': r.append("\\f"); break; + case '\n': r.append("\\n"); break; + case '\r': r.append("\\r"); break; + case '\t': r.append("\\t"); break; + default: + r += ch; + break; + } + } - return r; + return r; } //------------------------------------------------------------------------------ // Name: toJson //------------------------------------------------------------------------------ -QString QJsonDocument::toJson(const QJsonValue &v, JsonFormat format) const { +QString QJsonDocument::toJson(const QJsonValue &v, JsonFormat format, int indent) const { - QString b; - QTextStream ss(&b, QIODevice::WriteOnly | QIODevice::Text); + QString b; + QTextStream ss(&b, QIODevice::WriteOnly | QIODevice::Text); + bool compact = (format == JsonFormat::Compact); - switch(v.type()) { - case QJsonValue::Null: - ss << "null"; - break; - case QJsonValue::Bool: - ss << (v.toBool() ? "true" : "false"); - break; - case QJsonValue::Double: - { - double d = v.toDouble (); - if (qIsFinite(d)) { - // +2 to format to ensure the expected precision - ss << QByteArray::number(d, 'g', 15 + 2); // ::digits10 is 15 - } else { - ss << "null"; // +INF || -INF || NaN (see RFC4627#section2.4) - } - } - break; - case QJsonValue::String: - ss << '"' << escapeString(v.toString()) << '"'; - break; - case QJsonValue::Array: - { - const QJsonArray a = v.toArray(); - ss << "["; - if(!a.empty()) { - QJsonArray::const_iterator it = a.begin(); - QJsonArray::const_iterator e = a.end(); + switch(v.type()) { + case QJsonValue::Null: + ss << "null"; + break; + case QJsonValue::Bool: + ss << (v.toBool() ? "true" : "false"); + break; + case QJsonValue::Double: + { + double d = v.toDouble (); + if (qIsFinite(d)) { + // +2 to format to ensure the expected precision + ss << QByteArray::number(d, 'g', 15 + 2); // ::digits10 is 15 + } else { + ss << "null"; // +INF || -INF || NaN (see RFC4627#section2.4) + } + } + break; + case QJsonValue::String: + ss << '"' << escapeString(v.toString()) << '"'; + break; + case QJsonValue::Array: + { + const QJsonArray a = v.toArray(); + ss << (compact ? "[" : "[\n"); + if(!a.empty()) { + QJsonArray::const_iterator it = a.begin(); + QJsonArray::const_iterator e = a.end(); - ss << toJson(*it++, format); + if (!compact) ss << QByteArray(4*indent, ' '); + ss << toJson(*it++, format, indent+1); - for(;it != e; ++it) { - ss << ','; - ss << toJson(*it, format); - } - } - ss << "]"; - } - break; - case QJsonValue::Object: - { - const QJsonObject o = v.toObject(); - ss << "{"; - if(!o.empty()) { - QJsonObject::const_iterator it = o.begin(); - QJsonObject::const_iterator e = o.end(); + for(;it != e; ++it) { + ss << (compact ? "," : ",\n"); + if (!compact) ss << QByteArray(4*indent, ' '); + ss << toJson(*it, format, indent+1); + } + } + indent--; + ss << (compact ? "]" : QString("\n%1]").arg(QString(4*indent, ' '))); + } + break; + case QJsonValue::Object: + { + const QJsonObject o = v.toObject(); + ss << (compact ? "{" : "{\n"); + if(!o.empty()) { + QJsonObject::const_iterator it = o.begin(); + QJsonObject::const_iterator e = o.end(); - ss << '"' << escapeString(it.key()) << "\": " << toJson(it.value(), format); - ++it; - for(;it != e; ++it) { - ss << ','; - ss << '"' << escapeString(it.key()) << "\": " << toJson(it.value(), format); - } - } - ss << "}"; - } - break; - case QJsonValue::Undefined: - Q_ASSERT(0); - break; - } + if (!compact) ss << QByteArray(4*indent, ' '); + ss << '"' << escapeString(it.key()) << (compact ? "\":" : "\": ") << toJson(it.value(), format, indent+1); + ++it; + for(;it != e; ++it) { + ss << (compact ? "," : ",\n"); + if (!compact) ss << QByteArray(4*indent, ' '); + ss << '"' << escapeString(it.key()) << (compact ? "\":" : "\": ") << toJson(it.value(), format, indent+1); + } + } + indent--; + ss << (compact ? "}" : QString("\n%1}").arg(QString(4*indent, ' '))); + } + break; + case QJsonValue::Undefined: + Q_ASSERT(0); + break; + } - return b; + return b; } //------------------------------------------------------------------------------ @@ -274,19 +281,19 @@ QString QJsonDocument::toJson(const QJsonValue &v, JsonFormat format) const { //------------------------------------------------------------------------------ QByteArray QJsonDocument::toJson(JsonFormat format) const { - Q_UNUSED(format); + Q_UNUSED(format); - if(isArray()) { - QString s = toJson(array(), format); - return s.toUtf8(); - } + if(isArray()) { + QString s = toJson(array(), format); + return s.toUtf8(); + } - if(isObject()) { - QString s = toJson(object(), format); - return s.toUtf8(); - } + if(isObject()) { + QString s = toJson(object(), format); + return s.toUtf8(); + } - return QByteArray(); + return QByteArray(); } //------------------------------------------------------------------------------ @@ -294,17 +301,17 @@ QByteArray QJsonDocument::toJson(JsonFormat format) const { //------------------------------------------------------------------------------ QVariant QJsonDocument::toVariant() const { - if(!isEmpty()) { - if(QJsonObject *const object = root_->toObject()) { - return object->toVariantMap(); - } + if(!isEmpty()) { + if(QJsonObject *const object = root_->toObject()) { + return object->toVariantMap(); + } - if(QJsonArray *const array = root_->toArray()) { - return array->toVariantList(); - } - } + if(QJsonArray *const array = root_->toArray()) { + return array->toVariantList(); + } + } - return QVariant(); + return QVariant(); } //------------------------------------------------------------------------------ @@ -312,13 +319,13 @@ QVariant QJsonDocument::toVariant() const { //------------------------------------------------------------------------------ QJsonArray QJsonDocument::array() const { - if(!isEmpty()) { - if(QJsonArray *const array = root_->toArray()) { - return *array; - } - } + if(!isEmpty()) { + if(QJsonArray *const array = root_->toArray()) { + return *array; + } + } - return QJsonArray(); + return QJsonArray(); } //------------------------------------------------------------------------------ @@ -326,54 +333,54 @@ QJsonArray QJsonDocument::array() const { //------------------------------------------------------------------------------ QJsonObject QJsonDocument::object() const { - if(!isEmpty()) { - if(QJsonObject *const object = root_->toObject()) { - return *object; - } - } + if(!isEmpty()) { + if(QJsonObject *const object = root_->toObject()) { + return *object; + } + } - return QJsonObject(); + return QJsonObject(); } //------------------------------------------------------------------------------ // Name: rawData //------------------------------------------------------------------------------ const char *QJsonDocument::rawData(int *size) const { - Q_UNUSED(size); - // TODO(eteran): implement this - return 0; + Q_UNUSED(size); + // TODO(eteran): implement this + return 0; } //------------------------------------------------------------------------------ // Name: fromBinaryData //------------------------------------------------------------------------------ QJsonDocument QJsonDocument::fromBinaryData(const QByteArray &data, DataValidation validation) { - Q_UNUSED(data); - Q_UNUSED(validation); + Q_UNUSED(data); + Q_UNUSED(validation); - QJsonDocument doc; - // TODO(eteran): implement this - return doc; + QJsonDocument doc; + // TODO(eteran): implement this + return doc; } //------------------------------------------------------------------------------ // Name: fromJson //------------------------------------------------------------------------------ QJsonDocument QJsonDocument::fromJson(const QByteArray &json, QJsonParseError *error) { - QJsonDocument doc; + QJsonDocument doc; - const char *const begin = json.constData(); - const char *const end = begin + json.size(); + const char *const begin = json.constData(); + const char *const end = begin + json.size(); - QJsonParser parser(begin, end); + QJsonParser parser(begin, end); - doc.root_ = parser.parse(); + doc.root_ = parser.parse(); - if(error) { - *error = parser.state(); - } + if(error) { + *error = parser.state(); + } - return doc; + return doc; } //------------------------------------------------------------------------------ @@ -381,10 +388,10 @@ QJsonDocument QJsonDocument::fromJson(const QByteArray &json, QJsonParseError *e //------------------------------------------------------------------------------ QJsonDocument QJsonDocument::fromRawData(const char *data, int size, DataValidation validation) { - // data has to be aligned to a 4 byte boundary. - Q_ASSERT(!(reinterpret_cast(data) % 3)); + // data has to be aligned to a 4 byte boundary. + Q_ASSERT(!(reinterpret_cast(data) % 3)); - return fromBinaryData(QByteArray::fromRawData(data, size), validation); + return fromBinaryData(QByteArray::fromRawData(data, size), validation); } //------------------------------------------------------------------------------ @@ -392,26 +399,26 @@ QJsonDocument QJsonDocument::fromRawData(const char *data, int size, DataValidat //------------------------------------------------------------------------------ QJsonDocument QJsonDocument::fromVariant(const QVariant &variant) { - QJsonDocument doc; + QJsonDocument doc; - if (variant.type() == QVariant::Map) { - doc.setObject(QJsonObject::fromVariantMap(variant.toMap())); - } else if (variant.type() == QVariant::Hash) { - doc.setObject(QJsonObject::fromVariantHash(variant.toHash())); - } else if (variant.type() == QVariant::List) { - doc.setArray(QJsonArray::fromVariantList(variant.toList())); - } else if (variant.type() == QVariant::StringList) { - doc.setArray(QJsonArray::fromStringList(variant.toStringList())); - } + if (variant.type() == QVariant::Map) { + doc.setObject(QJsonObject::fromVariantMap(variant.toMap())); + } else if (variant.type() == QVariant::Hash) { + doc.setObject(QJsonObject::fromVariantHash(variant.toHash())); + } else if (variant.type() == QVariant::List) { + doc.setArray(QJsonArray::fromVariantList(variant.toList())); + } else if (variant.type() == QVariant::StringList) { + doc.setArray(QJsonArray::fromStringList(variant.toStringList())); + } - return doc; + return doc; } //------------------------------------------------------------------------------ // Name: swap //------------------------------------------------------------------------------ void QJsonDocument::swap(QJsonDocument &other) { - qSwap(root_, other.root_); + qSwap(root_, other.root_); } #endif diff --git a/qjson4/QJsonDocument.h b/qjson4/QJsonDocument.h index 12e8fc7..3731f62 100644 --- a/qjson4/QJsonDocument.h +++ b/qjson4/QJsonDocument.h @@ -1,5 +1,5 @@ /***************************************************************************** -* gta5sync GRAND THEFT AUTO V SYNC +* gta5view Grand Theft Auto V Profile Viewer * Copyright (C) 2016 Syping * * This program is free software: you can redistribute it and/or modify @@ -36,66 +36,66 @@ class QJsonRoot; class QJsonDocument { public: - enum DataValidation { - Validate = 0, - BypassValidation = 1 - }; + enum DataValidation { + Validate = 0, + BypassValidation = 1 + }; - enum JsonFormat { - Indented, - Compact - }; + enum JsonFormat { + Indented, + Compact + }; public: - QJsonDocument(); - QJsonDocument(const QJsonObject &object); - QJsonDocument(const QJsonArray &array); - QJsonDocument(const QJsonDocument &other); - ~QJsonDocument(); + QJsonDocument(); + QJsonDocument(const QJsonObject &object); + QJsonDocument(const QJsonArray &array); + QJsonDocument(const QJsonDocument &other); + ~QJsonDocument(); public: - QJsonDocument &operator=(const QJsonDocument &other); + QJsonDocument &operator=(const QJsonDocument &other); public: - bool operator!=(const QJsonDocument &other) const; - bool operator==(const QJsonDocument &other) const; + bool operator!=(const QJsonDocument &other) const; + bool operator==(const QJsonDocument &other) const; public: - bool isArray() const; - bool isEmpty() const; - bool isNull() const; - bool isObject() const; + bool isArray() const; + bool isEmpty() const; + bool isNull() const; + bool isObject() const; public: - QByteArray toBinaryData() const; - QByteArray toJson(JsonFormat format = Indented) const; - QVariant toVariant() const; + QByteArray toBinaryData() const; + QByteArray toJson(JsonFormat format = Indented) const; + QVariant toVariant() const; public: - QJsonArray array() const; - QJsonObject object() const; - const char *rawData(int *size) const; + QJsonArray array() const; + QJsonObject object() const; + const char *rawData(int *size) const; public: - void setArray(const QJsonArray &array); - void setObject(const QJsonObject &object); + void setArray(const QJsonArray &array); + void setObject(const QJsonObject &object); public: - static QJsonDocument fromBinaryData(const QByteArray &data, DataValidation validation = Validate); - static QJsonDocument fromJson(const QByteArray &json, QJsonParseError *error = 0); - static QJsonDocument fromRawData(const char *data, int size, DataValidation validation = Validate); - static QJsonDocument fromVariant(const QVariant &variant); + static QJsonDocument fromBinaryData(const QByteArray &data, DataValidation validation = Validate); + static QJsonDocument fromJson(const QByteArray &json, QJsonParseError *error = 0); + static QJsonDocument fromRawData(const char *data, int size, DataValidation validation = Validate); + static QJsonDocument fromVariant(const QVariant &variant); private: - void setRoot(const QJsonRoot &root); - QString toJson(const QJsonValue &v, JsonFormat format) const; - QString escapeString(const QString &s) const; + void setRoot(const QJsonRoot &root); + QString toJson(const QJsonValue &v, JsonFormat format, int indent = 1) const; + QString escapeString(const QString &s) const; private: - void swap(QJsonDocument &other); + void swap(QJsonDocument &other); private: - QJsonRoot *root_; + QJsonRoot *root_; }; #endif diff --git a/qjson4/QJsonObject.cpp b/qjson4/QJsonObject.cpp index 55f8cf1..ac36bb0 100644 --- a/qjson4/QJsonObject.cpp +++ b/qjson4/QJsonObject.cpp @@ -1,5 +1,5 @@ /***************************************************************************** -* gta5sync GRAND THEFT AUTO V SYNC +* gta5view Grand Theft Auto V Profile Viewer * Copyright (C) 2016 Syping * * This program is free software: you can redistribute it and/or modify diff --git a/qjson4/QJsonObject.h b/qjson4/QJsonObject.h index ad657bc..6ee3a97 100644 --- a/qjson4/QJsonObject.h +++ b/qjson4/QJsonObject.h @@ -1,5 +1,5 @@ /***************************************************************************** -* gta5sync GRAND THEFT AUTO V SYNC +* gta5view Grand Theft Auto V Profile Viewer * Copyright (C) 2016 Syping * * This program is free software: you can redistribute it and/or modify diff --git a/qjson4/QJsonParseError.cpp b/qjson4/QJsonParseError.cpp index 6bcfd98..598c67c 100644 --- a/qjson4/QJsonParseError.cpp +++ b/qjson4/QJsonParseError.cpp @@ -1,5 +1,5 @@ /***************************************************************************** -* gta5sync GRAND THEFT AUTO V SYNC +* gta5view Grand Theft Auto V Profile Viewer * Copyright (C) 2016 Syping * * This program is free software: you can redistribute it and/or modify diff --git a/qjson4/QJsonParseError.h b/qjson4/QJsonParseError.h index b87d7aa..eddf04d 100644 --- a/qjson4/QJsonParseError.h +++ b/qjson4/QJsonParseError.h @@ -1,5 +1,5 @@ /***************************************************************************** -* gta5sync GRAND THEFT AUTO V SYNC +* gta5view Grand Theft Auto V Profile Viewer * Copyright (C) 2016 Syping * * This program is free software: you can redistribute it and/or modify diff --git a/qjson4/QJsonParser.cpp b/qjson4/QJsonParser.cpp index 9b084f7..052c9a8 100644 --- a/qjson4/QJsonParser.cpp +++ b/qjson4/QJsonParser.cpp @@ -1,5 +1,5 @@ /***************************************************************************** -* gta5sync GRAND THEFT AUTO V SYNC +* gta5view Grand Theft Auto V Profile Viewer * Copyright (C) 2016 Syping * * This program is free software: you can redistribute it and/or modify diff --git a/qjson4/QJsonParser.h b/qjson4/QJsonParser.h index d54a0d9..f11f5a0 100644 --- a/qjson4/QJsonParser.h +++ b/qjson4/QJsonParser.h @@ -1,5 +1,5 @@ /***************************************************************************** -* gta5sync GRAND THEFT AUTO V SYNC +* gta5view Grand Theft Auto V Profile Viewer * Copyright (C) 2016 Syping * * This program is free software: you can redistribute it and/or modify diff --git a/qjson4/QJsonRoot.h b/qjson4/QJsonRoot.h index 77b9751..d249465 100644 --- a/qjson4/QJsonRoot.h +++ b/qjson4/QJsonRoot.h @@ -1,5 +1,5 @@ /***************************************************************************** -* gta5sync GRAND THEFT AUTO V SYNC +* gta5view Grand Theft Auto V Profile Viewer * Copyright (C) 2016 Syping * * This program is free software: you can redistribute it and/or modify diff --git a/qjson4/QJsonValue.cpp b/qjson4/QJsonValue.cpp index 68bf87f..8ac4770 100644 --- a/qjson4/QJsonValue.cpp +++ b/qjson4/QJsonValue.cpp @@ -1,5 +1,5 @@ /***************************************************************************** -* gta5sync GRAND THEFT AUTO V SYNC +* gta5view Grand Theft Auto V Profile Viewer * Copyright (C) 2016 Syping * * This program is free software: you can redistribute it and/or modify diff --git a/qjson4/QJsonValue.h b/qjson4/QJsonValue.h index bf32898..d902352 100644 --- a/qjson4/QJsonValue.h +++ b/qjson4/QJsonValue.h @@ -1,5 +1,5 @@ /***************************************************************************** -* gta5sync GRAND THEFT AUTO V SYNC +* gta5view Grand Theft Auto V Profile Viewer * Copyright (C) 2016 Syping * * This program is free software: you can redistribute it and/or modify diff --git a/qjson4/QJsonValueRef.cpp b/qjson4/QJsonValueRef.cpp index 7d67ef4..dade257 100644 --- a/qjson4/QJsonValueRef.cpp +++ b/qjson4/QJsonValueRef.cpp @@ -1,5 +1,5 @@ /***************************************************************************** -* gta5sync GRAND THEFT AUTO V SYNC +* gta5view Grand Theft Auto V Profile Viewer * Copyright (C) 2016 Syping * * This program is free software: you can redistribute it and/or modify diff --git a/qjson4/QJsonValueRef.h b/qjson4/QJsonValueRef.h index 567c68a..478b657 100644 --- a/qjson4/QJsonValueRef.h +++ b/qjson4/QJsonValueRef.h @@ -1,5 +1,5 @@ /***************************************************************************** -* gta5sync GRAND THEFT AUTO V SYNC +* gta5view Grand Theft Auto V Profile Viewer * Copyright (C) 2016 Syping * * This program is free software: you can redistribute it and/or modify diff --git a/res/5sync-128.png b/res/5sync-128.png deleted file mode 100644 index 4b4bf5a..0000000 Binary files a/res/5sync-128.png and /dev/null differ diff --git a/res/5sync-16.png b/res/5sync-16.png deleted file mode 100644 index 9db49fd..0000000 Binary files a/res/5sync-16.png and /dev/null differ diff --git a/res/5sync-24.png b/res/5sync-24.png deleted file mode 100644 index 01c2fcf..0000000 Binary files a/res/5sync-24.png and /dev/null differ diff --git a/res/5sync-256.png b/res/5sync-256.png deleted file mode 100644 index bf41d47..0000000 Binary files a/res/5sync-256.png and /dev/null differ diff --git a/res/5sync-32.png b/res/5sync-32.png deleted file mode 100644 index 6a768fd..0000000 Binary files a/res/5sync-32.png and /dev/null differ diff --git a/res/5sync-40.png b/res/5sync-40.png deleted file mode 100644 index 0852394..0000000 Binary files a/res/5sync-40.png and /dev/null differ diff --git a/res/5sync-48.png b/res/5sync-48.png deleted file mode 100644 index ff8390c..0000000 Binary files a/res/5sync-48.png and /dev/null differ diff --git a/res/5sync-64.png b/res/5sync-64.png deleted file mode 100644 index 123db7e..0000000 Binary files a/res/5sync-64.png and /dev/null differ diff --git a/res/5sync-96.png b/res/5sync-96.png deleted file mode 100644 index 49f1db7..0000000 Binary files a/res/5sync-96.png and /dev/null differ diff --git a/res/5sync.ico b/res/5sync.ico index 105c5cc..af3d0fc 100644 Binary files a/res/5sync.ico and b/res/5sync.ico differ diff --git a/res/960x536.png b/res/960x536.png deleted file mode 100644 index 6196065..0000000 Binary files a/res/960x536.png and /dev/null differ diff --git a/res/add.svgz b/res/add.svgz new file mode 100644 index 0000000..ca716e5 Binary files /dev/null and b/res/add.svgz differ diff --git a/res/app.qrc b/res/app.qrc deleted file mode 100644 index 461c44e..0000000 --- a/res/app.qrc +++ /dev/null @@ -1,44 +0,0 @@ - - - savegame.png - 5sync-48.png - 5sync-16.png - 5sync-24.png - 5sync-32.png - 5sync-40.png - 5sync-64.png - 5sync-96.png - 5sync-128.png - 5sync-256.png - back.png - next.png - 960x536.png - empty1x16.png - avatararea.png - avatarareaimport.png - mappreview.jpg - pointmaker-8.png - pointmaker-16.png - pointmaker-24.png - pointmaker-32.png - - - global.de.ini - global.en.ini - global.es.ini - global.es_MX.ini - global.fr.ini - global.it.ini - global.ja.ini - global.ko.ini - global.pl.ini - global.pt.ini - global.pt.loc - global.ru.ini - global.zh.ini - global.zh.loc - - - template.g5e - - diff --git a/res/app.rc b/res/app.rc index 1b2196a..56b1561 100644 --- a/res/app.rc +++ b/res/app.rc @@ -1,14 +1,11 @@ -IDI_ICON1 ICON DISCARDABLE "5sync.ico" - +IDI_ICON1 ICON DISCARDABLE "5sync.ico" #define RT_MANIFEST 24 #define CREATEPROCESS_MANIFEST_RESOURCE_ID 1 CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST "gta5view.exe.manifest" - #include - VS_VERSION_INFO VERSIONINFO -FILEVERSION 1, 5, 4, 0 -PRODUCTVERSION 1, 5, 4, 0 +FILEVERSION 1, 10, 2, 0 +PRODUCTVERSION 1, 10, 2, 0 FILEFLAGSMASK 0x3fL FILEFLAGS 0 FILEOS VOS_NT_WINDOWS32 @@ -17,7 +14,7 @@ FILESUBTYPE VFT2_UNKNOWN BEGIN BLOCK "VarFileInfo" BEGIN - VALUE "Translation", 0x0409, 1200 + VALUE "Translation", 0x0809, 1200 END BLOCK "StringFileInfo" BEGIN @@ -25,12 +22,12 @@ BEGIN BEGIN VALUE "CompanyName", "Syping" VALUE "FileDescription", "gta5view" - VALUE "FileVersion", "1.5.4" + VALUE "FileVersion", "1.10.2" VALUE "InternalName", "gta5view" - VALUE "LegalCopyright", "Copyright © 2016-2018 Syping" + VALUE "LegalCopyright", "Copyright © 2016-2023 Syping" VALUE "OriginalFilename", "gta5view.exe" VALUE "ProductName", "gta5view" - VALUE "ProductVersion", "1.5.4" + VALUE "ProductVersion", "1.10.2" END END END diff --git a/res/avatararea.png b/res/avatararea.png index 6305664..463d846 100644 Binary files a/res/avatararea.png and b/res/avatararea.png differ diff --git a/res/avatarareaimport.png b/res/avatarareaimport.png index 97c53cf..cddc1d8 100644 Binary files a/res/avatarareaimport.png and b/res/avatarareaimport.png differ diff --git a/res/back.png b/res/back.png deleted file mode 100644 index 49beb0a..0000000 Binary files a/res/back.png and /dev/null differ diff --git a/res/back.svgz b/res/back.svgz new file mode 100644 index 0000000..f2f90a5 Binary files /dev/null and b/res/back.svgz differ diff --git a/res/btc.str b/res/btc.str new file mode 100644 index 0000000..33e40a5 --- /dev/null +++ b/res/btc.str @@ -0,0 +1 @@ +Bitcoin \ No newline at end of file diff --git a/res/btc.svgz b/res/btc.svgz new file mode 100644 index 0000000..e10b3a5 Binary files /dev/null and b/res/btc.svgz differ diff --git a/res/de.syping.gta5view.desktop b/res/de.syping.gta5view.desktop new file mode 100644 index 0000000..ed9c7d1 --- /dev/null +++ b/res/de.syping.gta5view.desktop @@ -0,0 +1,13 @@ +[Desktop Entry] +Type=Application +Name=gta5view +Comment=Open and edit GTA V profiles +Comment[de]=GTA V Profile öffnen und bearbeiten +Comment[ko]=GTA V í”„ë¡œí•„ì„ ì—´ê³  편집 +Comment[ru]=ПроÑмотрщик и редактор профилей GTA V +Comment[zh_TW]=打開與編輯 GTA V 個人檔案 +Categories=Qt;Utility;FileTools; +TryExec=gta5view +Exec=gta5view %f +Icon=de.syping.gta5view +MimeType=application/x-gta5view-export; diff --git a/res/de.syping.gta5view.metainfo.xml b/res/de.syping.gta5view.metainfo.xml new file mode 100644 index 0000000..c5ab767 --- /dev/null +++ b/res/de.syping.gta5view.metainfo.xml @@ -0,0 +1,72 @@ + + + + de.syping.gta5view + de.syping.gta5view.desktop + CC0-1.0 + GPL-3.0+ + + gta5view + + Open and edit GTA V profiles + GTA V Profile öffnen und bearbeiten + GTA V í”„ë¡œí•„ì„ ì—´ê³  편집 + ПроÑмотрщик и редактор профилей GTA V + 打開與編輯 GTA V 個人檔案 + + https://img.syping.de/gta5view/gta5view.png + + +

Open Source Snapmatic and Savegame viewer/editor for GTA V

+

Features

+
    +
  • View Snapmatics with the ability to disable them in-game
  • +
  • Edit Snapmatic pictures and properties in multiple ways
  • +
  • Import/Export Snapmatics, Savegames and pictures
  • +
  • Choose between multiple Social Club accounts as GTA V profiles IDs
  • +
+
+ + + Utility + + + Syping + + + + + + + + + + + + + + User Interface + https://img.syping.de/gta5view/mainui_kde.png + + + Snapmatic Viewer + https://img.syping.de/gta5view/picture_kde.png + + + Picture Importer + https://img.syping.de/gta5view/import_kde.png + + + Snapmatic Player Editor + https://img.syping.de/gta5view/players_kde.png + + + Snapmatic Property Editor + https://img.syping.de/gta5view/prop_kde.png + + + + + + https://gta5view.syping.de/ +
diff --git a/res/de.syping.gta5view.png b/res/de.syping.gta5view.png new file mode 100644 index 0000000..3e955a6 Binary files /dev/null and b/res/de.syping.gta5view.png differ diff --git a/res/de.syping.gta5view.xml b/res/de.syping.gta5view.xml new file mode 100644 index 0000000..820ed88 --- /dev/null +++ b/res/de.syping.gta5view.xml @@ -0,0 +1,8 @@ + + + + gta5view Export + + + + diff --git a/res/donate.qrc b/res/donate.qrc new file mode 100644 index 0000000..e4a0005 --- /dev/null +++ b/res/donate.qrc @@ -0,0 +1,15 @@ + + + btc.str + btc.svgz + eth.str + eth.svgz + ltc.str + ltc.svgz + xmr.str + xmr.svgz + + + donate.svgz + + diff --git a/res/donate.svgz b/res/donate.svgz new file mode 100644 index 0000000..c9b7f63 Binary files /dev/null and b/res/donate.svgz differ diff --git a/res/empty1x16.png b/res/empty1x16.png deleted file mode 100644 index 6ab4481..0000000 Binary files a/res/empty1x16.png and /dev/null differ diff --git a/res/eth.str b/res/eth.str new file mode 100644 index 0000000..24b4676 --- /dev/null +++ b/res/eth.str @@ -0,0 +1 @@ +Ethereum \ No newline at end of file diff --git a/res/eth.svgz b/res/eth.svgz new file mode 100644 index 0000000..a2fbcc5 Binary files /dev/null and b/res/eth.svgz differ diff --git a/res/flag-de.png b/res/flag-de.png new file mode 100644 index 0000000..2aeaf35 Binary files /dev/null and b/res/flag-de.png differ diff --git a/res/flag-fr.png b/res/flag-fr.png new file mode 100644 index 0000000..d54b858 Binary files /dev/null and b/res/flag-fr.png differ diff --git a/res/flag-gb.png b/res/flag-gb.png new file mode 100644 index 0000000..a425dcc Binary files /dev/null and b/res/flag-gb.png differ diff --git a/res/flag-kr.png b/res/flag-kr.png new file mode 100644 index 0000000..c0058ea Binary files /dev/null and b/res/flag-kr.png differ diff --git a/res/flag-ru.png b/res/flag-ru.png new file mode 100644 index 0000000..c0111e2 Binary files /dev/null and b/res/flag-ru.png differ diff --git a/res/flag-tw.png b/res/flag-tw.png new file mode 100644 index 0000000..4a8faac Binary files /dev/null and b/res/flag-tw.png differ diff --git a/res/flag-ua.png b/res/flag-ua.png new file mode 100644 index 0000000..0ee802d Binary files /dev/null and b/res/flag-ua.png differ diff --git a/res/flag-us.png b/res/flag-us.png new file mode 100644 index 0000000..293337e Binary files /dev/null and b/res/flag-us.png differ diff --git a/res/global.de.ini b/res/global.de.ini index f3b21dc..0916785 100644 --- a/res/global.de.ini +++ b/res/global.de.ini @@ -42,6 +42,7 @@ HAWICK="Hawick" HEART="Heart Attacks Beach" HORS="Vinewood-Rennbahn" HUMLAB="Humane Labs and Research" +ISHEIST="Cayo Perico" JAIL="Bolingbroke-Strafanstalt" KOREAT="Little Seoul" LACT="Land-Act-Stausee" diff --git a/res/global.en.ini b/res/global.en.ini index 1cde708..97c84dd 100644 --- a/res/global.en.ini +++ b/res/global.en.ini @@ -42,6 +42,7 @@ HAWICK="Hawick" HEART="Heart Attacks Beach" HORS="Vinewood Racetrack" HUMLAB="Humane Labs and Research" +ISHEIST="Cayo Perico" JAIL="Bolingbroke Penitentiary" KOREAT="Little Seoul" LACT="Land Act Reservoir" diff --git a/res/global.es.ini b/res/global.es.ini index 60878a1..b364c50 100644 --- a/res/global.es.ini +++ b/res/global.es.ini @@ -42,6 +42,7 @@ HAWICK="Hawick" HEART="Heart Attacks Beach" HORS="Circuito de Vinewood" HUMLAB="Laboratorios Humane" +ISHEIST="Cayo Perico" JAIL="Penitenciaría de Bolingbroke" KOREAT="Little Seoul" LACT="Embalse de Land Act" diff --git a/res/global.es_MX.ini b/res/global.es_MX.ini index 48476ea..620ef14 100644 --- a/res/global.es_MX.ini +++ b/res/global.es_MX.ini @@ -42,6 +42,7 @@ HAWICK="Hawick" HEART="Heart Attacks Beach" HORS="Circuito de Vinewood" HUMLAB="Humane Labs and Research" +ISHEIST="Cayo Perico" JAIL="Penitenciaría de Bolingbroke" KOREAT="Little Seoul" LACT="Presa de Land Act" diff --git a/res/global.fr.ini b/res/global.fr.ini index 977500e..807c5ec 100644 --- a/res/global.fr.ini +++ b/res/global.fr.ini @@ -41,6 +41,7 @@ HAWICK="Hawick" HEART="Heart Attacks Beach" HORS="Hippodrome de Vinewood" HUMLAB="Laboratoires Humane" +ISHEIST="Cayo Perico" JAIL="Pénitencier de Bolingbroke" KOREAT="Little Seoul" LACT="Land Act Reservoir" diff --git a/res/global.it.ini b/res/global.it.ini index a98e012..4c89a36 100644 --- a/res/global.it.ini +++ b/res/global.it.ini @@ -42,6 +42,7 @@ HAWICK="Hawick" HEART="Heart Attacks Beach" HORS="Vinewood Racetrack" HUMLAB="Laboratori di ricerca Humane" +ISHEIST="Cayo Perico" JAIL="Bolingbroke Penitentiary" KOREAT="Little Seoul" LACT="Land Act Reservoir" diff --git a/res/global.ja.ini b/res/global.ja.ini index e1af8ad..f38223d 100644 --- a/res/global.ja.ini +++ b/res/global.ja.ini @@ -42,6 +42,7 @@ HAWICK="ãƒã‚¦ã‚£ãƒƒã‚¯" HEART="ãƒãƒ¼ãƒˆã‚¢ã‚¿ãƒƒã‚¯ãƒ»ãƒ“ーãƒ" HORS="ãƒã‚¤ãƒ³ã‚¦ãƒƒãƒ‰ãƒ»ãƒ¬ãƒ¼ã‚¹ãƒˆãƒ©ãƒƒã‚¯" HUMLAB="ヒューメイン研究所" +ISHEIST="カヨ・ペリコ" JAIL="ボーリングブローク刑務所" KOREAT="リトル・ソウル" LACT="ランド・アクト貯水池" diff --git a/res/global.ko.ini b/res/global.ko.ini index 11ef9f4..7296638 100644 --- a/res/global.ko.ini +++ b/res/global.ko.ini @@ -42,6 +42,7 @@ HAWICK="호ìµ" HEART="하트 ì–´íƒ í•´ë³€" HORS="ë°”ì¸ìš°ë“œ ë ˆì´ìŠ¤íŠ¸ëž™" HUMLAB="íœ´ë©”ì¸ ì‹¤í—˜ 연구소" +ISHEIST="ì¹´ìš” 페리코" JAIL="ë³¼ë§ë¸Œë¡œí¬ êµë„소" KOREAT="리틀 서울" LACT="랜드 액트 저수지" diff --git a/res/global.pl.ini b/res/global.pl.ini index 877a864..e318284 100644 --- a/res/global.pl.ini +++ b/res/global.pl.ini @@ -42,6 +42,7 @@ HAWICK="Hawick" HEART="Plaża ZawaÅ‚owców" HORS="Tor wyÅ›cigowy Vinewood" HUMLAB="Humane Labs and Research" +ISHEIST="Cayo Perico" JAIL="ZakÅ‚ad karny Bolingbroke" KOREAT="MaÅ‚y Seul" LACT="Jezioro zaporowe" diff --git a/res/global.pt.ini b/res/global.pt.ini index 8686d6d..79d46ab 100644 --- a/res/global.pt.ini +++ b/res/global.pt.ini @@ -41,6 +41,7 @@ HAWICK="Hawick" HEART="Heart Attacks Beach" HORS="Hipódromo de Vinewood" HUMLAB="Laboratórios e Pesquisas Humane" +ISHEIST="Cayo Perico" JAIL="Penitenciária Bolingbroke" KOREAT="Little Seoul" LACT="Reservatório Land Act" diff --git a/res/global.pt.loc b/res/global.pt.loc deleted file mode 100644 index 5f648c5..0000000 --- a/res/global.pt.loc +++ /dev/null @@ -1 +0,0 @@ -pt_PT diff --git a/res/global.qrc b/res/global.qrc new file mode 100644 index 0000000..424d0ab --- /dev/null +++ b/res/global.qrc @@ -0,0 +1,17 @@ + + + global.de.ini + global.en.ini + global.es.ini + global.es_MX.ini + global.fr.ini + global.it.ini + global.ja.ini + global.ko.ini + global.pl.ini + global.pt.ini + global.ru.ini + global.zh.ini + global.zh.loc + + diff --git a/res/global.rcc b/res/global.rcc new file mode 100644 index 0000000..60cc688 Binary files /dev/null and b/res/global.rcc differ diff --git a/res/global.ru.ini b/res/global.ru.ini index 37df696..996f807 100644 --- a/res/global.ru.ini +++ b/res/global.ru.ini @@ -42,6 +42,7 @@ HAWICK="Хавик" HEART="Харт-ÐттакÑ-Бич" HORS="Ð“Ð¾Ð½Ð¾Ñ‡Ð½Ð°Ñ Ñ‚Ñ€Ð°ÑÑа Вайнвуда" HUMLAB="Ð›Ð°Ð±Ð¾Ñ€Ð°Ñ‚Ð¾Ñ€Ð¸Ñ Humane Labs and Research" +ISHEIST="Кайо-Перико" JAIL="Тюрьма Болингброук" KOREAT="Маленький Сеул" LACT="ЛÑнд-Ñкт-резервуар" diff --git a/res/global.zh.ini b/res/global.zh.ini index 7d7a080..49650cc 100644 --- a/res/global.zh.ini +++ b/res/global.zh.ini @@ -41,6 +41,7 @@ HAWICK="éœä¼Šå…‹" HEART="驚心海ç˜" HORS="好麥塢賽馬場" HUMLAB="人é“研究實驗室" +ISHEIST="佩里克島" JAIL="åšæž—布魯克監ç„" KOREAT="å°é¦–爾" LACT="蘭艾水庫" diff --git a/lang/gta5sync_no.ts b/res/gta5sync.ts similarity index 61% rename from lang/gta5sync_no.ts rename to res/gta5sync.ts index eab7bef..88ab419 100644 --- a/lang/gta5sync_no.ts +++ b/res/gta5sync.ts @@ -1,6 +1,6 @@ - + AboutDialog @@ -10,7 +10,7 @@ - <span style=" font-weight:600;">%1</span><br/> + <span style="font-weight:600">%1</span><br/> <br/> %2<br/> <br/> @@ -23,76 +23,71 @@ Running with Qt %6<br/> - + &Close - + Translated by %1 Translated by translator, example Translated by Syping - + TRANSLATOR Insert your name here and profile here in following scheme, First Translator,First Profile\nSecond Translator\nThird Translator,Second Profile - - A project for viewing and sync Grand Theft Auto V Snapmatic<br/> -Pictures and Savegames - - - - + A project for viewing Grand Theft Auto V Snapmatic<br/> Pictures and Savegames - + Copyright &copy; <a href="%1">%2</a> %3 - + %1 is licensed under <a href="https://www.gnu.org/licenses/gpl-3.0.html#content">GNU GPLv3</a> - + Release - + Release Candidate - + Daily Build - + Developer - + Beta - + Alpha - + + Custom @@ -172,58 +167,50 @@ Pictures and Savegames ImageEditorDialog - - - - Snapmatic Image Editor - - - - + Overwrite Image... - - - Capacity: %1 + + Apply changes - - ? - - - - - &Import... - - - - + &Overwrite - + + Discard changes + + + + &Close - + + + + + Snapmatic Image Editor + + + + + Patching of Snapmatic Image failed because of I/O Error - + + Patching of Snapmatic Image failed because of Image Error - - - Every taken Snapmatic have a different Capacity, a Snapmatic with higher Capacity can store a picture with better quality. - - ImportDialog @@ -232,114 +219,276 @@ Pictures and Savegames Import... - - - - - Background Colour: <span style="color: %1">%1</span> - - - - - - Ignore Aspect Ratio - - Picture - + Avatar - + + + Ignore Aspect Ratio + + + + + Watermark + + + + + Crop to Aspect Ratio + + + + Background - - - ... + + + + + Background Colour: <span style="color: %1">%1</span> - - Background Image: %1 + + Select background colour - - X - - - - - Force Colour in Avatar Zone - - - - - Import picture - - - - - &OK - - - - - Discard picture - - - - - &Cancel - - - - - - + + + + Background Image: - - + + Select background image + + + + + Remove background image + + + + + Force Colour in Avatar Zone + + + + + Advanced + + + + + Resolution: + + + + + Snapmatic resolution + + + + + Avoid compression and expand buffer instead, improves picture quality, but may break Snapmatic + + + + + Unlimited Buffer + + + + + Import as-is, don't change the picture at all, guaranteed to break Snapmatic unless you know what you doing + + + + + Import as-is + + + + + Import options + + + + + &Options + + + + + Import picture + + + + + &OK + + + + + Discard picture + + + + + &Cancel + + + + + &Import new Picture... + + + + + &Crop Picture... + + + + + &Load Settings... + + + + + &Save Settings... + + + + + Custom Avatar Custom Avatar Description in SC, don't use Special Character! - - + + + Custom Picture Custom Picture Description in SC, don't use Special Character! - + + Storage + Background Image: Storage + + + + + Crop Picture... + + + + + &Crop + + + + + Crop Picture + + + + + + Please import a new picture first + + + + + + Default + Default as Default Profile + + + + + + + + + + + + + + + + + + + + + + + + Profile %1 + Profile %1 as Profile 1 + + + + + + Load Settings... + + + + + + Save Settings... + + + + + + Snapmatic Avatar Zone + + + + + Are you sure to use a square image outside of the Avatar Zone? When you want to use it as Avatar the image will be detached! - - Snapmatic Avatar Zone - - - - + Select Colour... - + + + Background Image: %1 + + + + + + Please select your settings profile + + + + File Background Image: File @@ -354,16 +503,26 @@ When you want to use it as Avatar the image will be detached! + Apply changes + + + + &Save - + + Discard changes + + + + &Close - + JSON Error @@ -376,32 +535,57 @@ When you want to use it as Avatar the image will be detached! - + + Close viewer + + + + &Close - + + Apply new position + + + + &Apply - + + Revert old position + + + + &Revert - - &Set + + Select new position - + + &Select + + + + + Quit select position + + + + &Done - + X: %1 Y: %2 X and Y position @@ -425,19 +609,9 @@ Y: %2 Content Open/Select Mode - - - Open with Singleclick - - - - - Open with Doubleclick - - - Select with Singleclick + Open with Doubleclick @@ -543,214 +717,269 @@ Y: %2 - Feedback - - - - - - Participate in %1 User Statistics - - - - - Hardware, Application and OS Specification - - - - - Application Configuration - - - - - Other - - - - - - - Participation ID: %1 - - - - - &Copy - - - - - - User Feedback - - - - - Limit: 1 message/day - - - - - Language for Areas - - - - - Style - - - - - Style: - - - - - Font - - - - - Always use Message Font (Windows 2003 and earlier) - - - - - Interface + Game + Social Club Version + + + + + + + + + + + + Found: %1 + + + + + + + + + + + + + + Language: %1 + + + + + Steam Version + + + + + Feedback + + + + Participation - + + + Participate in %1 User Statistics + + + + Categories - + + Hardware, Application and OS Specification + + + + System Language Configuration - - &Send + + Application Configuration - + + Personal Usage Data + + + + + Other + + + + + + + Participation ID: %1 + + + + + &Copy + + + + + Interface + + + + Language for Interface - - - - + + + + Current: %1 - + + Language for Areas + + + + + Style + + + + Use Default Style (Restart) - - Sync + + Style: - - Sync is not implemented at current time + + Font - + + Use Default Font (Restart) + + + + + Font: + + + + Apply changes - + &OK OK, Cancel, Apply - + Discard changes - + &Cancel OK, Cancel, Apply - - %1 (Next Closest Language) - First language a person can talk with a different person/application. "Native" or "Not Native". - - - - + System System in context of System default - + + %1 (Game language) + Next closest language compared to the Game settings + + + + + %1 (Closest to Interface) Next closest language compared to the Interface - + + + Auto Automatic language choice. - + + %1 (Language priority) + First language a person can talk with a different person/application. "Native" or "Not Native". + + + + %1 %1 - + The new Custom Folder will initialise after you restart %1. - + No Profile No Profile, as default - - - + + + Profile: %1 - + View %1 User Statistics Online - + Not registered - - A feedback message have to between 3-1024 characters long + + + + + Yes + + + + + + No + + + + + + OS defined + + + + + + Steam defined @@ -762,213 +991,211 @@ Y: %2 - - <span style=" font-weight:600;">Title: </span>%6<br/> -<span style=" font-weight:600;">Location: </span>%7 (%1, %2, %3)<br/> -<span style=" font-weight:600;">Players: </span>%4 (Crew %5)<br/> -<span style=" font-weight:600;">Created: </span>%8 + + <span style="font-weight:600">Title: </span>%6<br/> +<span style="font-weight:600">Location: </span>%7 (%1, %2, %3)<br/> +<span style="font-weight:600">Players: </span>%4 (Crew %5)<br/> +<span style="font-weight:600">Created: </span>%8 - + Manage picture - + &Manage - + Close viewer - + &Close - - + + Export as &Picture... - - + + Export as &Snapmatic... - - - &Overwrite Image... - - - - - + + &Edit Properties... - - + + + &Overwrite Image... + + + + + Open &Map Viewer... - + + + Open &JSON Editor... + + + + Key 1 - Avatar Preview Mode Key 2 - Toggle Overlay Arrow Keys - Navigate - - + Snapmatic Picture Viewer - - + Failed at %1 - - - No Crew - - - - - Unknown Location - - - - - - + + + No Players - + + + No Crew + + + + + Unknown Location + + + + Avatar Preview Mode Press 1 for Default View - - - Export - - - - + Export as Picture... - + + + Export + + + + JPEG Graphics (*.jpg *.jpeg) - + Portable Network Graphics (*.png) - - - Overwrite %1 with current Snapmatic picture? - - - - - + + + + + - - - Export as Picture - - - Failed to export current Snapmatic picture + + + Overwrite %1 with current Snapmatic picture? - - - No valid file is selected - - - - + Failed to export the picture because the system occurred a write failure - + Failed to export the picture because the format detection failures - + Failed to export the picture because the file can't be written - + Failed to export the picture because of an unknown reason - + + + No valid file is selected + + + + Export as Snapmatic... - + GTA V Export (*.g5e) - + GTA V Raw Export (*.auto) - + Snapmatic pictures (PGTA*) - - - - - + + + + + Export as Snapmatic - - Exported Snapmatic to "%1" because of using the .auto extension. + + + Failed to export current Snapmatic picture - - - Open &JSON Editor... + + Exported Snapmatic to "%1" because of using the .auto extension. @@ -985,43 +1212,43 @@ Press 1 for Default View - + Selected Players: - + &Apply - + &Cancel - + Add Players... - + Failed to add more Players because the limit of Players are %1! - - + + Add Player... - + Enter Social Club Player ID - + Failed to add Player %1 because Player %1 is already added! @@ -1064,283 +1291,268 @@ Press 1 for Default View - - - + + + Export file %1 of %2 files - - Enabled pictures: %1 of %2 - - - - - Loading... - - - - - - - + + + + + + + + + + + + + + + + + + + Import... - - - - - - - - - - - - - - - - - - - - + + + + + + Import - - - GTA V Export (*.g5e) - - - - - - Savegames files (SGTA*) - - - - - - Snapmatic pictures (PGTA*) - - - - - Importable files (%1) - - - - - Snapmatic Loader - - - - - <h4>Following Snapmatic Pictures got repaired</h4>%1 - - - - - - + + + All image files (%1) - - - - + + + + All files (**) - - - + + + + Can't import %1 because file can't be open + + + + + + + Can't import %1 because file can't be parsed properly + + + + + Enabled pictures: %1 of %2 + + + + + Loading... + + + + + Snapmatic Loader + + + + + <h4>Following Snapmatic Pictures got repaired</h4>%1 + + + + + Importable files (%1) + + + + + + GTA V Export (*.g5e) + + + + + + Savegames files (SGTA*) + + + + + + Snapmatic pictures (PGTA*) + + + + + + No valid file is selected - - + + Import file %1 of %2 files - + Import failed with... %1 - - + + Failed to read Snapmatic picture - - + + Failed to read Savegame file - - - - Can't import %1 because file can't be open - - - - - - - Can't import %1 because file can't be parsed properly - - - - + Can't import %1 because file format can't be detected - + + Prepare Content for Import... + + + + Failed to import the Snapmatic picture, file not begin with PGTA or end with .g5e - - Failed to import the Snapmatic picture, the picture is already in the game + + A Snapmatic picture already exists with the uid %1, you want assign your import a new uid and timestamp? - + Failed to import the Snapmatic picture, can't copy the file into profile - + Failed to import the Savegame, can't copy the file into profile - + Failed to import the Savegame, no Savegame slot is left - - - JPG pictures and GTA Snapmatic - - - - - - JPG pictures only - - - - - - GTA Snapmatic only - - - - - %1Export Snapmatic pictures%2<br><br>JPG pictures make it possible to open the picture with a Image Viewer<br>GTA Snapmatic make it possible to import the picture into the game<br><br>Export as: - - - - - - - - + + + + + Export selected... - + + + JPG pictures and GTA Snapmatic + + + + + + JPG pictures only + + + + + + GTA Snapmatic only + + + + + %1Export Snapmatic pictures%2<br><br>JPG pictures make it possible to open the picture with a Image Viewer<br>GTA Snapmatic make it possible to import the picture into the game<br><br>Export as: + + + + Initialising export... - + Export failed with... %1 - - + + No Snapmatic pictures or Savegames files are selected - - - + + + Remove selected - + You really want remove the selected Snapmatic picutres and Savegame files? - - - Qualify as Avatar + + Failed to remove all selected Snapmatic pictures and/or Savegame files - - - - - - + + + + + + No Snapmatic pictures are selected - - - - - Patch selected... - - - - - - - - - - - - Patch file %1 of %2 files - - - - - - - - - + + + + + + %1 failed with... %2 @@ -1348,99 +1560,102 @@ Press 1 for Default View - - Failed to remove all selected Snapmatic pictures and/or Savegame files + + + Qualify as Avatar - + + + + + Patch selected... + + + + + + + + + + + + Patch file %1 of %2 files + + + + Qualify %1 failed with... - - + + Change Players... - + Change Players %1 failed with... - - - + + + Change Crew... - + Failed to enter a valid Snapmatic Crew ID - + Change Crew %1 failed with... - - - + + + Change Title... - + Failed to enter a valid Snapmatic title - + Change Title %1 failed with... - + All profile files (*.g5e SGTA* PGTA*) - - QApplication - - - Font - - - - - Selected Font: %1 - - - - - <h4>Welcome to %1!</h4>You want to configure %1 before you start using it? - - - SavegameDialog - + Savegame Viewer - <span style=" font-weight:600;">Savegame</span><br><br>%1 + <span style="font-weight:600">Savegame</span><br><br>%1 @@ -1454,7 +1669,7 @@ Press 1 for Default View - + Failed at %1 @@ -1503,37 +1718,45 @@ Press 1 for Default View - + &View - + + + &Export - + + + &Remove - + + &Select - + + &Deselect - + + Select &All - + + &Deselect All @@ -1576,48 +1799,48 @@ Press 1 for Default View - + Export Savegame... - - + + AUTOSAVE - %1 %2 - - + + SAVE %3 - %1 %2 - - + + WRONG FORMAT - + UNKNOWN - - Are you sure to delete %1 from your savegames? - - - - - + + Delete Savegame - + + Are you sure to delete %1 from your savegames? + + + + Failed at deleting %1 from your savegames @@ -1627,13 +1850,13 @@ Press 1 for Default View - - - - - - - + + + + + + + Snapmatic Properties @@ -1677,34 +1900,6 @@ Press 1 for Default View Snapmatic Values - - - Crew: %1 (%2) - - - - - Title: %1 (%2) - - - - - Players: %1 (%2) - Multiple Player are inserted here - - - - - Player: %1 (%2) - One Player is inserted here - - - - - - Appropriate: %1 - - Extras @@ -1722,147 +1917,185 @@ Press 1 for Default View + Apply changes + + + + &Apply - + + Discard changes + + + + &Cancel - - - + + <h4>Unsaved changes detected</h4>You want to save the JSON content before you quit? + + + + + Patching of Snapmatic Properties failed because of %1 + + + + + + + + Patching of Snapmatic Properties failed because of I/O Error + + + + + Patching of Snapmatic Properties failed because of JSON Error + + + + + + Snapmatic Crew + + + + + + New Snapmatic crew: + + + + + + Snapmatic Title + + + + + + New Snapmatic title: + + + + + + Edit - + + Players: %1 (%2) + Multiple Player are inserted here + + + + + Player: %1 (%2) + One Player is inserted here + + + + + Title: %1 (%2) + + + + + + Appropriate: %1 + + + + Yes Yes, should work fine - + No No, could lead to issues - - <h4>Unsaved changes detected</h4>You want to save the JSON content before you quit? - - - - - Patching of Snapmatic Properties failed because of %1 - - - - - Patching of Snapmatic Properties failed because of JSON Error - - - - - - - - Patching of Snapmatic Properties failed because of I/O Error - - - - - - Snapmatic Title - - - - - - New Snapmatic title: - - - - - - Snapmatic Crew - - - - - - New Snapmatic crew: + + Crew: %1 (%2) SnapmaticPicture - - PHOTO - %1 - - - - - open file %1 - - - - - header not exists - - - - - header is malformed - - - - - picture not exists (%1) - - - - - JSON not exists (%1) - - - - - title not exists (%1) - - - - - description not exists (%1) - - - - - reading file %1 because of %2 - Example for %2: JSON is malformed error - - - - - + + JSON is incomplete and malformed - - + + JSON is incomplete - - + + JSON is malformed + + + PHOTO - %1 + + + + + open file %1 + + + + + header not exists + + + + + header is malformed + + + + + picture not exists (%1) + + + + + JSON not exists (%1) + + + + + title not exists (%1) + + + + + description not exists (%1) + + + + + reading file %1 because of %2 + Example for %2: JSON is malformed error + + SnapmaticWidget @@ -1908,8 +2141,8 @@ Press 1 for Default View - - + + Delete picture @@ -1919,72 +2152,82 @@ Press 1 for Default View - + + + Edi&t - + + + Show &In-game - + + + Hide &In-game - + &Export - + &View - + &Remove - + + &Select - + + &Deselect - + + Select &All - + + &Deselect All - + Are you sure to delete %1 from your Snapmatic pictures? - + Failed at deleting %1 from your Snapmatic pictures - + Failed to hide %1 In-game from your Snapmatic pictures - + Failed to show %1 In-game from your Snapmatic pictures @@ -1992,22 +2235,22 @@ Press 1 for Default View TelemetryDialog - + + You want help %1 to improve in the future by including personal usage data in your submission? + + + + %1 User Statistics - - You want help %1 to improve in the future by collection of data? + + Yes, I want include personal usage data. - - Yes, I would like to take part. - - - - + &OK @@ -2016,7 +2259,7 @@ Press 1 for Default View UserInterface - + %2 - %1 @@ -2048,6 +2291,9 @@ Press 1 for Default View + + + &Close @@ -2076,10 +2322,15 @@ Press 1 for Default View &Selection visibility + + + Selection &mass tools + + - - + + &About %1 @@ -2135,15 +2386,15 @@ Press 1 for Default View - + Select &GTA V Folder... - - - + + + Select GTA V Folder... @@ -2157,69 +2408,98 @@ Press 1 for Default View Hi&de In-game - - - Change &Players... - - - - - Selection &mass tools - - + + Change &Title... + + Change &Crew... + + &Qualify as Avatar - - - - Select Profile + + + + Change &Players... - - Open File... - - - - - - - - Open File - - - - - Can't open %1 because of not valid file format - - - - - - + + + Show In-game - - - + + + Hide In-game + + + + + Select Profile + + + + + + &Donate + + + + + Donate + + + + + Donation methods + + + + + &Copy + + + + + Open File... + + + + + + + + Open File + + + + + Can't open %1 because of not valid file format + + + + + %1 - Messages + + diff --git a/res/gta5sync_de.qm b/res/gta5sync_de.qm index e346dc2..f25770f 100644 Binary files a/res/gta5sync_de.qm and b/res/gta5sync_de.qm differ diff --git a/res/gta5sync_de.ts b/res/gta5sync_de.ts index d1d4f1b..054fac6 100644 --- a/res/gta5sync_de.ts +++ b/res/gta5sync_de.ts @@ -10,7 +10,7 @@ - <span style=" font-weight:600;">%1</span><br/> + <span style="font-weight:600">%1</span><br/> <br/> %2<br/> <br/> @@ -20,7 +20,7 @@ Built with Qt %5<br/> Running with Qt %6<br/> <br/> %7 - <span style=" font-weight:600;">%1</span><br/> + <span style="font-weight:600">%1</span><br/> <br/> %2<br/> <br/> @@ -32,78 +32,72 @@ Läuft auf Qt %6<br/> %7 - + &Close S&chließen - + Translated by %1 Translated by translator, example Translated by Syping Übersetzt von %1 - + TRANSLATOR Insert your name here and profile here in following scheme, First Translator,First Profile\nSecond Translator\nThird Translator,Second Profile - Syping,https://github.com/Syping/ + Syping,g5e://about?U3lwaW5n:R2l0TGFiOiA8YSBocmVmPSJodHRwczovL2dpdGxhYi5jb20vU3lwaW5nIj5TeXBpbmc8L2E+PGJyLz5HaXRIdWI6IDxhIGhyZWY9Imh0dHBzOi8vZ2l0aHViLmNvbS9TeXBpbmciPlN5cGluZzwvYT48YnIvPlNvY2lhbCBDbHViOiA8YSBocmVmPSJodHRwczovL3NvY2lhbGNsdWIucm9ja3N0YXJnYW1lcy5jb20vbWVtYmVyL1N5cGluZy80NjMwMzA1NiI+U3lwaW5nPC9hPg - + A project for viewing Grand Theft Auto V Snapmatic<br/> Pictures and Savegames Ein Projekt zum ansehen von Grand Theft Auto V<br/> Snapmatic Bilder und Spielständen - + Copyright &copy; <a href="%1">%2</a> %3 Copyright &copy; <a href="%1">%2</a> %3 - + %1 is licensed under <a href="https://www.gnu.org/licenses/gpl-3.0.html#content">GNU GPLv3</a> %1 ist lizenziert unter <a href="https://www.gnu.org/licenses/gpl-3.0.html#content">GNU GPLv3</a> - - A project for viewing and sync Grand Theft Auto V Snapmatic<br/> -Pictures and Savegames - Ein Projekt zum ansehen und synchronisieren von<br/> -Grand Theft Auto V Snapmatic Bilder und Spielständen - - - + Release Release - + Release Candidate Release Candidate - + Daily Build Daily Build - + Developer Entwickler - + Beta Beta - + Alpha Alpha - + + Custom Eigener @@ -183,58 +177,50 @@ Grand Theft Auto V Snapmatic Bilder und Spielständen ImageEditorDialog - - - + + + + Snapmatic Image Editor Snapmatic Bild Editor - + Overwrite Image... Bild überschreiben... - - - Capacity: %1 - Kapazität: %1 + + Apply changes + Änderungen übernehmen - - ? - ? - - - - &Import... - &Importieren... - - - + &Overwrite &Überschreiben - + + Discard changes + Änderungen verwerfen + + + &Close S&chließen - + + Patching of Snapmatic Image failed because of I/O Error Patchen von Snapmatic Bild fehlgeschlagen wegen I/O Fehler - + + Patching of Snapmatic Image failed because of Image Error Patchen von Snapmatic Bild fehlgeschlagen wegen Bild Fehler - - - Every taken Snapmatic have a different Capacity, a Snapmatic with higher Capacity can store a picture with better quality. - Jedes geschossene Snapmatic hat seine eigene Kapazität, ein Snapmatic mit besserer Kapazität kann ein Bild mit besserer Qualität beinhalten. - ImportDialog @@ -244,13 +230,13 @@ Grand Theft Auto V Snapmatic Bilder und Spielständen Importieren... - - + + Ignore Aspect Ratio Seitenverhältnis ignorieren - + Avatar Avatar @@ -260,98 +246,272 @@ Grand Theft Auto V Snapmatic Bilder und Spielständen Bild - + + Watermark + Wasserzeichen + + + Force Borderless + Erzwinge keine Ränder + + + Background Hintergrund - - - + + + + Background Colour: <span style="color: %1">%1</span> Hintergrundfarbe: <span style="color: %1">%1</span> - - - ... - ... + + Select background colour + Hintergrundfarbe auswählen - + ... + ... + + + + Select background image + Hintergrundbild auswählen + + + + Remove background image + Hintergrundbild entfernen + + + + Import as-is, don't change the picture at all, guaranteed to break Snapmatic unless you know what you doing + Importiere das Bild ohne Veränderungen, Snapmatic wird garantiert beschädigt wenn du nicht weißt was du tust + + + + Background Image: %1 Hintergrundbild: %1 - X - X + X - + + Crop to Aspect Ratio + Seitenverhältnis zuschneiden + + + Force Colour in Avatar Zone Erzwinge Farbe in Avatar Zone - + + Advanced + Erweitert + + + + Resolution: + Auflösung: + + + + Snapmatic resolution + Snapmatic Auflösung + + + + Avoid compression and expand buffer instead, improves picture quality, but may break Snapmatic + Vermeide Kom­pri­mie­rung und vergrößere Buffer stattdessen, verbessert Bild Qualität, aber könnte Snapmatic beschädigen + + + + Unlimited Buffer + Un­li­mi­tierter Buffer + + + + Import as-is + Importiere unverändert + + + + Import options + Import Optionen + + + + &Options + &Optionen + + + Import picture Bild importieren - + &OK &OK - + Discard picture Bild verwerfen - + &Cancel Abbre&chen - - - + + + + Background Image: Hintergrundbild: - - + + &Import new Picture... + Neues Bild &importieren... + + + + &Crop Picture... + Bild zu&schneiden... + + + + &Load Settings... + Einstellungen &laden... + + + + &Save Settings... + Einstellungen &speichern... + + + + Custom Avatar Custom Avatar Description in SC, don't use Special Character! Eigener Avatar - - + + + Custom Picture Custom Picture Description in SC, don't use Special Character! Eigenes Bild - + + Storage + Background Image: Storage + Speicher + + + + Crop Picture... + Bild zuschneiden... + + + + &Crop + Zu&schneiden + + + + Crop Picture + Bild zuschneiden + + + + + Please import a new picture first + Bitte importiere ein neues Bild zuerst + + + + + Default + Default as Default Profile + Standard + + + + + + + + + + + + + + + + + + + + + + + Profile %1 + Profile %1 as Profile 1 + Profil %1 + + + + + Load Settings... + Einstellungen laden... + + + + + Please select your settings profile + Bitte wähle dein Einstellungsprofil aus + + + + + Save Settings... + Einstellungen speichern... + + + + Are you sure to use a square image outside of the Avatar Zone? When you want to use it as Avatar the image will be detached! Bist du sicher ein Quadrat Bild außerhalb der Avatar Zone zu verwenden? Wenn du es als Avatar verwenden möchtest wird es abgetrennt! - + + Snapmatic Avatar Zone Snapmatic Avatar Zone - + Select Colour... Farbe auswählen... - + File Background Image: File Datei @@ -366,16 +526,26 @@ Wenn du es als Avatar verwenden möchtest wird es abgetrennt! + Apply changes + Änderungen übernehmen + + + &Save &Speichern - + + Discard changes + Änderungen verwerfen + + + &Close S&chließen - + JSON Error JSON Fehler @@ -388,32 +558,57 @@ Wenn du es als Avatar verwenden möchtest wird es abgetrennt! Snapmatic Kartenansicht - + + Close viewer + Ansicht schließen + + + &Close S&chließen - + + Apply new position + Neue Position festlegen + + + &Apply &Übernehmen - + + Revert old position + Alte Position wiederherstellen + + + &Revert &Zurücksetzen - - &Set - &Ändern + + Select new position + Neue Position auswählen - + + &Select + Au&swählen + + + + Quit select position + Position auswählen verlassen + + + &Done &Fertig - + X: %1 Y: %2 X and Y position @@ -429,19 +624,17 @@ Y: %2 Inhalte Öffnen/Auswählen Modus - Open with Singleclick - Ein Klick zum öffnen + Ein Klick zum öffnen - + Open with Doubleclick Doppelklick zum öffnen - Select with Singleclick - Ein Klick zum auswählen + Ein Klick zum auswählen @@ -556,297 +749,352 @@ Y: %2 + Game + Spiel + + + + Social Club Version + Social Club Version + + + + + + + + + + + Found: %1 + Gefunden: %1 + + + + + + + + + + + + + Language: %1 + Sprache: %1 + + + + Steam Version + Steam Version + + + Feedback Feedback - + Participation Teilnahme - - Categories - Kategorien - - - - &Send - &Senden - - - - + + Participate in %1 User Statistics An %1 Benutzerstatistik teilnehmen - + + Categories + Kategorien + + + Hardware, Application and OS Specification Hardware, Anwendung und OS Spezifikation - + System Language Configuration Spracheinstellungen des System - + Application Configuration Anwendungseinstellungen - + Other Sonstiges - - - + + + Participation ID: %1 Teilnahme ID: %1 - + &Copy &Kopieren - - - User Feedback - Benutzer Feedback - - - - Limit: 1 message/day - Limit: 1 Nachricht/Tag - - - + Language for Areas Sprache für Standorte - + Style Stil - + Use Default Style (Restart) Benutze Standard Stil (Neustart) - + Style: Stil: - + Font - Schrift + Schriftart - Always use Message Font (Windows 2003 and earlier) - Immer Nachrichtenschrift nutzen (Windows 2003 und früher) + Immer Nachrichtenschrift nutzen (Windows 2003 und früher) - + Interface Oberfläche - + + Personal Usage Data + Persönliche Nutzungsdaten + + + Language for Interface Sprache für Oberfläche - - - - + + + + Current: %1 Aktuell: %1 - - Sync - Sync + + Use Default Font (Restart) + Benutze Standard Schriftart (Neustart) - - Sync is not implemented at current time - Sync wurde bisher nicht implementiert + + Font: + Schriftart: - + Apply changes Änderungen übernehmen - + &OK OK, Cancel, Apply &OK - + Discard changes Änderungen verwerfen - + &Cancel OK, Cancel, Apply Abbre&chen - + %1 %1 %1 - - %1 (Next Closest Language) - First language a person can talk with a different person/application. "Native" or "Not Native". - %1 (Erste näheste Sprache) - - - + System System in context of System default System - + + %1 (Game language) + Next closest language compared to the Game settings + %1 (Spielsprache) + + + + %1 (Closest to Interface) Next closest language compared to the Interface %1 (Näheste zur Oberfläche) - + + + Auto Automatic language choice. Automatisch - + + %1 (Language priority) + First language a person can talk with a different person/application. "Native" or "Not Native". + %1 (Sprachenpriorität) + + + The new Custom Folder will initialise after you restart %1. Der eigene Ordner wird initialisiert sobald du %1 neugestartet hast. - + View %1 User Statistics Online %1 Benutzerstatistik Online ansehen - + Not registered Nicht registriert - - A feedback message have to between 3-1024 characters long - Eine Feedback Nachricht hat zwischen 3-1024 Zeichen lang zu sein + + + + + Yes + Ja - + + + No + Nein + + + + + OS defined + OS-defined + + + + + Steam defined + Steam-definiert + + + No Profile No Profile, as default Kein Profil - - - + + + Profile: %1 Profil: %1 PictureDialog - - %1 - Snapmatic Picture Viewer - %1 - Snapmatic Bildansicht - Snapmatic Picture Viewer - %1 Snapmatic Bildansicht - %1 - - <span style=" font-weight:600;">Title: </span>%6<br/> -<span style=" font-weight:600;">Location: </span>%7 (%1, %2, %3)<br/> -<span style=" font-weight:600;">Players: </span>%4 (Crew %5)<br/> -<span style=" font-weight:600;">Created: </span>%8 - <span style=" font-weight:600;">Titel: </span>%6<br/> -<span style=" font-weight:600;">Standort: </span>%7 (%1, %2, %3)<br/> -<span style=" font-weight:600;">Spieler: </span>%4 (Crew %5)<br/> -<span style=" font-weight:600;">Erstellt: </span>%8 + + <span style="font-weight:600">Title: </span>%6<br/> +<span style="font-weight:600">Location: </span>%7 (%1, %2, %3)<br/> +<span style="font-weight:600">Players: </span>%4 (Crew %5)<br/> +<span style="font-weight:600">Created: </span>%8 + <span style="font-weight:600">Titel: </span>%6<br/> +<span style="font-weight:600">Standort: </span>%7 (%1, %2, %3)<br/> +<span style="font-weight:600">Spieler: </span>%4 (Crew %5)<br/> +<span style="font-weight:600">Erstellt: </span>%8 - + Manage picture Bild verwalten - + &Manage &Verwalten - + Close viewer Ansicht schließen - + &Close S&chließen - - + + Export Exportieren - - + + Export as &Picture... Als &Bild exportieren... - - + + Export as &Snapmatic... Als &Snapmatic exportieren... - - + + &Edit Properties... Eigenschaften bearb&eiten... - - + + &Overwrite Image... Bild &überschreiben... - - + + Open &Map Viewer... &Kartenansicht öffnen... - + Key 1 - Avatar Preview Mode Key 2 - Toggle Overlay Arrow Keys - Navigate @@ -855,142 +1103,140 @@ Taste 2 - Overlay umschalten Pfeiltasten - Navigieren - - + Snapmatic Picture Viewer Snapmatic Bildansicht - - + Failed at %1 Fehlgeschlagen beim %1 - - + + No Crew Keine Crew - - - + + + No Players Keine Spieler - + Avatar Preview Mode Press 1 for Default View Avatar Vorschaumodus Drücke 1 für Standardmodus - + Unknown Location Unbekannter Standort - + Portable Network Graphics (*.png) Portable Network Graphics (*.png) - - + + Overwrite %1 with current Snapmatic picture? Überschreibe %1 mit aktuellen Snapmatic Bild? - + Export as Picture... Als Bild exportieren... - + JPEG Graphics (*.jpg *.jpeg) JPEG Graphics (*.jpg *.jpeg) - - + + + + + - - - Export as Picture Als Bild exportieren - + Failed to export the picture because the system occurred a write failure Fehlgeschlagen beim Exportieren weil das System ein Schreibfehler ausgelöst hat - + Failed to export the picture because the format detection failures Fehlgeschlagen beim Exportieren weil die Formaterkennung fehlschlägt - + Failed to export the picture because the file can't be written Fehlgeschlagen beim Exportieren weil die Datei nicht beschrieben werden kann - + Failed to export the picture because of an unknown reason Fehlgeschlagen beim Exportieren wegen einen unbekannten Grund - - + + Failed to export current Snapmatic picture Fehlgeschlagen beim Exportieren vom aktuellen Snapmatic Bild - + Export as Snapmatic... Als Snapmatic exportieren... - - - - - + + + + + Export as Snapmatic Als Snapmatic exportieren - + Exported Snapmatic to "%1" because of using the .auto extension. Snapmatic wurde wegen Benutzung der .auto Erweiterung zu "%1" exportiert. - + GTA V Export (*.g5e) GTA V Export (*.g5e) - + GTA V Raw Export (*.auto) GTA V Roher Export (*.auto) - + Snapmatic pictures (PGTA*) Snapmatic Bilder (PGTA*) - - + + No valid file is selected Keine gültige Datei wurde ausgewählt - - + + Open &JSON Editor... &JSON Editor öffnen... @@ -1008,43 +1254,43 @@ Drücke 1 für Standardmodus Verfügbare Spieler: - + Selected Players: Ausgewählte Spieler: - + &Apply &Übernehmen - + &Cancel Abbre&chen - + Add Players... Spieler hinzufügen... - + Failed to add more Players because the limit of Players are %1! Fehlgeschlagen beim Hinzufügen von mehr Spielern weil der Limit von Spielern %1 ist! - - + + Add Player... Spieler hinzufügen... - + Enter Social Club Player ID Social Club Spieler ID eingeben - + Failed to add Player %1 because Player %1 is already added! Fehlgeschlagen beim Hinzufügen vom Spieler %1 weil Spieler %1 bereits hinzugefügt wurde! @@ -1088,92 +1334,93 @@ Drücke 1 für Standardmodus S&chließen - + Loading... Lade... - + Snapmatic Loader Snapmatic Lader - + <h4>Following Snapmatic Pictures got repaired</h4>%1 <h4>Folgende Snapmatic Bilder wurden repariert</h4>%1 - - - - + + + + + + + + + + + + + + + + + + + Import... Importieren... - - - - - - - - - - - - - - - - - - - - + + + + + + Import Importieren - - + + Savegames files (SGTA*) Spielstanddateien (SGTA*) - - + + Snapmatic pictures (PGTA*) Snapmatic Bilder (PGTA*) - + Importable files (%1) Importfähige Dateien (%1) - - - + + + All image files (%1) Alle Bilddateien (%1) - - - - + + + + All files (**) Alle Dateien (**) - - + + Import file %1 of %2 files Importiere Datei %1 von %2 Dateien - + Import failed with... %1 @@ -1182,149 +1429,149 @@ Drücke 1 für Standardmodus %1 - - + + Failed to read Snapmatic picture Fehler beim Lesen vom Snapmatic Bild - - + + Failed to read Savegame file Fehler beim Lesen von Spielstanddatei - - - + + + Can't import %1 because file can't be open Kann %1 nicht importieren weil die Datei nicht geöffnet werden kann - - - + + + Can't import %1 because file can't be parsed properly Kann %1 nicht importieren weil die Datei nicht richtig gelesen werden kann - + Can't import %1 because file format can't be detected Kann %1 nicht importieren weil das Dateiformat nicht erkannt werden kann - + Initialising export... Initialisiere Export... - + Failed to import the Snapmatic picture, file not begin with PGTA or end with .g5e Fehlgeschlagen beim Importieren vom Snapmatic Bild, Datei beginnt nicht mit PGTA oder endet mit .g5e - - Failed to import the Snapmatic picture, the picture is already in the game - Fehlgeschlagen beim Importieren vom Snapmatic Bild, dieses Bild ist bereits im Spiel - - - + %1Export Snapmatic pictures%2<br><br>JPG pictures make it possible to open the picture with a Image Viewer<br>GTA Snapmatic make it possible to import the picture into the game<br><br>Export as: %1Exportiere Snapmatic Bilder%2<br><br>JPG Bilder machen es möglich sie mit ein Bildansicht Programm zu öffnen<br>Das GTA Snapmatic Format macht es möglich sie wieder ins Game zu importieren<br><br>Exportieren als: - - - + + + No valid file is selected Keine gültige Datei wurde ausgewählt - + Enabled pictures: %1 of %2 Aktivierte Bilder: %1 von %2 - + + A Snapmatic picture already exists with the uid %1, you want assign your import a new uid and timestamp? + Ein Snapmatic Bild mit der Uid %1 existiert bereits, möchtest du dein Import eine neue Uid und Zeitstempel zuweisen? + + + Failed to import the Snapmatic picture, can't copy the file into profile Fehlgeschlagen beim Importieren vom Snapmatic Bild, kann Snapmatic Bild nicht ins Profil kopieren - + Failed to import the Savegame, can't copy the file into profile Fehlgeschlagen beim Importieren vom Spielstand, kann Spielstanddatei nicht ins Profil kopieren - + Failed to import the Savegame, no Savegame slot is left Fehlgeschlagen beim Importieren vom Spielstand, kein Spielstandslot mehr frei - - + + JPG pictures and GTA Snapmatic JPG Bilder und GTA Snapmatic - - + + JPG pictures only Nur JPG Bilder - - + + GTA Snapmatic only Nur GTA Snapmatic - - - - + + + + Patch selected... Auswahl patchen... - - - - - - - - + + + + + + + + Patch file %1 of %2 files Patche Datei %1 von %2 Dateien - - + + Qualify as Avatar Als Avatar qualifizieren - - - - - - + + + + + + No Snapmatic pictures are selected Keine Snapmatic Bilder sind ausgewählt - + Failed to remove all selected Snapmatic pictures and/or Savegame files Fehlgeschlagen beim Entfernen von allen augewählten Snapmatic Bildern und/oder Spielstanddateien - - - - - - + + + + + + %1 failed with... %2 @@ -1334,108 +1581,113 @@ Drücke 1 für Standardmodus %2 - + + Prepare Content for Import... + Bereite Inhalt für Import vor... + + + Qualify %1 failed with... Qualifizieren - - + + Change Players... Spieler ändern... - + Change Players %1 failed with... Spieler ändern - - - + + + Change Crew... Crew ändern... - + Failed to enter a valid Snapmatic Crew ID Fehlgeschlagen beim Eingeben von einer gültigen Crew ID - + Change Crew %1 failed with... Crew ändern - - - + + + Change Title... Titel ändern... - + Failed to enter a valid Snapmatic title Fehlgeschlagen beim Eingeben eines gültigen Snapmatic Titel - + Change Title %1 failed with... Titel ändern - - + + No Snapmatic pictures or Savegames files are selected Keine Snapmatic Bilder oder Spielstände sind ausgewählt - - - + + + Remove selected Auswahl löschen - + You really want remove the selected Snapmatic picutres and Savegame files? Möchtest du wirklich die ausgewählten Snapmatic Bilder und Spielstanddateien löschen? - - - - - + + + + + Export selected... Auswahl exportieren... - + Export failed with... %1 Exportieren fehlgeschlagen bei...\n%1 - - - + + + Export file %1 of %2 files Exportiere Datei %1 von %2 Dateien - + All profile files (*.g5e SGTA* PGTA*) Alle Profildateien (*.g5e SGTA* PGTA*) - - + + GTA V Export (*.g5e) GTA V Export (*.g5e) @@ -1443,33 +1695,22 @@ Drücke 1 für Standardmodus QApplication - - Font - Schrift - - - - Selected Font: %1 - Ausgewähle Schrift: %1 - - - <h4>Welcome to %1!</h4>You want to configure %1 before you start using it? - <h4>Willkommen zu %1!</h4>Möchtest du %1 einstellen bevor du es nutzt? + <h4>Willkommen zu %1!</h4>Möchtest du %1 einstellen bevor du es nutzt? SavegameDialog - + Savegame Viewer Spielstandanzeiger - <span style=" font-weight:600;">Savegame</span><br><br>%1 - <span style=" font-weight:600;">Spielstand</span><br><br>%1 + <span style="font-weight:600">Savegame</span><br><br>%1 + <span style="font-weight:600">Spielstand</span><br><br>%1 @@ -1482,7 +1723,7 @@ Drücke 1 für Standardmodus S&chließen - + Failed at %1 Fehlgeschlagen bei %1 @@ -1516,7 +1757,7 @@ Drücke 1 für Standardmodus Savegame löschen - + Export Savegame... Spielstand exportieren... @@ -1526,75 +1767,81 @@ Drücke 1 für Standardmodus SPIELSTAND %3 - %1<br>%2 - - + + WRONG FORMAT FALSCHES FORMAT - - + + AUTOSAVE - %1 %2 AUTOSAVE - %1 %2 - - + + SAVE %3 - %1 %2 SPIELSTAND %3 - %1 %2 - + UNKNOWN UNKNOWN - + Are you sure to delete %1 from your savegames? Bist du sicher %1 von deinen Spielständen zu löschen? - - + + Delete Savegame Savegame löschen - + Failed at deleting %1 from your savegames Fehlgeschlagen beim Löschen %1 von deinen Spielständen - + &View A&nsehen - + + + &Remove Entfe&rnen - + + &Select Au&swählen - + + &Deselect A&bwählen - + + Select &All &Alles auswählen - + + &Deselect All Alles a&bwählen @@ -1609,7 +1856,9 @@ Drücke 1 für Standardmodus Spielstand kopieren - + + + &Export &Exportieren @@ -1657,13 +1906,13 @@ Drücke 1 für Standardmodus - - - - - - - + + + + + + + Snapmatic Properties Snapmatic Eigenschaften @@ -1690,7 +1939,7 @@ Drücke 1 für Standardmodus Mugshot - Fahndungsfoto + Polizeifoto @@ -1703,8 +1952,8 @@ Drücke 1 für Standardmodus Meme - - + + Snapmatic Title Snapmatic Titel @@ -1714,30 +1963,30 @@ Drücke 1 für Standardmodus Snapmatic Werte - + Crew: %1 (%2) Crew: %1 (%2) - + Title: %1 (%2) Titel: %1 (%2) - + Players: %1 (%2) Multiple Player are inserted here Spieler: %1 (%2) - + Player: %1 (%2) One Player is inserted here Spieler: %1 (%2) - - + + Appropriate: %1 Angemessen: %1 @@ -1758,71 +2007,81 @@ Drücke 1 für Standardmodus + Apply changes + Änderungen übernehmen + + + &Apply &Übernehmen - + + Discard changes + Änderungen verwerfen + + + &Cancel Abbre&chen - - - + + + Edit Bearbeiten - + Yes Yes, should work fine Ja - + No No, could lead to issues Nein - + <h4>Unsaved changes detected</h4>You want to save the JSON content before you quit? <h4>Ungespeicherte Änderungen erkannt</h4>Möchtest du den JSON Inhalt speichern bevor du verlässt? - + Patching of Snapmatic Properties failed because of %1 Patchen von Snapmatic Eigenschaften fehlgeschlagen wegen %1 - + Patching of Snapmatic Properties failed because of JSON Error Patchen von Snapmatic Eigenschaften fehlgeschlagen wegen JSON Fehler - - - - + + + + Patching of Snapmatic Properties failed because of I/O Error Patchen von Snapmatic Eigenschaften fehlgeschlagen wegen I/O Fehler - - + + New Snapmatic title: Neuer Snapmatic Titel: - - + + Snapmatic Crew Snapmatic Crew - - + + New Snapmatic crew: Neue Snapmatic Crew: @@ -1830,66 +2089,66 @@ Drücke 1 für Standardmodus SnapmaticPicture - + PHOTO - %1 FOTO - %1 - + open file %1 Datei öffnen %1 - + header not exists Header nicht existiert - + header is malformed Header fehlerhaft ist - + picture not exists (%1) Bild nicht existiert (%1) - + JSON not exists (%1) JSON nicht existiert (%1) - + title not exists (%1) Titel nicht existiert (%1) - + description not exists (%1) Beschreibung nicht existiert (%1) - + reading file %1 because of %2 Example for %2: JSON is malformed error Datei lesen von %1 weil %2 - - + + JSON is incomplete and malformed JSON ist unvollständig und Fehlerhaft - - + + JSON is incomplete JSON ist unvollständig - - + + JSON is malformed JSON ist Fehlerhaft @@ -1928,73 +2187,83 @@ Drücke 1 für Standardmodus - - + + Delete picture Bild löschen - + Are you sure to delete %1 from your Snapmatic pictures? Bist du sicher %1 von deine Snapmatic Bilder zu löschen? - + Failed to hide %1 In-game from your Snapmatic pictures Fehlgeschlagen beim Ausblenden von %1 im Spiel von deinen Snapmatic Bildern - + Failed to show %1 In-game from your Snapmatic pictures Fehlgeschlagen beim Anzeigen von %1 im Spiel von deinen Snapmatic Bildern - + + + Edi&t Bearbei&ten - + &Export &Exportieren - + + + Show &In-game &Im Spiel anzeigen - + + + Hide &In-game &Im Spiel ausblenden - + &View A&nsehen - + &Remove Entfe&rnen - + + &Select Au&swählen - + + &Deselect A&bwählen - + + Select &All Alles &auswählen - + + &Deselect All Alles a&bwählen @@ -2014,7 +2283,7 @@ Drücke 1 für Standardmodus Bild exportieren - + Failed at deleting %1 from your Snapmatic pictures Fehlgeschlagen beim Löschen von %1 von deinen Snapmatic Bildern @@ -2022,22 +2291,22 @@ Drücke 1 für Standardmodus TelemetryDialog - + %1 User Statistics %1 Benutzerstatistik - - You want help %1 to improve in the future by collection of data? - Möchtest du helfen %1 in der Zukunft zu verbessern durch Sammlung von Daten? + + You want help %1 to improve in the future by including personal usage data in your submission? + Sollen bei Einreichungen Persönliche Nutzungsdaten einbezogen werden um %1 in der Zukunft zu unterstützen? - - Yes, I would like to take part. - Ja, ich möchte teilnehmen. + + Yes, I want include personal usage data. + Ja, ich möchte Persönliche Nutzungsdaten einbeziehen. - + &OK &OK @@ -2107,21 +2376,29 @@ Drücke 1 für Standardmodus + + Change &Title... &Titel ändern... + + &Qualify as Avatar Als Avatar &qualifizieren + + Change &Players... S&pieler ändern... + + Change &Crew... &Crew ändern... @@ -2137,7 +2414,7 @@ Drücke 1 für Standardmodus - + Select &GTA V Folder... Wähle &GTA V Ordner... @@ -2153,6 +2430,9 @@ Drücke 1 für Standardmodus + + + &Close S&chließen @@ -2187,67 +2467,101 @@ Drücke 1 für Standardmodus Dateien &importieren... - - - + + + Select Profile Profil auswählen - - - + + + Select GTA V Folder... Wähle GTA V Ordner... - + Open File... Datei öffnen... - + %2 - %1 %2 - %1 - - + + &About %1 &Über %1 - - - - + + + &Donate + Spen&den + + + + Donate + Spenden + + + + Donation methods + Spendenmethoden + + + + &Copy + &Kopieren + + + View + Ansehen + + + Copy + Kopieren + + + + + + Open File Datei öffnen - + Can't open %1 because of not valid file format Kann nicht %1 öffnen weil Dateiformat nicht gültig ist + + + %1 - Messages + %1 - Nachrichten + &Reload &Neuladen - - - + + + Show In-game Im Spiel anzeigen - - - + + + Hide In-game Im Spiel ausblenden diff --git a/res/gta5sync_en_US.qm b/res/gta5sync_en_US.qm index 0554a5f..502289e 100644 Binary files a/res/gta5sync_en_US.qm and b/res/gta5sync_en_US.qm differ diff --git a/res/gta5sync_en_US.ts b/res/gta5sync_en_US.ts index bf2fe0f..1b4c531 100644 --- a/res/gta5sync_en_US.ts +++ b/res/gta5sync_en_US.ts @@ -6,11 +6,11 @@ About %1 - + - <span style=" font-weight:600;">%1</span><br/> + <span style="font-weight:600">%1</span><br/> <br/> %2<br/> <br/> @@ -20,81 +20,76 @@ Built with Qt %5<br/> Running with Qt %6<br/> <br/> %7 - + - + &Close - + - + Translated by %1 Translated by translator, example Translated by Syping - + - + TRANSLATOR Insert your name here and profile here in following scheme, First Translator,First Profile\nSecond Translator\nThird Translator,Second Profile - Syping,https://github.com/Syping/ + Syping,g5e://about?U3lwaW5n:R2l0TGFiOiA8YSBocmVmPSJodHRwczovL2dpdGxhYi5jb20vU3lwaW5nIj5TeXBpbmc8L2E+PGJyLz5HaXRIdWI6IDxhIGhyZWY9Imh0dHBzOi8vZ2l0aHViLmNvbS9TeXBpbmciPlN5cGluZzwvYT48YnIvPlNvY2lhbCBDbHViOiA8YSBocmVmPSJodHRwczovL3NvY2lhbGNsdWIucm9ja3N0YXJnYW1lcy5jb20vbWVtYmVyL1N5cGluZy80NjMwMzA1NiI+U3lwaW5nPC9hPg== - - A project for viewing and sync Grand Theft Auto V Snapmatic<br/> -Pictures and Savegames - - - - + A project for viewing Grand Theft Auto V Snapmatic<br/> Pictures and Savegames - - - - - Copyright &copy; <a href="%1">%2</a> %3 - + + Copyright &copy; <a href="%1">%2</a> %3 + + + + %1 is licensed under <a href="https://www.gnu.org/licenses/gpl-3.0.html#content">GNU GPLv3</a> - + - + Release - + - + Release Candidate - + - + Daily Build - + - + Developer - + - + Beta - + - + Alpha - + - + + Custom - + @@ -103,7 +98,7 @@ Pictures and Savegames No Crew - + @@ -111,118 +106,110 @@ Pictures and Savegames Dialog - + Export Format - + &JPEG/PNG format - + GTA &Snapmatic format - + Export Size - + Default &Size - + &Desktop Size - + &Custom Size - + Custom Size: - + x - + &Export - + &Close - + ImageEditorDialog - - - + + + + Snapmatic Image Editor - + - + Overwrite Image... - + - - - Capacity: %1 - + + Apply changes + - - ? - - - - - &Import... - - - - + &Overwrite - + - + + Discard changes + + + + &Close - + - + + Patching of Snapmatic Image failed because of I/O Error - + - + + Patching of Snapmatic Image failed because of Image Error - - - - - Every taken Snapmatic have a different Capacity, a Snapmatic with higher Capacity can store a picture with better quality. - + @@ -230,119 +217,281 @@ Pictures and Savegames Import... - + - - - + + Crop to Aspect Ratio + + + + + + + Background Colour: <span style="color: %1">%1</span> Background Color: <span style="color: %1">%1</span> - - - ... - + + Select background colour + Select background color - + Avatar - + Picture - + - - + + Ignore Aspect Ratio - + - + + Watermark + + + + Background - + - + + Background Image: %1 - + - - X - + + Select background image + - + + Remove background image + + + + Force Colour in Avatar Zone Force Color in Avatar Zone - - Import picture - + + Advanced + - &OK - + Resolution: + + Snapmatic resolution + + + + + Avoid compression and expand buffer instead, improves picture quality, but may break Snapmatic + + + + + Unlimited Buffer + + + + + Import as-is, don't change the picture at all, guaranteed to break Snapmatic unless you know what you doing + + + + + Import as-is + + + + + Import options + + + + + &Options + + + + + Import picture + + + + + &OK + + + + Discard picture - + - + &Cancel - + - - - + + + + Background Image: - + - - + + &Import new Picture... + + + + + &Crop Picture... + + + + + &Load Settings... + + + + + &Save Settings... + + + + + Custom Avatar Custom Avatar Description in SC, don't use Special Character! - + - - + + + Custom Picture Custom Picture Description in SC, don't use Special Character! - + - + + Storage + Background Image: Storage + + + + + Crop Picture... + + + + + &Crop + + + + + Crop Picture + + + + + + Please import a new picture first + + + + + + Default + Default as Default Profile + + + + + + + + + + + + + + + + + + + + + + + + Profile %1 + Profile %1 as Profile 1 + + + + + + Load Settings... + + + + + + Please select your settings profile + + + + + + Save Settings... + + + + + Snapmatic Avatar Zone - + - + + Are you sure to use a square image outside of the Avatar Zone? When you want to use it as Avatar the image will be detached! - + - + Select Colour... Select Color... - + File Background Image: File - + @@ -350,22 +499,32 @@ When you want to use it as Avatar the image will be detached! Snapmatic JSON Editor - + + Apply changes + + + + &Save - + - + + Discard changes + + + + &Close - + - + JSON Error - + @@ -373,39 +532,64 @@ When you want to use it as Avatar the image will be detached! Snapmatic Map Viewer - + - + + Close viewer + + + + &Close - + - + + Apply new position + + + + &Apply - + - + + Revert old position + + + + &Revert - + - - &Set - + + Select new position + - + + &Select + + + + + Quit select position + + + + &Done - + - + X: %1 Y: %2 X and Y position - + @@ -413,344 +597,389 @@ Y: %2 %1 - Settings - + Profiles - + Content Open/Select Mode - - - - - Open with Singleclick - - - - - Open with Doubleclick - + - Select with Singleclick - + Open with Doubleclick + Default Profile - + Custom GTA V Folder - + Force using Custom Folder - + ... - + Pictures - + Export Size - + Default: %1x%2 - + Screen Resolution: %1x%2 - + Custom Size: - + x - + Ignore Aspect Ratio - + Export Quality - + Enable Custom Quality - + Quality: - + %1% - + Picture Viewer - + Enable Navigation Bar - + Players - + ID - + Name - + - Feedback + Game + + + + + Social Club Version - + + + + + + + + Found: %1 + + + + + + + + + + + + + + Language: %1 + + + + + Steam Version + + + + + Feedback + + + + + Participate in %1 User Statistics - + Hardware, Application and OS Specification - + Application Configuration - + Other - - - + + + Participation ID: %1 - + &Copy - - - User Feedback - - - - - Limit: 1 message/day - - - - + Language for Areas - + - + Style - + - + Style: - + - + Font - + - - Always use Message Font (Windows 2003 and earlier) - - - - + Interface - + - + Participation - + Categories - + System Language Configuration - - &Send + + Personal Usage Data - - Language for Interface - - - - - - + Language for Interface + + + + + + + Current: %1 - + - + Use Default Style (Restart) - + - - Sync - + + Use Default Font (Restart) + - - Sync is not implemented at current time - + + Font: + - + Apply changes - + - + &OK OK, Cancel, Apply - + - + Discard changes - + - + &Cancel OK, Cancel, Apply - + - - %1 (Next Closest Language) - First language a person can talk with a different person/application. "Native" or "Not Native". - - - - + System System in context of System default - + - + + %1 (Game language) + Next closest language compared to the Game settings + + + + + %1 (Closest to Interface) Next closest language compared to the Interface - + - + + + Auto Automatic language choice. - + - + + %1 (Language priority) + First language a person can talk with a different person/application. "Native" or "Not Native". + + + + %1 %1 - + - + The new Custom Folder will initialise after you restart %1. The new Custom Folder will initialize after you restart %1. - + No Profile No Profile, as default - + - - - + + + Profile: %1 - + - + View %1 User Statistics Online - + Not registered - - A feedback message have to between 3-1024 characters long + + + + + Yes + + + + + + No + + + + + + OS defined + + + + + + Steam defined @@ -762,214 +991,212 @@ Y: %2 - - <span style=" font-weight:600;">Title: </span>%6<br/> -<span style=" font-weight:600;">Location: </span>%7 (%1, %2, %3)<br/> -<span style=" font-weight:600;">Players: </span>%4 (Crew %5)<br/> -<span style=" font-weight:600;">Created: </span>%8 - + + <span style="font-weight:600">Title: </span>%6<br/> +<span style="font-weight:600">Location: </span>%7 (%1, %2, %3)<br/> +<span style="font-weight:600">Players: </span>%4 (Crew %5)<br/> +<span style="font-weight:600">Created: </span>%8 + - + Manage picture - + - + &Manage - + - + Close viewer - + - + &Close - + - - + + Export as &Picture... - + - - + + Export as &Snapmatic... - + - - + + &Overwrite Image... - + - - + + &Edit Properties... - + - - + + Open &Map Viewer... - + - + Key 1 - Avatar Preview Mode Key 2 - Toggle Overlay Arrow Keys - Navigate - + - - + Snapmatic Picture Viewer - + - - + Failed at %1 - + - - - + + + No Players - + - - + + No Crew - + - + Unknown Location - + - + Avatar Preview Mode Press 1 for Default View - + - + Export as Picture... - + - - + + Export - + - + JPEG Graphics (*.jpg *.jpeg) - + - + Portable Network Graphics (*.png) - + - - + + + + + - - - Export as Picture - + - - + + Overwrite %1 with current Snapmatic picture? - + - - + + Failed to export current Snapmatic picture - - - - - - No valid file is selected - - - - - Failed to export the picture because the system occurred a write failure - + + + No valid file is selected + + + + + Failed to export the picture because the system occurred a write failure + + + + Failed to export the picture because the format detection failures - + - + Failed to export the picture because the file can't be written - + - + Failed to export the picture because of an unknown reason - + - + Export as Snapmatic... - + - + GTA V Export (*.g5e) - + - + GTA V Raw Export (*.auto) - + - + Snapmatic pictures (PGTA*) - + - - - - - + + + + + Export as Snapmatic - + - + Exported Snapmatic to "%1" because of using the .auto extension. - + - - + + Open &JSON Editor... - + @@ -977,53 +1204,53 @@ Press 1 for Default View Edit Players... - + Available Players: - + - + Selected Players: - + - + &Apply - + - + &Cancel - + - + Add Players... - + - + Failed to add more Players because the limit of Players are %1! - + - - + + Add Player... - + - + Enter Social Club Player ID - + - + Failed to add Player %1 because Player %1 is already added! - + @@ -1031,432 +1258,420 @@ Press 1 for Default View Profile Interface - + Loading file %1 of %2 files - + %1 %2 - + Import file - + &Import... - + Close profile - + &Close - + - - - + + + Export file %1 of %2 files - + - + Enabled pictures: %1 of %2 - + - + Loading... - + - + Snapmatic Loader - + - + <h4>Following Snapmatic Pictures got repaired</h4>%1 - + - - - - - Import... - - - - - - - - - - - - + + + + - - - - - + + + + - - - - + + + + + + + + + + Import... + + + + + + + + + Import - + - + Importable files (%1) - + - - + + GTA V Export (*.g5e) - + - - + + Savegames files (SGTA*) - + - - + + Snapmatic pictures (PGTA*) - + - - - + + + All image files (%1) - - - - - - - - All files (**) - - - - - - - No valid file is selected - - - - - - Import file %1 of %2 files - + + + + + All files (**) + + + + + + + No valid file is selected + + + + + + Import file %1 of %2 files + + + + Import failed with... %1 - + - - + + Failed to read Snapmatic picture - + - - + + Failed to read Savegame file - - - - - - - Can't import %1 because file can't be open - - - - - - - Can't import %1 because file can't be parsed properly - - - - - Can't import %1 because file format can't be detected - + + + + Can't import %1 because file can't be open + + + + + + + Can't import %1 because file can't be parsed properly + + + + + Can't import %1 because file format can't be detected + + + + Failed to import the Snapmatic picture, file not begin with PGTA or end with .g5e - + - - Failed to import the Snapmatic picture, the picture is already in the game - - - - + Failed to import the Snapmatic picture, can't copy the file into profile - + - + Failed to import the Savegame, can't copy the file into profile - + - + Failed to import the Savegame, no Savegame slot is left - + - - + + JPG pictures and GTA Snapmatic - + - - + + JPG pictures only - + - - + + GTA Snapmatic only - + - + %1Export Snapmatic pictures%2<br><br>JPG pictures make it possible to open the picture with a Image Viewer<br>GTA Snapmatic make it possible to import the picture into the game<br><br>Export as: - + - - - - - + + + + + Export selected... - + - + Initialising export... Initializing export... - + Export failed with... %1 - + - - + + No Snapmatic pictures or Savegames files are selected - + - - - + + + Remove selected - + - + You really want remove the selected Snapmatic picutres and Savegame files? - + - - + + Qualify as Avatar - - - - - - + + + + + + No Snapmatic pictures are selected - - - - + + + + Patch selected... - + - - - - - - - - + + + + + + + + Patch file %1 of %2 files - + - - - - - - + + + + + + %1 failed with... %2 Action failed with... - + - + Failed to remove all selected Snapmatic pictures and/or Savegame files - - Qualify - %1 failed with... - + + Prepare Content for Import... + - - + + A Snapmatic picture already exists with the uid %1, you want assign your import a new uid and timestamp? + + + + + Qualify + %1 failed with... + + + + + Change Players... - + Change Players %1 failed with... - + - - - + + + Change Crew... - + Failed to enter a valid Snapmatic Crew ID - + - + Change Crew %1 failed with... - + - - - + + + Change Title... - + Failed to enter a valid Snapmatic title - + - + Change Title %1 failed with... - + - + All profile files (*.g5e SGTA* PGTA*) - - - - - QApplication - - - Font - - - - - Selected Font: %1 - - - - - <h4>Welcome to %1!</h4>You want to configure %1 before you start using it? - + SavegameDialog - + Savegame Viewer - + - <span style=" font-weight:600;">Savegame</span><br><br>%1 - + <span style="font-weight:600">Savegame</span><br><br>%1 + &Export - + &Close - + - + Failed at %1 - + @@ -1464,88 +1679,96 @@ Press 1 for Default View Savegame Widget - + SAVE %3 - %1<br>%2 - + View savegame - + View - + Copy savegame - + Export - + Delete savegame - + Delete - + - + &View - + - + + + &Export - + - + + + &Remove - + - + + &Select - + - + + &Deselect - + - + + Select &All - + - + + &Deselect All - + Savegame files (SGTA*) - + All files (**) - + @@ -1553,73 +1776,73 @@ Press 1 for Default View Export Savegame - + Overwrite %1 with current Savegame? - + Failed to overwrite %1 with current Savegame - + Failed to export current Savegame - + No valid file is selected - + - + Export Savegame... - + - - + + AUTOSAVE - %1 %2 - + - - + + SAVE %3 - %1 %2 - + - - + + WRONG FORMAT - + - + UNKNOWN - + - + Are you sure to delete %1 from your savegames? - + - - + + Delete Savegame - + - + Failed at deleting %1 from your savegames - + @@ -1627,241 +1850,251 @@ Press 1 for Default View - - - - - - - + + + + + + + Snapmatic Properties - + Snapmatic Type - + Editor - + Selfie - + Regular - + Mugshot - + Meme - + Director - + Snapmatic Values - + - + Crew: %1 (%2) - + - + Title: %1 (%2) - + - + Players: %1 (%2) Multiple Player are inserted here - + - + Player: %1 (%2) One Player is inserted here - + - - + + Appropriate: %1 - + Extras - + Qualify as Avatar automatically at apply - + Qualify as Avatar allows you to use this Snapmatic as a Social Club profile picture - + + Apply changes + + + + &Apply - + - + + Discard changes + + + + &Cancel - + - - - + + + Edit - + - + Yes Yes, should work fine - + - + No No, could lead to issues - + - + <h4>Unsaved changes detected</h4>You want to save the JSON content before you quit? - + - + Patching of Snapmatic Properties failed because of %1 - + - + Patching of Snapmatic Properties failed because of JSON Error - + - - - - + + + + Patching of Snapmatic Properties failed because of I/O Error - + - - + + Snapmatic Title - + - - + + New Snapmatic title: - + - - + + Snapmatic Crew - + - - + + New Snapmatic crew: - + SnapmaticPicture - + PHOTO - %1 - + - + open file %1 - + - + header not exists - + - + header is malformed - + - + picture not exists (%1) - + - + JSON not exists (%1) - + - + title not exists (%1) - + - + description not exists (%1) - + - + reading file %1 because of %2 Example for %2: JSON is malformed error - + - - + + JSON is incomplete and malformed - - - - - - JSON is incomplete - + - + + JSON is incomplete + + + + + JSON is malformed - + @@ -1869,122 +2102,132 @@ Press 1 for Default View Snapmatic Widget - + PHOTO - 00/00/00 00:00:00 - + View picture - + View - + Copy picture - + Copy - + Export picture - + Export - + - - + + Delete picture - + Delete - + - + + + Edi&t - + - + + + Show &In-game - + - + + + Hide &In-game - + - + &Export - + - + &View - + - + &Remove - + - + + &Select - + - + + &Deselect - + - + + Select &All - + - + + &Deselect All - + - + Are you sure to delete %1 from your Snapmatic pictures? - + - + Failed at deleting %1 from your Snapmatic pictures - + - + Failed to hide %1 In-game from your Snapmatic pictures - + Failed to show %1 In-game from your Snapmatic pictures @@ -1992,22 +2235,22 @@ Press 1 for Default View TelemetryDialog - + + You want help %1 to improve in the future by including personal usage data in your submission? + + + + %1 User Statistics - - You want help %1 to improve in the future by collection of data? + + Yes, I want include personal usage data. - - Yes, I would like to take part. - - - - + &OK @@ -2016,208 +2259,245 @@ Press 1 for Default View UserInterface - + %2 - %1 - + Select profile - + %1 %2 - + Reload profile overview - + &Reload - + Close %1 Close %1 <- (gta5view/gta5sync) - %1 will be replaced automatically - + + + + &Close - + &File - + &Help - + &Edit - + &Profile - + &Selection visibility - + - - + + &About %1 - + &Exit - + Exit - + Close &Profile - + &Settings - + Select &All - + &Deselect All - + &Export selected... - + &Remove selected - + &Import files... - + &Open File... - + - + Select &GTA V Folder... - + - - - + + + Select GTA V Folder... - + Show In-gam&e - + Hi&de In-game - + + + Change &Players... - + Selection &mass tools - + + + Change &Title... - + + + Change &Crew... - + + + &Qualify as Avatar - - - - - - - Select Profile - + + + + Select Profile + + + + + + &Donate + + + + + Donate + + + + + Donation methods + + + + + &Copy + + + + Open File... - + - - - - + + + + Open File - + - + Can't open %1 because of not valid file format - + - - - + + %1 - Messages + + + + + + Show In-game - - - + + + Hide In-game diff --git a/res/gta5sync_fr.qm b/res/gta5sync_fr.qm index e8bc8a7..5b24a03 100644 Binary files a/res/gta5sync_fr.qm and b/res/gta5sync_fr.qm differ diff --git a/res/gta5sync_fr.ts b/res/gta5sync_fr.ts index 40cff02..7e90ba6 100644 --- a/res/gta5sync_fr.ts +++ b/res/gta5sync_fr.ts @@ -10,7 +10,7 @@ - <span style=" font-weight:600;">%1</span><br/> + <span style="font-weight:600">%1</span><br/> <br/> %2<br/> <br/> @@ -20,7 +20,7 @@ Built with Qt %5<br/> Running with Qt %6<br/> <br/> %7 - <span style=" font-weight:600;">%1</span><br/> + <span style="font-weight:600">%1</span><br/> <br/> %2<br/> <br/> @@ -32,78 +32,73 @@ Fonctionne avec Qt %6<br/> %7 - + &Close &Fermer - + Translated by %1 Translated by translator, example Translated by Syping Traduit par %1 - + TRANSLATOR Insert your name here and profile here in following scheme, First Translator,First Profile\nSecond Translator\nThird Translator,Second Profile - Ganjalo,https://github.com/Ganjalo/ + Ganjalo,https://github.com/Ganjalo/ +XeriosG,g5e://about?WGVyaW9zRw:RGlzY29yZDogWGVyaW9zRyM1MzIxPGJyLz5TdGVhbTogPGEgaHJlZj0iaHR0cHM6Ly9zdGVhbWNvbW11bml0eS5jb20vcHJvZmlsZXMvNzY1NjExOTg0MjU2NjU3MjQvIj5YZXJpb3NHPC9hPg - + A project for viewing Grand Theft Auto V Snapmatic<br/> Pictures and Savegames Un outil pour gérer les photos Snapmatic<br/> et les fichiers de sauvegarde de Grand Theft Auto V - + Copyright &copy; <a href="%1">%2</a> %3 Copyright &copy; <a href="%1">%2</a> %3 - + %1 is licensed under <a href="https://www.gnu.org/licenses/gpl-3.0.html#content">GNU GPLv3</a> %1 est distribué sous license <a href="https://www.gnu.org/licenses/gpl-3.0.html#content">GNU GPLv3</a> - - A project for viewing and sync Grand Theft Auto V Snapmatic<br/> -Pictures and Savegames - Un outil pour gérer et synchroniser les photos Snapmatic<br/> -et les fichiers de sauvegarde de Grand Theft Auto V - - - + Release Release - + Release Candidate Release Candidate - + Daily Build Daily Build - + Developer Developer - + Beta Beta - + Alpha Alpha - + + Custom Personnalisé @@ -183,58 +178,50 @@ et les fichiers de sauvegarde de Grand Theft Auto V ImageEditorDialog - - - + + + + Snapmatic Image Editor Éditeur d'images Snapmatic - + Overwrite Image... Remplacer l'image... - - - Capacity: %1 - Capacité : %1 + + Apply changes + Appliquer les modifications - - ? - ? - - - - &Import... - &Importer... - - - + &Overwrite &Remplacer - + + Discard changes + Annuler les modifications + + + &Close &Fermer - + + Patching of Snapmatic Image failed because of I/O Error Échec du patch Snapmatic : I/O Error - + + Patching of Snapmatic Image failed because of Image Error Échec du patch Snapmatic : Image Error - - - Every taken Snapmatic have a different Capacity, a Snapmatic with higher Capacity can store a picture with better quality. - Tous les Snapmatic ont une Capacité différente, un Snapmatic avec une haute Capacité peut stocker une image de meilleure qualité. - ImportDialog @@ -244,13 +231,13 @@ et les fichiers de sauvegarde de Grand Theft Auto V Importer... - - + + Ignore Aspect Ratio Déverrouiller le ratio d'aspect - + Avatar Avatar @@ -260,98 +247,268 @@ et les fichiers de sauvegarde de Grand Theft Auto V Image - + + Watermark + Filigrane + + + Background Fond - - - + + + + Background Colour: <span style="color: %1">%1</span> Couleur de fond : <span style="color: %1">%1</span> - - - ... - ... + + Select background colour + Choisir la couleur de fond - + ... + ... + + + + Select background image + Choisir l'image de fond + + + + Remove background image + Supprimer l'image de fond + + + + Background Image: %1 Image de fond : %1 - X - X + X - + + Crop to Aspect Ratio + Recadrage au ratio d'aspect + + + Force Colour in Avatar Zone - Forcer la couleur dans la Zone d'Avatar + Forcer la couleur dans la zone d'avatar - + + Advanced + Avancé + + + + Resolution: + Résolution : + + + + Snapmatic resolution + Résolution Snapmatic + + + + Avoid compression and expand buffer instead, improves picture quality, but may break Snapmatic + Éviter la compression et étendre la mémoire tampon à la place, améliore la qualité de l'image mais peut casser Snapmatic + + + + Unlimited Buffer + Mémoire tampon illimitée + + + + Import as-is, don't change the picture at all, guaranteed to break Snapmatic unless you know what you doing + Importer tel quel,ne changez pas du tout l'image, garantie de casser Snapmatic à moins que vous ne sachiez ce que vous faites + + + + Import as-is + Importer tel quel + + + + Import options + Options d'importation + + + + &Options + &Options + + + Import picture Importer l'image - + &OK &OK - + Discard picture Supprimer l'image - + &Cancel - A&nnuler + &Annuler - - - + + + + Background Image: Image de fond : - - + + &Import new Picture... + &Importer une nouvelle image... + + + + &Crop Picture... + &Rogner l'image... + + + + &Load Settings... + &Charger les paramètres... + + + + &Save Settings... + &Sauvegarder les paramètres... + + + + Custom Avatar Custom Avatar Description in SC, don't use Special Character! Avatar personnalisé - - + + + Custom Picture Custom Picture Description in SC, don't use Special Character! Image personnalisé - + + Storage + Background Image: Storage + Stockage + + + + Crop Picture... + Rogner l'image... + + + + &Crop + &Rogner + + + + Crop Picture + Rogner l'image + + + + + Please import a new picture first + Veuillez d'abord importer une nouvelle image + + + + + Default + Default as Default Profile + Défaut + + + + + + + + + + + + + + + + + + + + + + + Profile %1 + Profile %1 as Profile 1 + Profil %1 + + + + + Load Settings... + Charger les paramètres... + + + + + Please select your settings profile + Veuillez choisir votre profil de paramètres + + + + + Save Settings... + Sauvegarder les paramètres... + + + + Are you sure to use a square image outside of the Avatar Zone? When you want to use it as Avatar the image will be detached! Êtes-vous sûr d'utiliser une image carrée en dehors de la Zone d'Avatar ? Si vous l'utilisez comme Avatar, l'image sera détachée ! - + + Snapmatic Avatar Zone - Zone d'Avatar Snapmatic + Zone d'avatar Snapmatic - + Select Colour... Choisir une couleur... - + File Background Image: File Fichier @@ -366,16 +523,26 @@ Si vous l'utilisez comme Avatar, l'image sera détachée ! + Apply changes + Appliquer les modifications + + + &Save &Sauvegarder - + + Discard changes + Annuler les modifications + + + &Close &Fermer - + JSON Error Erreur JSON @@ -385,35 +552,60 @@ Si vous l'utilisez comme Avatar, l'image sera détachée ! Snapmatic Map Viewer - Visionneuse de Carte Snapmatic + Visionneuse de carte Snapmatic - + + Close viewer + Fermer la visionneuse + + + &Close &Fermer - + + Apply new position + Appliquer la nouvelle position + + + &Apply &Appliquer - + + Revert old position + Revenir à l'ancienne position + + + &Revert &Revenir - - &Set - &Définir + + Select new position + Sélectionner la nouvelle position - + + &Select + &Sélectionner + + + + Quit select position + Quitter la sélection de position + + + &Done &Terminer - + X: %1 Y: %2 X and Y position @@ -439,19 +631,17 @@ Y : %2 Ouverture/Sélection du contenu - Open with Singleclick - Ouvrir avec un clic + Ouvrir avec un clic - + Open with Doubleclick Ouvrir avec un double-clic - Select with Singleclick - Sélectionner avec un clic + Sélectionner avec un clic @@ -556,377 +746,432 @@ Y : %2 + Game + Jeu + + + + Social Club Version + Version du Social Club + + + + + + + + + + + Found: %1 + Trouvé : %1 + + + + + + + + + + + + + Language: %1 + Langue : %1 + + + + Steam Version + Version de Steam + + + Feedback Feedback - + Participation Participation - - + + Participate in %1 User Statistics Participer aux statistiques d'usage %1 - + Categories Catégories - + Hardware, Application and OS Specification Matériel, applications et OS - + System Language Configuration Langage système - + Application Configuration Configuration de l'application - + Other Autres - - - + + + Participation ID: %1 ID de participation : %1 - + &Copy &Copier - - - User Feedback - Feedback utilisateur - - - - Limit: 1 message/day - Limite : 1 message/jour - - - - &Send - &Envoyer - - - + Language for Areas Langage des Zones - + Style Style - + Style: Style : - + Font Police - Always use Message Font (Windows 2003 and earlier) - Toujours utiliser la police Message (Windows 2003 et précédent) + Toujours utiliser la police Message (Windows 2003 et précédent) - + Interface Interface - + + Personal Usage Data + Données d'utilisation + + + Language for Interface Langage de l'interface - - - - + + + + Current: %1 Actuel : %1 - + Use Default Style (Restart) - Utiliser le Style par Défaut (rédémarrage requis) + Utiliser le style par défaut (redémarrage requis) - - Sync - Synchronisation + + Use Default Font (Restart) + Utiliser la police par défaut (redémarrage requis) - - Sync is not implemented at current time - La synchronisation n'est pas encore implémentée + + Font: + Police : - + Apply changes Appliquer les changements - + &OK OK, Cancel, Apply &OK - + Discard changes Annuler les changements - + &Cancel OK, Cancel, Apply &Annuler - - %1 (Next Closest Language) - First language a person can talk with a different person/application. "Native" or "Not Native". - %1 (Langage proche) - - - + System System in context of System default Système - + + %1 (Game language) + Next closest language compared to the Game settings + %1 (Langue du jeu) + + + + %1 (Closest to Interface) Next closest language compared to the Interface %1 (Langage proche de l'interface) - + + + Auto Automatic language choice. Automatique - + + %1 (Language priority) + First language a person can talk with a different person/application. "Native" or "Not Native". + %1 (Priorité de la langue) + + + %1 %1 %1 - + The new Custom Folder will initialise after you restart %1. Le nouveau Dossier personnalisé sera initialisé au redémarrage de %1. - + View %1 User Statistics Online Voir les statistiques d'usage %1 en ligne - + Not registered Pas enregistré - - A feedback message have to between 3-1024 characters long - Un message doit contenir 3 à 1024 charactères + + + + + Yes + Oui - + + + No + Non + + + + + OS defined + Défini par le système d'exploitation + + + + + Steam defined + Défini par Steam + + + No Profile No Profile, as default Aucun profil - - - + + + Profile: %1 Profil : %1 PictureDialog - - %1 - Snapmatic Picture Viewer - %1 - Visionneuse de photo Snapmatic - Snapmatic Picture Viewer - %1 Visionneuse de photo Snapmatic - %1 - - <span style=" font-weight:600;">Title: </span>%6<br/> -<span style=" font-weight:600;">Location: </span>%7 (%1, %2, %3)<br/> -<span style=" font-weight:600;">Players: </span>%4 (Crew %5)<br/> -<span style=" font-weight:600;">Created: </span>%8 - <span style=" font-weight:600;">Titre : </span>%6<br/> -<span style=" font-weight:600;">Emplacement : </span>%7 (%1, %2, %3)<br/> -<span style=" font-weight:600;">Joueurs : </span>%4 (Crew %5)<br/> -<span style=" font-weight:600;">Créé le : </span>%8 + + <span style="font-weight:600">Title: </span>%6<br/> +<span style="font-weight:600">Location: </span>%7 (%1, %2, %3)<br/> +<span style="font-weight:600">Players: </span>%4 (Crew %5)<br/> +<span style="font-weight:600">Created: </span>%8 + <span style="font-weight:600">Titre : </span>%6<br/> +<span style="font-weight:600">Emplacement : </span>%7 (%1, %2, %3)<br/> +<span style="font-weight:600">Joueurs : </span>%4 (Crew %5)<br/> +<span style="font-weight:600">Créé le : </span>%8 - + Manage picture Gestion de l'image - + &Manage &Gestion - + Close viewer Fermer la visionneuse - + &Close &Fermer - + Failed to export the picture because the system occurred a write failure Échec de l'export de l'image : erreur d'écriture - + Failed to export the picture because the format detection failures Échec de l'export de l'image : erreur de détection du format - + Failed to export the picture because the file can't be written Échec de l'export de l'image : impossible d'écrire dans le fichier - + Failed to export the picture because of an unknown reason Échec de l'export de l'image : erreur inconnue - + Export as Snapmatic... Exporter comme Snapmatic... - + GTA V Export (*.g5e) GTA V Export (*.g5e) - + GTA V Raw Export (*.auto) GTA V Export Brut (*.g5e) - + Snapmatic pictures (PGTA*) Fichiers GTA Snapmatic (PGTA*) - - - - - + + + + + Export as Snapmatic Exporter comme Snapmatic - + Exported Snapmatic to "%1" because of using the .auto extension. Exporté comme "%1" avec l'utilisation de l'extension .auto. - - + + Overwrite %1 with current Snapmatic picture? %1 existe déjà. Vous-vous le remplacer ? - + Export as Picture... Exporter comme image... - + JPEG Graphics (*.jpg *.jpeg) JPEG Graphics (*.jpg *.jpeg) - - + + + + + - - - Export as Picture Exporter comme image - - + + No valid file is selected Fichier invalide - - + + Export as &Picture... Exporter comme &image... - - + + Export as &Snapmatic... Exporter comme &Snapmatic... - - + + &Overwrite Image... &Remplacer l'image... - - + + &Edit Properties... Modifier les &propriétés... - - + + Open &Map Viewer... Ouvrir la &Visionneuse de Carte... - + Key 1 - Avatar Preview Mode Key 2 - Toggle Overlay Arrow Keys - Navigate @@ -935,62 +1180,60 @@ Touche 2 - Activer/désactiver l'overlay Touches fléchées - Naviguer - - + Snapmatic Picture Viewer Visionneuse de photo Snapmatic - - + Failed at %1 Echec de %1 - - + + No Crew Aucun crew - - - + + + No Players Aucun joueurs - + Avatar Preview Mode Press 1 for Default View Mode Aperçu Avatar Appuyer sur 1 pour le mode par défaut - + Unknown Location Emplacement inconnu - - + + Export Exporter - + Portable Network Graphics (*.png) Portable Network Graphics (*.png) - - + + Failed to export current Snapmatic picture Échec de l'export de la photo Snapmatic - - + + Open &JSON Editor... Ouvrir l'éditeur &JSON... @@ -1008,43 +1251,43 @@ Appuyer sur 1 pour le mode par défaut Joueurs disponibles : - + Selected Players: Joueurs sélectionnés : - + &Apply A&ppliquer - + &Cancel A&nnuler - + Add Players... Ajouter des joueurs... - + Failed to add more Players because the limit of Players are %1! Échec de l'ajout de joueurs : la limite de %1 est atteinte ! - - + + Add Player... Ajouter un joueur... - + Enter Social Club Player ID Entrer l'ID Social Club du joueur - + Failed to add Player %1 because Player %1 is already added! Échec de l'ajout du joueur %1 car le joueur %1 est déjà ajouté ! @@ -1087,99 +1330,100 @@ Appuyer sur 1 pour le mode par défaut &Fermer - - - + + + Export file %1 of %2 files Copie du fichier %1 sur %2 - + Enabled pictures: %1 of %2 Photos activées : %1 sur %2 - + Loading... Chargement... - + Snapmatic Loader Snapmatic Loader - + <h4>Following Snapmatic Pictures got repaired</h4>%1 <h4>Les Snapmatic suivants ont été répaés</h4>%1 - - - - + + + + + + + + + + + + + + + + + + + Import... Importer... - - - - - - - - - - - - - - - - - - - - + + + + + + Import Importer - - + + Savegames files (SGTA*) Fichiers de sauvegarde GTA (SGTA*) - - + + Snapmatic pictures (PGTA*) Photos Snapmatic (PGTA*) - - - + + + All image files (%1) Toutes les images (%1) - - - - + + + + All files (**) Tous les fichiers (**) - - + + Import file %1 of %2 files Importation du fichier %1 sur %2 - + Import failed with... %1 @@ -1188,153 +1432,148 @@ Appuyer sur 1 pour le mode par défaut %1 - - - + + + No valid file is selected Fichier invalide - + Importable files (%1) Fichiers importables (%1) - - + + Failed to read Snapmatic picture Impossible d'ouvrir la photo Snapmatic - - + + Failed to read Savegame file Impossible de lire le fichier de sauvegarde - - - + + + Can't import %1 because file can't be open Impossible d'importer %1, le fichier ne peut pas être ouvert - - - + + + Can't import %1 because file can't be parsed properly Impossible d'importer %1, le fichier ne peut pas être parsé correctement - + Can't import %1 because file format can't be detected Impossible d'importer %1, le format du fichier n'est pas détecté - + Failed to import the Snapmatic picture, file not begin with PGTA or end with .g5e Impossible d'importer la photo Snapmatic,nom de fichier incorrect (PGTA*, *.g5e) - - Failed to import the Snapmatic picture, the picture is already in the game - Impossible d'importer la photo Snapmatic, un fichier du même nom existe déjà - - - + Failed to import the Snapmatic picture, can't copy the file into profile Impossible d'importer la photo Snapmatic, impossible de copier le fichier dans le profil - + Failed to import the Savegame, can't copy the file into profile Impossible d'importer la sauvegarde, impossible de copier le fichier dans le profil - + Failed to import the Savegame, no Savegame slot is left Impossible d'importer la sauvegarde, aucun emplacement libre - - + + JPG pictures and GTA Snapmatic Images JPG et GTA Snapmatic - - + + JPG pictures only Images JPG seulement - - + + GTA Snapmatic only GTA Snapmatic seulement - + %1Export Snapmatic pictures%2<br><br>JPG pictures make it possible to open the picture with a Image Viewer<br>GTA Snapmatic make it possible to import the picture into the game<br><br>Export as: %1Exporter les photos Snapmatic%2<br><br>Les fichiers JPG permettent d'ouvrir les photos avec une visionneuse d'images<br>Les GTA Snapmatic permettent d'importer les photos dans le jeu<br><br>Exporter comme : - - - - - + + + + + Export selected... Exporter la sélection... - + Initialising export... Initialisation de l'export... - - + + Qualify as Avatar Qualifier comme Avatar - - - - - - + + + + + + No Snapmatic pictures are selected Aucun Snapmatic sélectionné - - - - + + + + Patch selected... Patcher la sélection... - - - - - - - - + + + + + + + + Patch file %1 of %2 files Patch du fichier %1 sur %2 - - - - - - + + + + + + %1 failed with... %2 @@ -1344,66 +1583,76 @@ Appuyer sur 1 pour le mode par défaut %2 - + Failed to remove all selected Snapmatic pictures and/or Savegame files Échec de la supression des Snapmatic et/ou des fichiers de sauvegarde sélectionnés - + + Prepare Content for Import... + Préparation du contenu pour l'import... + + + + A Snapmatic picture already exists with the uid %1, you want assign your import a new uid and timestamp? + Un Snapmatic existe déjà avec le uid %1, voulez-vous assigner à votre import un nouvel uid et timestamp ? + + + Qualify %1 failed with... Qualifier - - + + Change Players... Modifier les joueurs... - + Change Players %1 failed with... Modifier les joueurs - - - + + + Change Crew... Modifier le Crew... - + Failed to enter a valid Snapmatic Crew ID Snapmatic Crew ID invalide - + Change Crew %1 failed with... Changer le Crew - - - + + + Change Title... Changer le titre... - + Failed to enter a valid Snapmatic title Titre Snapmatic invalide - + Change Title %1 failed with... Changer le titre - + Export failed with... %1 @@ -1412,31 +1661,31 @@ Appuyer sur 1 pour le mode par défaut %1 - - + + No Snapmatic pictures or Savegames files are selected Aucun fichier de sauvegarde ou photo Snapmatic sélectionné - - - + + + Remove selected Supprimer la sélection - + You really want remove the selected Snapmatic picutres and Savegame files? Supprimer la sélection ? - + All profile files (*.g5e SGTA* PGTA*) Tous les fichiers de profil (*.g5e SGTA* PGTA*) - - + + GTA V Export (*.g5e) GTA V Export (*.g5e) @@ -1444,33 +1693,30 @@ Appuyer sur 1 pour le mode par défaut QApplication - Font - Police + Police - Selected Font: %1 - Police sélectionnée : %1 + Police sélectionnée : %1 - <h4>Welcome to %1!</h4>You want to configure %1 before you start using it? - <h4>Bienvenue sur %1!</h4>Voulez-vous configurer %1 avant de l'utiliser t? + <h4>Bienvenue sur %1!</h4>Voulez-vous configurer %1 avant de l'utiliser t? SavegameDialog - + Savegame Viewer Gestionnaire de sauvegardes - <span style=" font-weight:600;">Savegame</span><br><br>%1 - <span style=" font-weight:600;">Sauvegarde</span><br><br>%1 + <span style="font-weight:600">Savegame</span><br><br>%1 + <span style="font-weight:600">Sauvegarde</span><br><br>%1 @@ -1483,7 +1729,7 @@ Appuyer sur 1 pour le mode par défaut &Fermer - + Failed at %1 Échec de %1 @@ -1532,7 +1778,9 @@ Appuyer sur 1 pour le mode par défaut Supprimer - + + + &Export &Exporter @@ -1575,80 +1823,86 @@ Appuyer sur 1 pour le mode par défaut Fichier invalide - + Export Savegame... Exporter la sauvegarde... - - + + AUTOSAVE - %1 %2 SAUVEGARDE AUTO - %1 %2 - - + + SAVE %3 - %1 %2 SAUVEGARDE %3 - %1 %2 - - + + WRONG FORMAT Format invalide - + UNKNOWN Inconnu - + Are you sure to delete %1 from your savegames? Supprimer %1 ? - - + + Delete Savegame Supprimer la sauvegarde - + Failed at deleting %1 from your savegames Impossible de supprimer %1 - + &View &Voir - + + + &Remove &Supprimer - + + &Select &Sélectionner - + + &Deselect &Déselectionner - + + Select &All Sélectionner to&ut - + + &Deselect All &Déselectionner tout @@ -1658,13 +1912,13 @@ Appuyer sur 1 pour le mode par défaut - - - - - - - + + + + + + + Snapmatic Properties Propriétés Snapmatic @@ -1704,8 +1958,8 @@ Appuyer sur 1 pour le mode par défaut Meme - - + + Snapmatic Title Titre Snapmatic @@ -1715,30 +1969,30 @@ Appuyer sur 1 pour le mode par défaut Valeurs Snapmatic - + Crew: %1 (%2) Crew : %1 (%2) - + Title: %1 (%2) Titre : %1 (%2) - + Players: %1 (%2) Multiple Player are inserted here Joueurs : %1 (%2) - + Player: %1 (%2) One Player is inserted here Joueur : %1 (%2) - - + + Appropriate: %1 Valide : %1 @@ -1759,73 +2013,83 @@ Appuyer sur 1 pour le mode par défaut - &Apply - A&ppliquer + Apply changes + Appliquer les modifications - + + &Apply + &Appliquer + + + + Discard changes + Annuler les modifications + + + &Cancel A&nnuler - - - + + + Edit Éditer - + Yes Yes, should work fine Oui, devrait fonctionner Oui - + No No, could lead to issues Non, pourrait causer des erreurs Non - + <h4>Unsaved changes detected</h4>You want to save the JSON content before you quit? <h4>Modifications détectées</h4>Voulez-vous sauvegarder le contenu JSON avant de quitter ? - + Patching of Snapmatic Properties failed because of %1 Patch des propriétés Snapmatic échoué : %1 - + Patching of Snapmatic Properties failed because of JSON Error Patch des propriétés Snapmatic échoué : erreur JSON - - - - + + + + Patching of Snapmatic Properties failed because of I/O Error La modification des propriétés Snapmatic a échoué : erreur d'entrée/sortie - - + + New Snapmatic title: Nouveau titre Snapmatic : - - + + Snapmatic Crew Crew Snapmatic - - + + New Snapmatic crew: Nouveau crew Snapmatic : @@ -1833,66 +2097,66 @@ Appuyer sur 1 pour le mode par défaut SnapmaticPicture - + PHOTO - %1 PHOTO - %1 - + open file %1 ouverture du fichier %1 - + header not exists les headers n'existent pas - + header is malformed les headers sont incorrects - + picture not exists (%1) l'image n'existe pas (%1) - + JSON not exists (%1) le JSON n'existe pas (%1) - + title not exists (%1) le titre n'existe pas (%1) - + description not exists (%1) la description n'existe pas (%1) - + reading file %1 because of %2 Example for %2: JSON is malformed error lecture du fichier %1 : %2 - - + + JSON is incomplete and malformed JSON incomplet ou incorrect - - + + JSON is incomplete JSON incomplet - - + + JSON is malformed JSON incorrect @@ -1941,8 +2205,8 @@ Appuyer sur 1 pour le mode par défaut - - + + Delete picture Supprimer la photo @@ -1952,72 +2216,82 @@ Appuyer sur 1 pour le mode par défaut Supprimer - + Are you sure to delete %1 from your Snapmatic pictures? Supprimer %1 ? - + Failed at deleting %1 from your Snapmatic pictures Impossible de supprimer %1 - + Failed to hide %1 In-game from your Snapmatic pictures %1 n'a pas pu être rendu invisible en jeu - + Failed to show %1 In-game from your Snapmatic pictures %1 n'a pas pu être rendu visible en jeu - + + + Edi&t Édi&ter - + + + Show &In-game &Visible en jeu - + + + Hide &In-game &Invisible en jeu - + &Export &Exporter - + &View &Voir - + &Remove S&upprimer - + + &Select &Sélectionner - + + &Deselect &Déselectionner - + + Select &All Sélectionner &tout - + + &Deselect All &Déselectionner tout @@ -2025,22 +2299,22 @@ Appuyer sur 1 pour le mode par défaut TelemetryDialog - + + You want help %1 to improve in the future by including personal usage data in your submission? + Voulez-vous aider au développement de %1 en transmettant vos données d'utilisation ? + + + %1 User Statistics - %1 Statistiques d'usage + Statistiques utilisateurs %1 - - You want help %1 to improve in the future by collection of data? - Voulez-vous aider à améliorer %1 en envoyant vos données d'utilisation ? + + Yes, I want include personal usage data. + Oui, je veux partager mes données d'utilisation. - - Yes, I would like to take part. - Oui, je veux participer. - - - + &OK &OK @@ -2075,6 +2349,9 @@ Appuyer sur 1 pour le mode par défaut + + + &Close Fer&mer @@ -2150,15 +2427,15 @@ Appuyer sur 1 pour le mode par défaut - + Select &GTA V Folder... Modifier l'emplacement de &GTA V... - - - + + + Select GTA V Folder... Modifier l'emplacement de GTA V... @@ -2169,6 +2446,8 @@ Appuyer sur 1 pour le mode par défaut + + Change &Players... Modifier les &joueurs... @@ -2179,16 +2458,22 @@ Appuyer sur 1 pour le mode par défaut + + Change &Title... Changer le &titre... + + Change &Crew... Changer le &Crew... + + &Qualify as Avatar &Qualifier comme Avatar @@ -2204,53 +2489,87 @@ Appuyer sur 1 pour le mode par défaut - + %2 - %1 %2 - %1 - - + + &About %1 &À propos de %1 - - - + + + Select Profile Sélectionner un profil - + + + &Donate + &Don + + + + Donate + Don + + + + Donation methods + Méthodes de don + + + View + Voir + + + Copy + Copier + + + + &Copy + &Copier + + + Open File... Ouvrir... - - - - + + + + Open File Ouvrir - + Can't open %1 because of not valid file format Impossible d'ouvrir %1, format invalide - - - + + %1 - Messages + %1 - Nouvelles + + + + + Show In-game Visible en jeu - - - + + + Hide In-game Invisible en jeu diff --git a/res/gta5sync_ko.qm b/res/gta5sync_ko.qm new file mode 100644 index 0000000..b100265 Binary files /dev/null and b/res/gta5sync_ko.qm differ diff --git a/res/gta5sync_ko.ts b/res/gta5sync_ko.ts new file mode 100644 index 0000000..d2ebaa3 --- /dev/null +++ b/res/gta5sync_ko.ts @@ -0,0 +1,2603 @@ + + + + + AboutDialog + + + About %1 + %1 ì •ë³´ + + + + <span style="font-weight:600">%1</span><br/> +<br/> +%2<br/> +<br/> +Version %3<br/> +Created on %4<br/> +Built with Qt %5<br/> +Running with Qt %6<br/> +<br/> +%7 + <span style="font-weight:600">%1</span><br/> +<br/> +%2<br/> +<br/> +버전 %3<br/> +ìƒì„±ë¨ %4<br/> +Qt로 제작 %5<br/> +Qt로 실행 %6<br/> +<br/> +%7 + + + + &Close + 닫기(&C) + + + + Translated by %1 + Translated by translator, example Translated by Syping + 번역 %1 + + + + TRANSLATOR + Insert your name here and profile here in following scheme, First Translator,First Profile\nSecond Translator\nThird Translator,Second Profile + 앙시모사우루스,https://steamcommunity.com/profiles/76561198166105984/ + + + + A project for viewing Grand Theft Auto V Snapmatic<br/> +Pictures and Savegames + ì´ í”„ë¡œê·¸ëž¨ì€ GTA 5 ìŠ¤ëƒ…ë§¤í‹±ì„ ìˆ˜ì •í•˜ê³  보기 위한 프로ì íŠ¸ìž…ë‹ˆë‹¤.<br/> +ì´ë¯¸ì§€ ë·°ì–´ ë° ì„¸ì´ë¸Œ íŒŒì¼ ê´€ë¦¬ ì§€ì› + + + + Copyright &copy; <a href="%1">%2</a> %3 + 저작권 &copy; <a href="%1">%2</a> %3 + + + + %1 is licensed under <a href="https://www.gnu.org/licenses/gpl-3.0.html#content">GNU GPLv3</a> + %1는 <a href="https://www.gnu.org/licenses/gpl-3.0.html#content">GNU GPLv3</a> ì— ë”°ë¼ ë¼ì´ì„¼ìŠ¤ê°€ 부여ë©ë‹ˆë‹¤ + + + + Release + 릴리즈 + + + + Release Candidate + 릴리즈 후보 + + + + Daily Build + ì¼ì¼ 빌드 + + + + Developer + ê°œë°œìž + + + + Beta + 베타 + + + + Alpha + 알파 + + + + + Custom + ì‚¬ìš©ìž ì§€ì • + + + + CrewDatabase + + + + No Crew + ì¡°ì§ ì—†ìŒ + + + + ExportDialog + + + Dialog + 다ì´ì–¼ë¡œê·¸ + + + + Export Format + 내보낼 í˜•ì‹ + + + + &JPEG/PNG format + JPEG/PNG 형ì‹(&J) + + + + GTA &Snapmatic format + GTA 스냅매틱 형ì‹(&S) + + + + Export Size + 내보낼 í¬ê¸° + + + + Default &Size + 기본 í¬ê¸°(&S) + + + + &Desktop Size + 바탕화면 í¬ê¸°(&D) + + + + &Custom Size + ì‚¬ìš©ìž ì •ì˜ í¬ê¸°(&C) + + + + Custom Size: + ì‚¬ìš©ìž ì •ì˜ í¬ê¸°: + + + + x + x + + + + &Export + 내보내기(&E) + + + + &Close + 닫기(&C) + + + + ImageEditorDialog + + + Overwrite Image... + ì´ë¯¸ì§€ ë®ì–´ì“°ê¸° + + + + Apply changes + 변경 사항 ì ìš© + + + + &Overwrite + ë®ì–´ì“°ê¸°(&O) + + + + Discard changes + 변경 사항 무시 + + + + &Close + 닫기(&C) + + + + + + + Snapmatic Image Editor + 스냅매틱 ì´ë¯¸ì§€ 편집기 + + + + + Patching of Snapmatic Image failed because of I/O Error + I/O 오류로 ì¸í•´ 스냅매틱 ì´ë¯¸ì§€ë¥¼ 패치하지 못했습니다. + + + + + Patching of Snapmatic Image failed because of Image Error + ì´ë¯¸ì§€ 오류로 ì¸í•´ 스냅매틱 ì´ë¯¸ì§€ë¥¼ 패치하지 못했습니다. + + + + ImportDialog + + + Import... + 가져오기 + + + + Picture + ì´ë¯¸ì§€ + + + + Avatar + 아바타 + + + + + Ignore Aspect Ratio + 화면 비율 무시 + + + + Watermark + ì›Œí„°ë§ˆí¬ + + + Force Borderless + ê°•ì œ í…Œë‘리 없는 ì°½ + + + + Background + ë°°ê²½ + + + + + + + Background Colour: <span style="color: %1">%1</span> + ë°°ê²½ 색ìƒ: <span style="color: %1">%1</span> + + + + Select background colour + ë°°ê²½ ìƒ‰ìƒ ì„ íƒ + + + ... + ... + + + + + + + Background Image: + ë°°ê²½ ì´ë¯¸ì§€: + + + + Select background image + ë°°ê²½ ì´ë¯¸ì§€ ì„ íƒ + + + + Remove background image + ë°°ê²½ ì´ë¯¸ì§€ 제거 + + + X + X + + + + Crop to Aspect Ratio + ì›ë³¸ 비율로 ìžë¥´ê¸° + + + + Force Colour in Avatar Zone + 아바타 êµ¬ì—­ì— ìƒ‰ìƒì„ ì ìš©í•©ë‹ˆë‹¤ + + + + Advanced + 고급 + + + + Resolution: + í•´ìƒë„: + + + + Snapmatic resolution + 스냅매틱 í•´ìƒë„ + + + + Avoid compression and expand buffer instead, improves picture quality, but may break Snapmatic + 압축하지 않고 버í¼ë¥¼ 확장하여 í™”ì§ˆì„ í–¥ìƒì‹œí‚¤ì§€ë§Œ ìŠ¤ëƒ…ë§¤í‹±ì´ ì†ìƒë  수 있습니다. + + + + Unlimited Buffer + ë²„í¼ ì œí•œ ì—†ìŒ + + + + Import as-is, don't change the picture at all, guaranteed to break Snapmatic unless you know what you doing + ì›ë³¸ 그대로 가져오기 ê¸°ëŠ¥ì€ ì´ë¯¸ì§€ë¥¼ 건들지 않지만 ì´ ê¸°ëŠ¥ìœ¼ë¡œ ì¸í•´ ë‹¹ì‹ ì˜ ìŠ¤ëƒ…ë§¤í‹±ì´ ì†ìƒë  수 있습니다. + + + + Import as-is + ì›ë³¸ 그대로 가져오기 + + + + Import options + 가져오기 옵션 + + + + &Options + 옵션(&O) + + + + Import picture + 사진 가져오기 + + + + &OK + 확ì¸(&O) + + + + Discard picture + 사진 ì‚­ì œ + + + + &Cancel + 취소(&C) + + + + &Import new Picture... + 새로운 사진 가져오기(&I) + + + + &Crop Picture... + 사진 ìžë¥´ê¸°(&C) + + + + &Load Settings... + 설정 불러오기(&L) + + + + &Save Settings... + 설정 저장(&S) + + + + + Custom Avatar + Custom Avatar Description in SC, don't use Special Character! + 소셜í´ëŸ½ì˜ ì‚¬ìš©ìž ì§€ì • 아바타 설명입니다. 특수 문ìžë¥¼ 사용하지 마십시오! + ì‚¬ìš©ìž ì§€ì • 아바타 + + + + + + Custom Picture + Custom Picture Description in SC, don't use Special Character! + 소셜í´ëŸ½ì˜ ì‚¬ìš©ìž ì§€ì • 그림 설명입니다. 특수 문ìžë¥¼ 사용하지 마십시오! + ì‚¬ìš©ìž ì§€ì • 사진 + + + + + Background Image: %1 + ë°°ê²½ ì´ë¯¸ì§€: %1 + + + + Storage + Background Image: Storage + ë°°ê²½ ì´ë¯¸ì§€: ì €ìž¥ë¨ + ì €ìž¥ë¨ + + + + Crop Picture... + 사진 ìžë¥´ê¸° + + + + &Crop + ìžë¥´ê¸°(&C) + + + + Crop Picture + 사진 ìžë¥´ê¸° + + + + + Load Settings... + 설정 불러오기 + + + + + Please import a new picture first + 먼저 새 ì´ë¯¸ì§€ë¥¼ 가져오세요 + + + + + Default + Default as Default Profile + 기본 프로필로 기본 설정 + 기본 + + + + + + + + + + + + + + + + + + + + + + + Profile %1 + Profile %1 as Profile 1 + %1ì„ í”„ë¡œí•„ 1로 지정합니다. + 프로필 %1 + + + + + Please select your settings profile + 설정 í”„ë¡œí•„ì„ ì„ íƒí•˜ì„¸ìš” + + + + + Save Settings... + 설정 저장 + + + + + Snapmatic Avatar Zone + 스냅매틱 아바타 ì˜ì—­ + + + + + Are you sure to use a square image outside of the Avatar Zone? +When you want to use it as Avatar the image will be detached! + 아바타 구역 ë°–ì—서 네모난 ì´ë¯¸ì§€ë¥¼ ì •ë§ ì‚¬ìš©í•©ë‹ˆê¹Œ? +아바타로 사용하려는 경우 ì´ë¯¸ì§€ê°€ 분리ë©ë‹ˆë‹¤! + + + + Select Colour... + ìƒ‰ìƒ ì„ íƒ + + + + File + Background Image: File + ë°°ê²½ ì´ë¯¸ì§€: íŒŒì¼ + íŒŒì¼ + + + + JsonEditorDialog + + + Snapmatic JSON Editor + 스냅매틱 JSON 편집기 + + + + Apply changes + 변경 사항 ì ìš© + + + + &Save + 저장(&S) + + + + Discard changes + 변경 사항 무시 + + + + &Close + 닫기(&C) + + + + JSON Error + JSON 오류 + + + + MapLocationDialog + + + Snapmatic Map Viewer + 스냅매틱 ì§€ë„ ë·°ì–´ + + + + Close viewer + ë·°ì–´ 닫기 + + + + &Close + 닫기(&C) + + + + Apply new position + 새 위치 ì ìš© + + + + &Apply + ì ìš©(&A) + + + + Revert old position + ì´ì „ 위치로 ë˜ëŒë¦¬ê¸° + + + + &Revert + ë˜ëŒë¦¬ê¸°(&R) + + + + Select new position + 새 위치 ì„ íƒ + + + + &Select + ì„ íƒ(&S) + + + + Quit select position + ì„ íƒ ìœ„ì¹˜ 종료 + + + + &Done + 완료(&D) + + + + X: %1 +Y: %2 + X and Y position + X ë° Y 위치 + X: %1 +Y: %2 + + + + OptionsDialog + + + %1 - Settings + %1 - 설정 + + + + Profiles + 프로필 + + + + Content Open/Select Mode + 컨í…츠 열기/ì„ íƒ ëª¨ë“œ + + + Open with Singleclick + 한 번 í´ë¦­ìœ¼ë¡œ 열기 + + + + Open with Doubleclick + ë‘ ë²ˆ í´ë¦­ìœ¼ë¡œ 열기 + + + Select with Singleclick + 한 번 í´ë¦­ìœ¼ë¡œ ì„ íƒ + + + + Default Profile + 기본 프로필 + + + + Custom GTA V Folder + ì‚¬ìš©ìž ì§€ì • GTA 5 í´ë” + + + + Force using Custom Folder + ì‚¬ìš©ìž ì§€ì • í´ë”를 강제로 사용합니다. + + + + ... + ... + + + + Pictures + ì´ë¯¸ì§€ + + + + Export Size + 내보낼 í¬ê¸° + + + + Default: %1x%2 + 기본: %1x%2 + + + + Screen Resolution: %1x%2 + 화면 í•´ìƒë„: %1x%2 + + + + + Custom Size: + ì‚¬ìš©ìž ì§€ì • í¬ê¸°: + + + + x + x + + + + Ignore Aspect Ratio + 화면 비율 무시 + + + + Export Quality + 내보낼 품질 + + + + Enable Custom Quality + ì‚¬ìš©ìž ì§€ì • 품질 사용 + + + + Quality: + 품질: + + + + %1% + %1% + + + + Picture Viewer + ì´ë¯¸ì§€ ë·°ì–´ + + + + Enable Navigation Bar + íƒìƒ‰ë°” 사용 + + + + Players + 플레ì´ì–´ + + + + ID + ì•„ì´ë”” + + + + Name + ì´ë¦„ + + + + Game + 게임 + + + + Social Club Version + 소셜 í´ëŸ½ 버전 + + + + + + + + + + + Found: %1 + ì°¾ìŒ: %1 + + + + + + + + + + + + + Language: %1 + 언어: %1 + + + + Steam Version + 스팀 버전 + + + + Feedback + 피드백 + + + + Participation + 참가 + + + + + Participate in %1 User Statistics + ì‚¬ìš©ìž í†µê³„ 참가 %1 + + + + Categories + 카테고리 + + + + Hardware, Application and OS Specification + 하드웨어, ì‘ìš© 프로그램 ë° OS 사양 + + + + System Language Configuration + 시스템 언어 설정 + + + + Application Configuration + ì‘ìš© 프로그램 설정 + + + + Personal Usage Data + ê°œì¸ ì‚¬ìš© ë°ì´í„° + + + + Other + ê·¸ 외 + + + + + + Participation ID: %1 + 참여 ì•„ì´ë””: %1 + + + + &Copy + 복사(&C) + + + + Interface + ì¸í„°íŽ˜ì´ìФ + + + + Language for Interface + ì¸í„°íŽ˜ì´ìФ 언어 + + + + + + + Current: %1 + 현재: %1 + + + + Language for Areas + 지역 언어 + + + + Style + ìŠ¤íƒ€ì¼ + + + + Use Default Style (Restart) + 기본 스타ì¼ì„ 사용합니다 (재시작 í•„ìš”) + + + + Style: + 스타ì¼: + + + + Font + í°íЏ + + + + Use Default Font (Restart) + 기본 í°íЏ 사용 (재시작 í•„ìš”) + + + + Font: + í°íЏ: + + + Always use Message Font (Windows 2003 and earlier) + í•­ìƒ ë©”ì‹œì§€ ê¸€ê¼´ì„ ì‚¬ìš©í•©ë‹ˆë‹¤.(Windows 2003 ë° ì´ì „ 버전) + + + + Apply changes + 변경 사항 ì ìš© + + + + &OK + OK, Cancel, Apply + 확ì¸, 취소, ì ìš© + 확ì¸(&O) + + + + Discard changes + 변경 사항 취소 + + + + &Cancel + OK, Cancel, Apply + 확ì¸, 취소, ì ìš© + 취소(&C) + + + + %1 (Language priority) + First language a person can talk with a different person/application. "Native" or "Not Native". + %1 (ìš°ì„  순위) + + + + System + System in context of System default + 시스템 + + + + %1 (Game language) + Next closest language compared to the Game settings + 게임 설정과 가장 가까운 언어 + %1 (게임 언어) + + + + + + Auto + Automatic language choice. + 언어 ìžë™ ì„ íƒ + ìžë™ + + + + + %1 (Closest to Interface) + Next closest language compared to the Interface + ì¸í„°íŽ˜ì´ìŠ¤ì™€ 가장 가까운 언어 + %1 (ì¸í„°íŽ˜ì´ìŠ¤ì™€ 가까운 언어) + + + + %1 + %1 + %1 + %1 + + + + The new Custom Folder will initialise after you restart %1. + 다시 시작한 후 새 ì‚¬ìš©ìž ì§€ì • í´ë”ê°€ 초기화ë©ë‹ˆë‹¤. %1. + + + + No Profile + No Profile, as default + 프로필 ì—†ìŒ (기본값) + 프로필 ì—†ìŒ + + + + + + Profile: %1 + 프로필: %1 + + + + View %1 User Statistics Online + 온ë¼ì¸ %1 ì‚¬ìš©ìž í†µê³„ 보기 + + + + Not registered + 등ë¡ë˜ì§€ 않았습니다. + + + + + + + Yes + 예 + + + + + No + 아니요 + + + + + OS defined + OS ì •ì˜ + + + + + Steam defined + 스팀 ì •ì˜ + + + + PictureDialog + + + Snapmatic Picture Viewer - %1 + 스냅매틱 ì´ë¯¸ì§€ ë·°ì–´ - %1 + + + + <span style="font-weight:600">Title: </span>%6<br/> +<span style="font-weight:600">Location: </span>%7 (%1, %2, %3)<br/> +<span style="font-weight:600">Players: </span>%4 (Crew %5)<br/> +<span style="font-weight:600">Created: </span>%8 + <span style="font-weight:600">제목: </span>%6<br/> +<span style="font-weight:600">위치: </span>%7 (%1, %2, %3)<br/> +<span style="font-weight:600">플레ì´ì–´: </span>%4 (Crew %5)<br/> +<span style="font-weight:600">ìƒì„± ë‚ ì§œ: </span>%8 + + + + Manage picture + ì´ë¯¸ì§€ 관리 + + + + &Manage + 관리(&M) + + + + Close viewer + ë·°ì–´ 닫기 + + + + &Close + 닫기(&C) + + + + + Export as &Picture... + ë‚´ PCì— ì´ë¯¸ì§€ë¡œ 내보내기(&P) + + + + + Export as &Snapmatic... + ë‚´ PCì— ìŠ¤ëƒ…ë§¤í‹±ìœ¼ë¡œ 내보내기(&S) + + + + + &Edit Properties... + ì†ì„± 편집(&E) + + + + + &Overwrite Image... + ì´ë¯¸ì§€ ë®ì–´ì“°ê¸°(&O) + + + + + Open &Map Viewer... + ì§€ë„ ë·°ì–´ 열기(&M) + + + + + Open &JSON Editor... + JSON 편집기 열기(&J) + + + + Key 1 - Avatar Preview Mode +Key 2 - Toggle Overlay +Arrow Keys - Navigate + ìˆ«ìž 1 - 아바타 미리보기 모드 +ìˆ«ìž 2 - ì˜¤ë²„ë ˆì´ ì „í™˜ +화살표키 - ì´ë™ + + + + Snapmatic Picture Viewer + 스냅매틱 ì´ë¯¸ì§€ ë·°ì–´ + + + + Failed at %1 + %1ì—서 실패했습니다. + + + + + + No Players + 플레ì´ì–´ ì—†ìŒ + + + + + No Crew + ì¡°ì§ ì—†ìŒ + + + + Unknown Location + 알 수 없는 위치 + + + + Avatar Preview Mode +Press 1 for Default View + 아바타 미리 보기 모드입니다. +ëŒì•„가려면 ìˆ«ìž 1ì„ ëˆ„ë¦…ë‹ˆë‹¤. + + + + Export as Picture... + ë‚´ PCì— ì´ë¯¸ì§€ë¡œ 내보내기 + + + + + Export + 내보내기 + + + + JPEG Graphics (*.jpg *.jpeg) + JPEG Graphics (*.jpg *.jpeg) + + + + Portable Network Graphics (*.png) + Portable Network Graphics (*.png) + + + + + + + + + Export as Picture + ë‚´ PCì— ì´ë¯¸ì§€ë¡œ 내보내기 + + + + + Overwrite %1 with current Snapmatic picture? + %1ì„ í˜„ìž¬ 스냅매틱 ì´ë¯¸ì§€ë¡œ ë®ì–´ì“°ì‹œê² ìŠµë‹ˆê¹Œ? + + + + Failed to export the picture because the system occurred a write failure + 시스템ì—서 쓰기 오류가 ë°œìƒí•˜ì—¬ ì´ë¯¸ì§€ë¥¼ ë‚´ë³´ë‚´ì§€ 못했습니다. + + + + Failed to export the picture because the format detection failures + í™•ìž¥ìž ê°ì§€ì— 실패하여 ì´ë¯¸ì§€ë¥¼ ë‚´ë³´ë‚´ì§€ 못했습니다. + + + + Failed to export the picture because the file can't be written + 파ì¼ì„ 쓸 수 없으므로 ì´ë¯¸ì§€ë¥¼ ë‚´ë³´ë‚´ì§€ 못했습니다. + + + + Failed to export the picture because of an unknown reason + 알 수 없는 ì´ìœ ë¡œ ì´ë¯¸ì§€ë¥¼ ë‚´ë³´ë‚´ì§€ 못했습니다. + + + + + No valid file is selected + 올바른 파ì¼ì´ ì„ íƒë˜ì§€ 않았습니다. + + + + Export as Snapmatic... + ë‚´ PCì— ìŠ¤ëƒ…ë§¤í‹±ìœ¼ë¡œ 내보내기 + + + + GTA V Export (*.g5e) + GTA V Export (*.g5e) + + + + GTA V Raw Export (*.auto) + GTA V Raw Export (*.auto) + + + + Snapmatic pictures (PGTA*) + Snapmatic pictures (PGTA*) + + + + + + + + Export as Snapmatic + ë‚´ PCì— ìŠ¤ëƒ…ë§¤í‹±ìœ¼ë¡œ 내보내기 + + + + + Failed to export current Snapmatic picture + 현재 스냅매틱 ì´ë¯¸ì§€ë¥¼ ë‚´ë³´ë‚´ì§€ 못했습니다. + + + + Exported Snapmatic to "%1" because of using the .auto extension. + .auto 확장ìžë¥¼ 사용하기 ë•Œë¬¸ì— ìŠ¤ëƒ…ë§¤í‹±ì„ "%1"로 내보냈습니다. + + + + PlayerListDialog + + + Edit Players... + 플레ì´ì–´ 편집 + + + + Available Players: + 사용 가능한 플레ì´ì–´: + + + + Selected Players: + ì„ íƒëœ 플레ì´ì–´: + + + + &Apply + ì ìš©(&A) + + + + &Cancel + 취소(&C) + + + + Add Players... + 플레ì´ì–´ 추가 + + + + Failed to add more Players because the limit of Players are %1! + 플레ì´ì–´ì˜ ì œí•œì´ %1ì´ë¯€ë¡œ 플레ì´ì–´ë¥¼ 추가하지 못했습니다! + + + + + Add Player... + 플레ì´ì–´ 추가 + + + + Enter Social Club Player ID + 소셜 í´ëŸ½ 플레ì´ì–´ ì•„ì´ë”” ìž…ë ¥ + + + + Failed to add Player %1 because Player %1 is already added! + %1 플레ì´ì–´ê°€ ì´ë¯¸ 추가ë˜ì–´ %1 플레ì´ì–´ë¥¼ 추가하지 못했습니다! + + + + ProfileInterface + + + Profile Interface + 프로필 ì¸í„°íŽ˜ì´ìФ + + + + Loading file %1 of %2 files + %2 파ì¼ì˜ %1 파ì¼ì„ 불러오는 중입니다. + + + + %1 %2 + %1 %2 + + + + Import file + íŒŒì¼ ê°€ì ¸ì˜¤ê¸° + + + + &Import... + 가져오기(&I) + + + + Close profile + 프로필 닫기 + + + + &Close + 닫기(&C) + + + + + + Export file %1 of %2 files + %2 íŒŒì¼ ì¤‘ %1 파ì¼ì„ 내보냅니다. + + + + + + + + + + + + + + + + + + + + + + Import... + 가져오기 + + + + + + + + + Import + 가져오기 + + + + + + All image files (%1) + 모든 ì´ë¯¸ì§€ íŒŒì¼ (%1) + + + + + + + All files (**) + 모든 íŒŒì¼ (**) + + + + + + Can't import %1 because file can't be open + 파ì¼ì„ ì—´ 수 없으므로 %1ì„ ê°€ì ¸ì˜¬ 수 없습니다. + + + + + + Can't import %1 because file can't be parsed properly + 파ì¼ì„ 구문 ë¶„ì„í•  수 없으므로 %1ì„ ê°€ì ¸ì˜¬ 수 없습니다. + + + + Enabled pictures: %1 of %2 + í™œì„±í™”ëœ ì´ë¯¸ì§€: %2ì˜ %1 + + + + Loading... + 불러오는 중... + + + + Snapmatic Loader + 스냅매틱 불러오기 + + + + <h4>Following Snapmatic Pictures got repaired</h4>%1 + <h4>ë‹¤ìŒ ìŠ¤ëƒ…ë§¤í‹± ì´ë¯¸ì§€ë¥¼ 복구했습니다. </h4>%1 + + + + Importable files (%1) + 가져올 수 있는 íŒŒì¼ (%1) + + + + + GTA V Export (*.g5e) + GTA V로 내보내기 (*.g5e) + + + + + Savegames files (SGTA*) + 세ì´ë¸Œ íŒŒì¼ (SGTA*) + + + + + Snapmatic pictures (PGTA*) + 스냅매틱 ì´ë¯¸ì§€ (PGTA*) + + + + + + No valid file is selected + 올바른 파ì¼ì´ ì„ íƒë˜ì§€ 않았습니다. + + + + + Import file %1 of %2 files + %2 íŒŒì¼ ì¤‘ %1 파ì¼ì„ 가져옵니다. + + + + Import failed with... + +%1 + ê°€ì ¸ì˜¤ê¸°ì— ì‹¤íŒ¨í–ˆìŠµë‹ˆë‹¤... + +%1 + + + + + Failed to read Snapmatic picture + 스냅매틱 ì´ë¯¸ì§€ë¥¼ ì½ì§€ 못했습니다. + + + + + Failed to read Savegame file + 세ì´ë¸Œ 파ì¼ì„ ì½ì§€ 못했습니다. + + + + Can't import %1 because file format can't be detected + íŒŒì¼ í˜•ì‹ì„ 검색할 수 없으므로 %1ì„ ê°€ì ¸ì˜¬ 수 없습니다. + + + + Prepare Content for Import... + 가져올 컨í…츠를 준비합니다. + + + + Failed to import the Snapmatic picture, file not begin with PGTA or end with .g5e + 스냅매틱 ì´ë¯¸ì§€ë¥¼ 가져오지 못했습니다. 파ì¼ì´ PGTA로 시작ë˜ê±°ë‚˜ .g5e로 ë나지 않습니다. + + + + A Snapmatic picture already exists with the uid %1, you want assign your import a new uid and timestamp? + uid %1ì´(ê°€) 있는 스냅매틱 ì´ë¯¸ì§€ê°€ ì´ë¯¸ 있습니다. 가져오기를 새 uid ë° íƒ€ìž„ìŠ¤íƒ¬í”„ë¥¼ 할당하시겠습니까? + + + + Failed to import the Snapmatic picture, can't copy the file into profile + 스냅매틱 ì´ë¯¸ì§€ë¥¼ 가져오지 못했습니다. 파ì¼ì„ í”„ë¡œí•„ì— ë³µì‚¬í•  수 없습니다. + + + + Failed to import the Savegame, can't copy the file into profile + 게임 저장 파ì¼ì„ 가져오지 못했습니다. 파ì¼ì„ í”„ë¡œí•„ì— ë³µì‚¬í•  수 없습니다. + + + + Failed to import the Savegame, no Savegame slot is left + 게임 저장 파ì¼ì„ 가져오지 못했습니다. 게임 저장 ìŠ¬ë¡¯ì´ ë‚¨ì•„ 있지 않습니다. + + + + + + + + Export selected... + 내보내기를 ì„ íƒí–ˆìŠµë‹ˆë‹¤. + + + + + JPG pictures and GTA Snapmatic + JPG ì´ë¯¸ì§€ ë° GTA 스냅매틱 + + + + + JPG pictures only + JPG ì´ë¯¸ì§€ë§Œ + + + + + GTA Snapmatic only + GTA 스냅매틱만 + + + + %1Export Snapmatic pictures%2<br><br>JPG pictures make it possible to open the picture with a Image Viewer<br>GTA Snapmatic make it possible to import the picture into the game<br><br>Export as: + %1 스냅매틱 ì´ë¯¸ì§€ 내보내기를 시작합니다.%2 <br><br>JPG ì´ë¯¸ì§€ë¥¼ 사용하면 ì´ë¯¸ì§€ 뷰어로 파ì¼ì„ ì—´ 수 있습니다.<br>GTA ìŠ¤ëƒ…ë§¤í‹±ì„ ì‚¬ìš©í•˜ë©´ 다ìŒê³¼ ê°™ì´ ì´ë¯¸ì§€ë¥¼ 게임으로 가져올 수 있습니다. + + + + Initialising export... + 내보내기를 초기화하는 중... + + + + Export failed with... + +%1 + ë‚´ë³´ë‚´ì§€ 못했습니다... + +%1 + + + + + No Snapmatic pictures or Savegames files are selected + 스냅매틱 ì´ë¯¸ì§€ ë˜ëŠ” 세ì´ë¸Œ 파ì¼ì´ ì„ íƒë˜ì§€ 않았습니다. + + + + + + Remove selected + ì„ íƒí•œ 항목 ì‚­ì œ + + + + You really want remove the selected Snapmatic picutres and Savegame files? + ì„ íƒí•œ 스냅매틱 ì´ë¯¸ì§€ ë° ì„¸ì´ë¸Œ 파ì¼ì„ 삭제하시겠습니까? + + + + Failed to remove all selected Snapmatic pictures and/or Savegame files + ì„ íƒí•œ 모든 스냅매틱 ì´ë¯¸ì§€ ë° ì„¸ì´ë¸Œ 파ì¼ì„ 삭제하지 못했습니다. + + + + + + + + + No Snapmatic pictures are selected + 스냅매틱 ì´ë¯¸ì§€ê°€ ì„ íƒë˜ì§€ 않았습니다. + + + + + + + + + %1 failed with... + +%2 + Action failed with... + 작업 실패... + %1ì´(ê°€) 실패했습니다. + +%2 + + + + + Qualify as Avatar + 아바타 ìžê²© 부여 + + + + + + + Patch selected... + 패치가 ì„ íƒë¨... + + + + + + + + + + + Patch file %1 of %2 files + %2 파ì¼ì˜ %1 패치 파ì¼ìž…니다. + + + + Qualify + %1 failed with... + %1ì´(ê°€) 실패한 경우... + ìžê²© 부여 + + + + + Change Players... + 플레ì´ì–´ 변경 + + + + Change Players + %1 failed with... + %1ì´(ê°€) 실패한 경우... + 플레ì´ì–´ 변경 + + + + + + Change Crew... + ì¡°ì§ ë³€ê²½ + + + + Failed to enter a valid Snapmatic Crew ID + 올바른 스냅매틱 ì¡°ì§ ì•„ì´ë””를 입력하지 못했습니다. + + + + Change Crew + %1 failed with... + %1ì´(ê°€) 실패한 경우... + ì¡°ì§ ë³€ê²½ + + + + + + Change Title... + 제목 변경 + + + + Failed to enter a valid Snapmatic title + 올바른 스냅매틱 ì œëª©ì„ ìž…ë ¥í•˜ì§€ 않았습니다. + + + + Change Title + %1 failed with... + %1ì´(ê°€) 실패한 경우... + 제목 변경 + + + + All profile files (*.g5e SGTA* PGTA*) + 모든 프로필 íŒŒì¼ (*.g5e SGTA* PGTA*) + + + + QApplication + + Font + í°íЏ + + + Selected Font: %1 + ì„ íƒëœ í°íЏ: %1 + + + <h4>Welcome to %1!</h4>You want to configure %1 before you start using it? + <h4>%1ì— ì˜¤ì‹  ê²ƒì„ í™˜ì˜í•©ë‹ˆë‹¤!</h4>%1ì„ ì‚¬ìš©í•˜ê¸° ì „ì— ì„¤ì • ì°½ì„ ì—¬ì‹œê² ìŠµë‹ˆê¹Œ? + + + + SavegameDialog + + + + Savegame Viewer + 세ì´ë¸Œ íŒŒì¼ ë³´ê¸° + + + + <span style="font-weight:600">Savegame</span><br><br>%1 + <span style="font-weight:600">세ì´ë¸Œ 파ì¼</span><br><br>%1 + + + + &Export + 내보내기(&E) + + + + &Close + 닫기(&C) + + + + Failed at %1 + 실패 %1 + + + + SavegameWidget + + + Savegame Widget + 세ì´ë¸Œ íŒŒì¼ ìœ„ì ¯ + + + + SAVE %3 - %1<br>%2 + 저장 %3 - %1<br>%2 + + + + View savegame + 세ì´ë¸Œ íŒŒì¼ ë³´ê¸° + + + + View + 보기 + + + + Copy savegame + 세ì´ë¸Œ íŒŒì¼ ë³µì‚¬ + + + + + Export + 내보내기 + + + + Delete savegame + 세ì´ë¸Œ íŒŒì¼ ì‚­ì œ + + + + Delete + ì‚­ì œ + + + + &View + 보기(&V) + + + + + + &Export + 내보내기(&E) + + + + + + &Remove + ì‚­ì œ(&R) + + + + + &Select + ì„ íƒ(&S) + + + + + &Deselect + ì„ íƒ í•´ì œ(&D) + + + + + Select &All + ëª¨ë‘ ì„ íƒ(&A) + + + + + &Deselect All + ëª¨ë‘ ì„ íƒ í•´ì œ(&D) + + + + Savegame files (SGTA*) + 세ì´ë¸Œ íŒŒì¼ (SGTA*) + + + + All files (**) + 모든 íŒŒì¼ (**) + + + + + + + Export Savegame + 세ì´ë¸Œ íŒŒì¼ ë‚´ë³´ë‚´ê¸° + + + + Overwrite %1 with current Savegame? + %1ì„ í˜„ìž¬ 세ì´ë¸Œ 파ì¼ë¡œ ë®ì–´ì“°ì‹œê² ìŠµë‹ˆê¹Œ? + + + + Failed to overwrite %1 with current Savegame + %1ì„ í˜„ìž¬ 세ì´ë¸Œ 파ì¼ë¡œ ë®ì–´ì“°ì§€ 못했습니다. + + + + Failed to export current Savegame + 현재 세ì´ë¸Œ 파ì¼ì„ ë‚´ë³´ë‚´ì§€ 못했습니다. + + + + No valid file is selected + 올바른 파ì¼ì´ ì„ íƒë˜ì§€ 않았습니다. + + + + Export Savegame... + 세ì´ë¸Œ íŒŒì¼ ë‚´ë³´ë‚´ê¸° + + + + + AUTOSAVE - %1 +%2 + ìžë™ 저장 - %1 +%2 + + + + + SAVE %3 - %1 +%2 + 저장 %3 - %1 +%2 + + + + + WRONG FORMAT + ìž˜ëª»ëœ í˜•ì‹ + + + + UNKNOWN + 알 수 ì—†ìŒ + + + + + Delete Savegame + 세ì´ë¸Œ íŒŒì¼ ì‚­ì œ + + + + Are you sure to delete %1 from your savegames? + %1ì„(를) 세ì´ë¸Œ 파ì¼ì—서 삭제하시겠습니까? + + + + Failed at deleting %1 from your savegames + %1ì„(를) 세ì´ë¸Œ 파ì¼ì—서 삭제하지 못했습니다. + + + + SnapmaticEditor + + + + + + + + + + + Snapmatic Properties + 스냅매틱 ì†ì„± + + + + Snapmatic Type + 스냅매틱 í˜•ì‹ + + + + Editor + 편집기 + + + + Selfie + 셀피 + + + + Regular + ì¼ë°˜ + + + + Mugshot + 머그샷 + + + + Meme + ë°ˆ + + + + Director + ê°ë… + + + + Snapmatic Values + 스냅매틱 ê°’ + + + + Extras + 기타 + + + + Qualify as Avatar automatically at apply + ì ìš© 시 ìžë™ìœ¼ë¡œ 아바타 ìžê²©ì„ 부여합니다. + + + + Qualify as Avatar allows you to use this Snapmatic as a Social Club profile picture + ì´ ìŠ¤ëƒ…ë§¤í‹±ì„ ì†Œì…œ í´ëŸ½ 프로필 ì´ë¯¸ì§€ë¡œ 사용할 수 있습니다. + + + + Apply changes + 변경 사항 ì ìš© + + + + &Apply + ì ìš©(&A) + + + + Discard changes + 변경 사항 취소 + + + + &Cancel + 취소(&C) + + + + <h4>Unsaved changes detected</h4>You want to save the JSON content before you quit? + < h4>저장ë˜ì§€ ì•Šì€ ë³€ê²½ ë‚´ìš©ì´ ê°ì§€ë˜ì—ˆìŠµë‹ˆë‹¤. </h4>그만ë‘기 ì „ì— JSON 콘í…츠를 저장하겠습니까? + + + + Patching of Snapmatic Properties failed because of %1 + %1로 ì¸í•´ 스냅매틱 ì†ì„±ì„ 패치하지 못했습니다. + + + + + + + Patching of Snapmatic Properties failed because of I/O Error + I/O 오류로 ì¸í•´ 스냅매틱 ì†ì„±ì„ 패치하지 못했습니다. + + + + Patching of Snapmatic Properties failed because of JSON Error + JSON 오류로 ì¸í•´ 스냅매틱 ì†ì„±ì„ 패치하지 못했습니다. + + + + + Snapmatic Crew + ì¡°ì§ ìŠ¤ëƒ…ë§¤í‹± + + + + + New Snapmatic crew: + 새로운 ì¡°ì§ ìŠ¤ëƒ…ë§¤í‹±: + + + + + Snapmatic Title + 스냅매틱 제목 + + + + + New Snapmatic title: + 새로운 스냅매틱 제목: + + + + + + Edit + 편집 + + + + Players: %1 (%2) + Multiple Player are inserted here + ì—¬ê¸°ì— ì—¬ëŸ¬ 플레ì´ì–´ê°€ 추가ë©ë‹ˆë‹¤. + 플레ì´ì–´: %1 (%2) + + + + Player: %1 (%2) + One Player is inserted here + ì—¬ê¸°ì— í”Œë ˆì´ì–´ 하나가 추가ë©ë‹ˆë‹¤. + 플레ì´ì–´: %1 (%2) + + + + Title: %1 (%2) + 제목: %1 (%2) + + + + + Appropriate: %1 + 변경: %1 + + + + Yes + Yes, should work fine + 네, 잘 ë  ê±°ì˜ˆìš”. + 예 + + + + No + No, could lead to issues + 아니요, 문제가 ë°œìƒí•  수 있습니다. + 아니요 + + + + Crew: %1 (%2) + ì¡°ì§: %1 (%2) + + + + SnapmaticPicture + + + + JSON is incomplete and malformed + JSON 파ì¼ì´ 불안정하거나 형ì‹ì´ 잘못ë˜ì—ˆìŠµë‹ˆë‹¤. + + + + + JSON is incomplete + JSON 파ì¼ì´ 불안정합니다. + + + + + JSON is malformed + ìž˜ëª»ëœ JSON í˜•ì‹ + + + + PHOTO - %1 + 사진 - %1 + + + + open file %1 + íŒŒì¼ ì—´ê¸° %1 + + + + header not exists + í—¤ë”ê°€ 존재하지 않습니다. + + + + header is malformed + í—¤ë”ì˜ í˜•ì‹ì´ 잘못ë˜ì—ˆìŠµë‹ˆë‹¤. + + + + picture not exists (%1) + ì´ë¯¸ì§€ê°€ 존재하지 않습니다. (%1) + + + + JSON not exists (%1) + JSON 파ì¼ì´ 존재하지 않습니다. (%1) + + + + title not exists (%1) + ì œëª©ì´ ì¡´ìž¬í•˜ì§€ 않습니다. (%1) + + + + description not exists (%1) + ì„¤ëª…ì´ ì¡´ìž¬í•˜ì§€ 않습니다. (%1) + + + + reading file %1 because of %2 + Example for %2: JSON is malformed error + %2ì˜ ì˜ˆ: JSONì´ ìž˜ëª»ëœ í˜•ì‹ìž…니다 + %2 ë•Œë¬¸ì— %1 파ì¼ì„ ì½ìŠµë‹ˆë‹¤. + + + + SnapmaticWidget + + + Snapmatic Widget + 스냅매틱 위젯 + + + + PHOTO - 00/00/00 00:00:00 + ì´ë¯¸ì§€ - 00/00/00 00:00:00 + + + + View picture + ì´ë¯¸ì§€ 보기 + + + + View + 보기 + + + + Copy picture + ì´ë¯¸ì§€ 복사 + + + + Copy + 복사 + + + + Export picture + ì´ë¯¸ì§€ 내보내기 + + + + Export + 내보내기 + + + + + + Delete picture + ì´ë¯¸ì§€ ì‚­ì œ + + + + Delete + ì‚­ì œ + + + + + + Edi&t + 편집(&T) + + + + + + Show &In-game + ì¸ê²Œìž„ì—서 ë³´ì´ê¸°(&I) + + + + + + Hide &In-game + ì¸ê²Œìž„ì—서 숨기기(&I) + + + + &Export + 내보내기(&E) + + + + &View + 보기(&V) + + + + &Remove + ì‚­ì œ(&R) + + + + + &Select + ì„ íƒ(&S) + + + + + &Deselect + ì„ íƒ í•´ì œ(&D) + + + + + Select &All + ëª¨ë‘ ì„ íƒ(&A) + + + + + &Deselect All + ëª¨ë‘ ì„ íƒ í•´ì œ(&D) + + + + Are you sure to delete %1 from your Snapmatic pictures? + 스냅매틱 ì´ë¯¸ì§€ì—서 %1ì„ ì‚­ì œí•˜ì‹œê² ìŠµë‹ˆê¹Œ? + + + + Failed at deleting %1 from your Snapmatic pictures + 스냅매틱 ì´ë¯¸ì§€ì—서 %1ì„ ì‚­ì œí•˜ì§€ 못했습니다. + + + + Failed to hide %1 In-game from your Snapmatic pictures + ì¸ê²Œìž„ 스냅매틱 ì´ë¯¸ì§€ì—서 %1 ì„ ìˆ¨ê¸°ì§€ 못했습니다. + + + + Failed to show %1 In-game from your Snapmatic pictures + ì¸ê²Œìž„ 스냅매틱 ì´ë¯¸ì§€ì—서 %1 ì„ í‘œì‹œí•˜ì§€ 못했습니다. + + + + TelemetryDialog + + + You want help %1 to improve in the future by including personal usage data in your submission? + ê°œì¸ ì‚¬ìš© ë°ì´í„°ë¥¼ ì œì¶œì— í¬í•¨ì‹œì¼œ %1ì´(ê°€) 개선ë˜ê¸°ë¥¼ ì›í•©ë‹ˆê¹Œ? + + + + %1 User Statistics + %1 ì‚¬ìš©ìž í†µê³„ + + + + Yes, I want include personal usage data. + 예, ê°œì¸ ì‚¬ìš© ë°ì´í„°ë¥¼ í¬í•¨ì‹œí‚¤ê³  싶습니다. + + + + &OK + 확ì¸(&O) + + + + UserInterface + + + + %2 - %1 + %2 - %1 + + + + Select profile + 프로필 ì„ íƒ + + + + %1 %2 + %1 %2 + + + + Reload profile overview + 프로필 다시 불러오기 + + + + &Reload + 새로고침(&R) + + + + Close %1 + Close %1 <- (gta5view/gta5sync) - %1 will be replaced automatically + 닫기 %1 <- (gta5view/gta5sync) - %1 ìžë™ìœ¼ë¡œ êµì²´ë©ë‹ˆë‹¤. + 닫기 %1 + + + + + + + &Close + 닫기(&C) + + + + &File + 파ì¼(&F) + + + + &Help + ë„움ë§(&H) + + + + &Edit + 편집(&E) + + + + &Profile + 프로필(&P) + + + + &Selection visibility + ì¸ê²Œìž„ 표시(&S) + + + + Selection &mass tools + ì„ íƒ ìž‘ì—…(&M) + + + + + + &About %1 + %1 ì •ë³´(&A) + + + + &Exit + 종료(&E) + + + + Exit + 종료 + + + + Close &Profile + 프로필 닫기(&P) + + + + &Settings + 설정(&S) + + + + Select &All + ëª¨ë‘ ì„ íƒ(&A) + + + + &Deselect All + ëª¨ë‘ ì„ íƒ í•´ì œ(&D) + + + + &Export selected... + ì„ íƒ ë‚´ë³´ë‚´ê¸°(&E) + + + + &Remove selected + ì„ íƒ ì‚­ì œ(&R) + + + + &Import files... + íŒŒì¼ ë¶ˆëŸ¬ì˜¤ê¸°(&I) + + + + &Open File... + íŒŒì¼ ì—´ê¸°(&O) + + + + + Select &GTA V Folder... + GTA V í´ë” ì„ íƒ(&G) + + + + + + + Select GTA V Folder... + GTA V í´ë” ì„ íƒ + + + + Show In-gam&e + ì¸ê²Œìž„ ë³´ì´ê¸°(&E) + + + + Hi&de In-game + ì¸ê²Œìž„ 숨기기(&D) + + + + + + Change &Title... + 제목 변경(&T) + + + + + + Change &Crew... + &ì¡°ì§ ìƒì§• 변경(&C) + + + + + + &Qualify as Avatar + 아바타 ìžê²© 부여(&Q) + + + + + + Change &Players... + 플레ì´ì–´ 변경(&P) + + + + + + Show In-game + ì¸ê²Œìž„ ë³´ì´ê¸° + + + + + + Hide In-game + ì¸ê²Œìž„ 숨기기 + + + + + + Select Profile + 프로필 ì„ íƒ + + + + + &Donate + 기부하기(&D) + + + + Donate + 기부하기 + + + + Donation methods + 기부 방법 + + + View + 보기 + + + Copy + 복사 + + + + &Copy + 복사(&C) + + + + Open File... + íŒŒì¼ ì—´ê¸°... + + + + + + + Open File + íŒŒì¼ ì—´ê¸° + + + + Can't open %1 because of not valid file format + 올바른 íŒŒì¼ í˜•ì‹ì´ 아니므로 %1ì„ ì—´ 수 없습니다. + + + + %1 - Messages + %1 - 뉴스 + + + diff --git a/res/gta5sync_ru.qm b/res/gta5sync_ru.qm index d85a42a..214adf1 100644 Binary files a/res/gta5sync_ru.qm and b/res/gta5sync_ru.qm differ diff --git a/res/gta5sync_ru.ts b/res/gta5sync_ru.ts index 7fcdc18..a05570c 100644 --- a/res/gta5sync_ru.ts +++ b/res/gta5sync_ru.ts @@ -10,7 +10,7 @@ - <span style=" font-weight:600;">%1</span><br/> + <span style="font-weight:600">%1</span><br/> <br/> %2<br/> <br/> @@ -20,7 +20,7 @@ Built with Qt %5<br/> Running with Qt %6<br/> <br/> %7 - <span style=" font-weight:600;">%1</span><br/> + <span style="font-weight:600">%1</span><br/> <br/> %2<br/> <br/> @@ -32,80 +32,75 @@ Running with Qt %6<br/> %7 - + &Close &Закрыть - + Translated by %1 Translated by translator, example Translated by Syping Перевёл %1 - + TRANSLATOR Insert your name here and profile here in following scheme, First Translator,First Profile\nSecond Translator\nThird Translator,Second Profile VADemon,https://github.com/VADemon/ - + A project for viewing Grand Theft Auto V Snapmatic<br/> Pictures and Savegames Проект Ð´Ð»Ñ Ð¿Ñ€Ð¾Ñмотра Grand Theft Auto V Snapmatic<br/> картинок и Ñохранений - + Copyright &copy; <a href="%1">%2</a> %3 Copyright &copy; <a href="%1">%2</a> %3 - + %1 is licensed under <a href="https://www.gnu.org/licenses/gpl-3.0.html#content">GNU GPLv3</a> %1 под лицензией <a href="https://www.gnu.org/licenses/gpl-3.0.html#content">GNU GPLv3</a> - - A project for viewing and sync Grand Theft Auto V Snapmatic<br/> -Pictures and Savegames - Проект Ð´Ð»Ñ Ð¿Ñ€Ð¾Ñмотра и ÑÐ¸Ð½Ñ…Ñ€Ð¾Ð½Ð¸Ð·Ð¸Ñ€Ð¾Ð²Ð°Ð½Ð¸Ñ <br/> -Grand Theft Auto V Snapmatic картинок и Ñохранений - - - + Release Релиз - + Release Candidate Предварительный выпуÑк - + Daily Build Ð”Ð½ÐµÐ²Ð½Ð°Ñ Ñборка - + Developer Разработчик - + Beta Бета - + Alpha Ðльфа - + + Custom - Ðе извеÑтен контекÑÑ‚ + Ðе извеÑтен контекÑÑ‚ + Ð¡Ð²Ð¾Ñ @@ -123,7 +118,8 @@ Grand Theft Auto V Snapmatic картинок и Ñохранений Dialog - Возможно не Ñто имелоÑÑŒ ввиду, немецкого перевода нету + Возможно не Ñто имелоÑÑŒ ввиду, немецкого перевода нету + Диалоговое окно @@ -185,58 +181,50 @@ Grand Theft Auto V Snapmatic картинок и Ñохранений ImageEditorDialog - - - + + + + Snapmatic Image Editor Редактор картинок Snapmatic - + Overwrite Image... ПерезапиÑать картинку... - - - Capacity: %1 - Величина: %1 + + Apply changes + Применить Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ - - ? - ? - - - - &Import... - &Импортировать... - - - + &Overwrite &ПерезапиÑать - + + Discard changes + Отменить Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ + + + &Close &Закрыть - + + Patching of Snapmatic Image failed because of I/O Error Ðе удалоÑÑŒ изменить картинку Snapmatic из-за ошибки ввода-вывода - + + Patching of Snapmatic Image failed because of Image Error Ðе удалоÑÑŒ изменить картинку Snapmatic из-за ошибки Image Error - - - Every taken Snapmatic have a different Capacity, a Snapmatic with higher Capacity can store a picture with better quality. - У каждого отÑнÑтого Snapmatic фото еÑть ÑÐ²Ð¾Ñ Ð²ÐµÐ»Ð¸Ñ‡Ð¸Ð½Ð°, в завиÑимоÑти от которой можно Ñохранить картинку Ñ Ð±Ð¾Ð»ÐµÐµ выÑоким качеÑтвом. - ImportDialog @@ -246,13 +234,13 @@ Grand Theft Auto V Snapmatic картинок и ÑохраненийИмпортировать... - - + + Ignore Aspect Ratio Игнорировать Ñоотн. Ñторон - + Avatar Ðватар @@ -262,99 +250,275 @@ Grand Theft Auto V Snapmatic картинок и ÑохраненийКартинка - + + Watermark + ВодÑной знак + + + Force Borderless + Обрезать рамки + + + Background Фон - - - + + + + Background Colour: <span style="color: %1">%1</span> Цвет фона: <span style="color: %1">%1</span> - - - ... - ... + + Select background colour + Выберите цвет фона - + ... + ... + + + + Select background image + Выбрать фоновое изображение + + + + Remove background image + Убрать фоновую картинку + + + + Import as-is, don't change the picture at all, guaranteed to break Snapmatic unless you know what you doing + Импортировать как еÑть, не менÑÑ ÐºÐ°Ñ€Ñ‚Ð¸Ð½ÐºÑƒ. ОбÑзательно поломает Snapmatic, еÑли не знаешь, что делаешь + + + + Background Image: %1 Ð¤Ð¾Ð½Ð¾Ð²Ð°Ñ ÐºÐ°Ñ€Ñ‚Ð¸Ð½ÐºÐ°: %1 - X - latin X - X + latin X + + X - + + Crop to Aspect Ratio + Обрезать до Ñоотн. Ñторон + + + Force Colour in Avatar Zone Задать цвет в зоне аватарки - + + Advanced + РаÑширенное + + + + Resolution: + Разрешение: + + + + Snapmatic resolution + Разрешение Snapmatic + + + + Avoid compression and expand buffer instead, improves picture quality, but may break Snapmatic + Ðе Ñжимать, а увеличить буфер. Улучшит качеÑтво картинки, но может поломать Snapmatic + + + + Unlimited Buffer + Ðеограниченный буфер + + + + Import as-is + Импортировать как еÑть + + + + Import options + Опции импорта + + + + &Options + &Опции + + + Import picture Импортировать картинку - + &OK &ОК - + Discard picture Отклонить картинку - + &Cancel - Я не уверен наÑчет горÑчих клавиш... + Я не уверен наÑчет горÑчих клавиш... + От&мена - - - + + + + Background Image: Ð¤Ð¾Ð½Ð¾Ð²Ð°Ñ ÐºÐ°Ñ€Ñ‚Ð¸Ð½ÐºÐ°: - - + + &Import new Picture... + &Импортировать картинку... + + + + &Crop Picture... + Об&резать картинку... + + + + &Load Settings... + &Загрузить наÑтройки... + + + + &Save Settings... + &Сохранить наÑтройки... + + + + Custom Avatar Custom Avatar Description in SC, don't use Special Character! Свой Ðватар - - + + + Custom Picture Custom Picture Description in SC, don't use Special Character! Ð¡Ð²Ð¾Ñ ÐšÐ°Ñ€Ñ‚Ð¸Ð½ÐºÐ° - + + Storage + Background Image: Storage + Хранилище + + + + Crop Picture... + Обрезать картинку... + + + + &Crop + Об&резать + + + + Crop Picture + Обрезать картинку + + + + + Please import a new picture first + Импортируй Ñначала новую картинку + + + + + Default + Default as Default Profile + По умолчанию + + + + + + + + + + + + + + + + + + + + + + + Profile %1 + Profile %1 as Profile 1 + Профиль %1 + + + + + Load Settings... + Загрузить наÑтройки... + + + + + Please select your settings profile + ПожалуйÑта, выбери профиль Ð´Ð»Ñ Ð½Ð°Ñтроек + + + + + Save Settings... + Сохранить наÑтройки... + + + + Are you sure to use a square image outside of the Avatar Zone? When you want to use it as Avatar the image will be detached! Ты точно хочешь иÑпользовать квадратное изображение вне зоны аватарки? ЕÑли Ñто аватар, то изображение будет обрезано! - + + Snapmatic Avatar Zone Зона Snapmatic Ðватарки - + Select Colour... Выбрать цвет... - + File Background Image: File Файл @@ -369,16 +533,26 @@ When you want to use it as Avatar the image will be detached! + Apply changes + Применить Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ + + + &Save &Сохранить - + + Discard changes + Отменить Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ + + + &Close &Закрыть - + JSON Error Ошибка JSON @@ -391,32 +565,57 @@ When you want to use it as Avatar the image will be detached! ПроÑмотрщик карты Snapmatic - + + Close viewer + Закрыть проÑмотрщик + + + &Close &Закрыть - + + Apply new position + Применить новую позицию + + + &Apply &Применить - + + Revert old position + Вернуть Ñтарую позицию + + + &Revert &Откатить - - &Set - &Изменить + + Select new position + Выбрать новую позицию - + + &Select + &Выбрать + + + + Quit select position + Покинуть выбор позиции + + + &Done &Готово - + X: %1 Y: %2 X and Y position @@ -432,19 +631,17 @@ Y: %2 Открывать/выбирать Ñодержимое - Open with Singleclick - Открывать одним щелчком + Открывать одним щелчком - + Open with Doubleclick Открывать двойным щелчком - Select with Singleclick - Выбирать одним щелчком + Выбирать одним щелчком @@ -464,7 +661,7 @@ Y: %2 Screen Resolution: %1x%2 - Разрешение Ñкрана: %1x%2 + Как разрешение Ñкрана: %1x%2 @@ -489,7 +686,7 @@ Y: %2 Force using Custom Folder - ИÑпользовать Ñту папку GTA V + ИÑпользовать Ñту папку @@ -520,7 +717,7 @@ Y: %2 Enable Custom Quality - ИÑпользовать другое качеÑтво + Ð’Ñ‹Ñтавить другое качеÑтво @@ -559,215 +756,276 @@ Y: %2 + Game + Игра + + + + Social Club Version + ВерÑÐ¸Ñ Social Club + + + + + + + + + + + Found: %1 + Ðайдено: %1 + + + + + + + + + + + + + Language: %1 + Язык: %1 + + + + Steam Version + ВерÑÐ¸Ñ Steam + + + Feedback ÐžÐ±Ñ€Ð°Ñ‚Ð½Ð°Ñ ÑвÑзь - + Participation - УчаÑтвие + УчаÑтие - - + + Participate in %1 User Statistics УчаÑтвовать в пользовательÑкой ÑтатиÑтике %1 - + Categories Категории - + Hardware, Application and OS Specification - Application = gta5view + Application = gta5view + Железо, выпуÑк программы, тип ОС - + System Language Configuration Языковые наÑтройки ÑиÑтемы - + Application Configuration ÐаÑтройки программы - + Other Другое - - - + + + Participation ID: %1 Ðомер учаÑтника: %1 - + &Copy &Копировать - - - User Feedback - Форма обратной ÑвÑзи - - - - Limit: 1 message/day - Ограничение: 1 Ñообщение в день - - - - &Send - &Отправить - - - + Language for Areas - Язык Ð´Ð»Ñ Ð¼ÐµÑтоположений? + Язык Ð´Ð»Ñ Ð¼ÐµÑтоположений? + Язык перевода меÑтоположений - + Style - Стиль + Внешний вид - + Style: Стиль: - + Font Шрифт - Always use Message Font (Windows 2003 and earlier) - Ð’Ñегда иÑпользовать шрифт Ñообщений (Windows 2003 и ранние) + Ð’Ñегда иÑпользовать шрифт Ñообщений (Windows 2003 и ранние) - + Interface Ð˜Ð½Ñ‚ÐµÑ€Ñ„ÐµÐ¹Ñ - + + Personal Usage Data + Пользование программой + + + Language for Interface Язык интерфейÑа - - - - + + + + Current: %1 СейчаÑ: %1 - + Use Default Style (Restart) ИÑпользовать Ñтандартный Ñтиль (ПерезапуÑк) - - Sync - Sync + + Use Default Font (Restart) + ИÑпользовать Ñтандартный шрифт (ПерезапуÑк) - - Sync is not implemented at current time - Ð¡Ð¸Ð½Ñ…Ñ€Ð¾Ð½Ð¸Ð·Ð°Ñ†Ð¸Ñ Ð¿Ð¾ÐºÐ° ещё не реализована + + Font: + Шрифт: - + Apply changes Применить Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ - + &OK OK, Cancel, Apply &ОК - + Discard changes Отвергнуть Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ - + &Cancel OK, Cancel, Apply От&мена - - %1 (Next Closest Language) - First language a person can talk with a different person/application. "Native" or "Not Native". - - - - + System System in context of System default СиÑтема - - %1 (Closest to Interface) - Next closest language compared to the Interface - %1 (Совпадает Ñ Ð¸Ð½Ñ‚ÐµÑ€Ñ„ÐµÐ¹Ñом) + + %1 (Game language) + Next closest language compared to the Game settings + %1 (Язык игры) - + + + %1 (Closest to Interface) + Next closest language compared to the Interface + %1 (Как Ñзык интерфейÑа) + + + + + Auto Automatic language choice. ÐвтоматичеÑки - + + %1 (Language priority) + First language a person can talk with a different person/application. "Native" or "Not Native". + %1 (Приоритетный Ñзык) + + + %1 %1 %1 - + The new Custom Folder will initialise after you restart %1. Ð”Ñ€ÑƒÐ³Ð°Ñ Ð¿Ð°Ð¿ÐºÐ° будет загружена поÑле перезапуÑка %1. - + View %1 User Statistics Online - ПоÑмотреть пользовательÑкую ÑтатиÑтику %1 онлайн + ПоÑмотреть ÑтатиÑтику %1 онлайн - + Not registered Ðе зарегиÑтрирован - - A feedback message have to between 3-1024 characters long - Сообщение обратное ÑвÑзи не должно быть длинее 1024 Ñимволов + + + + + Yes + Да - + + + No + Ðет + + + + + OS defined + ÐаÑтройка от ОС + + + + + Steam defined + ÐаÑтройка от Steam + + + No Profile No Profile, as default - Ðет Ð¿Ñ€Ð¾Ñ„Ð¸Ð»Ñ + Ðе выбран - - - + + + Profile: %1 Профиль: %1 @@ -775,27 +1033,23 @@ Y: %2 PictureDialog - %1 - Snapmatic Picture Viewer - %1 - ПроÑмотрщик фотографий Snapmatic + + <span style="font-weight:600">Title: </span>%6<br/> +<span style="font-weight:600">Location: </span>%7 (%1, %2, %3)<br/> +<span style="font-weight:600">Players: </span>%4 (Crew %5)<br/> +<span style="font-weight:600">Created: </span>%8 + <span style="font-weight:600">Заголовок: </span>%6<br/> +<span style="font-weight:600">МеÑто: </span>%7 (%1, %2, %3)<br/> +<span style="font-weight:600">Игроки: </span>%4 (Банда %5)<br/> +<span style="font-weight:600">Сделано: </span>%8 - - <span style=" font-weight:600;">Title: </span>%6<br/> -<span style=" font-weight:600;">Location: </span>%7 (%1, %2, %3)<br/> -<span style=" font-weight:600;">Players: </span>%4 (Crew %5)<br/> -<span style=" font-weight:600;">Created: </span>%8 - <span style=" font-weight:600;">Заголовок: </span>%6<br/> -<span style=" font-weight:600;">МеÑто: </span>%7 (%1, %2, %3)<br/> -<span style=" font-weight:600;">Игроки: </span>%4 (Банда %5)<br/> -<span style=" font-weight:600;">Сделано: </span>%8 - - - + &Manage &Управление - + Manage picture ÐаÑтройки картинки @@ -805,53 +1059,53 @@ Y: %2 ПроÑмотрщик фотографий Snapmatic - %1 - + Close viewer Закрыть проÑмотрщик - + &Close &Закрыть - - + + Export ЭкÑпортировать - - + + Export as &Picture... ЭкÑпортировать как &картинку... - - + + Export as &Snapmatic... ЭкÑпортировать как &Snapmatic... - - + + &Overwrite Image... &ПерезапиÑать картинку... - - + + &Edit Properties... &Изменить ÑвойÑтва... - - + + Open &Map Viewer... Открыть &карту... - + Key 1 - Avatar Preview Mode Key 2 - Toggle Overlay Arrow Keys - Navigate @@ -860,142 +1114,140 @@ Arrow Keys - Navigate Стрелки - ÐÐ°Ð²Ð¸Ð³Ð°Ñ†Ð¸Ñ - - + Snapmatic Picture Viewer ПроÑмотрщик фотографий Snapmatic - - + Failed at %1 Ошибка при %1 - - + + No Crew Вне банды - - - + + + No Players Игроков нет - + Avatar Preview Mode Press 1 for Default View Режим проÑмотра аватарок Ðажмите 1 Ð´Ð»Ñ Ñтандартного проÑмотра - + Unknown Location ÐеизвеÑтное меÑто - + Portable Network Graphics (*.png) Картинка Portable Network Graphics (*.png) - - + + Overwrite %1 with current Snapmatic picture? ПерезапиÑать %1 текущей картинкой Snapmatic? - + Export as Picture... ЭкÑпорт как картинку... - + JPEG Graphics (*.jpg *.jpeg) Картинка JPEG (*.jpg *.jpeg) - - + + + + + - - - Export as Picture ЭкÑпорт как картинку - + Failed to export the picture because the system occurred a write failure Ðе удалоÑÑŒ ÑкÑпортировать картинку из-за ошибки ÑиÑтемы при запиÑи - + Failed to export the picture because the format detection failures Ðе удалоÑÑŒ ÑкÑпортировать картинку, потому что произошла ошибка при раÑпозновании формата - + Failed to export the picture because the file can't be written Ðе удалоÑÑŒ ÑкÑпортировать картинку, так как файл не может быть запиÑан - + Failed to export the picture because of an unknown reason Ðе удалоÑÑŒ ÑкÑпортировать картинку по неизвеÑтной причине - - + + Failed to export current Snapmatic picture Ðе удалоÑÑŒ ÑкÑпортировать текущую картинку Snapmatic - + Export as Snapmatic... ЭкÑпортировать как Snapmatic... - - - - - + + + + + Export as Snapmatic ЭкÑпортировать как Snapmatic - + Exported Snapmatic to "%1" because of using the .auto extension. Snapmatic был ÑкÑпортирован как "%1" из-за раÑÑˆÐ¸Ñ€ÐµÐ½Ñ Ñ„Ð°Ð¹Ð»Ð°. - - + + No valid file is selected Выбранный файл неверен - + GTA V Export (*.g5e) GTA V Export (*.g5e) - + GTA V Raw Export (*.auto) GTA V ЭкÑпорт ИÑходника (*.auto) - + Snapmatic pictures (PGTA*) Картинки Snapmatic (PGTA*) - - + + Open &JSON Editor... Открыть &редактор JSON... @@ -1013,43 +1265,43 @@ Press 1 for Default View ДоÑтупные игроки: - + Selected Players: Выбранные игроки: - + &Apply &Применить - + &Cancel &Отмена - + Add Players... Добавить игроков... - + Failed to add more Players because the limit of Players are %1! Ðевозможно добавить больше игроков из-за Ð¾Ð³Ñ€Ð°Ð½Ð¸Ñ‡ÐµÐ½Ð¸Ñ Ð² %1! - - + + Add Player... Добавить игрока... - + Enter Social Club Player ID Введите идентификатор игрока из Social Club - + Failed to add Player %1 because Player %1 is already added! ÐÐµÐ»ÑŒÐ·Ñ Ð¿Ð¾Ð²Ñ‚Ð¾Ñ€Ð½Ð¾ добавить игрока %1, %1 уже добавлен! @@ -1092,81 +1344,83 @@ Press 1 for Default View &Закрыть - + Loading... Загрузка... - + Snapmatic Loader Загрузчик Snapmatic - + <h4>Following Snapmatic Pictures got repaired</h4>%1 - Change wording if the %1 is not a multiline beginning at new line + Change wording if the %1 is not a multiline beginning at new line + <h4>ÐижеÑледующие картинки Snapmatic были воÑÑтановлены</h4>%1 - - - - + + + + + + + + + + + + + + + + + + + Import... Импортировать... - - - - - - - - - - - - - - - - - - - - + + + + + + Import Импортировать - - + + Savegames files (SGTA*) Файлы ÑÐ¾Ñ…Ñ€Ð°Ð½ÐµÐ½Ð¸Ñ (SGTA*) - - + + Snapmatic pictures (PGTA*) Картинка Snapmatic (PGTA*) - - - - + + + + All files (**) Ð’Ñе файлы (**) - - + + Import file %1 of %2 files ИмпортируютÑÑ Ñ„Ð°Ð¹Ð»Ñ‹ %1 из %2 - + Import failed with... %1 @@ -1175,169 +1429,169 @@ Press 1 for Default View %1 - - + + Failed to read Snapmatic picture Ðе удалоÑÑŒ загрузить картинку Snapmatic - - + + Failed to read Savegame file Ðе удалоÑÑŒ загрузить файл ÑÐ¾Ñ…Ñ€Ð°Ð½ÐµÐ½Ð¸Ñ - - - + + + No valid file is selected Выбранный файл неверен - + Enabled pictures: %1 of %2 Включенные картинки: %1 из %2 - + Importable files (%1) Файлы Ð´Ð»Ñ Ð¸Ð¼Ð¿Ð¾Ñ€Ñ‚Ð° (%1) - - - + + + All image files (%1) Ð’Ñе файлы изображений (%1) - - - + + + Can't import %1 because file can't be open Ðе удалоÑÑŒ открыть %1, файл не может быть открыт - - - + + + Can't import %1 because file can't be parsed properly Ðе получилоÑÑŒ импортировать %1, файл не может быть правильно обработан - + Can't import %1 because file format can't be detected Ðе получилоÑÑŒ импортировать %1, не удалоÑÑŒ определить формат файла - + Failed to import the Snapmatic picture, file not begin with PGTA or end with .g5e Ðе удалоÑÑŒ импортировать картинку Snapmatic, название не начинаетÑÑ Ñ PGTA или не заканчиваетÑÑ Ñ .g5e - - Failed to import the Snapmatic picture, the picture is already in the game - Ðе удалоÑÑŒ импортировать картинку Snapmatic, картинка уже в игре - - - + Failed to import the Snapmatic picture, can't copy the file into profile Ðе удалоÑÑŒ импортировать картинку Snapmatic, не получилоÑÑŒ Ñкопировать файл в профиль - + Failed to import the Savegame, can't copy the file into profile Ðе удалоÑÑŒ импортировать Ñохранение, не получилоÑÑŒ Ñкопировать файл в профиль - + Failed to import the Savegame, no Savegame slot is left Ðе удалоÑÑŒ импортировать Ñохранение, нет пуÑтых Ñчеек под ÑÐ¾Ñ…Ñ€Ð°Ð½ÐµÐ½Ð¸Ñ - - + + JPG pictures and GTA Snapmatic Картинки JPG и GTA Snapmatic - - + + JPG pictures only Только картинки JPG - - + + GTA Snapmatic only Только GTA Snapmatic - + Initialising export... Подготовка к ÑкÑпорту... - - + + No Snapmatic pictures or Savegames files are selected - Ðе выделены ни один Snapmatic или Ñохранение + Ðе выделен ни один Snapmatic или Ñохранение - - - + + + Remove selected СнÑть выделение - + You really want remove the selected Snapmatic picutres and Savegame files? Точно ли хочешь удалить выбранные картинки Snapmatic и файлы Ñохранений? - - + + Prepare Content for Import... + Подготовка данных к импорту... + + + + Qualify as Avatar Пометить как Ðватар - - - - - - + + + + + + No Snapmatic pictures are selected Ðе выделена ни одна картинка Snapmatic - - - - + + + + Patch selected... Пропатчить выделенные... - - - - - - - - + + + + + + + + Patch file %1 of %2 files ИзменÑетÑÑ Ñ„Ð°Ð¹Ð» %1 из %2 - - - - - - + + + + + + %1 failed with... %2 @@ -1347,80 +1601,86 @@ Press 1 for Default View %2 - + + A Snapmatic picture already exists with the uid %1, you want assign your import a new uid and timestamp? + Можно иÑпользовать Ñлово "припиÑать" + + + + Failed to remove all selected Snapmatic pictures and/or Savegame files Ðе удалоÑÑŒ удалить вÑе выделенные картинки Snapmatic и/или ÑÐ¾Ñ…Ñ€Ð°Ð½ÐµÐ½Ð¸Ñ - + Qualify %1 failed with... Помечание - - + + Change Players... Изменить игроков... - + Change Players %1 failed with... Измение игроков - - - + + + Change Crew... Изменить банду... - + Failed to enter a valid Snapmatic Crew ID Введённый идентификатор банды не верен - + Change Crew %1 failed with... Изменение банды - - - + + + Change Title... Изменить заголовок... - + Failed to enter a valid Snapmatic title Введённый заголовок не верен - + Change Title %1 failed with... Изменение заголовка - + %1Export Snapmatic pictures%2<br><br>JPG pictures make it possible to open the picture with a Image Viewer<br>GTA Snapmatic make it possible to import the picture into the game<br><br>Export as: %1ЭÑкпортировать картинки Snapmatic%2<br><br>Картинки JPG можно открыть любым проÑмотрщиком<br>Картинки формата GTA Snapmatic можно Ñнова импортировать в игру<br><br>ЭкÑпортировать как: - - - - - + + + + + Export selected... Экпортировать выделенное... - + Export failed with... %1 @@ -1429,20 +1689,20 @@ Press 1 for Default View %1 - - - + + + Export file %1 of %2 files ЭкÑпортируетÑÑ Ñ„Ð°Ð¹Ð» %1 из %2 - + All profile files (*.g5e SGTA* PGTA*) Ð’Ñе файлы Ð¿Ñ€Ð¾Ñ„Ð¸Ð»Ñ (*.g5e SGTA* PGTA*) - - + + GTA V Export (*.g5e) GTA V Export (*.g5e) @@ -1450,33 +1710,22 @@ Press 1 for Default View QApplication - - Font - Шрифт - - - - Selected Font: %1 - Выбранный шрифт: %1 - - - <h4>Welcome to %1!</h4>You want to configure %1 before you start using it? - <h4>Добро пожаловать в %1!</h4>Хочешь изменить наÑтройки %1 перед иÑпользованием? + <h4>Добро пожаловать в %1!</h4>Хочешь изменить наÑтройки %1 перед иÑпользованием? SavegameDialog - + Savegame Viewer ПроÑмотрщик Ñохранений - <span style=" font-weight:600;">Savegame</span><br><br>%1 - <span style=" font-weight:600;">Сохранение</span><br><br>%1 + <span style="font-weight:600">Savegame</span><br><br>%1 + <span style="font-weight:600">Сохранение</span><br><br>%1 @@ -1489,7 +1738,7 @@ Press 1 for Default View &Закрыть - + Failed at %1 Ошибка при %1 @@ -1528,7 +1777,7 @@ Press 1 for Default View Удалить Ñохранение - + Export Savegame... ЭкÑпортировать Ñохранение... @@ -1538,75 +1787,81 @@ Press 1 for Default View СОХРÐÐЕÐИЕ %3 - %1<br>%2 - - + + WRONG FORMAT ÐЕВЕРÐЫЙ ФОРМÐТ - - + + AUTOSAVE - %1 %2 ÐВТОСОХРÐÐЕÐИЕ - %1 %2 - - + + SAVE %3 - %1 %2 СОХРÐÐЕÐИЕ %3 - %1 %2 - + UNKNOWN ÐЕИЗВЕСТÐО - + Are you sure to delete %1 from your savegames? Ð’Ñ‹ уверены, что хотите удалить Ñохранение %1? - - + + Delete Savegame Удалить Ñохранение - + Failed at deleting %1 from your savegames Ðе удалоÑÑŒ удалить Ñохранение %1 - + &View &ПроÑмотр - + + + &Remove &Удалить - + + &Select &Выбрать - + + &Deselect Сн&Ñть выбор - + + Select &All Ð’&ыбрать вÑе - + + &Deselect All СнÑть выбо&Ñ€ Ñо вÑех @@ -1616,7 +1871,9 @@ Press 1 for Default View Копировать Ñохранение - + + + &Export &ЭкÑпортировать @@ -1664,13 +1921,13 @@ Press 1 for Default View - - - - - - - + + + + + + + Snapmatic Properties СвойÑтва Snapmatic @@ -1710,7 +1967,7 @@ Press 1 for Default View Ð—Ð½Ð°Ñ‡ÐµÐ½Ð¸Ñ Ð² Snapmatic - + Crew: %1 (%2) Банда: %1 (%2) @@ -1720,31 +1977,31 @@ Press 1 for Default View Meme - - + + Snapmatic Title Заголовок Snapmatic - + Title: %1 (%2) Заголовок: %1 (%2) - + Players: %1 (%2) Multiple Player are inserted here Игроки: %1 (%2) - + Player: %1 (%2) One Player is inserted here Игрок: %1 (%2) - - + + Appropriate: %1 Подходит: %1 @@ -1756,7 +2013,7 @@ Press 1 for Default View Qualify as Avatar automatically at apply - При применении наÑтроек пометить как аватар + Пометить как аватарку @@ -1765,71 +2022,81 @@ Press 1 for Default View + Apply changes + Применить Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ + + + &Apply &Применить - + + Discard changes + + + + &Cancel &Отмена - - - + + + Edit Правка - + Yes Yes, should work fine Да - + No No, could lead to issues Ðет - + <h4>Unsaved changes detected</h4>You want to save the JSON content before you quit? <h4>ÐеÑохранённые изменениÑ</h4>Сохранить Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð² JSON перед выходом? - + Patching of Snapmatic Properties failed because of %1 Ðе удалоÑÑŒ изменить ÑвойÑтва Snapmatic из-за %1 - + Patching of Snapmatic Properties failed because of JSON Error Ðе удалоÑÑŒ измененить ÑвойÑтва Snapmatic из-за ошибки JSON - - - - + + + + Patching of Snapmatic Properties failed because of I/O Error Ðе удалоÑÑŒ измененить ÑвойÑтва Snapmatic из-за проблемы ввода/вывода - - + + New Snapmatic title: Ðовый заголовок Snapmatic: - - + + Snapmatic Crew Банда на Snapmatic - - + + New Snapmatic crew: ÐÐ¾Ð²Ð°Ñ Ð±Ð°Ð½Ð´Ð° на Snapmatic: @@ -1837,66 +2104,66 @@ Press 1 for Default View SnapmaticPicture - + PHOTO - %1 ФОТО - %1 - + open file %1 Открыть файл %1 - + header not exists ОтÑутÑтвует шапка (header) - + header is malformed Шапка (header) повреждена - + picture not exists (%1) Картинки не ÑущеÑтвует (%1) - + JSON not exists (%1) JSON не ÑущеÑтвует (%1) - + title not exists (%1) Заголовок отÑутÑтвует (%1) - + description not exists (%1) ОпиÑание отÑутÑтвует (%1) - + reading file %1 because of %2 Example for %2: JSON is malformed error Чтение из файла %1 из-за %2 - - + + JSON is incomplete and malformed JSON не полный и повреждён - - + + JSON is incomplete JSON чаÑтично отÑутÑтвует - - + + JSON is malformed JSON повреждён @@ -1940,78 +2207,88 @@ Press 1 for Default View - - + + Delete picture Удалить картинку - + Are you sure to delete %1 from your Snapmatic pictures? Уверены, что хотите удалить %1 из коллекции картинок Snapmatic? - + Failed at deleting %1 from your Snapmatic pictures - Ðе удалоÑÑŒ удалить %1 из колелкции картинок Snapmatic + Ðе удалоÑÑŒ удалить %1 из колелкции картинок Snapmatic - + Failed to hide %1 In-game from your Snapmatic pictures Ðе удалоÑÑŒ Ñкрыть %1 из ÑпиÑка картинок Snapmatic в игре - + Failed to show %1 In-game from your Snapmatic pictures Ðе удалоÑÑŒ показать %1 в ÑпиÑке картинок Snapmatic в игре - + + + Edi&t &Правка - + + + Show &In-game Показывать в &игре - + + + Hide &In-game Ск&рыть в игре - + &Export &ЭкÑпорт - + &View По&казать - + &Remove У&далить - + + &Select &Выделить - + + &Deselect Сн&Ñть выделение - + + Select &All Ð’&ыбрать вÑе - + + &Deselect All СнÑть выбо&Ñ€ Ñо вÑех @@ -2029,22 +2306,22 @@ Press 1 for Default View TelemetryDialog - + + You want help %1 to improve in the future by including personal usage data in your submission? + Разрешишь нам Ñобирать ÑтатиÑтику о пользовании тобой %1? Это поможет нам в разработке. + + + %1 User Statistics - %1 пользовательÑÐºÐ°Ñ ÑтатиÑтика + %1 ПользовательÑÐºÐ°Ñ ÑтатиÑтика - - You want help %1 to improve in the future by collection of data? - Хочешь ли помочь будущему развитию %1 отправкой данных? + + Yes, I want include personal usage data. + Да, передавать данные о пользовании программой. - - Yes, I would like to take part. - Да, хочу учаÑтвовать. - - - + &OK &ОК @@ -2119,7 +2396,7 @@ Press 1 for Default View - + Select &GTA V Folder... Выбрать &папку GTA V... @@ -2135,33 +2412,44 @@ Press 1 for Default View + + Change &Players... &Изменить игрока... + + Change &Title... Изменить &Заголовок... + + Change &Crew... Изменить &банду... + + &Qualify as Avatar &Пометить как Ðватар + + + &Close &Закрыть &Selection visibility - Ð’&идимоÑть выделение + Ð’&идимоÑть выделенного @@ -2194,67 +2482,101 @@ Press 1 for Default View &Открыть файл... - - - + + + Select Profile Выбор Ð¿Ñ€Ð¾Ñ„Ð¸Ð»Ñ - - - + + + Select GTA V Folder... Выбрать папку GTA V... - + %2 - %1 %2 - %1 - - + + &About %1 &О программе %1 - + + + &Donate + По&жертвовать + + + + Donate + Пожертвовать + + + + Donation methods + СпоÑобы Ð´Ð»Ñ Ð²Ð·Ð½Ð¾Ñа + + + + &Copy + &Копировать + + + View + ПроÑмотр + + + Copy + Копировать + + + Open File... Открыть файл... - - - - + + + + Open File Открыть файл - + Can't open %1 because of not valid file format Ðе удалоÑÑŒ открыть %1 из-за неверного формата файла + + + %1 - Messages + %1 - ÐовоÑти + &Reload Пере&загрузить - - - + + + Show In-game Показывать в игре - - - + + + Hide In-game Скрыть в игре diff --git a/res/gta5sync_uk.qm b/res/gta5sync_uk.qm index 8d3756b..07fed50 100644 Binary files a/res/gta5sync_uk.qm and b/res/gta5sync_uk.qm differ diff --git a/res/gta5sync_uk.ts b/res/gta5sync_uk.ts index 1448cd6..c49c9ce 100644 --- a/res/gta5sync_uk.ts +++ b/res/gta5sync_uk.ts @@ -10,7 +10,7 @@ - <span style=" font-weight:600;">%1</span><br/> + <span style="font-weight:600">%1</span><br/> <br/> %2<br/> <br/> @@ -20,7 +20,7 @@ Built with Qt %5<br/> Running with Qt %6<br/> <br/> %7 - <span style=" font-weight:600;">%1</span><br/> + <span style="font-weight:600">%1</span><br/> <br/> %2<br/> <br/> @@ -32,77 +32,75 @@ Running with Qt %6<br/> %7 - + &Close &Закрити - + Translated by %1 Translated by translator, example Translated by Syping Переклад %1 - + TRANSLATOR Insert your name here and profile here in following scheme, First Translator,First Profile\nSecond Translator\nThird Translator,Second Profile - VenJam1n,g5e://about?VmVuSmFtMW4=:U3RlYW06IDxhIGhyZWY9Imh0dHBzOi8vc3RlYW1jb21tdW5pdHkuY29tL3Byb2ZpbGVzLzc2NTYxMTk3OTg0NjM1ODE2LyI+UFJPRmVzc29SICdBcHBsZVNPZnQnPC9hPjxici8+U29jaWFsIENsdWI6IDxhIGhyZWY9Imh0dHBzOi8vc29jaWFsY2x1Yi5yb2Nrc3RhcmdhbWVzLmNvbS9tZW1iZXIvLS1WZW5KYW0xbi0tLzU2Mzc1NjkiPlZlbkphbTFuPC9hPjxici8+VHdpdHRlcjogPGEgaHJlZj0iaHR0cHM6Ly90d2l0dGVyLmNvbS9fVmVuSmFtMW4iPlZlbkphbTFuPC9hPg== + PROFessoR 'AppleSOft',https://steamcommunity.com/id/AppleSOft +VenJam1n,https://socialclub.rockstargames.com/member/--VenJam1n-- +twitter,https://twitter.com/_VenJam1n + VenJam1n,g5e://about?VmVuSmFtMW4:U3RlYW06IDxhIGhyZWY9Imh0dHBzOi8vc3RlYW1jb21tdW5pdHkuY29tL3Byb2ZpbGVzLzc2NTYxMTk3OTg0NjM1ODE2LyI+UFJPRmVzc29SICdBcHBsZVNPZnQnPC9hPjxici8+U29jaWFsIENsdWI6IDxhIGhyZWY9Imh0dHBzOi8vc29jaWFsY2x1Yi5yb2Nrc3RhcmdhbWVzLmNvbS9tZW1iZXIvLS1WZW5KYW0xbi0tLzU2Mzc1NjkiPlZlbkphbTFuPC9hPjxici8+VHdpdHRlcjogPGEgaHJlZj0iaHR0cHM6Ly90d2l0dGVyLmNvbS9fVmVuSmFtMW4iPlZlbkphbTFuPC9hPjxici8+PGJyLz7Qn9C+0LbQtdGA0YLQstGD0LLQsNC90L3Rjzxici8+PGEgaHJlZj0iaHR0cHM6Ly9zdGVhbWNvbW11bml0eS5jb20vdHJhZGVvZmZlci9uZXc/cGFydG5lcj0yNDM3MDA4OCZ0b2tlbj1HSW16XzhRSyI+U3RlYW0gVHJhZGU8L2E - - A project for viewing and sync Grand Theft Auto V Snapmatic<br/> -Pictures and Savegames - Проект Ð´Ð»Ñ Ð¿ÐµÑ€ÐµÐ³Ð»Ñду Ñ– Ñинхронізації Grand Theft Auto V Snapmatic - - - + A project for viewing Grand Theft Auto V Snapmatic<br/> Pictures and Savegames Проект Ð´Ð»Ñ Ð¿ÐµÑ€ÐµÐ³Ð»Ñду Grand Theft Auto V Snapmatic<br/> зображень та Ñейвів - + Copyright &copy; <a href="%1">%2</a> %3 ÐвторÑьке право &copy; <a href="%1">%2</a> %3 - + %1 is licensed under <a href="https://www.gnu.org/licenses/gpl-3.0.html#content">GNU GPLv3</a> %1 ліцензовано під <a href="https://www.gnu.org/licenses/gpl-3.0.html#content">GNU GPLv3</a> - + Release Реліз - + Release Candidate Реліз-Кандидат - + Daily Build Щоденна Збірка - + Developer Розробник - + Beta Бета - + Alpha Ðльфа - + + Custom Custom @@ -182,58 +180,50 @@ Pictures and Savegames ImageEditorDialog - + Overwrite Image... ПерезапиÑати зображеннÑ... - - - Capacity: %1 - Величина: %1 + + Apply changes + ЗаÑтоÑувати зміни - - ? - ? - - - - &Import... - &Імпорт... - - - + &Overwrite &ПерезапиÑати - + + Discard changes + СкаÑувати зміни + + + &Close &Закрити - - - + + + + Snapmatic Image Editor Редактор Snapmatic зображень - + + Patching of Snapmatic Image failed because of I/O Error Ð’Ð¸Ð¿Ñ€Ð°Ð²Ð»ÐµÐ½Ð½Ñ Snapmatic Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð½Ñ Ð½Ðµ вдалоÑÑ Ñ‡ÐµÑ€ÐµÐ· I/O Error - + + Patching of Snapmatic Image failed because of Image Error Ð’Ð¸Ð¿Ñ€Ð°Ð²Ð»ÐµÐ½Ð½Ñ Snapmatic Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð½Ñ Ð½Ðµ вдалоÑÑ Ñ‡ÐµÑ€ÐµÐ· помилку картинки - - - Every taken Snapmatic have a different Capacity, a Snapmatic with higher Capacity can store a picture with better quality. - Кожен знÑтий Snapmatic має різну величину, в залежноÑті від Ñкої можна зберігати Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð½Ñ Ð±Ñ–Ð»ÑŒÑˆ виÑокої ÑкоÑті. - ImportDialog @@ -248,109 +238,283 @@ Pictures and Savegames Ð—Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð½Ñ - + Avatar Ðватар - - + + Ignore Aspect Ratio Ігнорувати ÑÐ¿Ñ–Ð²Ð²Ñ–Ð´Ð½Ð¾ÑˆÐµÐ½Ð½Ñ Ñторін - + + Watermark + ВодÑний знак + + + Force Borderless + ПримуÑово без рамок + + + Background Фон - - - + + + + Background Colour: <span style="color: %1">%1</span> Фоновий колір: <span style="color: %1">%1</span> - - - ... - ... + + Select background colour + Виберіть колір фону - - - + ... + ... + + + + + + Background Image: Фонове зображеннÑ: - - X - Ð¥ + + Select background image + Виберіть фонове Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð½Ñ - + + Remove background image + Видалити фонове Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð½Ñ + + + X + Ð¥ + + + + Crop to Aspect Ratio + Обрізати під ÑÐ¿Ñ–Ð²Ð²Ñ–Ð´Ð½Ð¾ÑˆÐµÐ½Ð½Ñ Ñторін + + + Force Colour in Avatar Zone ПримуÑовий колір в зоні Ðватару - + + Advanced + Додатково + + + + Resolution: + РозширеннÑ: + + + + Snapmatic resolution + Ð Ð¾Ð·ÑˆÐ¸Ñ€ÐµÐ½Ð½Ñ Snapmatic + + + + Avoid compression and expand buffer instead, improves picture quality, but may break Snapmatic + Ðе ÑтиÑкати, а збільшити буфер. Поліпшить ÑкіÑть картинки, але може поламати Snapmatic + + + + Unlimited Buffer + Ðеобмежений буфер + + + + Import as-is, don't change the picture at all, guaranteed to break Snapmatic unless you know what you doing + Імпортуати Ñк Ñ”, взагалі не змінюєтьÑÑ Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð½Ñ, гарантовано зламаєтьÑÑ Snapmatic, тільки Ñкщо не знаєте, що робите + + + + Import as-is + Імпортувати Ñк Ñ” + + + + Import options + Параметри імпорту + + + + &Options + &Параметри + + + Import picture Імпортувати Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð½Ñ - + &OK &OK - + Discard picture Відхилити Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð½Ñ - + &Cancel &СкаÑувати - - + + &Import new Picture... + &Імпортувати нове зображеннÑ... + + + + &Crop Picture... + &Обрізати зображеннÑ... + + + + &Load Settings... + &Завантажити параметри... + + + + &Save Settings... + &Зберегти параметри... + + + + Custom Avatar Custom Avatar Description in SC, don't use Special Character! КориÑтувацький Ðватар - - + + + Custom Picture Custom Picture Description in SC, don't use Special Character! КориÑтувацьке Ð—Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð½Ñ - + + Storage + Background Image: Storage + Ð—Ð±ÐµÑ€Ñ–Ð³Ð°Ð½Ð½Ñ + + + + Crop Picture... + Обрізати зображеннÑ... + + + + &Crop + &Обрізати + + + + Crop Picture + Обрізати Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð½Ñ + + + + + Please import a new picture first + Спершу імпортуйте нове Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð½Ñ + + + + + Default + Default as Default Profile + Стандартний + + + + + + + + + + + + + + + + + + + + + + + Profile %1 + Profile %1 as Profile 1 + Профіль %1 + + + + + Load Settings... + Завантажити параметри... + + + + + Save Settings... + Зберегти параметри... + + + + Snapmatic Avatar Zone Зона Snapmatic Ðватару - + + Are you sure to use a square image outside of the Avatar Zone? When you want to use it as Avatar the image will be detached! Ви впевнені, що будете викориÑтовувати квадратне Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð½Ñ Ð¿Ð¾Ð·Ð° зоною аватара? Якщо ви хочете викориÑтовувати його Ñк Ðватар, Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð½Ñ Ð±ÑƒÐ´Ðµ відокремлено! - + Select Colour... Вибір кольору... - + + Background Image: %1 Фонове зображеннÑ: %1 - + + + Please select your settings profile + Будь лаÑка, виберіть Ñвій профіль налаштувань + + + File Background Image: File Файл @@ -365,16 +529,26 @@ When you want to use it as Avatar the image will be detached! + Apply changes + ЗаÑтоÑувати зміни + + + &Save &Зберегти - + + Discard changes + СкаÑувати зміни + + + &Close &Закрити - + JSON Error JSON помилка @@ -387,32 +561,57 @@ When you want to use it as Avatar the image will be detached! ПереглÑд карти Snapmatic - + + Close viewer + Закрити переглÑдач + + + &Close &Закрити - + + Apply new position + ЗаÑтоÑувати нову позицію + + + &Apply &ЗаÑтоÑувати - + + Revert old position + Повернути Ñтару позицію + + + &Revert &Повернути - - &Set - &Змінити + + Select new position + Виберіть нову позицію - + + &Select + &Ð’Ð¸Ð´Ñ–Ð»ÐµÐ½Ð½Ñ + + + + Quit select position + Вийти з вибору позиції + + + &Done &Готово - + X: %1 Y: %2 X and Y position @@ -438,19 +637,17 @@ Y: %2 Відкривати/обирати вміÑÑ‚ - Open with Singleclick - Відкривати одиночним кліком + Відкривати одиночним кліком - + Open with Doubleclick Відкривати подвійним кліком - Select with Singleclick - Обирати одиночним кліком + Обирати одиночним кліком @@ -490,7 +687,7 @@ Y: %2 Screen Resolution: %1x%2 - Ð Ð¾Ð·ÑˆÐ¸Ñ€ÐµÐ½Ð½Ñ ÐµÐºÑ€Ð°Ð½Ñƒ: %1x%2 + Ð Ð¾Ð·ÑˆÐ¸Ñ€ÐµÐ½Ð½Ñ Ð´Ð¸ÑплеÑ: %1x%2 @@ -555,215 +752,275 @@ Y: %2 + Game + Гра + + + + Social Club Version + Social Club верÑÑ–Ñ + + + + + + + + + + + Found: %1 + Знайдено:%1 + + + + + + + + + + + + + Language: %1 + Мова: %1 + + + + Steam Version + Steam верÑÑ–Ñ + Steam Version + + + Feedback ÐžÐ¿Ð¸Ñ‚ÑƒÐ²Ð°Ð½Ð½Ñ - + Participation УчаÑть - - + + Participate in %1 User Statistics ÐžÐ¿Ð¸Ñ‚ÑƒÐ²Ð°Ð½Ð½Ñ %1 про уÑÑ‚Ð°Ñ‚ÐºÑƒÐ²Ð°Ð½Ð½Ñ ÐŸÐš - + Categories Категорії - + Hardware, Application and OS Specification ОбладнаннÑ, випуÑк програми, Ñпецифікації ОС - + System Language Configuration Мовні Ð½Ð°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ ÑиÑтеми - + Application Configuration ÐÐ°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ð¿Ñ€Ð¾Ð³Ñ€Ð°Ð¼Ð¸ - + + Personal Usage Data + ОÑобиÑті дані викориÑÑ‚Ð°Ð½Ð½Ñ + + + Other Інше - - - + + + Participation ID: %1 ID учаÑника : %1 - + &Copy &Копіювати - - - User Feedback - Відгук кориÑтувача - - - - Limit: 1 message/day - Ліміт: 1 Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ / день - - - - &Send - &ÐадіÑлати - - - + Interface Ð†Ð½Ñ‚ÐµÑ€Ñ„ÐµÐ¹Ñ - + Language for Interface Мова інтерфейÑу - - - - + + + + Current: %1 Зараз: %1 - + Language for Areas Мова перекладу Ñ€Ð¾Ð·Ñ‚Ð°ÑˆÑƒÐ²Ð°Ð½Ð½Ñ - + Style Стиль - + Use Default Style (Restart) ВикориÑтовувати Ñтандартний Ñтиль (ПерезапуÑк) - + Style: Стиль: - + Font Шрифт - + + Use Default Font (Restart) + ВикориÑтовувати Ñтандартний шрифт (ПерезапуÑк) + + + + Font: + Шрифт: + + Always use Message Font (Windows 2003 and earlier) - Завжди викориÑтовуйте шрифт повідомлень (Windows 2003 Ñ– раніше) + Завжди викориÑтовуйте шрифт повідомлень (Windows 2003 Ñ– раніше) - - Sync - Ð¡Ð¸Ð½Ñ…Ñ€Ð¾Ð½Ñ–Ð·Ð°Ñ†Ñ–Ñ - - - - Sync is not implemented at current time - Ð¡Ð¸Ð½Ñ…Ñ€Ð¾Ð½Ñ–Ð·Ð°Ñ†Ñ–Ñ Ð½Ðµ здійÑнюєтьÑÑ Ð² даний Ñ‡Ð°Ñ - - - + Apply changes ЗаÑтоÑувати зміни - + &OK OK, Cancel, Apply &OK - + Discard changes СкаÑувати зміни - + &Cancel OK, Cancel, Apply &СкаÑувати - - %1 (Next Closest Language) - First language a person can talk with a different person/application. "Native" or "Not Native". - %1 (або наÑтупна найближча мова) - - - + System System in context of System default Як у ÑиÑтеми - + + %1 (Game language) + Next closest language compared to the Game settings + %1 (Мова гри) + + + + %1 (Closest to Interface) Next closest language compared to the Interface %1 (Співпадає з інтерфейÑом) - + + + Auto Automatic language choice. Ðвтоматично - + + %1 (Language priority) + First language a person can talk with a different person/application. "Native" or "Not Native". + %1 (пріоритет мови) + + + %1 %1 %1 - + The new Custom Folder will initialise after you restart %1. Ðова кориÑтувацька папка буде ініціалізована піÑÐ»Ñ Ð¿ÐµÑ€ÐµÐ·Ð°Ð¿ÑƒÑку %1. - + No Profile No Profile, as default Жодного - - - + + + Profile: %1 Профіль: %1 - + View %1 User Statistics Online ПереглÑнути кориÑтувацьку ÑтатиÑтику %1 онлайн - + Not registered Ðе зареєÑтрований - - A feedback message have to between 3-1024 characters long - ÐŸÐ¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ Ð²Ñ–Ð´Ð³ÑƒÐºÑƒ має Ñтановити від 3-1024 Ñимволів + + + + + Yes + Так + + + + + No + ÐÑ– + + + + + OS defined + ВизначаєтьÑÑ ÐžÐ¡ + + + + + Steam defined + ВизначаєтьÑÑ Steam @@ -774,74 +1031,74 @@ Y: %2 ПереглÑдач зображень Snapmatic - %1 - - <span style=" font-weight:600;">Title: </span>%6<br/> -<span style=" font-weight:600;">Location: </span>%7 (%1, %2, %3)<br/> -<span style=" font-weight:600;">Players: </span>%4 (Crew %5)<br/> -<span style=" font-weight:600;">Created: </span>%8 - <span style=" font-weight:600;">Ðазва: </span>%6<br/> -<span style=" font-weight:600;">РозташуваннÑ: </span>%7 (%1, %2, %3)<br/> -<span style=" font-weight:600;">Гравці: </span>%4 (Банда %5)<br/> -<span style=" font-weight:600;">Створено: </span>%8 + + <span style="font-weight:600">Title: </span>%6<br/> +<span style="font-weight:600">Location: </span>%7 (%1, %2, %3)<br/> +<span style="font-weight:600">Players: </span>%4 (Crew %5)<br/> +<span style="font-weight:600">Created: </span>%8 + <span style="font-weight:600">Ðазва: </span>%6<br/> +<span style="font-weight:600">РозташуваннÑ: </span>%7 (%1, %2, %3)<br/> +<span style="font-weight:600">Гравці: </span>%4 (Банда %5)<br/> +<span style="font-weight:600">Створено: </span>%8 - + Manage picture ÐšÐµÑ€ÑƒÐ²Ð°Ð½Ð½Ñ Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð½Ñм - + &Manage &Керувати - + Close viewer Закрити переглÑдач - + &Close &Закрити - - + + Export as &Picture... ЕкÑпортувати Ñк &зображеннÑ... - - + + Export as &Snapmatic... ЕкÑпортувати Ñк &Snapmatic... - - + + &Edit Properties... &Змінити влаÑтивоÑті... - - + + &Overwrite Image... &ПерезапиÑати зображеннÑ... - - + + Open &Map Viewer... Відкрити &карту... - - + + Open &JSON Editor... Відкрити редактор &JSON... - + Key 1 - Avatar Preview Mode Key 2 - Toggle Overlay Arrow Keys - Navigate @@ -850,142 +1107,140 @@ Arrow Keys - Navigate Стрілки - ÐÐ°Ð²Ñ–Ð³Ð°Ñ†Ñ–Ñ - - + Snapmatic Picture Viewer ПереглÑдач фотографій Snapmatic - - + Failed at %1 Помилка на%1 - - - + + + No Players Гравців немає - - + + No Crew Банди немає - + Unknown Location Ðевідома Ð»Ð¾ÐºÐ°Ñ†Ñ–Ñ - + Avatar Preview Mode Press 1 for Default View Режим Ð´Ð»Ñ Ð°Ð²Ð°Ñ‚Ð°Ñ€Ð¾Ðº ÐатиÑніть 1 Ð´Ð»Ñ Ñтандартного переглÑду - + Export as Picture... ЕкÑпортувати Ñк зображеннÑ... - - + + Export ЕкÑпорт - + JPEG Graphics (*.jpg *.jpeg) JPEG Graphics (*.jpg *.jpeg) - + Portable Network Graphics (*.png) Portable Network Graphics (*.png) - - + + + + + - - - Export as Picture ЕкÑпортувати Ñк Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð½Ñ - - + + Overwrite %1 with current Snapmatic picture? ПерезапиÑати %1 поточним Snapmatic зображеннÑм? - + Failed to export the picture because the system occurred a write failure Ðе вдалоÑÑ ÐµÐºÑпортувати зображеннÑ, оÑкільки в ÑиÑтемі виникла помилка запиÑу - + Failed to export the picture because the format detection failures Ðе вдалоÑÑ ÐµÐºÑпортувати Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð½Ñ Ñ‡ÐµÑ€ÐµÐ· помилки виÑÐ²Ð»ÐµÐ½Ð½Ñ Ñ„Ð¾Ñ€Ð¼Ð°Ñ‚Ñƒ - + Failed to export the picture because the file can't be written Ðе вдалоÑÑ ÐµÐºÑпортувати зображеннÑ, оÑкільки файл не може бути запиÑаний - + Failed to export the picture because of an unknown reason Ðе вдалоÑÑ ÐµÐºÑпортувати Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð½Ñ Ñ‡ÐµÑ€ÐµÐ· невідому причину - - + + No valid file is selected Вибрано невірний файл - + Export as Snapmatic... ЕкÑпортувати Ñк Snapmatic... - + GTA V Export (*.g5e) GTA V Export (*.g5e) - + GTA V Raw Export (*.auto) GTA V RAW-екÑпорт (*.auto) - + Snapmatic pictures (PGTA*) Snapmatic картинки (PGTA*) - - - - - + + + + + Export as Snapmatic ЕкÑпортувати Ñк Snapmatic - - + + Failed to export current Snapmatic picture Ðе вдалоÑÑ ÐµÐºÑпортувати поточну фотографію Snapmatic - + Exported Snapmatic to "%1" because of using the .auto extension. ЕкÑпортуєтьÑÑ Snapmatic до "%1" через викориÑÑ‚Ð°Ð½Ð½Ñ .auto розширеннÑ. @@ -1003,43 +1258,43 @@ Press 1 for Default View ДоÑтупні гравці: - + Selected Players: Вибрані гравці: - + &Apply &ЗаÑтоÑувати - + &Cancel &СкаÑувати - + Add Players... Додати гравців... - + Failed to add more Players because the limit of Players are %1! Ðе вдалоÑÑ Ð´Ð¾Ð´Ð°Ñ‚Ð¸ більше гравців, бо ліміт %1! - - + + Add Player... Додати гравцÑ... - + Enter Social Club Player ID Введіть ID Ð³Ñ€Ð°Ð²Ñ†Ñ Social Club - + Failed to add Player %1 because Player %1 is already added! Ðе вдалоÑÑ Ð´Ð¾Ð´Ð°Ñ‚Ð¸ Ð³Ñ€Ð°Ð²Ñ†Ñ %1, оÑкільки %1 вже доданий! @@ -1082,131 +1337,132 @@ Press 1 for Default View &Закрити - - - + + + Export file %1 of %2 files ЕкÑпортуєтьÑÑ Ñ„Ð°Ð¹Ð» %1 з %2 файлів - - - - + + + + + + + + + + + + + + + + + + + Import... ІмпортуваннÑ... - - - - - - - - - - - - - - - - - - - - + + + + + + Import Імпорт - - - + + + All image files (%1) Файли зображень (%1) - - - - + + + + All files (**) УÑÑ– файли (**) - - - + + + Can't import %1 because file can't be open Ðеможливо імпортувати %1, оÑкільки файл не може бути відкритий - - - + + + Can't import %1 because file can't be parsed properly Ðеможливо імпортувати %1, оÑкільки файл неможливо розібрати правильно - + Enabled pictures: %1 of %2 Увімкнено фотографії:%1 з%2 - + Loading... ЗавантаженнÑ... - + Snapmatic Loader Snapmatic Loader - + <h4>Following Snapmatic Pictures got repaired</h4>%1 <h4>ÐаÑтупні Snapmatic Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð½Ñ Ð±ÑƒÐ»Ð¸ відновлені</h4>%1 - + Importable files (%1) ІмпортуютьÑÑ Ñ„Ð°Ð¹Ð»Ð¸ (%1) - - + + GTA V Export (*.g5e) GTA V Export (*.g5e) - - + + Savegames files (SGTA*) Файли Ð·Ð±ÐµÑ€ÐµÐ¶ÐµÐ½Ð½Ñ Ð³Ñ€Ð¸ (SGTA*) - - + + Snapmatic pictures (PGTA*) Snapmatic Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð½Ñ (PGTA*) - - - + + + No valid file is selected Вибрані недійÑні файли - - + + Import file %1 of %2 files ІмпортуєтьÑÑ Ñ„Ð°Ð¹Ð» %1 з %2 файлів - + Import failed with... %1 @@ -1215,86 +1471,81 @@ Press 1 for Default View %1 - - + + Failed to read Snapmatic picture Ðе вдалоÑÑ Ð¿Ñ€Ð¾Ñ‡Ð¸Ñ‚Ð°Ñ‚Ð¸ Snapmatic картинку - - + + Failed to read Savegame file Ðе вдалоÑÑ Ð¿Ñ€Ð¾Ñ‡Ð¸Ñ‚Ð°Ñ‚Ð¸ файл Ð·Ð±ÐµÑ€ÐµÐ¶ÐµÐ½Ð½Ñ Ð³Ñ€Ð¸ - + Can't import %1 because file format can't be detected Ðеможливо імпортувати%1, оÑкільки формат файлу не може бути виÑвлений - + Failed to import the Snapmatic picture, file not begin with PGTA or end with .g5e Ðе вдалоÑÑ Ñ–Ð¼Ð¿Ð¾Ñ€Ñ‚ÑƒÐ²Ð°Ñ‚Ð¸ Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð½Ñ Snapmatic, файл не починаєтьÑÑ Ð· PGTA або закінчуєтьÑÑ .g5e - - Failed to import the Snapmatic picture, the picture is already in the game - Ðе вдалоÑÑ Ñ–Ð¼Ð¿Ð¾Ñ€Ñ‚ÑƒÐ²Ð°Ñ‚Ð¸ знімок Snapmatic, картинка вже в грі - - - + Failed to import the Snapmatic picture, can't copy the file into profile Ðе вдалоÑÑ Ñ–Ð¼Ð¿Ð¾Ñ€Ñ‚ÑƒÐ²Ð°Ñ‚Ð¸ Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð½Ñ Snapmatic, не можна Ñкопіювати файл у профіль - + Failed to import the Savegame, can't copy the file into profile Ðе вдалоÑÑ Ñ–Ð¼Ð¿Ð¾Ñ€Ñ‚ÑƒÐ²Ð°Ñ‚Ð¸ Сейв, не можна Ñкопіювати файл у профіль - + Failed to import the Savegame, no Savegame slot is left Ðе вдалоÑÑ Ñ–Ð¼Ð¿Ð¾Ñ€Ñ‚ÑƒÐ²Ð°Ñ‚Ð¸ Сейв, немає вільного Ñлота - - - - - + + + + + Export selected... ЕкÑпорт обраних... - - + + JPG pictures and GTA Snapmatic JPG картинки Ñ– GTA Snapmatic - - + + JPG pictures only Тільки JPG картинки - - + + GTA Snapmatic only Тільки GTA Snapmatic - + %1Export Snapmatic pictures%2<br><br>JPG pictures make it possible to open the picture with a Image Viewer<br>GTA Snapmatic make it possible to import the picture into the game<br><br>Export as: %1 ЕкÑпортувати Snapmatic фотографії %2 <br><br> Фотографії JPG дозволÑють відкривати Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð½Ñ Ð·Ð° допомогою заÑобу переглÑду зображень<br>GTA Snapmatic дає змогу імпортувати Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð½Ñ Ð² гру<br><br>ЕкÑпортувати Ñк: - + Initialising export... Ð†Ð½Ñ–Ñ†Ñ–Ð°Ð»Ñ–Ð·Ð°Ñ†Ñ–Ñ ÐµÐºÑпорту... - + Export failed with... %1 @@ -1303,45 +1554,45 @@ Press 1 for Default View %1 - - + + No Snapmatic pictures or Savegames files are selected Ðе вибрано жодного Snapmatic Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð½Ñ Ð°Ð±Ð¾ файлу Ð·Ð±ÐµÑ€ÐµÐ¶ÐµÐ½Ð½Ñ - - - + + + Remove selected Видалити вибрані - + You really want remove the selected Snapmatic picutres and Savegame files? Ви дійÑно хочете видалити вибрані Snapmatic фотографії та файли Ð·Ð±ÐµÑ€ÐµÐ¶ÐµÐ½Ð½Ñ Ð³Ñ€Ð¸? - + Failed to remove all selected Snapmatic pictures and/or Savegame files Ðе вдалоÑÑ Ð²Ð¸Ð´Ð°Ð»Ð¸Ñ‚Ð¸ вÑÑ– обрані Snapmatic фотографії та/або Сейви - - - - - - + + + + + + No Snapmatic pictures are selected Ðе вибрано жодного Snapmatic Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð½Ñ - - - - - - + + + + + + %1 failed with... %2 @@ -1351,87 +1602,97 @@ Press 1 for Default View %2 - - + + Prepare Content for Import... + Підготувати контент Ð´Ð»Ñ Ñ–Ð¼Ð¿Ð¾Ñ€Ñ‚Ñƒ ... + + + + A Snapmatic picture already exists with the uid %1, you want assign your import a new uid and timestamp? + Snapmatic Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð½Ñ Ð· uid %1 вже Ñ–Ñнує, ви хочете призначити Ð´Ð»Ñ Ñ–Ð¼Ð¿Ð¾Ñ€Ñ‚Ñƒ новий uid та мітку чаÑу? + + + + Qualify as Avatar Позначити Ñк Ðватар - - - - + + + + Patch selected... Вибір патчу... - - - - - - - - + + + + + + + + Patch file %1 of %2 files Патч файлу %1 з %2 файлів - + Qualify %1 failed with... ЯкіÑть - - + + Change Players... Зміна гравців... - + Change Players %1 failed with... Змінити гравців - - - + + + Change Crew... Зміна банди... - + Failed to enter a valid Snapmatic Crew ID Ðе вдалоÑÑ Ð²Ð²ÐµÑти дійÑний ID Банди Snapmatic - + Change Crew %1 failed with... Змінити банду - - - + + + Change Title... Зміна назви... - + Failed to enter a valid Snapmatic title Ðе вдалоÑÑ Ð²Ð²ÐµÑти дійÑний заголовок Snapmatic - + Change Title %1 failed with... Змінити назву - + All profile files (*.g5e SGTA* PGTA*) УÑÑ– файли зображень (*.g5e SGTA* PGTA*) @@ -1439,33 +1700,30 @@ Press 1 for Default View QApplication - Font - Шрифт + Шрифт - Selected Font: %1 - Вибраний шрифт:%1 + Вибраний шрифт:%1 - <h4>Welcome to %1!</h4>You want to configure %1 before you start using it? - <h4>ЛаÑкаво проÑимо до %1!</h4>Ви хочете налаштувати %1 перед викориÑтаннÑм? + <h4>ЛаÑкаво проÑимо до %1!</h4>Ви хочете налаштувати %1 перед викориÑтаннÑм? SavegameDialog - + Savegame Viewer ПереглÑд файлів Ð·Ð±ÐµÑ€ÐµÐ¶ÐµÐ½Ð½Ñ Ð³Ñ€Ð¸ - <span style=" font-weight:600;">Savegame</span><br><br>%1 - <span style=" font-weight:600;">Ігрове збереженнÑ</span><br><br>%1 + <span style="font-weight:600">Savegame</span><br><br>%1 + <span style="font-weight:600">Ігрове збереженнÑ</span><br><br>%1 @@ -1478,7 +1736,7 @@ Press 1 for Default View &Закрити - + Failed at %1 Помилка на %1 @@ -1527,37 +1785,45 @@ Press 1 for Default View Видалити - + &View &ПереглÑд - + + + &Export &ЕкÑпорт - + + + &Remove &Ð’Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ - + + &Select &Ð’Ð¸Ð´Ñ–Ð»ÐµÐ½Ð½Ñ - + + &Deselect &ЗнÑти Ð²Ð¸Ð´Ñ–Ð»ÐµÐ½Ð½Ñ - + + Select &All Вибрати &уÑÑ– - + + &Deselect All &ЗнÑти Ð²Ð¸Ð´Ñ–Ð»ÐµÐ½Ð½Ñ ÑƒÑÑ–Ñ… @@ -1600,50 +1866,50 @@ Press 1 for Default View Вибрано невірний файл - + Export Savegame... ЕкÑÐ¿Ð¾Ñ€Ñ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ñ„Ð°Ð¹Ð»Ñƒ збереженнÑ... - - + + AUTOSAVE - %1 %2 ÐВТОМÐТИЧÐЕ ЗБЕРЕЖЕÐÐЯ - %1 %2 - - + + SAVE %3 - %1 %2 ЗБЕРЕЖЕÐÐЯ %3 - %1 %2 - - + + WRONG FORMAT ÐЕПРÐВИЛЬÐИЙ ФОРМÐТ - + UNKNOWN ÐЕВІДОМИЙ - - + + Delete Savegame Видалити файл Ð·Ð±ÐµÑ€ÐµÐ¶ÐµÐ½Ð½Ñ - + Are you sure to delete %1 from your savegames? Ви впевнені, що хочете видалити %1 зі Ñвоїх Ñейвів? - + Failed at deleting %1 from your savegames Ðе вдалоÑÑ Ð²Ð¸Ð´Ð°Ð»Ð¸Ñ‚Ð¸ %1 із ваших збережених ігор @@ -1653,13 +1919,13 @@ Press 1 for Default View - - - - - - - + + + + + + + Snapmatic Properties ВлаÑтивоÑті Snapmatic @@ -1676,7 +1942,7 @@ Press 1 for Default View Selfie - Селфі + Ðвтопортрет @@ -1686,7 +1952,7 @@ Press 1 for Default View Mugshot - Ðвтопортрет + Кухоль поÑтріл @@ -1720,105 +1986,115 @@ Press 1 for Default View + Apply changes + ЗаÑтоÑувати зміни + + + &Apply &ЗаÑтоÑувати - + + Discard changes + СкаÑувати зміни + + + &Cancel &СкаÑувати - + <h4>Unsaved changes detected</h4>You want to save the JSON content before you quit? <h4> ВиÑвлені незбережені зміни </h4> Ви хочете зберегти вміÑÑ‚ JSON перед тим, Ñк вийти? - + Patching of Snapmatic Properties failed because of %1 Змінити влаÑтивоÑті Snapmatic не вдалоÑÑ Ñ‚Ð¾Ð¼Ñƒ що%1 - - - - + + + + Patching of Snapmatic Properties failed because of I/O Error Змінити влаÑтивоÑті Snapmatic не вдалоÑÑ Ñ‡ÐµÑ€ÐµÐ· I/O Помилку - + Patching of Snapmatic Properties failed because of JSON Error Змінити влаÑтивоÑті Snapmatic не вдалоÑÑ Ñ‡ÐµÑ€ÐµÐ· JSON Помилку - - + + Snapmatic Crew Snapmatic банда - - + + New Snapmatic crew: Ðова Snapmatic банда: - - + + Snapmatic Title Snapmatic назва - - + + New Snapmatic title: Ðовий Snapmatic заголовок: - - - + + + Edit Правка - + Players: %1 (%2) Multiple Player are inserted here Гравці: %1 (%2) - + Player: %1 (%2) One Player is inserted here Гравець: %1 (%2) - + Title: %1 (%2) Ðазва: %1 (%2) - - + + Appropriate: %1 Підходить: %1 - + Yes Yes, should work fine Так - + No No, could lead to issues ÐÑ– - + Crew: %1 (%2) Банда: %1 (%2) @@ -1826,65 +2102,65 @@ Press 1 for Default View SnapmaticPicture - - + + JSON is incomplete and malformed JSON неповний та неправильний - - + + JSON is incomplete JSON неповний - - + + JSON is malformed JSON неправильний - + PHOTO - %1 ФОТО - %1 - + open file %1 відкрити файл%1 - + header not exists заголовок не Ñ–Ñнує - + header is malformed заголовок неправильний - + picture not exists (%1) Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð½Ñ Ð½Ðµ Ñ–Ñнує (%1) - + JSON not exists (%1) JSON не Ñ–Ñнує (%1) - + title not exists (%1) заголовок не Ñ–Ñнує (%1) - + description not exists (%1) Ð¾Ð¿Ð¸Ñ Ð½Ðµ Ñ–Ñнує (%1) - + reading file %1 because of %2 Example for %2: JSON is malformed error Ñ‡Ð¸Ñ‚Ð°Ð½Ð½Ñ Ñ„Ð°Ð¹Ð»Ñƒ %1 тому що %2 @@ -1934,8 +2210,8 @@ Press 1 for Default View - - + + Delete picture Видалити фото @@ -1945,72 +2221,82 @@ Press 1 for Default View Видалити - + + + Edi&t Редагува&ти - + + + Show &In-game Показати &у грі - + + + Hide &In-game Сховати &у грі - + &Export &ЕкÑпортувати - + &View &ПереглÑнути - + &Remove &Видалити - + + &Select &Ð’Ð¸Ð´Ñ–Ð»ÐµÐ½Ð½Ñ - + + &Deselect &ЗнÑти Ð²Ð¸Ð´Ñ–Ð»ÐµÐ½Ð½Ñ - + + Select &All Вибрати &уÑÑ– - + + &Deselect All &ЗнÑти Ð²Ð¸Ð´Ñ–Ð»ÐµÐ½Ð½Ñ ÑƒÑÑ–Ñ… - + Are you sure to delete %1 from your Snapmatic pictures? Ви дійÑно бажаєте видалити %1 з ваших Snapmatic фотографій? - + Failed at deleting %1 from your Snapmatic pictures Ðе вдалоÑÑ Ð²Ð¸Ð´Ð°Ð»Ð¸Ñ‚Ð¸%1 з ваших Snapmatic фотографій - + Failed to hide %1 In-game from your Snapmatic pictures Ðе вдалоÑÑ Ñховати %1 Snapmatic у грі - + Failed to show %1 In-game from your Snapmatic pictures Ðе вдалоÑÑ Ð¿Ð¾ÐºÐ°Ð·Ð°Ñ‚Ð¸ %1 Snapmatic у грі @@ -2018,23 +2304,24 @@ Press 1 for Default View TelemetryDialog - - You want help %1 to improve in the future by collection of data? - Ви хочете допомогти %1 подальшому вдоÑконаленню шлÑхом збору даних? + + You want help %1 to improve in the future by including personal usage data in your submission? + Ви хочете допомогти %1 покращитиÑÑŒ у майбутньому, включивши дані оÑобиÑтого кориÑтуваннÑ? - + %1 User Statistics %1 СтатиÑтика кориÑтувачів - - Yes, I would like to take part. - Так, Ñ Ñ…Ð¾Ñ‚Ñ–Ð² би взÑти учаÑть. + + Yes, I want include personal usage data. + Так, Ñ Ñ…Ð¾Ñ‡Ñƒ включити дані оÑобиÑтого кориÑтуваннÑ. - + &OK + &OK &OK @@ -2042,7 +2329,7 @@ Press 1 for Default View UserInterface - + %2 - %1 %2 - %1 @@ -2074,6 +2361,9 @@ Press 1 for Default View + + + &Close &Закрити @@ -2109,8 +2399,8 @@ Press 1 for Default View - - + + &About %1 &Про %1 @@ -2166,15 +2456,15 @@ Press 1 for Default View - + Select &GTA V Folder... Вибрати &GTA V теку... - - - + + + Select GTA V Folder... Вибрати GTA V теку... @@ -2190,62 +2480,104 @@ Press 1 for Default View + + Change &Title... Змінити &заголовок... + + Change &Crew... Змінити &банду... + + &Qualify as Avatar Позначити Ñк &аватар + + Change &Players... Змінити &гравців... - - - + + + Show In-game Показати у грі - - - + + + Hide In-game Сховати у грі - - - + + + Select Profile Вибрати профіль - + + + &Donate + &ÐŸÐ¾Ð¶ÐµÑ€Ñ‚Ð²ÑƒÐ²Ð°Ð½Ð½Ñ + + + + Donate + ÐŸÐ¾Ð¶ÐµÑ€Ñ‚Ð²ÑƒÐ²Ð°Ð½Ð½Ñ + + + + Donation methods + Метод Ð¿Ð¾Ð¶ÐµÑ€Ñ‚Ð²ÑƒÐ²Ð°Ð½Ð½Ñ + + + View + ПереглÑд + + + Copy + Копіювати + + + + &Copy + &Копіювати + + + Open File... Відкрити файл... - - - - + + + + Open File Відкрити файл - + Can't open %1 because of not valid file format Ðеможливо відкрити %1 через невідомий формат файлу + + + %1 - Messages + %1 - Ðовини + diff --git a/res/gta5sync_zh_TW.qm b/res/gta5sync_zh_TW.qm index 79d4647..d4eb70d 100644 Binary files a/res/gta5sync_zh_TW.qm and b/res/gta5sync_zh_TW.qm differ diff --git a/res/gta5sync_zh_TW.ts b/res/gta5sync_zh_TW.ts index 638135b..fb4d4bc 100644 --- a/res/gta5sync_zh_TW.ts +++ b/res/gta5sync_zh_TW.ts @@ -10,7 +10,7 @@ - <span style=" font-weight:600;">%1</span><br/> + <span style="font-weight:600">%1</span><br/> <br/> %2<br/> <br/> @@ -20,7 +20,7 @@ Built with Qt %5<br/> Running with Qt %6<br/> <br/> %7 - <span style=" font-weight:600;">%1</span><br/> + <span style="font-weight:600">%1</span><br/> <br/> %2<br/> <br/> @@ -32,76 +32,71 @@ Running with Qt %6<br/> %7 - + &Close 關閉(&C) - + Translated by %1 Translated by translator, example Translated by Syping ç¹é«”中文化: %1 - + TRANSLATOR Insert your name here and profile here in following scheme, First Translator,First Profile\nSecond Translator\nThird Translator,Second Profile Ray,https://steamcommunity.com/profiles/76561198282701714/ - - A project for viewing and sync Grand Theft Auto V Snapmatic<br/> -Pictures and Savegames - 一個 Grand Theft Auto V Snapmatic 圖片ã€éŠæˆ²å­˜æª”æª¢è¦–å’ŒåŒæ­¥å°ˆæ¡ˆ - - - + A project for viewing Grand Theft Auto V Snapmatic<br/> Pictures and Savegames 一個 Grand Theft Auto V Snapmatic 圖片ã€éŠæˆ²å­˜æª”檢視專案 - + Copyright &copy; <a href="%1">%2</a> %3 版權 &copy; <a href="%1">%2</a> %3 - + %1 is licensed under <a href="https://www.gnu.org/licenses/gpl-3.0.html#content">GNU GPLv3</a> %1 使用 <a href="https://www.gnu.org/licenses/gpl-3.0.html#content">GNU GPLv3</a> æŽˆæ¬Šæ¢æ¬¾ç™¼å¸ƒ - + Release æ­£å¼ç‰ˆæœ¬ - + Release Candidate - 最終發布版本 + 最終測試版本 - + Daily Build æ¯æ—¥å»ºç½®ç‰ˆæœ¬ - + Developer 開發版本 - + Beta Beta 版本 - + Alpha Alpha 版本 - + + Custom 自訂 @@ -181,58 +176,50 @@ Pictures and Savegames ImageEditorDialog - + Overwrite Image... 修改圖片... - - - Capacity: %1 - 大å°: %1 + + Apply changes + 套用變更 - - ? - ? - - - - &Import... - 匯入(&I)... - - - + &Overwrite 修改(&O) - + + Discard changes + æ¨æ£„變更 + + + &Close 關閉(&C) - - - + + + + Snapmatic Image Editor Snapmatic 圖片編輯器 - + + Patching of Snapmatic Image failed because of I/O Error I/O 錯誤,Snapmatic 圖片更新失敗 - + + Patching of Snapmatic Image failed because of Image Error 圖片錯誤,Snapmatic 圖片更新失敗 - - - Every taken Snapmatic have a different Capacity, a Snapmatic with higher Capacity can store a picture with better quality. - æ¯å¼µ Snapmatic 大å°çš†ä¸åŒï¼Œé€šå¸¸è¼ƒå¤§çš„ Snapmatic 圖片å“質較佳。 - ImportDialog @@ -247,108 +234,282 @@ Pictures and Savegames 圖片 - + Avatar 大頭貼 - - + + Ignore Aspect Ratio 忽略長寬比 - + + Watermark + æµ®æ°´å° + + + Force Borderless + 強制無邊框 + + + Background 背景 - - - + + + + Background Colour: <span style="color: %1">%1</span> 背景é¡è‰²: <span style="color: %1">%1</span> - - - ... - ... + + Select background colour + 鏿“‡èƒŒæ™¯é¡è‰² - - - + ... + ... + + + + + + Background Image: 背景圖片: - - X - X + + Select background image + 鏿“‡èƒŒæ™¯åœ–片 - + + Remove background image + 移除背景圖片 + + + X + X + + + + Crop to Aspect Ratio + è£å‰ªé•·å¯¬æ¯” + + + Force Colour in Avatar Zone 強制在大頭貼å€åŸŸä½¿ç”¨é¡è‰² - + + Advanced + 進階 + + + + Resolution: + è§£æžåº¦: + + + + Snapmatic resolution + Snapmatic è§£æžåº¦ + + + + Avoid compression and expand buffer instead, improves picture quality, but may break Snapmatic + é¿å…壓縮來æé«˜åœ–片å“質,但å¯èƒ½æœƒç ´å£ž Snapmatic é‹ä½œ + + + + Unlimited Buffer + ç„¡é™ç·©è¡ + + + + Import as-is, don't change the picture at all, guaranteed to break Snapmatic unless you know what you doing + 除éžä½ çŸ¥é“自己在幹什麼,å¦å‰‡ä¿®æ”¹åœ–片將使 Snapmatic æ•…éšœ + + + + Import as-is + 照原樣匯入 + + + + Import options + 匯入é¸é … + + + + &Options + é¸é …(&O) + + + Import picture 匯入圖片 - + &OK 確定(&O) - + Discard picture æ¨æ£„圖片 - + &Cancel å–æ¶ˆ(&C) - - + + &Import new Picture... + 匯入新圖片(&I)... + + + + &Crop Picture... + è£å‰ªåœ–片(&C)... + + + + &Load Settings... + 載入設定(&L)... + + + + &Save Settings... + 儲存設定(&S)... + + + + Custom Avatar Custom Avatar Description in SC, don't use Special Character! 自訂大頭貼 - - + + + Custom Picture Custom Picture Description in SC, don't use Special Character! 自訂圖片 - + + Storage + Background Image: Storage + 儲存 + + + + Crop Picture... + è£å‰ªåœ–片... + + + + &Crop + è£å‰ª(&C) + + + + Crop Picture + è£å‰ªåœ–片 + + + + + Please import a new picture first + 請先匯入新圖片 + + + + + Default + Default as Default Profile + é è¨­ + + + + + + + + + + + + + + + + + + + + + + + Profile %1 + Profile %1 as Profile 1 + 設定檔 %1 + + + + + Load Settings... + 載入設定... + + + + + Save Settings... + 儲存設定... + + + + Snapmatic Avatar Zone Snapmatic 大頭貼å€åŸŸ - + + Are you sure to use a square image outside of the Avatar Zone? When you want to use it as Avatar the image will be detached! 你確定è¦åœ¨å¤§é ­è²¼å€åŸŸä»¥å¤–的地方使用方形圖片嗎? 作為大頭貼的圖片將被分離! - + Select Colour... 鏿“‡é¡è‰²... - + + Background Image: %1 背景圖片: %1 - + + + Please select your settings profile + è«‹é¸æ“‡è¨­å®šæª” + + + File Background Image: File 文件 @@ -363,16 +524,26 @@ When you want to use it as Avatar the image will be detached! + Apply changes + 套用變更 + + + &Save ä¿å­˜(&S) - + + Discard changes + æ¨æ£„變更 + + + &Close 關閉(&C) - + JSON Error JSON 錯誤 @@ -385,32 +556,57 @@ When you want to use it as Avatar the image will be detached! Snapmatic 地圖檢視器 - + + Close viewer + 關閉檢視器 + + + &Close 關閉(&C) - + + Apply new position + 套用新ä½ç½® + + + &Apply 套用(&A) - + + Revert old position + 還原舊ä½ç½® + + + &Revert 還原(&R) - - &Set - 設置(&S) + + Select new position + 鏿“‡æ–°ä½ç½® - + + &Select + 鏿“‡(&S) + + + + Quit select position + 離開ä½ç½®é¸æ“‡ + + + &Done 完æˆ(&D) - + X: %1 Y: %2 X and Y position @@ -436,19 +632,17 @@ Y: %2 開啟/鏿“‡æ¨¡å¼ - Open with Singleclick - 點一次開啟 + 點一次開啟 - + Open with Doubleclick 點兩次開啟 - Select with Singleclick - 點一次é¸å– + 點一次é¸å– @@ -553,297 +747,352 @@ Y: %2 + Game + éŠæˆ² + + + + Social Club Version + Social Club 版 + + + + + + + + + + + Found: %1 + 找到: %1 + + + + + + + + + + + + + Language: %1 + 語言: %1 + + + + Steam Version + Steam 版 + + + Feedback å饋 - + Participation åƒèˆ‡ - - + + Participate in %1 User Statistics åƒèˆ‡ %1 使用者統計 - + Categories 分類 - + Hardware, Application and OS Specification 硬體ã€è»Ÿé«”å’Œ OS è¦æ ¼ - + System Language Configuration 系統語言設定 - + Application Configuration 應用程å¼è¨­å®š - + + Personal Usage Data + 個人使用數據 + + + Other å…¶ä»– - - - + + + Participation ID: %1 åƒèˆ‡ ID: %1 - + &Copy 複製(&C) - - - User Feedback - 使用者å饋 - - - - Limit: 1 message/day - 一天åªèƒ½ä¸€å‰‡è¨Šæ¯ - - - - &Send - é€å‡º(&S) - - - + Interface ä»‹é¢ - + Language for Interface 介é¢èªžè¨€ - - - - + + + + Current: %1 ç›®å‰: %1 - + Language for Areas å€åŸŸèªžè¨€ - + Style - 風格 + æ¨£å¼ - + Use Default Style (Restart) - 使用é è¨­é¢¨æ ¼ (éœ€é‡æ–°å•Ÿå‹•) + 使用é è¨­æ¨£å¼ (éœ€é‡æ–°å•Ÿå‹•) - + Style: - 風格: + 樣å¼: - + Font å­—é«” - + + Use Default Font (Restart) + 使用é è¨­å­—é«” (éœ€é‡æ–°å•Ÿå‹•) + + + + Font: + å­—é«”: + + Always use Message Font (Windows 2003 and earlier) - 總是使用訊æ¯å­—é«” (Windows 2003 和更早版本) + 總是使用訊æ¯å­—é«” (Windows 2003 和更早版本) - - Sync - åŒæ­¥ - - - - Sync is not implemented at current time - ç›®å‰ä¸¦æœªåŒæ­¥ - - - + Apply changes 套用變更 - + &OK OK, Cancel, Apply 確定(&O) - + Discard changes æ¨æ£„變更 - + &Cancel OK, Cancel, Apply å–æ¶ˆ(&C) - - %1 (Next Closest Language) - First language a person can talk with a different person/application. "Native" or "Not Native". - %1 (接近的語言) - - - + System System in context of System default 系統 - + + %1 (Closest to Interface) Next closest language compared to the Interface %1 (èˆ‡ä»‹é¢æŽ¥è¿‘çš„èªžè¨€) - + + + Auto Automatic language choice. 自動 - + + %1 (Language priority) + First language a person can talk with a different person/application. "Native" or "Not Native". + %1 (語言優先) + + + + %1 (Game language) + Next closest language compared to the Game settings + %1 (éŠæˆ²èªžè¨€) + + + %1 %1 %1 - + The new Custom Folder will initialise after you restart %1. 自訂資料夾將在 %1 釿–°å•Ÿå‹•後åˆå§‹åŒ–. - + No Profile No Profile, as default ç„¡ - - - + + + Profile: %1 設定檔: %1 - + View %1 User Statistics Online 檢視 %1 使用者統計資訊 - + Not registered 未註冊åƒèˆ‡ - - A feedback message have to between 3-1024 characters long - å饋訊æ¯å¿…須在 3 到 1024 個字之間 + + + + + Yes + 是 + + + + + No + å¦ + + + + + OS defined + 系統定義 + + + + + Steam defined + Steam 定義 PictureDialog - - %1 - Snapmatic Picture Viewer - %1 - Snapmatic 圖片檢視器 - Snapmatic Picture Viewer - %1 Snapmatic 圖片檢視器 - %1 - - <span style=" font-weight:600;">Title: </span>%6<br/> -<span style=" font-weight:600;">Location: </span>%7 (%1, %2, %3)<br/> -<span style=" font-weight:600;">Players: </span>%4 (Crew %5)<br/> -<span style=" font-weight:600;">Created: </span>%8 - <span style=" font-weight:600;">標題: </span>%6<br/> -<span style=" font-weight:600;">地點: </span>%7 (%1, %2, %3)<br/> -<span style=" font-weight:600;">玩家: </span>%4 (Crew %5)<br/> -<span style=" font-weight:600;">建立於: </span>%8 + + <span style="font-weight:600">Title: </span>%6<br/> +<span style="font-weight:600">Location: </span>%7 (%1, %2, %3)<br/> +<span style="font-weight:600">Players: </span>%4 (Crew %5)<br/> +<span style="font-weight:600">Created: </span>%8 + <span style="font-weight:600">標題: </span>%6<br/> +<span style="font-weight:600">地點: </span>%7 (%1, %2, %3)<br/> +<span style="font-weight:600">玩家: </span>%4 (Crew %5)<br/> +<span style="font-weight:600">建立於: </span>%8 - + Manage picture 管ç†åœ–片 - + &Manage 管ç†(&M) - + Close viewer 關閉檢視器 - + &Close 關閉(&C) - - + + Export as &Picture... 匯出æˆåœ–片(&P)... - - + + Export as &Snapmatic... åŒ¯å‡ºæˆ Snapmatic(&S)... - - + + &Edit Properties... 編輯屬性(&E) ... - - + + &Overwrite Image... 修改圖片(&O)... - - + + Open &Map Viewer... 開啟地圖檢視器(&M)... - - + + Open &JSON Editor... 開啟 JSON 編輯器(&J)... - + Key 1 - Avatar Preview Mode Key 2 - Toggle Overlay Arrow Keys - Navigate @@ -852,142 +1101,140 @@ Arrow Keys - Navigate æ–¹å‘éµ - 導覽 - - + Snapmatic Picture Viewer Snapmatic 圖片檢視器 - - + Failed at %1 失敗: %1 - - - + + + No Players ç„¡ - - + + No Crew ç„¡ - + Unknown Location 未知地點 - + Avatar Preview Mode Press 1 for Default View 大頭貼é è¦½æ¨¡å¼ 按 1 åˆ‡æ›æ ¼é è¨­æª¢è¦– - + Export as Picture... 匯出æˆåœ–片... - - + + Export 匯出 - + JPEG Graphics (*.jpg *.jpeg) JPEG åœ–å½¢æ ¼å¼ (*.jpg *.jpeg) - + Portable Network Graphics (*.png) 坿”œå¼ç¶²è·¯åœ–å½¢ (*.png) - - + + + + + - - - Export as Picture 匯出æˆåœ–片 - - + + Overwrite %1 with current Snapmatic picture? 確定修改目å‰çš„ Snapmatic 圖片 %1 ? - + Failed to export the picture because the system occurred a write failure 系統寫入失敗,無法匯出圖片 - + Failed to export the picture because the format detection failures æ ¼å¼æª¢æ¸¬å¤±æ•—,無法匯出圖片 - + Failed to export the picture because the file can't be written 文件無法寫入,匯出圖片失敗 - + Failed to export the picture because of an unknown reason 未知的錯誤,無法匯出圖片 - - + + No valid file is selected æœªé¸æ“‡æœ‰æ•ˆçš„æª”案 - + Export as Snapmatic... åŒ¯å‡ºæˆ Snapmatic... - + GTA V Export (*.g5e) GTA V Export (*.g5e) - + GTA V Raw Export (*.auto) GTA V Raw Export (*.auto) - + Snapmatic pictures (PGTA*) Snapmatic 圖片 (PGTA*) - - - - - + + + + + Export as Snapmatic åŒ¯å‡ºæˆ Snapmatic - - + + Failed to export current Snapmatic picture 匯出目å‰çš„ Snapmatic 圖片失敗 - + Exported Snapmatic to "%1" because of using the .auto extension. 因為使用 .auto æ ¼å¼ï¼Œå°‡ Snapmatic 匯出到 "%1". @@ -1005,43 +1252,43 @@ Press 1 for Default View å¯ç”¨çš„玩家: - + Selected Players: 已鏿“‡çީ家: - + &Apply 套用(&A) - + &Cancel å–æ¶ˆ(&C) - + Add Players... 新增玩家... - + Failed to add more Players because the limit of Players are %1! 因為數é‡é™åˆ¶ %1,無法新增更多玩家! - - + + Add Player... 新增玩家... - + Enter Social Club Player ID 輸入玩家的 Social Club ID - + Failed to add Player %1 because Player %1 is already added! 新增 %1 失敗,因為 %1 已被新增! @@ -1084,262 +1331,258 @@ Press 1 for Default View 關閉(&C) - - - + + + Export file %1 of %2 files 匯出檔案中 %1 å…± %2 個檔案 - - - - + + + + + + + + + + + + + + + + + + + Import... 匯入... - - - - - - - - - - - - - - - - - - - - + + + + + + Import 匯入 - - - + + + All image files (%1) 所有圖片 (%1) - - - - + + + + All files (**) 所有檔案 (**) - - - + + + Can't import %1 because file can't be open 無法匯入 %1,因為檔案無法開啟 - - - + + + Can't import %1 because file can't be parsed properly 無法匯入 %1ï¼Œå› ç‚ºæª”æ¡ˆç„¡æ³•æ­£ç¢ºè§£æž - + Enabled pictures: %1 of %2 開啟圖片 %1 å…± %2 - + Loading... 載入中... - + Snapmatic Loader Snapmatic 載入器 - + <h4>Following Snapmatic Pictures got repaired</h4>%1 <h4>下列的 Snapmatic 圖片已被更新</h4>%1 - + Importable files (%1) å¯åŒ¯å…¥çš„æª”案 (%1) - - + + GTA V Export (*.g5e) GTA V Export (*.g5e) - - + + Savegames files (SGTA*) éŠæˆ²å­˜æª” (SGTA*) - - + + Snapmatic pictures (PGTA*) Snapmatic 圖片 (PGTA*) - - - + + + No valid file is selected æ²’æœ‰é¸æ“‡æœ‰æ•ˆçš„æª”案 - - + + Import file %1 of %2 files 匯入檔案 %1 å…± %2 個 - + Import failed with... %1 %1 匯入失敗 - - + + Failed to read Snapmatic picture ç„¡æ³•è®€å– Snapmatic 圖片 - - + + Failed to read Savegame file 無法讀å–éŠæˆ²å­˜æª” - + Can't import %1 because file format can't be detected 無法匯入 %1ï¼Œå› ç‚ºç„¡æ³•æª¢æ¸¬è©²æª”æ¡ˆæ ¼å¼ - + Failed to import the Snapmatic picture, file not begin with PGTA or end with .g5e 匯入 Snapmatic åœ–ç‰‡å¤±æ•—ï¼Œæª”æ¡ˆä¸æ˜¯ PGTA 開頭或附檔å䏿˜¯ .g5e - - Failed to import the Snapmatic picture, the picture is already in the game - 匯入 Snapmatic åœ–ç‰‡å¤±æ•—ï¼Œåœ–ç‰‡å·²ç¶“åœ¨éŠæˆ²ä¸­ - - - + Failed to import the Snapmatic picture, can't copy the file into profile 匯入 Snapmatic 圖片失敗,無法將該檔案複製到設定檔中 - + Failed to import the Savegame, can't copy the file into profile åŒ¯å…¥éŠæˆ²å­˜æª”失敗,無法將該檔案複製到設定檔中 - + Failed to import the Savegame, no Savegame slot is left åŒ¯å…¥éŠæˆ²å­˜æª”å¤±æ•—ï¼Œæ²’æœ‰éŠæˆ²å­˜æª”æ¬„ä½ - - - - - + + + + + Export selected... 匯出所é¸... - - + + JPG pictures and GTA Snapmatic JPG 圖片和 GTA Snapmatic - - + + JPG pictures only åªæœ‰ JPG 圖片 - - + + GTA Snapmatic only åªæœ‰ GTA Snapmatic - + %1Export Snapmatic pictures%2<br><br>JPG pictures make it possible to open the picture with a Image Viewer<br>GTA Snapmatic make it possible to import the picture into the game<br><br>Export as: %1 匯出 Snapmatic 圖片 %2<br><br>JPG 圖片å¯ä½¿ç”¨åœ–片檢視器開啟<br>GTA Snapmatic å¯ä»¥åŒ¯å…¥åˆ°éŠæˆ²ä¸­<br><br>匯出æˆ: - + Initialising export... åˆå§‹åŒ–... - + Export failed with... %1 %1 匯出失敗 - - + + No Snapmatic pictures or Savegames files are selected æœªé¸æ“‡ Snapmatic åœ–ç‰‡æˆ–éŠæˆ²å­˜æª” - - - + + + Remove selected ç§»é™¤æ‰€é¸ - + You really want remove the selected Snapmatic picutres and Savegame files? 你想移除所é¸çš„ Snapmatic 圖片/存檔嗎? - + Failed to remove all selected Snapmatic pictures and/or Savegame files ç„¡æ³•ç§»é™¤æ‰€é¸æ“‡çš„ Snapmatic 圖片/éŠæˆ²å­˜æª” - - - - - - + + + + + + No Snapmatic pictures are selected æœªé¸æ“‡ Snapmatic 圖片 - - - - - - + + + + + + %1 failed with... %2 @@ -1349,87 +1592,97 @@ Press 1 for Default View %2 - - + + Prepare Content for Import... + 準備匯入內容... + + + + A Snapmatic picture already exists with the uid %1, you want assign your import a new uid and timestamp? + 已有與 uid %1 相åŒçš„ Snapmatic 圖片,你想è¦åŒ¯å…¥æ–°çš„ uid 和時間戳嗎? + + + + Qualify as Avatar åˆæ ¼å¤§é ­è²¼ - - - - + + + + Patch selected... 修改所é¸... - - - - - - - - + + + + + + + + Patch file %1 of %2 files 修改檔案 %1 å…± %2 個檔案 - + Qualify %1 failed with... åˆæ ¼ - - + + Change Players... 更改玩家... - + Change Players %1 failed with... 更改玩家 - - - + + + Change Crew... 更改幫會... - + Failed to enter a valid Snapmatic Crew ID 輸入了無效的幫會 ID - + Change Crew %1 failed with... 更改幫會 - - - + + + Change Title... 更改標題... - + Failed to enter a valid Snapmatic title 輸入了無效的標題 - + Change Title %1 failed with... 更改標題 - + All profile files (*.g5e SGTA* PGTA*) 所有設定檔檔案 (*.g5e SGTA* PGTA*) @@ -1437,33 +1690,22 @@ Press 1 for Default View QApplication - - Font - å­—é«” - - - - Selected Font: %1 - 鏿“‡çš„å­—é«”: %1 - - - <h4>Welcome to %1!</h4>You want to configure %1 before you start using it? - <h4>歡迎使用 %1!</h4> 你想在開始å‰å…ˆè¨­å®š %1 å—Ž? + <h4>歡迎使用 %1!</h4> 你想在開始å‰å…ˆè¨­å®š %1 å—Ž? SavegameDialog - + Savegame Viewer éŠæˆ²å­˜æª”檢視器 - <span style=" font-weight:600;">Savegame</span><br><br>%1 - <span style=" font-weight:600;">éŠæˆ²å­˜æª”</span><br><br>%1 + <span style="font-weight:600">Savegame</span><br><br>%1 + <span style="font-weight:600">éŠæˆ²å­˜æª”</span><br><br>%1 @@ -1476,7 +1718,7 @@ Press 1 for Default View 關閉(&C) - + Failed at %1 失敗 %1 @@ -1525,37 +1767,45 @@ Press 1 for Default View 刪除 - + &View 檢視(&V) - + + + &Export 匯出(&E) - + + + &Remove 移除(&R) - + + &Select 鏿“‡(&S) - + + &Deselect å–æ¶ˆé¸æ“‡(&D) - + + Select &All 鏿“‡å…¨éƒ¨(&A) - + + &Deselect All å–æ¶ˆé¸æ“‡å…¨éƒ¨(&D) @@ -1598,50 +1848,50 @@ Press 1 for Default View æ²’æœ‰é¸æ“‡æœ‰æ•ˆçš„æª”案 - + Export Savegame... åŒ¯å‡ºéŠæˆ²å­˜æª”... - - + + AUTOSAVE - %1 %2 自動存檔 - %1 %2 - - + + SAVE %3 - %1 %2 存檔 %3 - %1 %2 - - + + WRONG FORMAT æ ¼å¼éŒ¯èª¤ - + UNKNOWN 未知 - - + + Delete Savegame 刪除存檔 - + Are you sure to delete %1 from your savegames? 你確定è¦åˆªé™¤å­˜æª” %1? - + Failed at deleting %1 from your savegames 刪除存檔 %1 失敗 @@ -1651,13 +1901,13 @@ Press 1 for Default View - - - - - - - + + + + + + + Snapmatic Properties Snapmatic 屬性 @@ -1718,105 +1968,115 @@ Press 1 for Default View + Apply changes + 套用變更 + + + &Apply 套用(&A) - + + Discard changes + æ¨æ£„變更 + + + &Cancel å–æ¶ˆ(&C) - + <h4>Unsaved changes detected</h4>You want to save the JSON content before you quit? <h4>ç›®å‰çš„變更未儲存</h4> 你想è¦åœ¨é€€å‡ºä¹‹å‰å„²å­˜ JSON å—Ž? - + Patching of Snapmatic Properties failed because of %1 æ›´æ–° Snapmatic 屬性失敗,因為 %1 - - - - + + + + Patching of Snapmatic Properties failed because of I/O Error 讀寫錯誤,未能更新 Snapmatic 屬性 - + Patching of Snapmatic Properties failed because of JSON Error JSON 錯誤,未能更新 Snapmatic 屬性 - - + + Snapmatic Crew 幫會 - - + + New Snapmatic crew: 輸入新的幫會: - - + + Snapmatic Title 標題 - - + + New Snapmatic title: 輸入新的標題: - - - + + + Edit 編輯 - + Players: %1 (%2) Multiple Player are inserted here 玩家: %1 (%2) - + Player: %1 (%2) One Player is inserted here 玩家: %1 (%2) - + Title: %1 (%2) 標題: %1 (%2) - - + + Appropriate: %1 å¯ä½¿ç”¨: %1 - + Yes Yes, should work fine 是 - + No No, could lead to issues å¦ - + Crew: %1 (%2) 幫會: %1 (%2) @@ -1824,65 +2084,65 @@ Press 1 for Default View SnapmaticPicture - - + + JSON is incomplete and malformed JSON ä¸å®Œæ•´å’Œç•°å¸¸ - - + + JSON is incomplete JSON ä¸å®Œæ•´ - - + + JSON is malformed JSON 異常 - + PHOTO - %1 照片 - %1 - + open file %1 開啟檔案 - %1 - + header not exists 標頭ä¸å­˜åœ¨ - + header is malformed 標頭異常 - + picture not exists (%1) 圖片ä¸å­˜åœ¨ (%1) - + JSON not exists (%1) JSON ä¸å­˜åœ¨ (%1) - + title not exists (%1) 標題ä¸å­˜åœ¨ (%1) - + description not exists (%1) æè¿°ä¸å­˜åœ¨ (%1) - + reading file %1 because of %2 Example for %2: JSON is malformed error è®€å–æª”案 %1 失敗,因為 %2 @@ -1932,8 +2192,8 @@ Press 1 for Default View - - + + Delete picture 刪除圖片 @@ -1943,72 +2203,82 @@ Press 1 for Default View 刪除 - + + + Edi&t 編輯(&E) - + + + Show &In-game åœ¨éŠæˆ²ä¸­é¡¯ç¤º(&I) - + + + Hide &In-game åœ¨éŠæˆ²ä¸­éš±è—(&I) - + &Export 匯出(&E) - + &View 檢視(&V) - + &Remove 移除(&R) - + + &Select 鏿“‡(&S) - + + &Deselect å–æ¶ˆé¸æ“‡(&D) - + + Select &All 鏿“‡å…¨éƒ¨(&A) - + + &Deselect All å–æ¶ˆé¸æ“‡å…¨éƒ¨(&D) - + Are you sure to delete %1 from your Snapmatic pictures? 你確定è¦åˆªé™¤Snapmatic 圖片 %1 å—Ž? - + Failed at deleting %1 from your Snapmatic pictures 刪除 Snapmatic 圖片 %1 失敗 - + Failed to hide %1 In-game from your Snapmatic pictures åœ¨éŠæˆ²ä¸­éš±è—圖片 %1 失敗 - + Failed to show %1 In-game from your Snapmatic pictures åœ¨éŠæˆ²ä¸­é¡¯ç¤ºåœ–片 %1 失敗 @@ -2016,22 +2286,22 @@ Press 1 for Default View TelemetryDialog - - You want help %1 to improve in the future by collection of data? + + You want help %1 to improve in the future by including personal usage data in your submission? ä½ å¸Œæœ›é€šéŽæ”¶é›†è³‡æ–™ä¾†å¹«åŠ©æ”¹å–„ %1 å—Ž? - + %1 User Statistics %1 使用者統計 - - Yes, I would like to take part. + + Yes, I want include personal usage data. 是的,我想幫忙. - + &OK 確定(&O) @@ -2040,7 +2310,7 @@ Press 1 for Default View UserInterface - + %2 - %1 %2 - %1 @@ -2072,6 +2342,9 @@ Press 1 for Default View + + + &Close 關閉(&C) @@ -2107,8 +2380,8 @@ Press 1 for Default View - - + + &About %1 關於 %1(&A) @@ -2164,15 +2437,15 @@ Press 1 for Default View - + Select &GTA V Folder... 鏿“‡ GTA V 資料夾(&G)... - - - + + + Select GTA V Folder... 鏿“‡ GTA V 資料夾... @@ -2188,62 +2461,104 @@ Press 1 for Default View + + Change &Title... 更改標題(&T)... + + Change &Crew... 更改幫會(&C)... + + &Qualify as Avatar 符åˆå¤§é ­è²¼è³‡æ ¼(&Q) + + Change &Players... 更改玩家(&P)... - - - + + + Show In-game åœ¨éŠæˆ²ä¸­é¡¯ç¤º - - - + + + Hide In-game åœ¨éŠæˆ²ä¸­éš±è— - - - + + + Select Profile 鏿“‡è¨­å®šæª” - + + + &Donate + 贊助(&D) + + + + Donate + 贊助 + + + + Donation methods + è´ŠåŠ©æ–¹å¼ + + + View + 檢視 + + + Copy + 複製 + + + + &Copy + 複製(&C) + + + Open File... 開啟檔案... - - - - + + + + Open File 開啟檔案 - + Can't open %1 because of not valid file format æ ¼å¼ç„¡æ•ˆï¼Œç„¡æ³•開啟 %1 + + + %1 - Messages + %1 - æ–°èž + diff --git a/res/gta5view-128.png b/res/gta5view-128.png new file mode 100644 index 0000000..fc3d97a Binary files /dev/null and b/res/gta5view-128.png differ diff --git a/res/gta5view-16.png b/res/gta5view-16.png new file mode 100644 index 0000000..662b09d Binary files /dev/null and b/res/gta5view-16.png differ diff --git a/res/gta5view-24.png b/res/gta5view-24.png new file mode 100644 index 0000000..3b2797b Binary files /dev/null and b/res/gta5view-24.png differ diff --git a/res/gta5view-256.png b/res/gta5view-256.png new file mode 100644 index 0000000..e728fb4 Binary files /dev/null and b/res/gta5view-256.png differ diff --git a/res/gta5view-32.png b/res/gta5view-32.png new file mode 100644 index 0000000..b0f64cd Binary files /dev/null and b/res/gta5view-32.png differ diff --git a/res/gta5view-40.png b/res/gta5view-40.png new file mode 100644 index 0000000..27a6988 Binary files /dev/null and b/res/gta5view-40.png differ diff --git a/res/gta5view-48.png b/res/gta5view-48.png new file mode 100644 index 0000000..3f32f7e Binary files /dev/null and b/res/gta5view-48.png differ diff --git a/res/gta5view-512.png b/res/gta5view-512.png new file mode 100644 index 0000000..cb29d15 Binary files /dev/null and b/res/gta5view-512.png differ diff --git a/res/gta5view-64.png b/res/gta5view-64.png new file mode 100644 index 0000000..e48fb58 Binary files /dev/null and b/res/gta5view-64.png differ diff --git a/res/gta5view-96.png b/res/gta5view-96.png new file mode 100644 index 0000000..975157d Binary files /dev/null and b/res/gta5view-96.png differ diff --git a/res/gta5view.desktop b/res/gta5view.desktop deleted file mode 100644 index 168bfd0..0000000 --- a/res/gta5view.desktop +++ /dev/null @@ -1,11 +0,0 @@ -[Desktop Entry] -Version=1.0 -Encoding=UTF-8 -Type=Application -Name=gta5view -Comment=gta5view -Categories=Qt;Application;Utility; -Exec=gta5view -Icon=gta5view -Terminal=false -StartupNotify=false diff --git a/res/5sync.icns b/res/gta5view.icns similarity index 100% rename from res/5sync.icns rename to res/gta5view.icns diff --git a/res/gta5view.png b/res/gta5view.png deleted file mode 100644 index bf41d47..0000000 Binary files a/res/gta5view.png and /dev/null differ diff --git a/res/gta5view.xpm b/res/gta5view.xpm deleted file mode 100644 index f1ee97b..0000000 --- a/res/gta5view.xpm +++ /dev/null @@ -1,135 +0,0 @@ -/* XPM */ -static char * C:\Users\Rafael\Documents\Projects\gta5view\res\gta5view_xpm[] = { -"128 128 4 1", -" c None", -". c #000000", -"+ c #FE0000", -"@ c #FF0000", -" ", -" ", -" ", -" ", -" ", -" ", -" ", -" ", -" ", -" ...................................................... ", -" ....................................................... ", -" ....................................................... ", -" ....................................................... ", -" ....................................................... ", -" ....................................................... ", -" ....................................................... ", -" ....................................................... ", -" ....................................................... ", -" ....................................................... ", -" ....................................................... ", -" ....................................................... ", -" ............. ", -" ............. ", -" ............. ", -" .............. ", -" ............. ", -" ............. ", -" ............. ", -" .............. ", -" .............. ", -" ............. ", -" ............. ", -" ............. ", -" .............. ", -" ............. ", -" ............. ", -" ............. ", -" ............. ", -" .............. ", -" ............. ", -" ............. ", -" ............. ", -" ............. ", -" .............. ", -" ............. ", -" ............. ", -" ............. ", -" .............. ", -" .............. ", -" ............. .. ", -" ................................ ", -" .................................... ", -" ........................................ ", -" .......................................... ", -" ............................................ ", -" ............................................. ", -" .............................................. ", -" ................................................. ", -" ................................................. ", -" .................................................. ", -" ..... ............................. ", -" ......................... ", -" ...................... ", -" ..................... ", -" ................... ", -" ................... ", -" .................. ", -" ................. ", -" ................ ", -" ............... ", -" ................ ", -" ................ ", -" ............... ", -" ............... ", -" ............... ", -" ............... ", -" .............. ", -" ++ .............. ", -" +@@@+ .............. ", -" @@@@@ .............. ", -" +@@@@@ .............. ", -" @@@@+ ............... ", -" @@@+ .............. ", -" .............. ", -" ", -" ", -" ", -" ", -" @@@@ @@@@ @@@ +++@@++ ++++ ++++ @@@@@ ", -" @@@@+ +@@@@ @@@@+ ++@@@@@@@@@+ @@@@+ +@@@@+ +@@@@+ ", -" @@@@+ @@@@+ @@@@+ +@@@@@@@@@@@@@ @@@@+ @@@@@@ @@@@@ ", -" +@@@+ +@@@@ +@@@@ +@@@@@@@@@@@@@@+ @@@@+ +@@@@@@ @@@@+ ", -" +@@@@ +@@@@+ +@@@@ @@@@@++ +@@@@@ @@@@+ @@@@@@@ +@@@@ ", -" +@@@@ +@@@+ +@@@+ +@@@@+ +@@@@+ @@@@+ +@@@+@@@ @@@@+ ", -" +@@@@ +@@@@+ @@@@+ +@@@@+ @@@@+ @@@@+ @@@++@@@ +@@@@ ", -" +@@@@ +@@@+ @@@@+ +@@@@+ +@@@+ @@@@+ @@@@ +@@@ @@@@+ ", -" @@@@ +@@@@+ +@@@@ +@@@@ @@@@+ @@@@@ @@@+ +@@@ +@@@@ ", -" @@@@+ @@@@+ +@@@@ +@@@@+ +@@@@+ +@@@+ +@@@ +@@@ +@@@+ ", -" @@@@+ +@@@@ +@@@+ +@@@@ @@@@+ @@@@+ @@@+ +@@@ +@@@@+ ", -" @@@@+ @@@@+ @@@@+ @@@@+ ++@@@@+ +@@@+ +@@@+ +@@@+ @@@@@ ", -" +@@@+ +@@@@ @@@@ +@@@@ ++@@@@@+ +@@@+ +@@@ +@@@+ @@@@+ ", -" +@@@+ @@@@+ +@@@@ +@@@@+++++++@@@@@@@+ +@@@+ +@@@+ +@@@+ +@@@@ ", -" +@@@@ +@@@@ +@@@+ @@@@@@@@@@@@@@@@@++ +@@@+ +@@@ +@@@+ @@@@+ ", -" +@@@@ @@@@+ @@@@+ @@@@@@@@@@@@@@@++ +@@@+ @@@+ @@@+ +@@@@ ", -" @@@@ +@@@+ @@@@+ +@@@@@@@@@@@+++ +@@@+ +@@@ @@@+ @@@@+ ", -" @@@@ +@@@@@ +@@@@ +@@@@++ +@@@+ @@@+ @@@+ +@@@@ ", -" @@@@ +@@@+ +@@@@ +@@@@ +@@@@ +@@@ @@@+ +@@@+ ", -" @@@@+ +@@@@+ +@@@+ +@@@@ +@@@@ @@@+ @@@+ @@@@ ", -" +@@@+ +@@@+ @@@@+ +@@@@ @@@@+ +@@@ @@@+ @@@@+ ", -" +@@@+ @@@@@ @@@@+ +@@@@ @@@+ @@@+ @@@+ @@@@ ", -" +@@@+ +@@@+ +@@@@ @@@@+ @@@+ +@@@+ @@@+ +@@@+ ", -" +@@@+ @@@@ +@@@@ @@@@+ @@@+ +@@+ @@@+ +@@@+ ", -" +@@@+ +@@@+ +@@@+ +@@@@+ @@@+ @@@+ @@@+ @@@+ ", -" @@@+ @@@@ @@@@+ +@@@@@ ++ @@@++@@+ +@@++@@@+ ", -" @@@++@@@+ @@@@+ +@@@@@+ ++@@@ @@@+@@@+ +@@++@@@ ", -" @@@@@@@@ +@@@@ @@@@@@@@@@@@@@@@@ @@@@@@@ +@@@@@@+ ", -" @@@@@@@+ +@@@@ +@@@@@@@@@@@@@@@ @@@@@@+ +@@@@@@ ", -" +@@@@@+ +@@@+ +@@@@@@@@@@@++ @@@@@@ +@@@@@+ ", -" @@@@@ @@@@ ++++@++++ @@@@@ @@@@@ ", -" ", -" ", -" ", -" ", -" ", -" ", -" ", -" ", -" "}; diff --git a/res/img.cmake b/res/img.cmake new file mode 100644 index 0000000..7a51a90 --- /dev/null +++ b/res/img.cmake @@ -0,0 +1,27 @@ +cmake_minimum_required(VERSION 3.7) + +set(GTA5VIEW_IMGFILES + res/add.svgz + res/avatararea.png + res/avatarareaimport.png + res/back.svgz + res/flag-de.png + res/flag-fr.png + res/flag-gb.png + res/flag-kr.png + res/flag-ru.png + res/flag-tw.png + res/flag-ua.png + res/flag-us.png + res/mapcayoperico.jpg + res/mappreview.jpg + res/next.svgz + res/pointmaker-8.png + res/pointmaker-16.png + res/pointmaker-24.png + res/pointmaker-32.png + res/savegame.svgz + res/watermark_1b.png + res/watermark_2b.png + res/watermark_2r.png +) diff --git a/res/img.qrc b/res/img.qrc new file mode 100644 index 0000000..4fe80f2 --- /dev/null +++ b/res/img.qrc @@ -0,0 +1,36 @@ + + + add.svgz + avatararea.png + avatarareaimport.png + back.svgz + flag-de.png + flag-fr.png + flag-gb.png + flag-kr.png + flag-ru.png + flag-tw.png + flag-ua.png + flag-us.png + gta5view-16.png + gta5view-24.png + gta5view-32.png + gta5view-40.png + gta5view-48.png + gta5view-64.png + gta5view-96.png + gta5view-128.png + gta5view-256.png + mapcayoperico.jpg + mappreview.jpg + next.svgz + pointmaker-8.png + pointmaker-16.png + pointmaker-24.png + pointmaker-32.png + savegame.svgz + watermark_1b.png + watermark_2b.png + watermark_2r.png + + diff --git a/res/ltc.str b/res/ltc.str new file mode 100644 index 0000000..865862c --- /dev/null +++ b/res/ltc.str @@ -0,0 +1 @@ +Litecoin \ No newline at end of file diff --git a/res/ltc.svgz b/res/ltc.svgz new file mode 100644 index 0000000..ae954e1 Binary files /dev/null and b/res/ltc.svgz differ diff --git a/res/mapcayoperico.jpg b/res/mapcayoperico.jpg new file mode 100644 index 0000000..a9cdab1 Binary files /dev/null and b/res/mapcayoperico.jpg differ diff --git a/res/next.png b/res/next.png deleted file mode 100644 index 4a36bc2..0000000 Binary files a/res/next.png and /dev/null differ diff --git a/res/next.svgz b/res/next.svgz new file mode 100644 index 0000000..bdf7ee9 Binary files /dev/null and b/res/next.svgz differ diff --git a/res/pointmaker-16.png b/res/pointmaker-16.png index 3244d6e..f038879 100644 Binary files a/res/pointmaker-16.png and b/res/pointmaker-16.png differ diff --git a/res/pointmaker-24.png b/res/pointmaker-24.png index d337357..87e9669 100644 Binary files a/res/pointmaker-24.png and b/res/pointmaker-24.png differ diff --git a/res/pointmaker-32.png b/res/pointmaker-32.png index 610efd4..de591b1 100644 Binary files a/res/pointmaker-32.png and b/res/pointmaker-32.png differ diff --git a/res/pointmaker-8.png b/res/pointmaker-8.png index a116c72..5c0e653 100644 Binary files a/res/pointmaker-8.png and b/res/pointmaker-8.png differ diff --git a/res/qt_de.qm b/res/qt4/qt_de.qm similarity index 100% rename from res/qt_de.qm rename to res/qt4/qt_de.qm diff --git a/res/qt_fr.qm b/res/qt4/qt_fr.qm similarity index 100% rename from res/qt_fr.qm rename to res/qt4/qt_fr.qm diff --git a/res/qt4/qt_ko.qm b/res/qt4/qt_ko.qm new file mode 100644 index 0000000..e3d1231 Binary files /dev/null and b/res/qt4/qt_ko.qm differ diff --git a/res/qt_ru.qm b/res/qt4/qt_ru.qm similarity index 100% rename from res/qt_ru.qm rename to res/qt4/qt_ru.qm diff --git a/res/qt_uk.qm b/res/qt4/qt_uk.qm similarity index 100% rename from res/qt_uk.qm rename to res/qt4/qt_uk.qm diff --git a/res/qt_zh_TW.qm b/res/qt4/qt_zh_TW.qm similarity index 100% rename from res/qt_zh_TW.qm rename to res/qt4/qt_zh_TW.qm diff --git a/res/tr_qt4.qrc b/res/qt4/tr_qt.qrc similarity index 87% rename from res/tr_qt4.qrc rename to res/qt4/tr_qt.qrc index f397ce6..0b79a15 100644 --- a/res/tr_qt4.qrc +++ b/res/qt4/tr_qt.qrc @@ -2,6 +2,7 @@ qt_de.qm qt_fr.qm + qt_ko.qm qt_ru.qm qt_uk.qm qt_zh_TW.qm diff --git a/res/qt5/qtbase_de.qm b/res/qt5/qtbase_de.qm new file mode 100644 index 0000000..4a4c988 Binary files /dev/null and b/res/qt5/qtbase_de.qm differ diff --git a/res/qtbase_en_GB.qm b/res/qt5/qtbase_en_GB.qm similarity index 71% rename from res/qtbase_en_GB.qm rename to res/qt5/qtbase_en_GB.qm index f88ef88..8a7e376 100644 Binary files a/res/qtbase_en_GB.qm and b/res/qt5/qtbase_en_GB.qm differ diff --git a/res/qt5/qtbase_en_GB.ts b/res/qt5/qtbase_en_GB.ts new file mode 100644 index 0000000..0fbfffd --- /dev/null +++ b/res/qt5/qtbase_en_GB.ts @@ -0,0 +1,65 @@ + + + + + QColorDialog + + Hu&e: + + + + &Sat: + + + + &Val: + + + + &Red: + + + + &Green: + + + + Bl&ue: + + + + A&lpha channel: + + + + &HTML: + + + + Cursor at %1, %2 +Press ESC to cancel + Cursour at %1, %2 +Press ESC to cancel + + + Select Color + Select Colour + + + &Basic colors + &Basic colours + + + &Custom colors + &Custom colours + + + &Add to Custom Colors + &Add to Custom Colours + + + &Pick Screen Color + &Pick Screen Colour + + + diff --git a/res/qtbase_fr.qm b/res/qt5/qtbase_fr.qm similarity index 99% rename from res/qtbase_fr.qm rename to res/qt5/qtbase_fr.qm index 8353f0a..009fb5a 100644 Binary files a/res/qtbase_fr.qm and b/res/qt5/qtbase_fr.qm differ diff --git a/res/qt5/qtbase_ko.qm b/res/qt5/qtbase_ko.qm new file mode 100644 index 0000000..20e4661 Binary files /dev/null and b/res/qt5/qtbase_ko.qm differ diff --git a/res/qt5/qtbase_ru.qm b/res/qt5/qtbase_ru.qm new file mode 100644 index 0000000..c1a2286 Binary files /dev/null and b/res/qt5/qtbase_ru.qm differ diff --git a/res/qtbase_uk.qm b/res/qt5/qtbase_uk.qm similarity index 82% rename from res/qtbase_uk.qm rename to res/qt5/qtbase_uk.qm index e24be7f..21a3038 100644 Binary files a/res/qtbase_uk.qm and b/res/qt5/qtbase_uk.qm differ diff --git a/res/qt5/qtbase_zh_TW.qm b/res/qt5/qtbase_zh_TW.qm new file mode 100644 index 0000000..6205298 Binary files /dev/null and b/res/qt5/qtbase_zh_TW.qm differ diff --git a/res/tr_qt5.qrc b/res/qt5/tr_qt.qrc similarity index 88% rename from res/tr_qt5.qrc rename to res/qt5/tr_qt.qrc index 1141d73..7bfe7cc 100644 --- a/res/tr_qt5.qrc +++ b/res/qt5/tr_qt.qrc @@ -3,6 +3,7 @@ qtbase_en_GB.qm qtbase_de.qm qtbase_fr.qm + qtbase_ko.qm qtbase_ru.qm qtbase_uk.qm qtbase_zh_TW.qm diff --git a/res/qt6/qtbase_de.qm b/res/qt6/qtbase_de.qm new file mode 100644 index 0000000..a0d2672 Binary files /dev/null and b/res/qt6/qtbase_de.qm differ diff --git a/res/qt6/qtbase_en_GB.qm b/res/qt6/qtbase_en_GB.qm new file mode 100644 index 0000000..8a7e376 Binary files /dev/null and b/res/qt6/qtbase_en_GB.qm differ diff --git a/res/qt6/qtbase_en_GB.ts b/res/qt6/qtbase_en_GB.ts new file mode 100644 index 0000000..0fbfffd --- /dev/null +++ b/res/qt6/qtbase_en_GB.ts @@ -0,0 +1,65 @@ + + + + + QColorDialog + + Hu&e: + + + + &Sat: + + + + &Val: + + + + &Red: + + + + &Green: + + + + Bl&ue: + + + + A&lpha channel: + + + + &HTML: + + + + Cursor at %1, %2 +Press ESC to cancel + Cursour at %1, %2 +Press ESC to cancel + + + Select Color + Select Colour + + + &Basic colors + &Basic colours + + + &Custom colors + &Custom colours + + + &Add to Custom Colors + &Add to Custom Colours + + + &Pick Screen Color + &Pick Screen Colour + + + diff --git a/res/qt6/qtbase_fr.qm b/res/qt6/qtbase_fr.qm new file mode 100644 index 0000000..009fb5a Binary files /dev/null and b/res/qt6/qtbase_fr.qm differ diff --git a/res/qt6/qtbase_ko.qm b/res/qt6/qtbase_ko.qm new file mode 100644 index 0000000..20e4661 Binary files /dev/null and b/res/qt6/qtbase_ko.qm differ diff --git a/res/qt6/qtbase_ru.qm b/res/qt6/qtbase_ru.qm new file mode 100644 index 0000000..c1a2286 Binary files /dev/null and b/res/qt6/qtbase_ru.qm differ diff --git a/res/qt6/qtbase_uk.qm b/res/qt6/qtbase_uk.qm new file mode 100644 index 0000000..21a3038 Binary files /dev/null and b/res/qt6/qtbase_uk.qm differ diff --git a/res/qt6/qtbase_zh_TW.qm b/res/qt6/qtbase_zh_TW.qm new file mode 100644 index 0000000..f32a72f Binary files /dev/null and b/res/qt6/qtbase_zh_TW.qm differ diff --git a/res/qt6/tr_qt.qrc b/res/qt6/tr_qt.qrc new file mode 100644 index 0000000..7bfe7cc --- /dev/null +++ b/res/qt6/tr_qt.qrc @@ -0,0 +1,11 @@ + + + qtbase_en_GB.qm + qtbase_de.qm + qtbase_fr.qm + qtbase_ko.qm + qtbase_ru.qm + qtbase_uk.qm + qtbase_zh_TW.qm + + diff --git a/res/qtbase_de.qm b/res/qtbase_de.qm deleted file mode 100644 index 29e518e..0000000 Binary files a/res/qtbase_de.qm and /dev/null differ diff --git a/res/qtbase_en_GB.ts b/res/qtbase_en_GB.ts deleted file mode 100644 index 33da638..0000000 --- a/res/qtbase_en_GB.ts +++ /dev/null @@ -1,16450 +0,0 @@ - - - - - - - Intro - - - - Introx - - - - - AddDialog - - Add a Contact - - - - - AddTorrentDialog - - Choose a torrent file - - - - Torrents (*.torrent);; All files (*.*) - - - - Choose a destination directory - - - - - AddTorrentFile - - Add a torrent - - - - Select a torrent source - - - - Destination: - - - - Tracker URL: - - - - Browse - - - - File(s): - - - - Size: - - - - Creator: - - - - <none> - - - - Torrent file: - - - - Comment: - - - - 0 - - - - &OK - - - - &Cancel - - - - - AddressBook - - Name: - - - - Address: - - - - Simple Address Book - - - - &Add - - - - &Submit - - - - &Cancel - - - - Empty Field - - - - Please enter a name and address. - - - - Add Successful - - - - "%1" has been added to your address book. - - - - Add Unsuccessful - - - - Sorry, "%1" is already in your address book. - - - - &Next - - - - &Previous - - - - &Edit - - - - &Remove - - - - Edit Successful - - - - "%1" has been edited in your address book. - - - - Edit Unsuccessful - - - - Confirm Remove - - - - Are you sure you want to remove "%1"? - - - - Remove Successful - - - - "%1" has been removed from your address book. - - - - &Find - - - - Contact Not Found - - - - Sorry, "%1" is not in your address book. - - - - &Load... - - - - Load contacts from a file - - - - &Save... - - - - Save contacts to a file - - - - Save Address Book - - - - Address Book (*.abk);;All Files (*) - - - - Unable to open file - - - - Open Address Book - - - - No contacts in file - - - - The file you are attempting to open contains no contacts. - - - - E&xport - - - - Export as vCard - - - - Export Contact - - - - vCard Files (*.vcf);;All Files (*) - - - - Export Successful - - - - "%1" has been exported as a vCard. - - - - - AddressWidget - - Duplicate Name - - - - The name "%1" already exists. - - - - Edit a Contact - - - - Unable to open file - - - - No contacts in file - - - - The file you are attempting to open contains no contacts. - - - - - AnalogClock - - Analog Clock - - - - - ApplicationsTab - - Open with: - - - - Application %1 - - - - Always use this application to open this type of file - - - - Always use this application to open files with the extension '%1' - - - - - BasicToolsPlugin - - Pencil - - - - Air Brush - - - - Random Letters - - - - Circle - - - - Star - - - - Text... - - - - Text Shape - - - - Enter text: - - - - Qt - - - - Invert Pixels - - - - Swap RGB - - - - Grayscale - - - - - BearerMonitor - - BearerMonitor - - - - System State - - - - Online State: - - - - Configurations - - - - Name: - - - - State: - - - - Type: - - - - Invalid - - - - Purpose: - - - - Unknown - - - - Identifier: - - - - Roaming: - - - - Children: - - - - Network Location Awareness - - - - Register - - - - Unregister - - - - New Session - - - - Delete Session - - - - Scan - - - - %p% - - - - 1 - - - - Sessions - - - - Session 1 - - - - Online - - - - Offline - - - - Active - - - - Discovered - - - - Defined - - - - Undefined - - - - Internet Access Point - - - - Service Network - - - - User Choice - - - - Public - - - - Private - - - - Service Specific - - - - Available - - - - Not available - - - - - BlockingClient - - &Server name: - - - - S&erver port: - - - - This examples requires that you run the Fortune Server example as well. - - - - Get Fortune - - - - Quit - - - - Blocking Fortune Client - - - - The host was not found. Please check the host and port settings. - - - - The connection was refused by the peer. Make sure the fortune server is running, and check that the host name and port settings are correct. - - - - The following error occurred: %1. - - - - - BookWindow - - Books - - - - Details - - - - <b>Title:</b> - - - - <b>Author: </b> - - - - <b>Genre:</b> - - - - <b>Year:</b> - - - - <b>Rating:</b> - - - - Author Name - - - - Genre - - - - Title - - - - Year - - - - Rating - - - - - Browser - - Qt SQL Browser - - - - SQL Query - - - - &Clear - - - - &Submit - - - - &Insert Row - - - - Inserts a new Row - - - - &Delete Row - - - - Deletes the current Row - - - - Submit on &Field Change - - - - Commit on Field Change - - - - Submit on &Row Change - - - - Commit on Row Change - - - - Submit &Manually - - - - Commit Manually - - - - &Submit All - - - - Submit Changes - - - - &Revert All - - - - Revert - - - - S&elect - - - - Refresh Data from Database - - - - No database drivers found - - - - This demo requires at least one Qt database driver. Please check the documentation how to build the Qt SQL plugins. - - - - Ready. - - - - Query OK. - - - - Query OK, number of affected rows: %1 - - - - Unable to open database - - - - An error occurred while opening the connection: - - - - About - - - - The SQL Browser demonstration shows how a data browser can be used to visualize the results of SQLstatements on a live database - - - - - Calculator - - . - - - - ± - - - - Backspace - - - - Clear - - - - Clear All - - - - MC - - - - MR - - - - MS - - - - M+ - - - - ÷ - - - - × - - - - - - - - - + - - - - Sqrt - - - - x² - - - - 1/x - - - - = - - - - Calculator - - - - #### - - - - - CertificateInfo - - Display Certificate Information - - - - Certification Path - - - - Certificate Information - - - - %1%2 (%3) - - - - Issued by: - - - - Organization: %1 - - - - Subunit: %1 - - - - Country: %1 - - - - Locality: %1 - - - - State/Province: %1 - - - - Common Name: %1 - - - - Issuer Organization: %1 - - - - Issuer Unit Name: %1 - - - - Issuer Country: %1 - - - - Issuer Locality: %1 - - - - Issuer State/Province: %1 - - - - Issuer Common Name: %1 - - - - - ChatDialog - - Chat - - - - Message: - - - - ! Unknown command: %1 - - - - * %1 has joined - - - - * %1 has left - - - - Launch several instances of this program on your local network and start chatting! - - - - - ChatMainWindow - - Qt D-Bus Chat - - - - Messages sent and received from other users - - - - Message: - - - - Sends a message to other people - - - - Send - - - - Help - - - - File - - - - Quit - - - - Ctrl+Q - - - - About Qt... - - - - Change nickname... - - - - Ctrl+N - - - - - ChipTester - - Chip Example - - - - - ClassInfoPage - - Class Information - - - - Specify basic information about the class for which you want to generate skeleton source code files. - - - - &Class name: - - - - B&ase class: - - - - Generate Q_OBJECT &macro - - - - C&onstructor - - - - &QObject-style constructor - - - - Q&Widget-style constructor - - - - &Default constructor - - - - &Generate copy constructor and operator= - - - - - ClassWizard - - Class Wizard - - - - - Client - - &Server name: - - - - This examples requires that you run the Fortune Server example as well. - - - - Get Fortune - - - - Quit - - - - Fortune Client - - - - The host was not found. Please check the host name and port settings. - - - - The connection was refused by the peer. Make sure the fortune server is running, and check that the host name and port settings are correct. - - - - The following error occurred: %1. - - - - S&erver port: - - - - Opening network session. - - - - - CloseButton - - Close Tab - - - - - CodeStylePage - - Code Style Options - - - - Choose the formatting of the generated code. - - - - &Start generated files with a comment - - - - &Protect header file against multiple inclusions - - - - &Macro name: - - - - &Include base class definition - - - - Base class include: - - - - - ColorDock - - Size Hint: - - - - Min Size Hint: - - - - Max Size: - - - - Dock Widget Max Size: - - - - - ColorSwatch - - Closable - - - - Movable - - - - Floatable - - - - Vertical title bar - - - - Floating - - - - Allow on Left - - - - Allow on Right - - - - Allow on Top - - - - Allow on Bottom - - - - Place on Left - - - - Place on Right - - - - Place on Top - - - - Place on Bottom - - - - Tab into - - - - Split horizontally into - - - - Split vertically into - - - - Modified - - - - Raise - - - - Change Size Hints... - - - - - CompositionWidget - - Composition Modes - - - - Mode - - - - Clear - - - - Source - - - - Destination - - - - Source Over - - - - Destination Over - - - - Source In - - - - Dest In - - - - Source Out - - - - Dest Out - - - - Source Atop - - - - Dest Atop - - - - Xor - - - - Plus - - - - Multiply - - - - Screen - - - - Overlay - - - - Darken - - - - Lighten - - - - Color Dodge - - - - Color Burn - - - - Hard Light - - - - Soft Light - - - - Difference - - - - Exclusion - - - - Circle color - - - - Circle alpha - - - - Show Source - - - - Use OpenGL - - - - What's This? - - - - Animated - - - - - ConclusionPage - - Conclusion - - - - Click %1 to generate the class skeleton. - - - - Complete Your Registration - - - - I agree to the terms of the license - - - - <u>Evaluation License Agreement:</u> You can use this software for 30 days and make one backup, but you are not allowed to distribute it. - - - - <u>First-Time License Agreement:</u> You can use this software subject to the license you will receive by email. - - - - <u>Upgrade License Agreement:</u> This software is licensed under the terms of your current license. - - - - &Print - - - - Print License - - - - As an environmentally friendly measure, the license text will not actually be printed. - - - - - ConfigDialog - - Close - - - - Config Dialog - - - - Configuration - - - - Update - - - - Query - - - - - ConfigurationPage - - Server configuration - - - - Server: - - - - Qt (Australia) - - - - Qt (Germany) - - - - Qt (Norway) - - - - Qt (People's Republic of China) - - - - Qt (USA) - - - - - Connection - - undefined - - - - unknown - - - - - ConnectionWidget - - database - - - - Refresh - - - - Show Schema - - - - - Controller - - Controller - - - - Decelerate - - - - Accelerate - - - - Right - - - - Left - - - - - ControllerWindow - - &Quit - - - - Window Flags - - - - Type - - - - Window - - - - Dialog - - - - Sheet - - - - Drawer - - - - Popup - - - - Tool - - - - Tooltip - - - - Splash screen - - - - Hints - - - - MS Windows fixed size dialog - - - - X11 bypass window manager - - - - Frameless window - - - - No drop shadow - - - - Window title - - - - Window system menu - - - - Window minimize button - - - - Window maximize button - - - - Window close button - - - - Window context help button - - - - Window shade button - - - - Window stays on top - - - - Window stays on bottom - - - - Customize window - - - - - CreateDockWidgetDialog - - Add Dock Widget - - - - Object name: - - - - Location: - - - - Top - - - - Left - - - - Right - - - - Bottom - - - - Restore - - - - - DetailsDialog - - Name: - - - - Address: - - - - Send information about products and special offers - - - - T-shirt - - - - Badge - - - - Reference book - - - - Coffee cup - - - - Incomplete Form - - - - The form does not contain all the necessary information. -Do you want to discard it? - - - - - DetailsPage - - Fill In Your Details - - - - Please fill all three fields. Make sure to provide a valid email address (e.g., tanaka.aya@example.co.jp). - - - - &Company name: - - - - &Email address: - - - - &Postal address: - - - - - Dialog - - Dialog - - - - Load Image From File... - - - - Launch two of these dialogs. In the first, press the top button and load an image from a file. In the second, press the bottom button and display the loaded image from shared memory. - - - - Display Image From Shared Memory - - - - SharedMemory Example - - - - Select an image file - - - - Images (*.png *.xpm *.jpg) - - - - Selected file is not an image, please select another. - - - - Unable to create shared memory segment. - - - - Unable to attach to shared memory segment. -Load an image first. - - - - Unable to detach from shared memory. - - - - Http authentication required - - - - You need to supply a Username and a Password to access this site - - - - Username: - - - - Password: - - - - Site: - - - - %1 at %2 - - - - Client ready - - - - Server ready - - - - &Start - - - - &Quit - - - - Loopback - - - - Unable to start the test: %1. - - - - Listening - - - - Connecting - - - - Accepted connection - - - - Connected - - - - Received %1MB - - - - Sent %1MB - - - - Network error - - - - The following error occurred: %1. - - - - Quit - - - - Threaded Fortune Server - - - - Unable to start the server: %1. - - - - The server is running on - -IP: %1 -port: %2 - -Run the Fortune Client example now. - - - - Add Album - - - - Please provide both the name of the artist and the title of the album. - - - - Artist: - - - - Title: - - - - Year: - - - - Tracks (separated by comma): - - - - &Close - - - - &Revert - - - - &Submit - - - - <p>Message boxes have a caption, a text, and any number of buttons, each with standard or custom texts.<p>Click a button to close the message box. Pressing the Esc button will activate the detected escape button (if any). - - - - If a message box has detailed text, the user can reveal it by pressing the Show Details... button. - - - - Options - - - - QInputDialog::get&Int() - - - - QInputDialog::get&Double() - - - - QInputDialog::getIte&m() - - - - QInputDialog::get&Text() - - - - QInputDialog::get&MultiLineText() - - - - QColorDialog::get&Color() - - - - QFontDialog::get&Font() - - - - QFileDialog::getE&xistingDirectory() - - - - QFileDialog::get&OpenFileName() - - - - QFileDialog::&getOpenFileNames() - - - - QFileDialog::get&SaveFileName() - - - - QMessageBox::critica&l() - - - - QMessageBox::i&nformation() - - - - QMessageBox::&question() - - - - QMessageBox::&warning() - - - - QErrorMessage::showM&essage() - - - - Input Dialogs - - - - Do not use native dialog - - - - Show alpha channel - - - - No buttons - - - - Color Dialog - - - - Show scalable fonts - - - - Show non scalable fonts - - - - Show monospaced fonts - - - - Show proportional fonts - - - - Font Dialog - - - - Show directories only - - - - Do not resolve symlinks - - - - Do not confirm overwrite - - - - Do not use sheet - - - - Readonly - - - - Hide name filter details - - - - Do not use custom directory icons (Windows) - - - - File Dialogs - - - - Message Boxes - - - - QInputDialog::getInteger() - - - - Percentage: - - - - %1% - - - - QInputDialog::getDouble() - - - - Amount: - - - - Spring - - - - Summer - - - - Fall - - - - Winter - - - - QInputDialog::getItem() - - - - Season: - - - - QInputDialog::getText() - - - - User name: - - - - QInputDialog::getMultiLineText() - - - - Address: - - - - QFileDialog::getExistingDirectory() - - - - QFileDialog::getOpenFileName() - - - - All Files (*);;Text Files (*.txt) - - - - QFileDialog::getOpenFileNames() - - - - QFileDialog::getSaveFileName() - - - - QMessageBox::critical() - - - - Abort - - - - Retry - - - - Ignore - - - - QMessageBox::information() - - - - OK - - - - Escape - - - - QMessageBox::question() - - - - Yes - - - - No - - - - Cancel - - - - QMessageBox::warning() - - - - Save &Again - - - - &Continue - - - - Save Again - - - - Continue - - - - This dialog shows and remembers error messages. If the checkbox is checked (as it is by default), the shown message will be shown again, but if the user unchecks the box the message will not appear again if QErrorMessage::showMessage() is called with the same message. - - - - If the box is unchecked, the message won't appear again. - - - - Standard Dialogs - - - - SIP Dialog Example - - - - Open and close the SIP - - - - This dialog resizes if the SIP is opened - - - - Close Dialog - - - - This widget takes up all the remaining space in the top-level layout. - - - - Basic Layouts - - - - &File - - - - E&xit - - - - Horizontal layout - - - - Button %1 - - - - Grid layout - - - - Line %1: - - - - This widget takes up about two thirds of the grid layout. - - - - Form layout - - - - Line 1: - - - - Line 2, long text: - - - - Line 3: - - - - Dynamic Layouts - - - - Dynamic Layouts Help - - - - This example shows how to change layouts dynamically. - - - - Rotable Widgets - - - - Orientation of buttons: - - - - Horizontal - - - - Vertical - - - - Rotate &Widgets - - - - Hello world! - - - - Wiggly - - - - - Dials - - Form - - - - - DigitalClock - - Digital Clock - - - - - DomModel - - Name - - - - Attributes - - - - Value - - - - - DragWidget - - Draggable Text - - - - Fridge Magnets - - - - - DropArea - - <drop content> - - - - Cannot display data - - - - - DropSiteWindow - - This example accepts drags from other applications and displays the MIME types provided by the drag object. - - - - Format - - - - Content - - - - Clear - - - - Quit - - - - Drop Site - - - - - EchoWindow - - Send Message - - - - Message: - - - - Answer: - - - - - EmbeddedDialog - - Embedded Dialog - - - - Layout Direction: - - - - Left to Right - - - - Right to Left - - - - Select Font: - - - - Style: - - - - Layout spacing: - - - - - EvaluatePage - - Evaluate <i>Super Product One</i>&trade; - - - - Please fill both fields. Make sure to provide a valid email address (e.g., john.smith@example.com). - - - - N&ame: - - - - &Email address: - - - - - ExtraFiltersPlugin - - Flip Horizontally - - - - Flip Vertically - - - - Smudge... - - - - Threshold... - - - - Smudge Filter - - - - Enter number of iterations: - - - - Threshold Filter - - - - Enter threshold: - - - - - FadeMessage - - Press me - - - - - FileManager - - Failed to create directory %1 - - - - Failed to open/create file %1: %2 - - - - Failed to resize file %1: %2 - - - - Failed to read from file %1: %2 - - - - Failed to read from file %1 (read %3 bytes): %2 - - - - Failed to write to file %1: %2 - - - - - FilterWidget - - Case Sensitive - - - - - FindDialog - - Find &what: - - - - Match &case - - - - Search from &start - - - - &Find - - - - &More - - - - &Whole words - - - - Search &backward - - - - Search se&lection - - - - Extension - - - - Enter the name of a contact: - - - - Find a Contact - - - - Empty Field - - - - Please enter a name. - - - - - Form - - Form - - - - QFrame { -background-color: #45629a; -} - -QLabel { -color: white; -} - - - - Powered by FlightView - - - - background-color: white; -color: #45629a; - - - - Ready - - - - color: black; -border: 1px solid black; -background: white; -selection-background-color: lightgray; - - - - color: rgb(255, 255, 255); -background-color: rgb(85, 85, 255); -padding: 2px; -border: 2px solid rgb(0, 0, 127); - - - - Search - - - - QFrame { border: 2px solid white; -border-radius: 10px; -margin: 5px; -background-color: rgba(69, 98, 154, 192); } - - - - color: white; -border: none; -background-color: none; - - - - Secure Socket Client - - - - Host name: - - - - www.qt.io - - - - Port: - - - - Active session - - - - Connect to host - - - - Cryptographic Cipher: - - - - <none> - - - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> -p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans Serif'; font-size:9pt;"></p></body></html> - - - - Input: - - - - &Send - - - - Easing curves - - - - Path type - - - - Line - - - - buttonGroup - - - - Circle - - - - Properties - - - - Period - - - - Overshoot - - - - Amplitude - - - - BackSide - - - - Settings - - - - Title: - - - - Pad Navigator Example - - - - Modified: - - - - Extent - - - - Other input - - - - Widgets On Graphics View - - - - QGraphicsProxyWidget - - - - QGraphicsWidget - - - - QObject - - - - QGraphicsItem - - - - QGraphicsLayoutItem - - - - QGraphicsGridLayout - - - - QGraphicsLayout - - - - QGraphicsLinearLayout - - - - - FortuneServer - - You've been leading a dog's life. Stay off the furniture. - - - - You've got to think about tomorrow. - - - - You will be surprised by a loud noise. - - - - You will feel hungry again in another hour. - - - - You might have mail. - - - - You cannot kill time without injuring eternity. - - - - Computers are not intelligent. They only think they are. - - - - - GeneralTab - - File Name: - - - - Path: - - - - Size: - - - - %1 K - - - - Last Read: - - - - Last Modified: - - - - - GradientWidget - - Gradients - - - - Color Editor - - - - Gradient Type - - - - Linear Gradient - - - - Radial Gradient - - - - Conical Gradient - - - - Spread Method - - - - Pad Spread - - - - Reflect Spread - - - - Repeat Spread - - - - Defaults - - - - 1 - - - - 2 - - - - 3 - - - - Reset - - - - Show Source - - - - Use OpenGL - - - - What's This? - - - - - GraphWidget - - Elastic Nodes - - - - Click and drag the nodes around, and zoom with the mouse wheel or the '+' and '-' keys - - - - - GraphicsView - - Boxes - - - - - HttpWindow - - Please enter the URL of a file you want to download. - - - - - - Download - - - - HTTP - - - - &URL: - - - - &Download directory: - - - - Default &file: - - - - Quit - - - - Downloading %1... - - - - Error - - - - Invalid URL: %1: %2 - - - - Overwrite Existing File - - - - There already exists a file called %1 in the current directory. Overwrite? - - - - Unable to save the file %1: %2. - - - - Download canceled. - - - - Download failed: -%1. - - - - Redirect - - - - Redirect to %1 ? - - - - Downloaded %1 bytes to %2 -in -%3 - - - - %1 at %2 - - - - SSL Errors - - - - One or more SSL errors has occurred: -%1 - - - - - IconPreviewArea - - Normal - - - - Active - - - - Disabled - - - - Selected - - - - Off - - - - On - - - - <b>%1</b> - - - - Size: %1x%2 -Actual size: %3x%4 -Device pixel ratio: %5 - - - - - IconSizeSpinBox - - (\d+)(\s*[xx]\s*\d+)? - - - - %1 x %1 - - - - - ImageComposer - - SourceOver - - - - DestinationOver - - - - Clear - - - - Source - - - - Destination - - - - SourceIn - - - - DestinationIn - - - - SourceOut - - - - DestinationOut - - - - SourceAtop - - - - DestinationAtop - - - - Xor - - - - Plus - - - - Multiply - - - - Screen - - - - Overlay - - - - Darken - - - - Lighten - - - - ColorDodge - - - - ColorBurn - - - - HardLight - - - - SoftLight - - - - Difference - - - - Exclusion - - - - = - - - - Image Composition - - - - Choose Source Image - - - - Choose Destination Image - - - - - ImageViewer - - Cannot load %1: %2 - - - - Opened "%1", %2x%3, Depth: %4 - - - - Cannot write %1: %2 - - - - Wrote "%1" - - - - Open File - - - - Save File As - - - - No image in clipboard - - - - Obtained image from clipboard, %1x%2, Depth: %3 - - - - About Image Viewer - - - - <p>The <b>Image Viewer</b> example shows how to combine QLabel and QScrollArea to display an image. QLabel is typically used for displaying a text, but it can also display an image. QScrollArea provides a scrolling view around another widget. If the child widget exceeds the size of the frame, QScrollArea automatically provides scroll bars. </p><p>The example demonstrates how QLabel's ability to scale its contents (QLabel::scaledContents), and QScrollArea's ability to automatically resize its contents (QScrollArea::widgetResizable), can be used to implement zooming and scaling features. </p><p>In addition the example shows how to use QPainter to print an image.</p> - - - - &File - - - - &Open... - - - - &Save As... - - - - &Print... - - - - E&xit - - - - Ctrl+Q - - - - &Edit - - - - &Copy - - - - &Paste - - - - &View - - - - Zoom &In (25%) - - - - Zoom &Out (25%) - - - - &Normal Size - - - - Ctrl+S - - - - &Fit to Window - - - - Ctrl+F - - - - &Help - - - - &About - - - - About &Qt - - - - Image Viewer - - - - [file] - - - - Image file to open. - - - - - Images - - Image loading and scaling example - - - - Open Images - - - - Cancel - - - - Pause/Resume - - - - Select Images - - - - - InformationWindow - - Item: - - - - Description: - - - - Image file: - - - - &Close - - - - &Revert - - - - &Submit - - - - - IntroPage - - Introduction - - - - This wizard will generate a skeleton C++ class definition, including a few functions. You simply need to specify the class name and set a few options to produce a header file and an implementation file for your new C++ class. - - - - This wizard will help you register your copy of <i>Super Product One</i>&trade; or start evaluating the product. - - - - &Register your copy - - - - &Evaluate the product for 30 days - - - - - ItemDialog - - Items (double click to flip) - - - - Add Qt box - - - - Add circle - - - - Add square - - - - - LicenseWizard - - License Wizard - - - - The decision you make here will affect which page you get to see next. - - - - Make sure to provide a valid email address, such as toni.buddenbrook@example.de. - - - - If you don't provide an upgrade key, you will be asked to fill in your details. - - - - Make sure to provide a valid email address, such as thomas.gradgrind@example.co.uk. - - - - You must accept the terms and conditions of the license to proceed. - - - - This help is likely not to be of any help. - - - - Sorry, I already gave what help I could. Maybe you should try asking a human? - - - - License Wizard Help - - - - - LocationDialog - - Native - - - - INI - - - - User - - - - System - - - - QtProject - - - - Any - - - - Qt Creator - - - - Application Example - - - - Assistant - - - - Designer - - - - Linguist - - - - &Format: - - - - &Scope: - - - - &Organization: - - - - &Application: - - - - Setting Locations - - - - Location - - - - Access - - - - Open Application Settings - - - - Read-write - - - - Read-only - - - - Read-only fallback - - - - - MAC_APPLICATION_MENU - - Services - - - - Hide %1 - - - - Hide Others - - - - Show All - - - - Preferences... - - - - Quit %1 - - - - About %1 - - - - - MainWindow - - Torrent - - - - Peers/Seeds - - - - Progress - - - - Down rate - - - - Up rate - - - - Status - - - - Downloading - - - - Add &new torrent - - - - &Pause torrent - - - - &Remove torrent - - - - &File - - - - E&xit - - - - &Help - - - - &About - - - - About &Qt - - - - Tools - - - - :/icons/1downarrow.png - - - - Move down - - - - :/icons/1uparrow.png - - - - Move up - - - - Rate control - - - - Max download: - - - - 0 KB/s - - - - 99999 KB/s - - - - Max upload: - - - - Torrent Client - - - - Choose a torrent file - - - - Torrents (*.torrent);; All files (*.*) - - - - Error - - - - An error occurred while downloading %0: %1 - - - - Already downloading - - - - The torrent file %1 is already being downloaded. - - - - The torrent file %1 cannot not be opened/resumed. - - - - Torrent: %1<br>Destination: %2 - - - - 0/0 - - - - Idle - - - - Torrent: %1<br>Destination: %2<br>State: %3 - - - - %1/%2 - - - - Resume torrent - - - - Pause torrent - - - - %1 KB/s - - - - About Torrent Client - - - - Disconnecting from trackers - - - - Abort - - - - Music Archive - - - - Artist : %1 -Number of Albums: %2 - - - - Title: %1 (%2) - - - - Delete Album - - - - Are you sure you want to delete '%1' by '%2'? - - - - Select the album you want to delete. - - - - Artist - - - - Album - - - - Details - - - - &Add album... - - - - &Delete album... - - - - &Quit - - - - Ctrl+A - - - - Ctrl+D - - - - About Music Archive - - - - <p>The <b>Music Archive</b> example shows how to present data from different data sources in the same application. The album titles, and the corresponding artists and release dates, are kept in a database, while each album's tracks are stored in an XML file. </p><p>The example also shows how to add as well as remove data from both the database and the associated XML file using the API provided by the Qt SQL and Qt XML modules, respectively.</p> - - - - Finger Paint - - - - Open File - - - - About Scribble - - - - <p>The <b>Scribble</b> example shows how to use QMainWindow as the base widget for an application, and how to reimplement some of QWidget's event handlers to receive the events generated for the application's widgets:</p><p> We reimplement the mouse event handlers to facilitate drawing, the paint event handler to update the application and the resize event handler to optimize the application's appearance. In addition we reimplement the close event handler to intercept the close events before terminating the application.</p><p> The example also demonstrates how to use QPainter to draw an image in real time, as well as to repaint widgets.</p> - - - - &Open... - - - - Ctrl+O - - - - %1... - - - - &Print... - - - - Ctrl+Q - - - - &Clear Screen - - - - Ctrl+L - - - - &Save As - - - - &Options - - - - Scribble - - - - The image has been modified. -Do you want to save your changes? - - - - Save As - - - - %1 Files (*.%2);;All Files (*) - - - - New Game - - - - Quit - - - - Puzzle - - - - Open Image - - - - Image Files (*.png *.jpg *.bmp) - - - - The image file could not be loaded. - - - - Puzzle Completed - - - - Congratulations! You have completed the puzzle! -Click OK to start again. - - - - &Game - - - - &Restart - - - - Chip Example - - - - Diagramscene - - - - Blue Grid - - - - White Grid - - - - Gray Grid - - - - % - - - - About Diagram Scene - - - - The <b>Diagram Scene</b> example shows use of the graphics framework. - - - - Conditional - - - - Process - - - - Input/Output - - - - Text - - - - No Grid - - - - Basic Flowchart Shapes - - - - Backgrounds - - - - Bring to &Front - - - - Ctrl+F - - - - Bring item to front - - - - Send to &Back - - - - Ctrl+T - - - - Send item to back - - - - &Delete - - - - Delete - - - - Delete item from diagram - - - - Quit Scenediagram example - - - - Bold - - - - Ctrl+B - - - - Italic - - - - Ctrl+I - - - - Underline - - - - Ctrl+U - - - - A&bout - - - - F1 - - - - &Item - - - - Edit - - - - Font - - - - Color - - - - 50% - - - - 75% - - - - 100% - - - - 125% - - - - 150% - - - - Pointer type - - - - black - - - - white - - - - red - - - - blue - - - - yellow - - - - Address Book - - - - &Save As... - - - - &Tools - - - - &Add Entry... - - - - &Edit Entry... - - - - &Remove Entry - - - - Chart - - - - Label - - - - Quantity - - - - Choose a data file - - - - Loaded %1 - - - - Save file as - - - - Saved %1 - - - - Editable Tree Model - - - - &Actions - - - - Insert Row - - - - Ctrl+I, R - - - - Remove Row - - - - Ctrl+R, R - - - - Insert Column - - - - Ctrl+I, C - - - - Remove Column - - - - Ctrl+R, C - - - - Insert Child - - - - Ctrl+N - - - - Title - - - - Description - - - - Position: (%1,%2) - - - - Position: (%1,%2) in top level - - - - Pixel size: - - - - Pixelator - - - - Choose an image - - - - %1 - Pixelator - - - - Large Image Size - - - - The printed image may be very large. Are you sure that you want to print it? - - - - Print Image - - - - Printing... - - - - Cancel - - - - Printing canceled - - - - The printing process was canceled. - - - - Printing is not supported on this Qt build - - - - About the Pixelator example - - - - This example demonstrates how a standard view and a custom -delegate can be used to produce a specialized representation -of data in a simple custom model. - - - - Simple DOM Model - - - - XML files (*.xml);;HTML files (*.html);;SVG files (*.svg);;User Interface files (*.ui) - - - - About Application - - - - The <b>Application</b> example demonstrates how to write modern GUI applications using Qt, with a menu bar, toolbars, and a status bar. - - - - File - - - - &New - - - - Create a new file - - - - Open an existing file - - - - &Save - - - - Save the document to disk - - - - Save &As... - - - - Save the document under a new name - - - - Exit the application - - - - &Edit - - - - Cu&t - - - - Cut the current selection's contents to the clipboard - - - - &Copy - - - - Copy the current selection's contents to the clipboard - - - - &Paste - - - - Paste the clipboard's contents into the current selection - - - - Show the application's About box - - - - Show the Qt library's About box - - - - Ready - - - - Application - - - - The document has been modified. -Do you want to save your changes? - - - - Cannot read file %1: -%2. - - - - File loaded - - - - Cannot write file %1: -%2. - - - - File saved - - - - Dock Widgets - - - - Yours sincerely, - - - - Choose a file name - - - - Saved '%1' - - - - About Dock Widgets - - - - The <b>Dock Widgets</b> example demonstrates how to use Qt's dock widgets. You can enter your own text, click a customer to add a customer name and address, and click standard paragraphs to add them. - - - - &New Letter - - - - Create a new form letter - - - - &Save... - - - - Save the current form letter - - - - Print the current form letter - - - - Quit the application - - - - &Undo - - - - Undo the last editing action - - - - &View - - - - Customers - - - - Paragraphs - - - - Status Bar - - - - Save layout... - - - - Load layout... - - - - Switch layout direction - - - - Main window - - - - Animated docks - - - - Allow nested docks - - - - Allow tabbed docks - - - - Force tabbed docks - - - - Vertical tabs - - - - Grouped dragging - - - - Tool bars - - - - Unified - - - - &Dock Widgets - - - - Save layout - - - - Failed to open %1 -%2 - - - - Error writing to %1 -%2 - - - - Load layout - - - - Error reading %1 - - - - Top left corner - - - - Top dock area - - - - Left dock area - - - - Top right corner - - - - Right dock area - - - - Bottom left corner - - - - Bottom dock area - - - - Bottom right corner - - - - Destroy dock widget - - - - Add dock widget... - - - - Failed to restore dock widget - - - - MDI - - - - &%1 %2 - - - - About MDI - - - - The <b>MDI</b> example demonstrates how to write multiple document interface applications using Qt. - - - - %1 %2 - - - - Recent... - - - - &Window - - - - Cl&ose - - - - Close the active window - - - - Close &All - - - - Close all the windows - - - - &Tile - - - - Tile the windows - - - - &Cascade - - - - Cascade the windows - - - - Ne&xt - - - - Move the focus to the next window - - - - Pre&vious - - - - Move the focus to the previous window - - - - <i>Choose a menu option, or right-click to invoke a context menu</i> - - - - A context menu is available by right-clicking - - - - Menus - - - - Invoked <b>File|New</b> - - - - Invoked <b>File|Open</b> - - - - Invoked <b>File|Save</b> - - - - Invoked <b>File|Print</b> - - - - Invoked <b>Edit|Undo</b> - - - - Invoked <b>Edit|Redo</b> - - - - Invoked <b>Edit|Cut</b> - - - - Invoked <b>Edit|Copy</b> - - - - Invoked <b>Edit|Paste</b> - - - - Invoked <b>Edit|Format|Bold</b> - - - - Invoked <b>Edit|Format|Italic</b> - - - - Invoked <b>Edit|Format|Left Align</b> - - - - Invoked <b>Edit|Format|Right Align</b> - - - - Invoked <b>Edit|Format|Justify</b> - - - - Invoked <b>Edit|Format|Center</b> - - - - Invoked <b>Edit|Format|Set Line Spacing</b> - - - - Invoked <b>Edit|Format|Set Paragraph Spacing</b> - - - - Invoked <b>Help|About</b> - - - - About Menu - - - - The <b>Menu</b> example shows how to create menu-bar menus and context menus. - - - - Invoked <b>Help|About Qt</b> - - - - Print the document - - - - Undo the last operation - - - - &Redo - - - - Redo the last operation - - - - &Bold - - - - Make the text bold - - - - &Italic - - - - Make the text italic - - - - Set &Line Spacing... - - - - Change the gap between the lines of a paragraph - - - - Set &Paragraph Spacing... - - - - Change the gap between paragraphs - - - - &Left Align - - - - Left align the selected text - - - - &Right Align - - - - Ctrl+R - - - - Right align the selected text - - - - &Justify - - - - Ctrl+J - - - - Justify the selected text - - - - &Center - - - - Ctrl+E - - - - Center the selected text - - - - &Format - - - - Alignment - - - - About SDI - - - - The <b>SDI</b> example demonstrates how to write single document interface applications using Qt. - - - - &Close - - - - Ctrl+W - - - - Close this window - - - - SDI - - - - document%1.txt - - - - Preparing font samples... - - - - &Cancel - - - - Font Sampler - - - - Date: - - - - Font size: - - - - Calendar for %1 %2 - - - - &New... - - - - Order Form - - - - Date: %1 - - - - I would like to place an order for the following items: - - - - Product - - - - Please update my records to take account of the following privacy information: - - - - I want to receive more information about your company's products and special offers. - - - - I do not want to receive any promotional information from your company. - - - - Sincerely, - - - - Enter Customer Details - - - - Print Document - - - - Syntax Highlighter - - - - About Syntax Highlighter - - - - <p>The <b>Syntax Highlighter</b> example shows how to perform simple syntax highlighting by subclassing the QSyntaxHighlighter class and describing highlighting rules using regular expressions.</p> - - - - Gesture example - - - - Codecs - - - - Cannot read file %1: -%2 - - - - Choose Encoding for %1 - - - - Save As (%1) - - - - Cannot write file %1: -%2 - - - - About Codecs - - - - The <b>Codecs</b> example demonstrates how to read and write files using various encodings. - - - - Model - - - - QFileSytemModel - - - - QFileSytemModel that shows full path - - - - Country list - - - - Word list - - - - Completion Mode - - - - Inline - - - - Filtered Popup - - - - Unfiltered Popup - - - - Case Sensitivity - - - - Case Insensitive - - - - Case Sensitive - - - - Max Visible Items - - - - Wrap around completions - - - - Completer - - - - Exit - - - - About - - - - About Qt - - - - Enter file path - - - - Enter name of your country - - - - Enter a word - - - - This example demonstrates the different features of the QCompleter class. - - - - Plug & Paint - - - - Cannot load %1. - - - - Select brush width: - - - - About Plug & Paint - - - - The <b>Plug & Paint</b> example demonstrates how to write Qt applications that can be extended through plugins. - - - - &Brush Color... - - - - &Brush Width... - - - - About &Plugins - - - - &Brush - - - - &Shapes - - - - &Filter - - - - Open INI File - - - - INI Files (*.ini *.conf) - - - - Open Property List - - - - Property List Files (*.plist) - - - - Open Registry Path - - - - Enter the path in the Windows registry: - - - - About Settings Editor - - - - The <b>Settings Editor</b> example shows how to access application settings using Qt. - - - - &Open Application Settings... - - - - Open I&NI File... - - - - Open Apple &Property List... - - - - Ctrl+P - - - - Open Windows &Registry Path... - - - - Ctrl+G - - - - &Refresh - - - - &Auto-Refresh - - - - &Fallbacks - - - - %1 (read only) - - - - %1 - %2 - - - - Opened "%1" - - - - Tree Model<br>(Double click items to edit) - - - - Tree Separator - - - - Tree Model Completer - - - - This example demonstrates how to use a QCompleter with a custom tree model. - - - - Type path from model above with items at each level separated by a '%1' - - - - Tab 1 - - - - Macros - - - - Help - - - - File actions - - - - Shape actions - - - - Undo Stack - - - - Undo limit - - - - &Open - - - - Red - - - - Green - - - - Blue - - - - Add Rectangle - - - - Add Circle - - - - Remove Shape - - - - Add robot - - - - Add snowan - - - - addTriangle - - - - File error - - - - Failed to open -%1 - - - - Parse error - - - - Failed to parse -%1 - - - - Unnamed - - - - Unsaved changes - - - - Would you like to save this document? - - - - Add snowman - - - - About Undo - - - - The Undo demonstration shows how to use the Qt Undo framework. - - - - Command List - - - - &Delete Item - - - - Del - - - - Add &Box - - - - Add &Triangle - - - - The <b>Undo</b> example demonstrates how to use Qt's undo framework. - - - - Show Font Info - - - - Filter: - - - - All - - - - Scalable - - - - Monospaced - - - - Proportional - - - - Font: - - - - Size: - - - - Style: - - - - Automatic Font Merging: - - - - &To clipboard - - - - Character Map - - - - %n font(s) found - - - - - - - Fonts - - - - Icons - - - - [file] - - - - Icon file(s) to open. - - - - Preview - - - - About Icons - - - - The <b>Icons</b> example illustrates how Qt renders an icon in different modes (active, normal, disabled, and selected) and states (on and off) based on a set of images. - - - - Small (%1 x %1) - - - - Large (%1 x %1) - - - - Toolbars (%1 x %1) - - - - List views (%1 x %1) - - - - Icon views (%1 x %1) - - - - Tab bars (%1 x %1) - - - - Open Images - - - - Directory: %1 -File: %2 -File@2x: %3 -Size: %4x%5 - - - - <None> - - - - Images - - - - Image - - - - Mode - - - - State - - - - Icon Size - - - - Other: - - - - Enter a custom size within %1..%2 - - - - "%1" (%2x%3) - - - - High DPI Scaling - - - - Screen: - - - - Device pixel ratio: - - - - Add &Sample Images... - - - - &Add Images... - - - - &Remove All Images - - - - %1 Style - - - - &Settings - - - - &Guess Image Mode/State - - - - &Use Native File Dialog - - - - Select pen width: - - - - &Pen Color... - - - - Pen &Width... - - - - Style Sheet - - - - Please read the LICENSE file before checking - - - - I accept the terms and &conditions - - - - Profession: - - - - &Name: - - - - Check this if you are male - - - - &Male - - - - &Password: - - - - Specify country of origin - - - - Egypt - - - - France - - - - Germany - - - - India - - - - Italy - - - - Norway - - - - Pakistan - - - - &Age: - - - - Country: - - - - Gender: - - - - Specify your password - - - - Password - - - - Check this if you are female - - - - &Female - - - - Specify your age - - - - Specify your name - - - - Select your profession - - - - Specify your name here - - - - Developer - - - - Student - - - - Fisherman - - - - &Exit - - - - Edit &Style... - - - - About Style sheet - - - - The <b>Style Sheet</b> example shows how widgets can be styled using <a href="http://doc.qt.io/qt-5/stylesheet.html">Qt Style Sheets</a>. Click <b>File|Edit Style Sheet</b> to pop up the style editor, and either choose an existing style sheet or design your own. - - - - Tablet Example - - - - Save Picture - - - - Open Picture - - - - About Tablet Example - - - - This example shows how to use a graphics drawing tablet in Qt. - - - - &Tablet - - - - &Line Width - - - - &Pressure - - - - &Tilt - - - - &Fixed - - - - &Alpha Channel - - - - T&angential Pressure - - - - No Alpha Channel - - - - &Color Saturation - - - - &Vertical Tilt - - - - &Horizontal Tilt - - - - &No Color Saturation - - - - DOM Bookmarks - - - - Open Bookmark File - - - - XBEL Files (*.xbel *.xml) - - - - SAX Bookmarks - - - - Save Bookmark File - - - - About DOM Bookmarks - - - - The <b>DOM Bookmarks</b> example demonstrates how to use Qt's DOM classes to read and write XML documents. - - - - Location - - - - About SAX Bookmarks - - - - The <b>SAX Bookmarks</b> example demonstrates how to use Qt's SAX classes to read XML documents and how to generate XML by hand. - - - - QXmlStream Bookmarks - - - - Parse error in file %1: - -%2 - - - - About QXmlStream Bookmarks - - - - The <b>QXmlStream Bookmarks</b> example demonstrates how to use Qt's QXmlStream classes to read and write XML documents. - - - - Add new - - - - Cannot add new window - - - - Already occupied. Undock first. - - - - - MainWindowBase - - Font Sampler - - - - &Selection - - - - &File - - - - Available Fonts - - - - &Print... - - - - Ctrl+P - - - - E&xit - - - - Ctrl+Q - - - - &Mark - - - - Ctrl+M - - - - &Unmark - - - - Ctrl+U - - - - &Clear - - - - Print Preview... - - - - - MandelbrotWidget - - Mandelbrot - - - - Rendering initial image, please wait... - - - - Use mouse wheel or the '+' and '-' keys to zoom. Press and hold left mouse button to scroll. - - - - - MapZoom - - &Oslo - - - - &Berlin - - - - &Jakarta - - - - Night Mode - - - - About OpenStreetMap - - - - &Options - - - - Light Maps - - - - - MdiChild - - document%1.txt - - - - MDI - - - - Cannot read file %1: -%2. - - - - Save As - - - - Cannot write file %1: -%2. - - - - '%1' has been modified. -Do you want to save your changes? - - - - - MoviePlayer - - No movie loaded - - - - Movie Player - - - - Open a Movie - - - - Fit to Window - - - - Current frame: - - - - Speed: - - - - % - - - - Open File - - - - Play - - - - Pause - - - - Stop - - - - Quit - - - - - NewAddressTab - - There are currently no contacts in your address book. -Click Add to add new contacts. - - - - Add - - - - - NicknameDialog - - Set nickname - - - - New nickname: - - - - OK - - - - Cancel - - - - - Notepad - - Quit - - - - Notepad - - - - Do you really want to quit? - - - - &Load - - - - &Save - - - - E&xit - - - - &File - - - - Open File - - - - Text Files (*.txt);;C++ Files (*.cpp *.h) - - - - Error - - - - Could not open file - - - - Save File - - - - - OutputFilesPage - - Output Files - - - - Specify where you want the wizard to put the generated skeleton code. - - - - &Output directory: - - - - &Header file name: - - - - &Implementation file name: - - - - - PathDeformControls - - Controls - - - - Lens Radius - - - - Deformation - - - - Font Size - - - - Text - - - - Animated - - - - Show Source - - - - Use OpenGL - - - - What's This? - - - - Qt - - - - Lens Radius: - - - - Deformation: - - - - Font Size: - - - - Quit - - - - OK - - - - - PathDeformWidget - - Vector Deformation - - - - - PathStrokeControls - - Cap Style - - - - Flat - - - - Square - - - - Round - - - - Join Style - - - - Bevel - - - - Miter - - - - Pen Style - - - - Custom - - - - Line Style - - - - Curves - - - - Lines - - - - Path Stroking - - - - Pen Width - - - - Animate - - - - Show Source - - - - Use OpenGL - - - - What's This? - - - - OK - - - - Quit - - - - Width: - - - - - PathStrokeWidget - - Path Stroking - - - - - PermissionsTab - - Permissions - - - - Readable - - - - Writable - - - - Executable - - - - Ownership - - - - Owner - - - - Group - - - - - PluginDialog - - OK - - - - Plugin Information - - - - Plug & Paint found the following plugins -(looked in %1): - - - - %1 (Static Plugin) - - - - - PreviewForm - - &Encoding: - - - - Preview - - - - Hex Dump - - - - %1: conversion error at character %2 - - - - %1: %n invalid characters - - - - - - - %1: %n bytes converted - - - - - - - - PreviewWindow - - &Close - - - - Preview - - - - - ProgressDialog - - Download Progress - - - - Downloading %1. - - - - - QAbstractSocket - - Socket operation timed out - - - - Operation on socket is not supported - - - - Host not found - - - - Connection refused - - - - Connection timed out - - - - Trying to connect while connection is in progress - - - - Socket is not connected - - - - Network unreachable - - - - - QAbstractSpinBox - - &Select All - - - - &Step up - - - - Step &down - - - - - QAccessibleActionInterface - - Press - - - - Increase - - - - Decrease - - - - ShowMenu - - - - SetFocus - - - - Toggle - - - - Scroll Left - - - - Scroll Right - - - - Scroll Up - - - - Scroll Down - - - - Previous Page - - - - Next Page - - - - Triggers the action - - - - Increase the value - - - - Decrease the value - - - - Shows the menu - - - - Sets the focus - - - - Toggles the state - - - - Scrolls to the left - - - - Scrolls to the right - - - - Scrolls up - - - - Scrolls down - - - - Goes back a page - - - - Goes to the next page - - - - - QAndroidPlatformTheme - - Yes - - - - Yes to All - - - - No - - - - No to All - - - - - QApplication - - Executable '%1' requires Qt %2, found Qt %3. - - - - Incompatible Qt Library Error - - - - - QCocoaMenuItem - - About Qt - - - - About - - - - Config - - - - Preference - - - - Options - - - - Setting - - - - Setup - - - - Quit - - - - Exit - - - - Cut - - - - Copy - - - - Paste - - - - Select All - - - - - QCocoaTheme - - Don't Save - - - - - QColorDialog - - Hu&e: - - - - &Sat: - - - - &Val: - - - - &Red: - - - - &Green: - - - - Bl&ue: - - - - A&lpha channel: - - - - &HTML: - - - - Cursor at %1, %2 -Press ESC to cancel - - - - Select Color - Select Colour - - - &Basic colors - &Basic colours - - - &Custom colors - &Custom colours - - - &Add to Custom Colors - &Add to Custom Colours - - - &Pick Screen Color - &Pick Screen Colour - - - - QComboBox - - Open the combo box selection popup - - - - False - - - - True - - - - - QCommandLineParser - - Displays version information. - - - - Displays this help. - - - - Unknown option '%1'. - - - - Unknown options: %1. - - - - Missing value after '%1'. - - - - Unexpected value after '%1'. - - - - [options] - - - - Usage: %1 - - - - Options: - - - - Arguments: - - - - - QCoreApplication - - %1: key is empty - QSystemSemaphore - - - - %1: unable to make key - QSystemSemaphore - - - - %1: ftok failed - QSystemSemaphore - - - - - QCupsJobWidget - - Job - - - - Job Control - - - - Scheduled printing: - - - - Billing information: - - - - Job priority: - - - - Banner Pages - - - - End: - Banner page at end - - - - Start: - Banner page at start - - - - Print Immediately - - - - Hold Indefinitely - - - - Day (06:00 to 17:59) - - - - Night (18:00 to 05:59) - - - - Second Shift (16:00 to 23:59) - - - - Third Shift (00:00 to 07:59) - - - - Weekend (Saturday to Sunday) - - - - Specific Time - - - - None - CUPS Banner page - - - - Standard - CUPS Banner page - - - - Unclassified - CUPS Banner page - - - - Confidential - CUPS Banner page - - - - Classified - CUPS Banner page - - - - Secret - CUPS Banner page - - - - Top Secret - CUPS Banner page - - - - - QDB2Driver - - Unable to connect - - - - Unable to commit transaction - - - - Unable to rollback transaction - - - - Unable to set autocommit - - - - - QDB2Result - - Unable to execute statement - - - - Unable to prepare statement - - - - Unable to bind variable - - - - Unable to fetch record %1 - - - - Unable to fetch next - - - - Unable to fetch first - - - - - QDBusTrayIcon - - OK - - - - - QDateTimeParser - - AM - - - - am - - - - PM - - - - pm - - - - - QDialog - - What's This? - - - - - QDialogButtonBox - - OK - - - - - QDirModel - - Name - - - - Size - - - - Kind - Match OS X Finder - - - - Type - All other platforms - - - - Date Modified - - - - - QDnsLookup - - Operation cancelled - - - - - QDnsLookupExample - - DNS Lookup Example - - - - An example demonstrating the class QDnsLookup. - - - - - QDnsLookupRunnable - - IPv6 addresses for nameservers are currently not supported - - - - Invalid domain name - - - - Not yet supported on Android - - - - Resolver functions not found - - - - Resolver initialization failed - - - - Server could not process query - - - - Server failure - - - - Non existent domain - - - - Server refused to answer - - - - Invalid reply received - - - - Could not expand domain name - - - - Invalid IPv4 address record - - - - Invalid IPv6 address record - - - - Invalid canonical name record - - - - Invalid name server record - - - - Invalid pointer record - - - - Invalid mail exchange record - - - - Invalid service record - - - - Invalid text record - - - - Resolver library can't be loaded: No runtime library loading support - - - - No hostname given - - - - Invalid hostname - - - - Host %1 could not be found. - - - - Unknown error - - - - - QDockWidget - - Float - Accessible name for button undocking a dock widget (floating state) - - - - Undocks and re-attaches the dock widget - - - - Close - Accessible name for button closing a dock widget - - - - Closes the dock widget - - - - - QErrorMessage - - Debug Message: - - - - Warning: - - - - Fatal Error: - - - - &Show this message again - - - - &OK - - - - - QFile - - Destination file is the same file. - - - - Source file does not exist. - - - - Destination file exists - - - - Error while renaming. - - - - Unable to restore from %1: %2 - - - - Will not rename sequential file using block copy - - - - Cannot remove source file - - - - Cannot open %1 for input - - - - Cannot open for output - - - - Failure to write block - - - - Cannot create %1 for output - - - - - QFileDevice - - No file engine available or engine does not support UnMapExtension - - - - - QFileDialog - - Look in: - - - - Back - - - - Go back - - - - Alt+Left - - - - Forward - - - - Go forward - - - - Alt+Right - - - - Parent Directory - - - - Go to the parent directory - - - - Alt+Up - - - - Create New Folder - - - - Create a New Folder - - - - List View - - - - Change to list view mode - - - - Detail View - - - - Change to detail view mode - - - - Sidebar - - - - List of places and bookmarks - - - - Files - - - - Files of type: - - - - Find Directory - - - - Open - - - - Save As - - - - Directory: - - - - File &name: - - - - &Open - - - - &Choose - - - - &Save - - - - All Files (*) - - - - Show - - - - &Rename - - - - &Delete - - - - Show &hidden files - - - - &New Folder - - - - All files (*) - - - - Directories - - - - %1 -Directory not found. -Please verify the correct directory name was given. - - - - %1 already exists. -Do you want to replace it? - - - - %1 -File not found. -Please verify the correct file name was given. - - - - New Folder - - - - Delete - - - - '%1' is write protected. -Do you want to delete it anyway? - - - - Are you sure you want to delete '%1'? - - - - Could not delete directory. - - - - Recent Places - - - - Remove - - - - My Computer - - - - Drive - - - - %1 File - %1 is a file name suffix, for example txt - - - - File - - - - File Folder - Match Windows Explorer - - - - Folder - All other platforms - - - - Alias - OS X Finder - - - - Shortcut - All other platforms - - - - Unknown - - - - - QFileSystemModel - - %1 TB - - - - %1 GB - - - - %1 MB - - - - %1 KB - - - - %1 bytes - - - - Invalid filename - - - - <b>The name "%1" can not be used.</b><p>Try using another name, with fewer characters or no punctuations marks. - - - - Name - - - - Size - - - - Kind - Match OS X Finder - - - - Type - All other platforms - - - - Date Modified - - - - My Computer - - - - Computer - - - - %1 byte(s) - - - - - QFontDatabase - - Normal - The Normal or Regular font weight - - - - Bold - - - - Demi Bold - - - - Medium - The Medium font weight - - - - Black - - - - Light - - - - Thin - - - - Extra Light - - - - Extra Bold - - - - Extra - The word for "Extra" as in "Extra Bold, Extra Thin" used as a pattern for string searches - - - - Demi - The word for "Demi" as in "Demi Bold" used as a pattern for string searches - - - - Italic - - - - Oblique - - - - Any - - - - Latin - - - - Greek - - - - Cyrillic - - - - Armenian - - - - Hebrew - - - - Arabic - - - - Syriac - - - - Thaana - - - - Devanagari - - - - Bengali - - - - Gurmukhi - - - - Gujarati - - - - Oriya - - - - Tamil - - - - Telugu - - - - Kannada - - - - Malayalam - - - - Sinhala - - - - Thai - - - - Lao - - - - Tibetan - - - - Myanmar - - - - Georgian - - - - Khmer - - - - Simplified Chinese - - - - Traditional Chinese - - - - Japanese - - - - Korean - - - - Vietnamese - - - - Symbol - - - - Ogham - - - - Runic - - - - N'Ko - - - - - QFontDialog - - Select Font - - - - &Font - - - - Font st&yle - - - - &Size - - - - Effects - - - - Stri&keout - - - - &Underline - - - - Sample - - - - Wr&iting System - - - - - QFtp - - Not connected - - - - Host %1 not found - - - - Connection refused to host %1 - - - - Connection timed out to host %1 - - - - Connected to host %1 - - - - Data Connection refused - - - - Unknown error - - - - Connecting to host failed: -%1 - - - - Login failed: -%1 - - - - Listing directory failed: -%1 - - - - Changing directory failed: -%1 - - - - Downloading file failed: -%1 - - - - Uploading file failed: -%1 - - - - Removing file failed: -%1 - - - - Creating directory failed: -%1 - - - - Removing directory failed: -%1 - - - - Connection closed - - - - - QGnomeTheme - - &OK - - - - &Save - - - - &Cancel - - - - &Close - - - - Close without Saving - - - - - QGuiApplication - - QT_LAYOUT_DIRECTION - Translate this string to the string 'LTR' in left-to-right languages or to 'RTL' in right-to-left languages (such as Hebrew and Arabic) to get proper widget layout. - - - - - QHostInfo - - No host name given - - - - Unknown error - - - - - QHostInfoAgent - - No host name given - - - - Invalid hostname - - - - Unknown address type - - - - Host not found - - - - Unknown error - - - - Unknown error (%1) - - - - - QHttp - - Host %1 not found - - - - Connection refused - - - - Connection closed - - - - Proxy requires authentication - - - - Host requires authentication - - - - Data corrupted - - - - Unknown protocol specified - - - - SSL handshake failed - - - - Too many redirects - - - - Insecure redirect - - - - - QHttpSocketEngine - - Did not receive HTTP response from proxy - - - - Error parsing authentication request from proxy - - - - Authentication required - - - - Proxy denied connection - - - - Error communicating with HTTP proxy - - - - Proxy server not found - - - - Proxy connection refused - - - - Proxy server connection timed out - - - - Proxy connection closed prematurely - - - - - QIBaseDriver - - Error opening database - - - - Could not start transaction - - - - Unable to commit transaction - - - - Unable to rollback transaction - - - - - QIBaseResult - - Unable to create BLOB - - - - Unable to write BLOB - - - - Unable to open BLOB - - - - Unable to read BLOB - - - - Could not find array - - - - Could not get array data - - - - Could not get query info - - - - Could not start transaction - - - - Unable to commit transaction - - - - Could not allocate statement - - - - Could not prepare statement - - - - Could not describe input statement - - - - Could not describe statement - - - - Unable to close statement - - - - Unable to execute query - - - - Could not fetch next item - - - - Could not get statement info - - - - - QIODevice - - Permission denied - - - - Too many open files - - - - No such file or directory - - - - No space left on device - - - - file to open is a directory - - - - Unknown error - - - - - QImageReader - - Invalid device - - - - File not found - - - - Unsupported image format - - - - Unable to read image data - - - - Unknown error - - - - - QImageWriter - - Unknown error - - - - Device is not set - - - - Device not writable - - - - Unsupported image format - - - - - QInputDialog - - Enter a value: - - - - - QJsonParseError - - no error occurred - - - - unterminated object - - - - missing name separator - - - - unterminated array - - - - missing value separator - - - - illegal value - - - - invalid termination by number - - - - illegal number - - - - invalid escape sequence - - - - invalid UTF8 string - - - - unterminated string - - - - object is missing after a comma - - - - too deeply nested document - - - - too large document - - - - garbage at the end of the document - - - - - QKeySequenceEdit - - Press shortcut - - - - %1, ... - This text is an "unfinished" shortcut, expands like "Ctrl+A, ..." - - - - - QLibrary - - '%1' is not an ELF object (%2) - - - - '%1' is not an ELF object - - - - '%1' is an invalid ELF object (%2) - - - - Failed to extract plugin meta data from '%1' - - - - The shared library was not found. - - - - The file '%1' is not a valid Qt plugin. - - - - The plugin '%1' uses incompatible Qt library. (%2.%3.%4) [%5] - - - - The plugin '%1' uses incompatible Qt library. (Cannot mix debug and release libraries.) - - - - Unknown error - - - - Cannot load library %1: %2 - - - - Cannot unload library %1: %2 - - - - Cannot resolve symbol "%1" in %2: %3 - - - - '%1' is not a valid Mach-O binary (%2) - - - - file is corrupt - - - - file too small - - - - no suitable architecture in fat binary - - - - invalid magic %1 - - - - wrong architecture - - - - not a dynamic library - - - - '%1' is not a Qt plugin - - - - - QLineEdit - - &Undo - - - - &Redo - - - - Cu&t - - - - &Copy - - - - &Paste - - - - Delete - - - - Select All - - - - - QLocalServer - - %1: Name error - - - - %1: Permission denied - - - - %1: Address in use - - - - %1: Unknown error %2 - - - - - QLocalSocket - - %1: Connection refused - - - - %1: Remote closed - - - - %1: Invalid name - - - - %1: Socket access error - - - - %1: Socket resource error - - - - %1: Socket operation timed out - - - - %1: Datagram too large - - - - %1: Connection error - - - - %1: The socket operation is not supported - - - - %1: Operation not permitted when socket is in this state - - - - %1: Unknown error - - - - Trying to connect while connection is in progress - - - - %1: Unknown error %2 - - - - %1: Access denied - - - - - QMYSQLDriver - - Unable to allocate a MYSQL object - - - - Unable to open database '%1' - - - - Unable to connect - - - - Unable to begin transaction - - - - Unable to commit transaction - - - - Unable to rollback transaction - - - - - QMYSQLResult - - Unable to fetch data - - - - Unable to execute query - - - - Unable to store result - - - - Unable to execute next query - - - - Unable to store next result - - - - Unable to prepare statement - - - - Unable to reset statement - - - - Unable to bind value - - - - Unable to execute statement - - - - Unable to bind outvalues - - - - Unable to store statement results - - - - - QMdiArea - - (Untitled) - - - - - QMdiSubWindow - - - [%1] - - - - %1 - [%2] - - - - Minimize - - - - Maximize - - - - Unshade - - - - Shade - - - - Restore Down - - - - Restore - - - - Close - - - - Help - - - - Menu - - - - &Restore - - - - &Move - - - - &Size - - - - Mi&nimize - - - - Ma&ximize - - - - Stay on &Top - - - - &Close - - - - - QMessageBox - - Show Details... - - - - Hide Details... - - - - OK - - - - Help - - - - <h3>About Qt</h3><p>This program uses Qt version %1.</p> - - - - <p>Qt is a C++ toolkit for cross-platform application development.</p><p>Qt provides single-source portability across all major desktop operating systems. It is also available for embedded Linux and other embedded and mobile operating systems.</p><p>Qt is available under three different licensing options designed to accommodate the needs of our various users.</p><p>Qt licensed under our commercial license agreement is appropriate for development of proprietary/commercial software where you do not want to share any source code with third parties or otherwise cannot comply with the terms of the GNU LGPL version 3 or GNU LGPL version 2.1.</p><p>Qt licensed under the GNU LGPL version 3 is appropriate for the development of Qt&nbsp;applications provided you can comply with the terms and conditions of the GNU LGPL version 3.</p><p>Qt licensed under the GNU LGPL version 2.1 is appropriate for the development of Qt&nbsp;applications provided you can comply with the terms and conditions of the GNU LGPL version 2.1.</p><p>Please see <a href="http://%2/">%2</a> for an overview of Qt licensing.</p><p>Copyright (C) %1 The Qt Company Ltd and other contributors.</p><p>Qt and the Qt logo are trademarks of The Qt Company Ltd.</p><p>Qt is The Qt Company Ltd product developed as an open source project. See <a href="http://%3/">%3</a> for more information.</p> - - - - About Qt - - - - - QNativeSocketEngine - - Unable to initialize non-blocking socket - - - - Unable to initialize broadcast socket - - - - Attempt to use IPv6 socket on a platform with no IPv6 support - - - - The remote host closed the connection - - - - Network operation timed out - - - - Out of resources - - - - Unsupported socket operation - - - - Protocol type not supported - - - - Invalid socket descriptor - - - - Host unreachable - - - - Network unreachable - - - - Permission denied - - - - Connection timed out - - - - Connection refused - - - - The bound address is already in use - - - - The address is not available - - - - The address is protected - - - - Datagram was too large to send - - - - Unable to send a message - - - - Unable to receive a message - - - - Unable to write - - - - Network error - - - - Another socket is already listening on the same port - - - - Operation on non-socket - - - - The proxy type is invalid for this operation - - - - Temporary error - - - - Network dropped connection on reset - - - - Connection reset by peer - - - - Unknown error - - - - - QNetworkAccessCacheBackend - - Error opening %1 - - - - - QNetworkAccessDataBackend - - Invalid URI: %1 - - - - - QNetworkAccessDebugPipeBackend - - Write error writing to %1: %2 - - - - Socket error on %1: %2 - - - - Remote host closed the connection prematurely on %1 - - - - - QNetworkAccessFileBackend - - Request for opening non-local file %1 - - - - Error opening %1: %2 - - - - Write error writing to %1: %2 - - - - Cannot open %1: Path is a directory - - - - Read error reading from %1: %2 - - - - - QNetworkAccessFtpBackend - - No suitable proxy found - - - - Cannot open %1: is a directory - - - - Logging in to %1 failed: authentication required - - - - Error while downloading %1: %2 - - - - Error while uploading %1: %2 - - - - - QNetworkAccessManager - - Network access is disabled. - - - - - QNetworkReply - - Error transferring %1 - server replied: %2 - - - - Background request not allowed. - - - - Network session error. - - - - backend start error. - - - - Temporary network failure. - - - - Protocol "%1" is unknown - - - - - QNetworkReplyHttpImpl - - Operation canceled - - - - No suitable proxy found - - - - - QNetworkReplyImpl - - Operation canceled - - - - - QNetworkSession - - Invalid configuration. - - - - - QNetworkSessionPrivateImpl - - Unknown session error. - - - - The session was aborted by the user or system. - - - - The requested operation is not supported by the system. - - - - The specified configuration cannot be used. - - - - Roaming was aborted or is not possible. - - - - - QOCIDriver - - Unable to initialize - QOCIDriver - - - - Unable to logon - - - - Unable to begin transaction - - - - Unable to commit transaction - - - - Unable to rollback transaction - - - - - QOCIResult - - Unable to bind column for batch execute - - - - Unable to execute batch statement - - - - Unable to goto next - - - - Unable to alloc statement - - - - Unable to prepare statement - - - - Unable to get statement type - - - - Unable to bind value - - - - Unable to execute statement - - - - - QODBCDriver - - Unable to connect - - - - Unable to connect - Driver doesn't support all functionality required - - - - Unable to disable autocommit - - - - Unable to commit transaction - - - - Unable to rollback transaction - - - - Unable to enable autocommit - - - - - QODBCResult - - Unable to fetch last - - - - QODBCResult::reset: Unable to set 'SQL_CURSOR_STATIC' as statement attribute. Please check your ODBC driver configuration - - - - Unable to execute statement - - - - Unable to fetch - - - - Unable to fetch next - - - - Unable to fetch first - - - - Unable to fetch previous - - - - Unable to prepare statement - - - - Unable to bind variable - - - - - QObject - - ID - - - - Name - - - - City - - - - Country - - - - Relational Table Model - - - - First name - - - - Last name - - - - Plain Query Model - - - - Editable Query Model - - - - Custom Query Model - - - - Table Model (View 1) - - - - Table Model (View 2) - - - - Qt SQL Browser - - - - &File - - - - Add &Connection... - - - - &Quit - - - - &Help - - - - About - - - - About Qt - - - - Systray - - - - I couldn't detect any system tray on this system. - - - - Simple Wizard - - - - Cannot write file %1: -%2 - - - - Draggable Icons - - - - Invalid enum. - - - - Invalid value. - - - - Invalid operation. - - - - Stack overflow. - - - - Stack underflow. - - - - Out of memory. - - - - Unknown error. - - - - Subject - - - - Sender - - - - Date - - - - Dir View - - - - Frozen Column Example - - - - Simple Tree Model - - - - Spin Box Delegate - - - - Add %1 - - - - Remove %1 - - - - Set %1's color - - - - Change %1's geometry - - - - Move %1 - - - - Delete %1 - - - - %1 at (%2, %3) - - - - Code Editor Example - - - - Folder - - - - The file is not an XBEL file. - - - - The file is not an XBEL version 1.0 file. - - - - Unknown title - - - - SAX Bookmarks - - - - Parse error at line %1, column %2: -%3 - - - - %1 -Line %2, column %3 - - - - - QPSQLDriver - - Unable to connect - - - - Could not begin transaction - - - - Could not commit transaction - - - - Could not rollback transaction - - - - Unable to subscribe - - - - Unable to unsubscribe - - - - - QPSQLResult - - Unable to create query - - - - Unable to prepare statement - - - - - QPageSetupWidget - - Form - - - - Paper - - - - Page size: - - - - Width: - - - - Height: - - - - Paper source: - - - - Orientation - - - - Portrait - - - - Landscape - - - - Reverse landscape - - - - Reverse portrait - - - - Margins - - - - top margin - - - - left margin - - - - right margin - - - - bottom margin - - - - Page Layout - - - - Page order: - - - - Pages per sheet: - - - - Millimeters (mm) - - - - Inches (in) - - - - Points (pt) - - - - Pica (P̸) - - - - Didot (DD) - - - - Cicero (CC) - - - - Custom - - - - mm - Unit 'Millimeter' - - - - pt - Unit 'Points' - - - - in - Unit 'Inch' - - - - P̸ - Unit 'Pica' - - - - DD - Unit 'Didot' - - - - CC - Unit 'Cicero' - - - - - QPageSize - - Custom (%1mm x %2mm) - Custom size name in millimeters - - - - Custom (%1pt x %2pt) - Custom size name in points - - - - Custom (%1in x %2in) - Custom size name in inches - - - - Custom (%1pc x %2pc) - Custom size name in picas - - - - Custom (%1DD x %2DD) - Custom size name in didots - - - - Custom (%1CC x %2CC) - Custom size name in ciceros - - - - %1 x %2 in - Page size in 'Inch'. - - - - A0 - - - - A1 - - - - A2 - - - - A3 - - - - A4 - - - - A5 - - - - A6 - - - - A7 - - - - A8 - - - - A9 - - - - A10 - - - - B0 - - - - B1 - - - - B2 - - - - B3 - - - - B4 - - - - B5 - - - - B6 - - - - B7 - - - - B8 - - - - B9 - - - - B10 - - - - Executive (7.5 x 10 in) - - - - Executive (7.25 x 10.5 in) - - - - Folio (8.27 x 13 in) - - - - Legal - - - - Letter / ANSI A - - - - Tabloid / ANSI B - - - - Ledger / ANSI B - - - - Custom - - - - A3 Extra - - - - A4 Extra - - - - A4 Plus - - - - A4 Small - - - - A5 Extra - - - - B5 Extra - - - - JIS B0 - - - - JIS B1 - - - - JIS B2 - - - - JIS B3 - - - - JIS B4 - - - - JIS B5 - - - - JIS B6 - - - - JIS B7 - - - - JIS B8 - - - - JIS B9 - - - - JIS B10 - - - - ANSI C - - - - ANSI D - - - - ANSI E - - - - Legal Extra - - - - Letter Extra - - - - Letter Plus - - - - Letter Small - - - - Tabloid Extra - - - - Architect A - - - - Architect B - - - - Architect C - - - - Architect D - - - - Architect E - - - - Note - - - - Quarto - - - - Statement - - - - Super A - - - - Super B - - - - Postcard - - - - Double Postcard - - - - PRC 16K - - - - PRC 32K - - - - PRC 32K Big - - - - Fan-fold US (14.875 x 11 in) - - - - Fan-fold German (8.5 x 12 in) - - - - Fan-fold German Legal (8.5 x 13 in) - - - - Envelope B4 - - - - Envelope B5 - - - - Envelope B6 - - - - Envelope C0 - - - - Envelope C1 - - - - Envelope C2 - - - - Envelope C3 - - - - Envelope C4 - - - - Envelope C5 - - - - Envelope C6 - - - - Envelope C65 - - - - Envelope C7 - - - - Envelope DL - - - - Envelope US 9 - - - - Envelope US 10 - - - - Envelope US 11 - - - - Envelope US 12 - - - - Envelope US 14 - - - - Envelope Monarch - - - - Envelope Personal - - - - Envelope Chou 3 - - - - Envelope Chou 4 - - - - Envelope Invite - - - - Envelope Italian - - - - Envelope Kaku 2 - - - - Envelope Kaku 3 - - - - Envelope PRC 1 - - - - Envelope PRC 2 - - - - Envelope PRC 3 - - - - Envelope PRC 4 - - - - Envelope PRC 5 - - - - Envelope PRC 6 - - - - Envelope PRC 7 - - - - Envelope PRC 8 - - - - Envelope PRC 9 - - - - Envelope PRC 10 - - - - Envelope You 4 - - - - - QPlatformTheme - - OK - - - - Save - - - - Save All - - - - Open - - - - &Yes - - - - Yes to &All - - - - &No - - - - N&o to All - - - - Abort - - - - Retry - - - - Ignore - - - - Close - - - - Cancel - - - - Discard - - - - Help - - - - Apply - - - - Reset - - - - Restore Defaults - - - - - QPluginLoader - - The plugin was not loaded. - - - - Unknown error - - - - - QPrintDialog - - Print - - - - Left to Right, Top to Bottom - - - - Left to Right, Bottom to Top - - - - Right to Left, Bottom to Top - - - - Right to Left, Top to Bottom - - - - Bottom to Top, Left to Right - - - - Bottom to Top, Right to Left - - - - Top to Bottom, Left to Right - - - - Top to Bottom, Right to Left - - - - 1 (1x1) - - - - 2 (2x1) - - - - 4 (2x2) - - - - 6 (2x3) - - - - 9 (3x3) - - - - 16 (4x4) - - - - All Pages - - - - Odd Pages - - - - Even Pages - - - - &Options >> - - - - &Print - - - - &Options << - - - - Print to File (PDF) - - - - Local file - - - - Write PDF file - - - - Print To File ... - - - - %1 is a directory. -Please choose a different file name. - - - - File %1 is not writable. -Please choose a different file name. - - - - %1 already exists. -Do you want to overwrite it? - - - - Options 'Pages Per Sheet' and 'Page Set' cannot be used together. -Please turn one of those options off. - - - - The 'From' value cannot be greater than the 'To' value. - - - - OK - - - - Automatic - - - - - QPrintPreviewDialog - - Page Setup - - - - %1% - - - - Print Preview - - - - Next page - - - - Previous page - - - - First page - - - - Last page - - - - Fit width - - - - Fit page - - - - Zoom in - - - - Zoom out - - - - Portrait - - - - Landscape - - - - Show single page - - - - Show facing pages - - - - Show overview of all pages - - - - Print - - - - Page setup - - - - Export to PDF - - - - - QPrintPropertiesDialog - - Printer Properties - - - - Job Options - - - - - QPrintPropertiesWidget - - Form - - - - Page - - - - - QPrintSettingsOutput - - Form - - - - Copies - - - - Print range - - - - Print all - - - - Pages from - - - - to - - - - Current Page - - - - Selection - - - - Page Set: - - - - Output Settings - - - - Copies: - - - - Collate - - - - Reverse - - - - Options - - - - Color Mode - - - - Color - - - - Grayscale - - - - Duplex Printing - - - - None - - - - Long side - - - - Short side - - - - - QPrintWidget - - Form - - - - Printer - - - - &Name: - - - - P&roperties - - - - Location: - - - - Preview - - - - Type: - - - - Output &file: - - - - ... - - - - - QProcess - - Process failed to start - - - - Process crashed - - - - Process operation timed out - - - - Error reading from process - - - - Error writing to process - - - - No program defined - - - - Could not open input redirection for reading - - - - Resource error (fork failure): %1 - - - - Could not open output redirection for writing - - - - Process failed to start: %1 - - - - - QProgressDialog - - Cancel - - - - - QPushButton - - Hello world! - - - - Hello %n world(s)! - - - - - - - It's a small world - - - - - QQnxFileDialogHelper - - All files (*.*) - - - - - QQnxFilePicker - - Pick a file - - - - - QRegExp - - no error occurred - - - - disabled feature used - - - - bad char class syntax - - - - bad lookahead syntax - - - - lookbehinds not supported, see QTBUG-2371 - - - - bad repetition syntax - - - - invalid octal value - - - - missing left delim - - - - unexpected end - - - - met internal limit - - - - invalid interval - - - - invalid category - - - - - QRegularExpression - - no error - - - - \ at end of pattern - - - - \c at end of pattern - - - - unrecognized character follows \ - - - - numbers out of order in {} quantifier - - - - number too big in {} quantifier - - - - missing terminating ] for character class - - - - invalid escape sequence in character class - - - - range out of order in character class - - - - nothing to repeat - - - - internal error: unexpected repeat - - - - unrecognized character after (? or (?- - - - - POSIX named classes are supported only within a class - - - - missing ) - - - - reference to non-existent subpattern - - - - erroffset passed as NULL - - - - unknown option bit(s) set - - - - missing ) after comment - - - - regular expression is too large - - - - failed to get memory - - - - unmatched parentheses - - - - internal error: code overflow - - - - unrecognized character after (?< - - - - lookbehind assertion is not fixed length - - - - malformed number or name after (?( - - - - conditional group contains more than two branches - - - - assertion expected after (?( - - - - (?R or (?[+-]digits must be followed by ) - - - - unknown POSIX class name - - - - POSIX collating elements are not supported - - - - this version of PCRE is not compiled with PCRE_UTF8 support - - - - character value in \x{...} sequence is too large - - - - invalid condition (?(0) - - - - \C not allowed in lookbehind assertion - - - - PCRE does not support \L, \l, \N{name}, \U, or \u - - - - number after (?C is > 255 - - - - closing ) for (?C expected - - - - recursive call could loop indefinitely - - - - unrecognized character after (?P - - - - syntax error in subpattern name (missing terminator) - - - - two named subpatterns have the same name - - - - invalid UTF-8 string - - - - support for \P, \p, and \X has not been compiled - - - - malformed \P or \p sequence - - - - unknown property name after \P or \p - - - - subpattern name is too long (maximum 32 characters) - - - - too many named subpatterns (maximum 10000) - - - - octal value is greater than \377 (not in UTF-8 mode) - - - - internal error: overran compiling workspace - - - - internal error: previously-checked referenced subpattern not found - - - - DEFINE group contains more than one branch - - - - repeating a DEFINE group is not allowed - - - - inconsistent NEWLINE options - - - - \g is not followed by a braced, angle-bracketed, or quoted name/number or by a plain number - - - - a numbered reference must not be zero - - - - an argument is not allowed for (*ACCEPT), (*FAIL), or (*COMMIT) - - - - (*VERB) not recognized - - - - number is too big - - - - subpattern name expected - - - - digit expected after (?+ - - - - ] is an invalid data character in JavaScript compatibility mode - - - - different names for subpatterns of the same number are not allowed - - - - (*MARK) must have an argument - - - - this version of PCRE is not compiled with PCRE_UCP support - - - - \c must be followed by an ASCII character - - - - \k is not followed by a braced, angle-bracketed, or quoted name - - - - internal error: unknown opcode in find_fixedlength() - - - - \N is not supported in a class - - - - too many forward references - - - - disallowed Unicode code point (>= 0xd800 && <= 0xdfff) - - - - invalid UTF-16 string - - - - name is too long in (*MARK), (*PRUNE), (*SKIP), or (*THEN) - - - - character value in \u.... sequence is too large - - - - invalid UTF-32 string - - - - setting UTF is disabled by the application - - - - non-hex character in \x{} (closing brace missing?) - - - - non-octal character in \o{} (closing brace missing?) - - - - missing opening brace after \o - - - - parentheses are too deeply nested - - - - invalid range in character class - - - - group name must start with a non-digit - - - - parentheses are too deeply nested (stack check) - - - - digits missing in \x{} or \o{} - - - - - QSQLite2Driver - - Error opening database - - - - Unable to begin transaction - - - - Unable to commit transaction - - - - Unable to rollback transaction - - - - - QSQLite2Result - - Unable to fetch results - - - - Unable to execute statement - - - - - QSQLiteDriver - - Error opening database - - - - Error closing database - - - - Unable to begin transaction - - - - Unable to commit transaction - - - - Unable to rollback transaction - - - - - QSQLiteResult - - Unable to fetch row - - - - No query - - - - Unable to execute statement - - - - Unable to execute multiple statements at a time - - - - Unable to reset statement - - - - Unable to bind parameters - - - - Parameter count mismatch - - - - - QSaveFile - - Existing file %1 is not writable - - - - Filename refers to a directory - - - - Writing canceled by application - - - - - QScrollBar - - Scroll here - - - - Left edge - - - - Top - - - - Right edge - - - - Bottom - - - - Page left - - - - Page up - - - - Page right - - - - Page down - - - - Scroll left - - - - Scroll up - - - - Scroll right - - - - Scroll down - - - - - QSharedMemory - - %1: unable to set key on lock - - - - %1: create size is less then 0 - - - - %1: unable to lock - - - - %1: unable to unlock - - - - %1: key is empty - - - - %1: bad name - - - - %1: UNIX key file doesn't exist - - - - %1: ftok failed - - - - %1: unable to make key - - - - %1: system-imposed size restrictions - - - - %1: not attached - - - - %1: permission denied - - - - %1: already exists - - - - %1: doesn't exist - - - - %1: out of resources - - - - %1: unknown error %2 - - - - %1: invalid size - - - - %1: key error - - - - %1: size query failed - - - - - QShortcut - - Space - This and all following "incomprehensible" strings in QShortcut context are key names. Please use the localized names appearing on actual keyboards or whatever is commonly used. - - - - Esc - - - - Tab - - - - Backtab - - - - Backspace - - - - Return - - - - Enter - - - - Ins - - - - Del - - - - Pause - - - - Print - - - - SysReq - - - - Home - - - - End - - - - Left - - - - Up - - - - Right - - - - Down - - - - PgUp - - - - PgDown - - - - CapsLock - - - - NumLock - - - - ScrollLock - - - - Menu - - - - Help - - - - Back - - - - Forward - - - - Stop - - - - Refresh - - - - Volume Down - - - - Volume Mute - - - - Volume Up - - - - Bass Boost - - - - Bass Up - - - - Bass Down - - - - Treble Up - - - - Treble Down - - - - Media Play - - - - Media Stop - - - - Media Previous - - - - Media Next - - - - Media Record - - - - Media Pause - Media player pause button - - - - Toggle Media Play/Pause - Media player button to toggle between playing and paused - - - - Home Page - - - - Favorites - - - - Search - - - - Standby - - - - Open URL - - - - Launch Mail - - - - Launch Media - - - - Launch (0) - - - - Launch (1) - - - - Launch (2) - - - - Launch (3) - - - - Launch (4) - - - - Launch (5) - - - - Launch (6) - - - - Launch (7) - - - - Launch (8) - - - - Launch (9) - - - - Launch (A) - - - - Launch (B) - - - - Launch (C) - - - - Launch (D) - - - - Launch (E) - - - - Launch (F) - - - - Monitor Brightness Up - - - - Monitor Brightness Down - - - - Keyboard Light On/Off - - - - Keyboard Brightness Up - - - - Keyboard Brightness Down - - - - Power Off - - - - Wake Up - - - - Eject - - - - Screensaver - - - - WWW - - - - Sleep - - - - LightBulb - - - - Shop - - - - History - - - - Add Favorite - - - - Hot Links - - - - Adjust Brightness - - - - Finance - - - - Community - - - - Media Rewind - - - - Back Forward - - - - Application Left - - - - Application Right - - - - Book - - - - CD - - - - Calculator - - - - Clear - - - - Clear Grab - - - - Close - - - - Copy - - - - Cut - - - - Display - - - - DOS - - - - Documents - - - - Spreadsheet - - - - Browser - - - - Game - - - - Go - - - - iTouch - - - - Logoff - - - - Market - - - - Meeting - - - - Keyboard Menu - - - - Menu PB - - - - My Sites - - - - News - - - - Home Office - - - - Option - - - - Paste - - - - Phone - - - - Reply - - - - Reload - - - - Rotate Windows - - - - Rotation PB - - - - Rotation KB - - - - Save - - - - Send - - - - Spellchecker - - - - Split Screen - - - - Support - - - - Task Panel - - - - Terminal - - - - Tools - - - - Travel - - - - Video - - - - Word Processor - - - - XFer - - - - Zoom In - - - - Zoom Out - - - - Away - - - - Messenger - - - - WebCam - - - - Mail Forward - - - - Pictures - - - - Music - - - - Battery - - - - Bluetooth - - - - Wireless - - - - Ultra Wide Band - - - - Media Fast Forward - - - - Audio Repeat - - - - Audio Random Play - - - - Subtitle - - - - Audio Cycle Track - - - - Time - - - - Hibernate - - - - View - - - - Top Menu - - - - Power Down - - - - Suspend - - - - Microphone Mute - - - - Red - - - - Green - - - - Yellow - - - - Blue - - - - Channel Up - - - - Channel Down - - - - Guide - - - - Info - - - - Settings - - - - Microphone Volume Up - - - - Microphone Volume Down - - - - New - - - - Open - - - - Find - - - - Undo - - - - Redo - - - - Print Screen - - - - Page Up - - - - Page Down - - - - Caps Lock - - - - Num Lock - - - - Number Lock - - - - Scroll Lock - - - - Insert - - - - Delete - - - - Escape - - - - System Request - - - - Select - - - - Yes - - - - No - - - - Context1 - - - - Context2 - - - - Context3 - - - - Context4 - - - - Call - Button to start a call (note: a separate button is used to end the call) - - - - Hangup - Button to end a call (note: a separate button is used to start the call) - - - - Toggle Call/Hangup - Button that will hang up if we're in call, or make a call if we're not. - - - - Flip - - - - Voice Dial - Button to trigger voice dialing - - - - Last Number Redial - Button to redial the last number called - - - - Camera Shutter - Button to trigger the camera shutter (take a picture) - - - - Camera Focus - Button to focus the camera - - - - Kanji - - - - Muhenkan - - - - Henkan - - - - Romaji - - - - Hiragana - - - - Katakana - - - - Hiragana Katakana - - - - Zenkaku - - - - Hankaku - - - - Zenkaku Hankaku - - - - Touroku - - - - Massyo - - - - Kana Lock - - - - Kana Shift - - - - Eisu Shift - - - - Eisu toggle - - - - Code input - - - - Multiple Candidate - - - - Previous Candidate - - - - Hangul - - - - Hangul Start - - - - Hangul End - - - - Hangul Hanja - - - - Hangul Jamo - - - - Hangul Romaja - - - - Hangul Jeonja - - - - Hangul Banja - - - - Hangul PreHanja - - - - Hangul PostHanja - - - - Hangul Special - - - - Cancel - - - - Printer - - - - Execute - - - - Play - - - - Zoom - - - - Exit - - - - Touchpad Toggle - - - - Touchpad On - - - - Touchpad Off - - - - Ctrl - - - - Shift - - - - Alt - - - - Meta - - - - Num - - - - + - - - - F%1 - - - - - QSocks5SocketEngine - - Connection to proxy refused - - - - Connection to proxy closed prematurely - - - - Proxy host not found - - - - Connection to proxy timed out - - - - Proxy authentication failed - - - - Proxy authentication failed: %1 - - - - SOCKS version 5 protocol error - - - - General SOCKSv5 server failure - - - - Connection not allowed by SOCKSv5 server - - - - TTL expired - - - - SOCKSv5 command not supported - - - - Address type not supported - - - - Unknown SOCKSv5 proxy error code 0x%1 - - - - Network operation timed out - - - - - QSpiAccessibleBridge - - invalid role - Role of an accessible object - the object is in an invalid state or could not be constructed - - - - title bar - Role of an accessible object - - - - menu bar - Role of an accessible object - - - - scroll bar - Role of an accessible object - - - - grip - Role of an accessible object - the grip is usually used for resizing another object - - - - sound - Role of an accessible object - - - - cursor - Role of an accessible object - - - - text caret - Role of an accessible object - - - - alert message - Role of an accessible object - - - - frame - Role of an accessible object: a window with frame and title ----------- -Role of an accessible object - - - - filler - Role of an accessible object - - - - popup menu - Role of an accessible object - - - - menu item - Role of an accessible object - - - - tool tip - Role of an accessible object - - - - application - Role of an accessible object - - - - document - Role of an accessible object - - - - panel - Role of an accessible object - - - - chart - Role of an accessible object - - - - dialog - Role of an accessible object - - - - separator - Role of an accessible object - - - - tool bar - Role of an accessible object - - - - status bar - Role of an accessible object - - - - table - Role of an accessible object - - - - column header - Role of an accessible object - part of a table - - - - row header - Role of an accessible object - part of a table - - - - column - Role of an accessible object - part of a table - - - - row - Role of an accessible object - part of a table - - - - cell - Role of an accessible object - part of a table - - - - link - Role of an accessible object - - - - help balloon - Role of an accessible object - - - - assistant - Role of an accessible object - a helper dialog - - - - list - Role of an accessible object - - - - list item - Role of an accessible object - - - - tree - Role of an accessible object - - - - tree item - Role of an accessible object - - - - page tab - Role of an accessible object - - - - property page - Role of an accessible object - - - - indicator - Role of an accessible object - - - - graphic - Role of an accessible object - - - - label - Role of an accessible object - - - - text - Role of an accessible object - - - - push button - Role of an accessible object - - - - check box - Role of an accessible object - - - - radio button - Role of an accessible object - - - - combo box - Role of an accessible object - - - - progress bar - Role of an accessible object - - - - dial - Role of an accessible object - - - - hotkey field - Role of an accessible object - - - - slider - Role of an accessible object - - - - spin box - Role of an accessible object - - - - canvas - Role of an accessible object - - - - animation - Role of an accessible object - - - - equation - Role of an accessible object - - - - button with drop down - Role of an accessible object - - - - button menu - Role of an accessible object - - - - button with drop down grid - Role of an accessible object - a button that expands a grid. - - - - space - Role of an accessible object - blank space between other objects. - - - - page tab list - Role of an accessible object - - - - clock - Role of an accessible object - - - - splitter - Role of an accessible object - - - - layered pane - Role of an accessible object - - - - web document - Role of an accessible object - - - - paragraph - Role of an accessible object - - - - section - Role of an accessible object - - - - color chooser - Role of an accessible object - - - - footer - Role of an accessible object - - - - form - Role of an accessible object - - - - heading - Role of an accessible object - - - - note - Role of an accessible object - - - - complementary content - Role of an accessible object - - - - unknown - Role of an accessible object - - - - - QSqlConnectionDialog - - No database driver selected - - - - Please select a database driver - - - - - QSqlConnectionDialogUi - - Connect... - - - - Connection settings - - - - &Username: - - - - D&river - - - - Default - - - - Database Name: - - - - &Hostname: - - - - P&ort: - - - - &Password: - - - - Us&e predefined in-memory database - - - - &OK - - - - &Cancel - - - - - QSslSocket - - Error when setting the elliptic curves (%1) - - - - Error creating SSL context (%1) - - - - unsupported protocol - - - - Invalid or empty cipher list (%1) - - - - Cannot provide a certificate with no key, %1 - - - - Error loading local certificate, %1 - - - - Error loading private key, %1 - - - - Private key does not certify public key, %1 - - - - OpenSSL version too old, need at least v1.0.2 - - - - No error - - - - The issuer certificate could not be found - - - - The certificate signature could not be decrypted - - - - The public key in the certificate could not be read - - - - The signature of the certificate is invalid - - - - The certificate is not yet valid - - - - The certificate has expired - - - - The certificate's notBefore field contains an invalid time - - - - The certificate's notAfter field contains an invalid time - - - - The certificate is self-signed, and untrusted - - - - The root certificate of the certificate chain is self-signed, and untrusted - - - - The issuer certificate of a locally looked up certificate could not be found - - - - No certificates could be verified - - - - One of the CA certificates is invalid - - - - The basicConstraints path length parameter has been exceeded - - - - The supplied certificate is unsuitable for this purpose - - - - The root CA certificate is not trusted for this purpose - - - - The root CA certificate is marked to reject the specified purpose - - - - The current candidate issuer certificate was rejected because its subject name did not match the issuer name of the current certificate - - - - The current candidate issuer certificate was rejected because its issuer name and serial number was present and did not match the authority key identifier of the current certificate - - - - The peer did not present any certificate - - - - The host name did not match any of the valid hosts for this certificate - - - - The peer certificate is blacklisted - - - - Unknown error - - - - The TLS/SSL connection has been closed - - - - Error creating SSL session, %1 - - - - Error creating SSL session: %1 - - - - Unable to init SSL Context: %1 - - - - Unable to write data: %1 - - - - Unable to decrypt data: %1 - - - - Error while reading: %1 - - - - Error during SSL handshake: %1 - - - - - QStandardPaths - - Desktop - - - - Documents - - - - Fonts - - - - Applications - - - - Music - - - - Movies - - - - Pictures - - - - Temporary Directory - - - - Home - - - - Cache - - - - Shared Data - - - - Runtime - - - - Configuration - - - - Shared Configuration - - - - Shared Cache - - - - Download - - - - Application Data - - - - Application Configuration - - - - - QStateMachine - - Missing initial state in compound state '%1' - - - - Missing default state in history state '%1' - - - - No common ancestor for targets and source of transition from state '%1' - - - - Unknown error - - - - - QSystemSemaphore - - %1: permission denied - - - - %1: already exists - - - - %1: does not exist - - - - %1: out of resources - - - - %1: unknown error %2 - - - - - QTDSDriver - - Unable to open connection - - - - Unable to use database - - - - - QTabBar - - Scroll Left - - - - Scroll Right - - - - - QTcpServer - - Operation on socket is not supported - - - - - QUndoGroup - - Undo %1 - - - - Undo - Default text for undo action - - - - Redo %1 - - - - Redo - Default text for redo action - - - - - QUndoModel - - <empty> - - - - - QUndoStack - - Undo %1 - - - - Undo - Default text for undo action - - - - Redo %1 - - - - Redo - Default text for redo action - - - - - QUnicodeControlCharacterMenu - - LRM Left-to-right mark - - - - RLM Right-to-left mark - - - - ZWJ Zero width joiner - - - - ZWNJ Zero width non-joiner - - - - ZWSP Zero width space - - - - LRE Start of left-to-right embedding - - - - RLE Start of right-to-left embedding - - - - LRO Start of left-to-right override - - - - RLO Start of right-to-left override - - - - PDF Pop directional formatting - - - - LRI Left-to-right isolate - - - - RLI Right-to-left isolate - - - - FSI First strong isolate - - - - PDI Pop directional isolate - - - - Insert Unicode control character - - - - - QWhatsThisAction - - What's This? - - - - - QWidget - - * - - - - - QWidgetTextControl - - &Undo - - - - &Redo - - - - Cu&t - - - - &Copy - - - - Copy &Link Location - - - - &Paste - - - - Delete - - - - Select All - - - - - QWindowsDirect2DIntegration - - Qt cannot load the direct2d platform plugin because the Direct2D version on this system is too old. The minimum system requirement for this platform plugin is Windows 7 SP1 with Platform Update. - -The minimum Direct2D version required is %1.%2.%3.%4. The Direct2D version on this system is %5.%6.%7.%8. - - - - Cannot load direct2d platform plugin - - - - - QWizard - - Go Back - - - - < &Back - - - - Continue - - - - &Next - - - - &Next > - - - - Commit - - - - Done - - - - &Finish - - - - Cancel - - - - Help - - - - &Help - - - - - QXml - - no error occurred - - - - error triggered by consumer - - - - unexpected end of file - - - - more than one document type definition - - - - error occurred while parsing element - - - - tag mismatch - - - - error occurred while parsing content - - - - unexpected character - - - - invalid name for processing instruction - - - - version expected while reading the XML declaration - - - - wrong value for standalone declaration - - - - encoding declaration or standalone declaration expected while reading the XML declaration - - - - standalone declaration expected while reading the XML declaration - - - - error occurred while parsing document type definition - - - - letter is expected - - - - error occurred while parsing comment - - - - error occurred while parsing reference - - - - internal general entity reference not allowed in DTD - - - - external parsed general entity reference not allowed in attribute value - - - - external parsed general entity reference not allowed in DTD - - - - unparsed entity reference in wrong context - - - - recursive entities - - - - error in the text declaration of an external entity - - - - - QXmlStream - - Extra content at end of document. - - - - Invalid entity value. - - - - Invalid XML character. - - - - Sequence ']]>' not allowed in content. - - - - Encountered incorrectly encoded content. - - - - Namespace prefix '%1' not declared - - - - Illegal namespace declaration. - - - - Attribute '%1' redefined. - - - - Unexpected character '%1' in public id literal. - - - - Invalid XML version string. - - - - Unsupported XML version. - - - - The standalone pseudo attribute must appear after the encoding. - - - - %1 is an invalid encoding name. - - - - Encoding %1 is unsupported - - - - Standalone accepts only yes or no. - - - - Invalid attribute in XML declaration. - - - - Premature end of document. - - - - Invalid document. - - - - Expected - - - - , but got ' - - - - Unexpected ' - - - - Expected character data. - - - - Recursive entity detected. - - - - Start tag expected. - - - - NDATA in parameter entity declaration. - - - - XML declaration not at start of document. - - - - %1 is an invalid processing instruction name. - - - - Invalid processing instruction name. - - - - %1 is an invalid PUBLIC identifier. - - - - Invalid XML name. - - - - Opening and ending tag mismatch. - - - - Entity '%1' not declared. - - - - Reference to unparsed entity '%1'. - - - - Reference to external entity '%1' in attribute value. - - - - Invalid character reference. - - - - - QueryPage - - Look for packages - - - - Name: - - - - Released after: - - - - Releases - - - - Upgrades - - - - Return up to - - - - results - - - - Return only the first result - - - - Start query - - - - - RSSListing - - Fetch - - - - Title - - - - Link - - - - RSS listing example - - - - - Receiver - - Listening for broadcasted messages - - - - &Quit - - - - Broadcast Receiver - - - - Received datagram: "%1" - - - - Listening for multicasted messages - - - - Multicast Receiver - - - - - RegExpDialog - - &Pattern: - - - - &Escaped Pattern: - - - - Regular expression v1 - - - - Regular expression v2 - - - - Wildcard - - - - Fixed string - - - - W3C Xml Schema 1.1 - - - - &Pattern Syntax: - - - - &Text: - - - - Case &Sensitive - - - - &Minimal - - - - Index of Match: - - - - Matched Length: - - - - Capture %1: - - - - Match: - - - - [A-Za-z_]+([A-Za-z_0-9]*) - - - - (10 + delta4) * 32 - - - - RegExp - - - - - RegisterPage - - Register Your Copy of <i>Super Product One</i>&trade; - - - - If you have an upgrade key, please fill in the appropriate field. - - - - N&ame: - - - - &Upgrade key: - - - - - RegularExpressionDialog - - QRegularExpression Example - - - - (\+?\d+)-(?<prefix>\d+)-(?<number>\w+) - - - - My office number is +43-152-0123456, my mobile is 001-41-255512 instead. - - - - Valid - - - - <no name> - - - - Invalid: syntax error at position %1 (%2) - - - - <h3>Regular expression and text input</h3> - - - - &Pattern: - - - - Copy to clipboard - - - - &Escaped pattern: - - - - &Subject text: - - - - Case insensitive (/i) - - - - Dot matches everything (/s) - - - - Multiline (/m) - - - - Extended pattern (/x) - - - - Inverted greediness - - - - Don't capture - - - - Use unicode properties (/u) - - - - Optimize on first usage - - - - Don't automatically optimize - - - - Pattern options: - - - - Match &offset: - - - - Normal - - - - Partial prefer complete - - - - Partial prefer first - - - - No match - - - - Match &type: - - - - Don't check subject string - - - - Anchored match - - - - Match options: - - - - <h3>Match information</h3> - - - - Match index - - - - Group index - - - - Captured string - - - - Match details: - - - - <h3>Regular expression information</h3> - - - - Pattern status: - - - - Index - - - - Named group - - - - Named groups: - - - - - RenderArea - - Qt by -The Qt Company - - - - x - - - - y - - - - - RenderOptionsDialog - - Options (double click to flip) - - - - Dynamic cube map - - - - Texture: - - - - Shader: - - - - - RenderWindow - - makeCurrent() failed - - - - - Screenshot - - Options - - - - s - - - - Hide This Window - - - - Screenshot Delay: - - - - New Screenshot - - - - Save Screenshot - - - - Quit - - - - Screenshot - - - - /untitled. - - - - Save As - - - - Save Error - - - - The image could not be saved to "%1". - - - - - Sender - - Ready to broadcast datagrams on port 45454 - - - - &Start - - - - &Quit - - - - Broadcast Sender - - - - Now broadcasting datagram %1 - - - - Ready to multicast datagrams to group %1 on port 45454 - - - - TTL for multicast datagrams: - - - - Multicast Sender - - - - Now sending datagram %1 - - - - - Server - - Quit - - - - Fortune Server - - - - Unable to start the server: %1. - - - - The server is running. -Run the Fortune Client example now. - - - - You've been leading a dog's life. Stay off the furniture. - - - - You've got to think about tomorrow. - - - - You will be surprised by a loud noise. - - - - You will feel hungry again in another hour. - - - - You might have mail. - - - - You cannot kill time without injuring eternity. - - - - Computers are not intelligent. They only think they are. - - - - Opening network session. - - - - The server is running on - -IP: %1 -port: %2 - -Run the Fortune Client example now. - - - - - SessionWidget - - Session Details - - - - Session ID: - - - - Session State: - - - - Invalid - - - - Configuration: - - - - Bearer: - - - - Interface Name: - - - - Interface GUID: - - - - Last Error: - - - - Error String: - - - - 0 - - - - Active Time: - - - - 0 seconds - - - - Open - - - - Blocking Open - - - - Close - - - - Stop - - - - %1 (%2) - - - - Not Available - - - - Connecting - - - - Connected - - - - Closing - - - - Disconnected - - - - Roaming - - - - Unknown - - - - Closed - - - - - SettingsTree - - Setting - - - - Type - - - - Value - - - - - ShapedClock - - E&xit - - - - Ctrl+Q - - - - Drag the clock with the left mouse button. -Use the right mouse button to open a context menu. - - - - Shaped Analog Clock - - - - - SortingBox - - New Circle - - - - New Square - - - - New Triangle - - - - Tool Tips - - - - Circle - - - - Square - - - - Triangle - - - - Circle <%1> - - - - Square <%1> - - - - Triangle <%1> - - - - - SplashItem - - Welcome to the Pad Navigator Example. You can use the keyboard arrows to navigate the icons, and press enter to activate an item. Press any key to begin. - - - - - SpreadSheet - - Spreadsheet - - - - Sum - - - - &Add - - - - &Subtract - - - - &Multiply - - - - &Divide - - - - Font... - - - - Background &Color... - - - - Clear - - - - About Spreadsheet - - - - E&xit - - - - &Print - - - - &File - - - - &Cell - - - - &Help - - - - Cell: (%1) - - - - Cancel - - - - OK - - - - Sum cells - - - - First cell: - - - - Last cell: - - - - Output to: - - - - sum %1 %2 - - - - Cell 1 - - - - Cell 2 - - - - %1 %2 %3 - - - - Addition - - - - Subtraction - - - - Multiplication - - - - Division - - - - - SslClient - - &lt;not connected&gt; - - - - <none> - - - - Display encryption details. - - - - Connection error - - - - - SslErrors - - Unable To Validate The Connection - - - - <html><head><meta name="qrichtext" content="1" /><style type="text/css"> -p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'Sans Serif'; font-size:9pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600; color:#ff0000;">Warning</span><span style=" color:#ff0000;">:</span><span style=" color:#000000;"> One or more errors with this connection prevent validating the authenticity of the host you are connecting to. Please review the following list of errors, and click </span><span style=" color:#000000;">Ignore</span><span style=" color:#000000;"> to continue, or </span><span style=" color:#000000;">Cancel</span><span style=" color:#000000;"> to abort the connection.</span></p></body></html> - - - - View Certificate Chain - - - - Ignore - - - - Cancel - - - - - StorageModel - - 0 b - - - - %1 %2 - this should expand to "1.23 GB" - - - - Root path : %1 -Name: %2 -Display Name: %3 -Device: %4 -FileSystem: %5 -Total size: %6 -Free size: %7 -Available size: %8 -Is Ready: %9 -Is Read-only: %10 -Is Valid: %11 -Is Root: %12 - - - - true - - - - false - - - - Root path - - - - Volume Name - - - - Device - - - - File system - - - - Total - - - - Free - - - - Available - - - - Ready - - - - Read-only - - - - Valid - - - - - StyleSheetEditor - - Style Editor - - - - Default - - - - Coffee - - - - Pagefold - - - - Style: - - - - &Apply - - - - Style Sheet: - - - - - StyleWidget - - Form - - - - Styles - - - - Transp. - - - - Blue - - - - Khaki - - - - None - - - - Value: - - - - Show - - - - Enable - - - - Close - - - - - StyleWindow - - Big Red Button - - - - A simple style button - - - - Style Plugin Example - - - - - TabDialog - - General - - - - Permissions - - - - Applications - - - - Tab Dialog - - - - - TableEditor - - ID - - - - First name - - - - Last name - - - - Submit - - - - &Revert - - - - Quit - - - - Cached Table - - - - The database reported an error: %1 - - - - - TableModel - - Name - - - - Address - - - - - TabletCanvas - - This input device is not supported by the example. - - - - Unknown tablet device - treating as stylus - - - - - TestWidget - - But soft, what light through yonder window breaks? / It is the east, and Juliet is the sun. / Arise, fair sun, and kill the envious moon, / Who is already sick and pale with grief / That thou, her maid, art far more fair than she. - - - - To-morrow, and to-morrow, and to-morrow, / Creeps in this petty pace from day to day, / To the last syllable of recorded time; / And all our yesterdays have lighted fools / The way to dusty death. Out, out, brief candle! / Life's but a walking shadow, a poor player, / That struts and frets his hour upon the stage, / And then is heard no more. It is a tale / Told by an idiot, full of sound and fury, / Signifying nothing. - - - - Feeling lucky, punk? - - - - Switch text - - - - Exit - - - - Elided - - - - - TetrixBoard - - Pause - - - - - TetrixWindow - - &Start - - - - &Quit - - - - &Pause - - - - NEXT - - - - LEVEL - - - - SCORE - - - - LINES REMOVED - - - - Tetrix - - - - - TextEdit - - Help - - - - About - - - - About &Qt - - - - File Actions - - - - &File - - - - &New - - - - &Open... - - - - &Save - - - - Save &As... - - - - &Print... - - - - Print Preview... - - - - &Export PDF... - - - - &Quit - - - - Edit Actions - - - - &Edit - - - - &Undo - - - - &Redo - - - - Cu&t - - - - &Copy - - - - &Paste - - - - Format Actions - - - - F&ormat - - - - &Bold - - - - &Italic - - - - &Underline - - - - &Left - - - - C&enter - - - - &Right - - - - &Justify - - - - &Color... - - - - The document has been modified. -Do you want to save your changes? - - - - %1[*] - %2 - - - - Open File... - - - - Opened "%1" - - - - Could not open "%1" - - - - Wrote "%1" - - - - Could not write to file "%1" - - - - Save as... - - - - Print Document - - - - Export PDF - - - - Exported "%1" - - - - This example demonstrates Qt's rich text editing facilities in action, providing an example document for you to experiment with. - - - - This TextEdit provides autocompletions for words that have more than 3 characters. You can trigger autocompletion using - - - - - ToolBar - - Order Items in Tool Bar - - - - Randomize Items in Tool Bar - - - - Add Spin Box - - - - Remove Spin Box - - - - Movable - - - - Allow on Left - - - - Allow on Right - - - - Allow on Top - - - - Allow on Bottom - - - - Place on Left - - - - Place on Right - - - - Place on Top - - - - Place on Bottom - - - - Insert break - - - - - UpdatePage - - Package selection - - - - Update system - - - - Update applications - - - - Update documentation - - - - Existing packages - - - - Qt - - - - QSA - - - - Teambuilder - - - - Start update - - - - - ValidatorsForm - - Validators - - - - QIntValidator - - - - Min: - - - - Max: - - - - editingFinished() - - - - QDoubleValidator - - - - Format: - - - - Standard - - - - Scientific - - - - Decimals: - - - - Quit - - - - - View - - 0 - - - - Pointer Mode - - - - Select - - - - Drag - - - - Antialiasing - - - - OpenGL - - - - - Widget - - Context &version: - - - - Create context - - - - Profile - - - - Options - - - - Renderable type - - - - Failed to create context - - - - OpenGL version: %1.%2 - - - - Profile: %1 - - - - Options: %1 - - - - Renderable type: %1 - - - - Depth buffer size: %1 - - - - Stencil buffer size: %1 - - - - Samples: %1 - - - - Red buffer size: %1 - - - - Green buffer size: %1 - - - - Blue buffer size: %1 - - - - Alpha buffer size: %1 - - - - Swap interval: %1 - - - - *** Context information *** - - - - Vendor: %1 - - - - Renderer: %1 - - - - OpenGL version: %1 - - - - GLSL version: %1 - - - - -*** QSurfaceFormat from context *** - - - - -*** QSurfaceFormat from window surface *** - - - - -*** Qt build information *** - - - - Qt OpenGL configuration: %1 - - - - Qt OpenGL library handle: %1 - - - - Found %1 extensions: - - - - An error has occurred: -%1 - - - - - WidgetGallery - - &Style: - - - - &Use style's standard palette - - - - &Disable widgets - - - - Styles - - - - Group 1 - - - - Radio button 1 - - - - Radio button 2 - - - - Radio button 3 - - - - Tri-state check box - - - - Group 2 - - - - Default Push Button - - - - Toggle Push Button - - - - Flat Push Button - - - - Twinkle, twinkle, little star, -How I wonder what you are. -Up above the world so high, -Like a diamond in the sky. -Twinkle, twinkle, little star, -How I wonder what you are! - - - - - &Table - - - - Text &Edit - - - - Group 3 - - - - - Window - - &Load image... - - - - &Stop - - - - Queued Custom Type - - - - Open Image - - - - Image files (%1) - - - - &Send message - - - - Custom Type Sending - - - - Na&me: - - - - &Address: - - - - &Type: - - - - &Next - - - - &Previous - - - - SQL Widget Mapper - - - - Cannot open database - - - - Unable to establish a database connection. -This example needs SQLite support. Please read the Qt SQL driver documentation for information how to build it. - - - - Systray - - - - The program will keep running in the system tray. To terminate the program, choose <b>Quit</b> in the context menu of the system tray entry. - - - - Sorry, I already gave what help I could. -Maybe you should try asking a human? - - - - Tray Icon - - - - Bad - - - - Heart - - - - Trash - - - - Show icon - - - - Balloon Message - - - - Type: - - - - None - - - - Information - - - - Warning - - - - Critical - - - - Duration: - - - - (some systems might ignore this hint) - - - - Title: - - - - Cannot connect to network - - - - Body: - - - - Don't believe me. Honestly, I don't have a clue. -Click this balloon for details. - - - - Show Message - - - - Mi&nimize - - - - Ma&ximize - - - - &Restore - - - - &Quit - - - - &Browse... - - - - &Find - - - - * - - - - Named: - - - - Containing text: - - - - In directory: - - - - Find Files - - - - &Cancel - - - - Searching file number %1 of %n... - - - - - - - %1 KB - - - - %n file(s) found (Double click on a file to open it) - - - - - - - Filename - - - - Size - - - - Basic Graphics Layouts Example - - - - Case sensitive sorting - - - - Case sensitive filter - - - - &Filter pattern: - - - - Regular expression - - - - Wildcard - - - - Fixed string - - - - Filter &syntax: - - - - Subject - - - - Sender - - - - Date - - - - Filter &column: - - - - Original Model - - - - Sorted/Filtered Model - - - - Basic Sort/Filter Model - - - - Alice - - - - Neptun - - - - Ferdinand - - - - Name - - - - Hair Color - - - - Color Editor Factory - - - - Delegate Widget Mapper - - - - Home - - - - Work - - - - Other - - - - F&rom: - - - - &To: - - - - Custom Sort/Filter Model - - - - &Directory: - - - - Fetch More Example - - - - %1 items added. - - - - A&ge (in years): - - - - Simple Widget Mapper - - - - Central widget - - - - Border Layout - - - - Short - - - - Longer - - - - Different text - - - - More text - - - - Even longer button text - - - - Flow Layout - - - - Polygon - - - - Rectangle - - - - Rounded Rectangle - - - - Ellipse - - - - Pie - - - - Chord - - - - Path - - - - Line - - - - Polyline - - - - Arc - - - - Points - - - - Text - - - - Pixmap - - - - &Shape: - - - - 0 (cosmetic pen) - - - - Pen &Width: - - - - Solid - - - - Dash - - - - Dot - - - - Dash Dot - - - - Dash Dot Dot - - - - &Pen Style: - - - - Flat - - - - Square - - - - Round - - - - Pen &Cap: - - - - Miter - - - - Bevel - - - - Pen &Join: - - - - Linear Gradient - - - - Radial Gradient - - - - Conical Gradient - - - - Texture - - - - Horizontal - - - - Vertical - - - - Cross - - - - Backward Diagonal - - - - Forward Diagonal - - - - Diagonal Cross - - - - Dense 1 - - - - Dense 2 - - - - Dense 3 - - - - Dense 4 - - - - Dense 5 - - - - Dense 6 - - - - Dense 7 - - - - &Brush: - - - - Options: - - - - &Antialiasing - - - - &Transformations - - - - Basic Drawing - - - - Aliased - - - - Antialiased - - - - Int - - - - Float - - - - Concentric Circles - - - - Qt - - - - Odd Even - - - - Winding - - - - Fill &Rule: - - - - &Fill Gradient: - - - - to - - - - &Pen Width: - - - - Pen &Color: - - - - &Rotation Angle: - - - - Painter Paths - - - - Clock - - - - House - - - - Truck - - - - No transformation - - - - Rotate by 60° - - - - Scale to 75% - - - - Translate by (50, 50) - - - - Transformations - - - - Calendar Widget - - - - Bold - - - - Italic - - - - Green - - - - Preview - - - - General Options - - - - &Locale - - - - Sunday - - - - Monday - - - - Tuesday - - - - Wednesday - - - - Thursday - - - - Friday - - - - Saturday - - - - Wee&k starts on: - - - - Single selection - - - - &Selection mode: - - - - &Grid - - - - &Navigation bar - - - - Single letter day names - - - - Short day names - - - - &Horizontal header: - - - - ISO week numbers - - - - &Vertical header: - - - - Dates - - - - &Minimum Date: - - - - &Current Date: - - - - Ma&ximum Date: - - - - Text Formats - - - - Black - - - - &Weekday color: - - - - Red - - - - Week&end color: - - - - Plain - - - - &Header text: - - - - &First Friday in blue - - - - May &1 in red - - - - Blue - - - - Magenta - - - - Group Boxes - - - - Exclusive Radio Buttons - - - - &Radio button 1 - - - - R&adio button 2 - - - - Ra&dio button 3 - - - - E&xclusive Radio Buttons - - - - Rad&io button 1 - - - - Radi&o button 2 - - - - Radio &button 3 - - - - Ind&ependent checkbox - - - - Non-Exclusive Checkboxes - - - - &Checkbox 1 - - - - C&heckbox 2 - - - - Tri-&state button - - - - &Push Buttons - - - - &Normal Button - - - - &Toggle Button - - - - &Flat Button - - - - Pop&up Button - - - - &First Item - - - - &Second Item - - - - &Third Item - - - - F&ourth Item - - - - Submenu - - - - Popup Submenu - - - - Item 1 - - - - Item 2 - - - - Item 3 - - - - Echo - - - - Mode: - - - - Normal - - - - Password - - - - PasswordEchoOnEdit - - - - No Echo - - - - Validator - - - - No validator - - - - Integer validator - - - - Double validator - - - - Alignment - - - - Left - - - - Centered - - - - Right - - - - Input mask - - - - No mask - - - - Phone number - - - - ISO date - - - - License key - - - - Access - - - - Read-only: - - - - False - - - - True - - - - Line Edits - - - - Controls - - - - Sliders - - - - Minimum value: - - - - Maximum value: - - - - Current value: - - - - Inverted appearance - - - - Inverted key bindings - - - - Horizontal slider-like widgets - - - - Vertical slider-like widgets - - - - Spin Boxes - - - - Spinboxes - - - - Enter a value between %1 and %2: - - - - Enter a zoom value between %1 and %2: - - - - Automatic - - - - Enter a price between %1 and %2: - - - - Show group separator - - - - Date and time spin boxes - - - - Appointment date (between %0 and %1): - - - - Appointment time (between %0 and %1): - - - - Format string for the meeting date and time: - - - - Meeting date (between %0 and %1): - - - - Meeting time (between %0 and %1): - - - - Double precision spinboxes - - - - Number of decimal places to show: - - - - Enter a scale factor between %1 and %2: - - - - No scaling - - - - 2D Painting on Native and OpenGL Widgets - - - - Native - - - - OpenGL - - - - Undock - - - - Hello GL - - - - Dock - - - - Cannot dock - - - - Main window already closed - - - - Main window already occupied - - - - Textures - - - - - XFormWidget - - Affine Transformations - - - - Rotate - - - - Scale - - - - Shear - - - - Type - - - - Vector Image - - - - Pixmap - - - - Text - - - - Reset Transform - - - - Animate - - - - Show Source - - - - Use OpenGL - - - - What's This? - - - - - XbelTree - - Title - - - - Location - - - - DOM Bookmarks - - - - Parse error at line %1, column %2: -%3 - - - - The file is not an XBEL file. - - - - The file is not an XBEL version 1.0 file. - - - - - XmlStreamLint - - Usage: xmlstreamlint <path to XML file> - - - - - File %1 does not exist. - - - - - Failed to open file %1. - - - - - Failed to open stdout. - - - - Error: %1 in file %2 at line %3, column %4. - - - - - - childwidget - - Child widget - - - - Press me - - - - - contekst - - Intro - - - - Introx - - - - - nestedlayouts - - Query: - - - - Name - - - - Office - - - - Nested layouts - - - - - simpleanchorlayout - - QGraphicsAnchorLayout in use - - - - Simple Anchor Layout - - - - - toplevel - - Top-level widget - - - - - tst_QKeySequence - - Shift++ - - - - Ctrl++ - - - - Alt++ - - - - Meta++ - - - - Shift+,, Shift++ - - - - Shift+,, Ctrl++ - - - - Shift+,, Alt++ - - - - Shift+,, Meta++ - - - - Ctrl+,, Shift++ - - - - Ctrl+,, Ctrl++ - - - - Ctrl+,, Alt++ - - - - Ctrl+,, Meta++ - - - - - tst_QLocale - - tr_TR - - - - - windowlayout - - Name: - - - - Window layout - - - - diff --git a/res/qtbase_ru.qm b/res/qtbase_ru.qm deleted file mode 100644 index a11b7c7..0000000 Binary files a/res/qtbase_ru.qm and /dev/null differ diff --git a/res/qtbase_zh_TW.qm b/res/qtbase_zh_TW.qm deleted file mode 100644 index 559d80a..0000000 Binary files a/res/qtbase_zh_TW.qm and /dev/null differ diff --git a/res/savegame.png b/res/savegame.png deleted file mode 100644 index a2c61b0..0000000 Binary files a/res/savegame.png and /dev/null differ diff --git a/res/savegame.svgz b/res/savegame.svgz new file mode 100644 index 0000000..a1c8247 Binary files /dev/null and b/res/savegame.svgz differ diff --git a/res/src/AvatarAreaProject.xcf b/res/src/AvatarAreaProject.xcf index d09d1e4..98c204e 100644 Binary files a/res/src/AvatarAreaProject.xcf and b/res/src/AvatarAreaProject.xcf differ diff --git a/res/src/mainui.png b/res/src/mainui.png new file mode 100644 index 0000000..211e3cc Binary files /dev/null and b/res/src/mainui.png differ diff --git a/res/src/picture.png b/res/src/picture.png new file mode 100644 index 0000000..94d46a5 Binary files /dev/null and b/res/src/picture.png differ diff --git a/res/src/prop.png b/res/src/prop.png new file mode 100644 index 0000000..7acd21c Binary files /dev/null and b/res/src/prop.png differ diff --git a/res/template.g5e b/res/template.g5e index 2700af1..c74ba7f 100644 Binary files a/res/template.g5e and b/res/template.g5e differ diff --git a/res/template.qrc b/res/template.qrc new file mode 100644 index 0000000..06dcf5a --- /dev/null +++ b/res/template.qrc @@ -0,0 +1,5 @@ + + + template.g5e + + diff --git a/res/tr_g5p.qrc b/res/tr_g5p.qrc index c8ab30f..b51e8fd 100644 --- a/res/tr_g5p.qrc +++ b/res/tr_g5p.qrc @@ -3,8 +3,9 @@ gta5sync_en_US.qm gta5sync_de.qm gta5sync_fr.qm + gta5sync_ko.qm gta5sync_ru.qm gta5sync_uk.qm gta5sync_zh_TW.qm - + diff --git a/res/watermark_1b.png b/res/watermark_1b.png new file mode 100644 index 0000000..c0a8adc Binary files /dev/null and b/res/watermark_1b.png differ diff --git a/res/watermark_2b.png b/res/watermark_2b.png new file mode 100644 index 0000000..0cf74b9 Binary files /dev/null and b/res/watermark_2b.png differ diff --git a/res/watermark_2r.png b/res/watermark_2r.png new file mode 100644 index 0000000..51aa4b2 Binary files /dev/null and b/res/watermark_2r.png differ diff --git a/res/xmr.str b/res/xmr.str new file mode 100644 index 0000000..131342e --- /dev/null +++ b/res/xmr.str @@ -0,0 +1 @@ +Monero \ No newline at end of file diff --git a/res/xmr.svgz b/res/xmr.svgz new file mode 100644 index 0000000..55c269a Binary files /dev/null and b/res/xmr.svgz differ diff --git a/tmext/TelemetryClassAuthenticator.cpp b/tmext/TelemetryClassAuthenticator.cpp index 6fb5375..fc523f7 100644 --- a/tmext/TelemetryClassAuthenticator.cpp +++ b/tmext/TelemetryClassAuthenticator.cpp @@ -1,5 +1,5 @@ /***************************************************************************** -* gta5sync GRAND THEFT AUTO V SYNC +* gta5view Grand Theft Auto V Profile Viewer * Copyright (C) 2018 Syping * * This program is free software: you can redistribute it and/or modify diff --git a/tmext/TelemetryClassAuthenticator.cpp.enc b/tmext/TelemetryClassAuthenticator.cpp.enc deleted file mode 100644 index 0458989..0000000 Binary files a/tmext/TelemetryClassAuthenticator.cpp.enc and /dev/null differ diff --git a/tmext/TelemetryClassAuthenticator.h b/tmext/TelemetryClassAuthenticator.h index d2dd840..4180029 100644 --- a/tmext/TelemetryClassAuthenticator.h +++ b/tmext/TelemetryClassAuthenticator.h @@ -1,5 +1,5 @@ /***************************************************************************** -* gta5sync GRAND THEFT AUTO V SYNC +* gta5view Grand Theft Auto V Profile Viewer * Copyright (C) 2018 Syping * * This program is free software: you can redistribute it and/or modify diff --git a/anpro/JSHighlighter.cpp b/uimod/JSHighlighter.cpp similarity index 71% rename from anpro/JSHighlighter.cpp rename to uimod/JSHighlighter.cpp index 6d3b93c..42a988c 100644 --- a/anpro/JSHighlighter.cpp +++ b/uimod/JSHighlighter.cpp @@ -1,6 +1,6 @@ /***************************************************************************** -* gta5sync GRAND THEFT AUTO V SYNC -* Copyright (C) 2017 Syping +* gta5view Grand Theft Auto V Profile Viewer +* Copyright (C) 2017-2020 Syping * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -17,7 +17,6 @@ *****************************************************************************/ #include "JSHighlighter.h" -#include JSHighlighter::JSHighlighter(QTextDocument *parent) : QSyntaxHighlighter(parent) @@ -31,34 +30,61 @@ JSHighlighter::JSHighlighter(QTextDocument *parent) : keywordPatterns << "\\btrue\\b" << "\\bfalse\\b"; for (QString pattern : keywordPatterns) { +#if QT_VERSION >= 0x050000 + rule.pattern = QRegularExpression(pattern); +#else rule.pattern = QRegExp(pattern); +#endif rule.format = keywordFormat; highlightingRules.append(rule); } QBrush doubleBrush(QColor::fromRgb(66, 137, 244)); doubleFormat.setForeground(doubleBrush); +#if QT_VERSION >= 0x050000 + rule.pattern = QRegularExpression("[+-]?\\d*\\.?\\d+"); +#else rule.pattern = QRegExp("[+-]?\\d*\\.?\\d+"); +#endif rule.format = doubleFormat; highlightingRules.append(rule); QBrush quotationBrush(QColor::fromRgb(66, 244, 104)); quotationFormat.setForeground(quotationBrush); +#if QT_VERSION >= 0x050000 + rule.pattern = QRegularExpression("\"[^\"]*\""); +#else rule.pattern = QRegExp("\"[^\"]*\""); +#endif rule.format = quotationFormat; highlightingRules.append(rule); QBrush objectBrush(QColor::fromRgb(255, 80, 80)); objectFormat.setForeground(objectBrush); +#if QT_VERSION >= 0x050000 + rule.pattern = QRegularExpression("\"[^\"]*\"(?=:)"); +#else rule.pattern = QRegExp("\"[^\"]*\"(?=:)"); +#endif rule.format = objectFormat; highlightingRules.append(rule); } void JSHighlighter::highlightBlock(const QString &text) { - for (HighlightingRule rule : highlightingRules) +#if QT_VERSION >= 0x050000 + for (const HighlightingRule &rule : qAsConst(highlightingRules)) +#else + for (const HighlightingRule &rule : highlightingRules) +#endif { +#if QT_VERSION >= 0x050000 + QRegularExpressionMatchIterator matchIterator = rule.pattern.globalMatch(text); + while (matchIterator.hasNext()) { + QRegularExpressionMatch match = matchIterator.next(); + setFormat(match.capturedStart(), match.capturedLength(), rule.format); + } +#else QRegExp expression(rule.pattern); int index = expression.indexIn(text); while (index >= 0) @@ -67,6 +93,7 @@ void JSHighlighter::highlightBlock(const QString &text) setFormat(index, length, rule.format); index = expression.indexIn(text, index + length); } +#endif } setCurrentBlockState(0); } diff --git a/anpro/JSHighlighter.h b/uimod/JSHighlighter.h similarity index 87% rename from anpro/JSHighlighter.h rename to uimod/JSHighlighter.h index 08a0eb5..d93bfa9 100644 --- a/anpro/JSHighlighter.h +++ b/uimod/JSHighlighter.h @@ -1,6 +1,6 @@ /***************************************************************************** -* gta5sync GRAND THEFT AUTO V SYNC -* Copyright (C) 2017 Syping +* gta5view Grand Theft Auto V Profile Viewer +* Copyright (C) 2017-2020 Syping * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -24,10 +24,15 @@ #include #include #include -#include #include #include +#if QT_VERSION >= 0x050000 +#include +#else +#include +#endif + class QTextDocument; class JSHighlighter : public QSyntaxHighlighter @@ -37,7 +42,11 @@ class JSHighlighter : public QSyntaxHighlighter public: struct HighlightingRule { +#if QT_VERSION >= 0x050000 + QRegularExpression pattern; +#else QRegExp pattern; +#endif QTextCharFormat format; }; QVector highlightingRules; diff --git a/uimod/UiModLabel.cpp b/uimod/UiModLabel.cpp index 27c6a7d..4a2c2d4 100644 --- a/uimod/UiModLabel.cpp +++ b/uimod/UiModLabel.cpp @@ -1,5 +1,5 @@ /***************************************************************************** -* gta5sync GRAND THEFT AUTO V SYNC +* gta5view Grand Theft Auto V Profile Viewer * Copyright (C) 2016-2017 Syping * * This program is free software: you can redistribute it and/or modify diff --git a/uimod/UiModLabel.h b/uimod/UiModLabel.h index 7d35e87..0988a4e 100644 --- a/uimod/UiModLabel.h +++ b/uimod/UiModLabel.h @@ -1,5 +1,5 @@ /***************************************************************************** -* gta5sync GRAND THEFT AUTO V SYNC +* gta5view Grand Theft Auto V Profile Viewer * Copyright (C) 2016-2017 Syping * * This program is free software: you can redistribute it and/or modify diff --git a/uimod/UiModWidget.cpp b/uimod/UiModWidget.cpp index b517463..990ce7f 100644 --- a/uimod/UiModWidget.cpp +++ b/uimod/UiModWidget.cpp @@ -1,5 +1,5 @@ /***************************************************************************** -* gta5sync GRAND THEFT AUTO V SYNC +* gta5view Grand Theft Auto V Profile Viewer * Copyright (C) 2017 Syping * * This program is free software: you can redistribute it and/or modify @@ -26,38 +26,43 @@ UiModWidget::UiModWidget(QWidget *parent) : QWidget(parent) { - filesMode = false; + filesDropEnabled = false; + imageDropEnabled = false; } UiModWidget::~UiModWidget() { } -void UiModWidget::setFilesMode(bool filesModeEnabled) +void UiModWidget::setFilesDropEnabled(bool enabled) { - filesMode = filesModeEnabled; + filesDropEnabled = enabled; +} + +void UiModWidget::setImageDropEnabled(bool enabled) +{ + imageDropEnabled = enabled; } void UiModWidget::dragEnterEvent(QDragEnterEvent *dragEnterEvent) { - if (filesMode && dragEnterEvent->mimeData()->hasUrls()) - { - QStringList pathList; - QList urlList = dragEnterEvent->mimeData()->urls(); + if (filesDropEnabled && dragEnterEvent->mimeData()->hasUrls()) { + QVector pathList; + const QList urlList = dragEnterEvent->mimeData()->urls(); - foreach(const QUrl ¤tUrl, urlList) - { - if (currentUrl.isLocalFile()) - { + for (const QUrl ¤tUrl : urlList) { + if (currentUrl.isLocalFile()) { pathList.append(currentUrl.toLocalFile()); } } - if (!pathList.isEmpty()) - { + if (!pathList.isEmpty()) { dragEnterEvent->acceptProposedAction(); } } + else if (imageDropEnabled && dragEnterEvent->mimeData()->hasImage()) { + dragEnterEvent->acceptProposedAction(); + } } void UiModWidget::dropEvent(QDropEvent *dropEvent) @@ -70,7 +75,7 @@ void UiModWidget::paintEvent(QPaintEvent *paintEvent) { Q_UNUSED(paintEvent) QStyleOption opt; - opt.init(this); + opt.initFrom(this); QPainter p(this); style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this); } diff --git a/uimod/UiModWidget.h b/uimod/UiModWidget.h index d30637d..469c000 100644 --- a/uimod/UiModWidget.h +++ b/uimod/UiModWidget.h @@ -1,5 +1,5 @@ /***************************************************************************** -* gta5sync GRAND THEFT AUTO V SYNC +* gta5view Grand Theft Auto V Profile Viewer * Copyright (C) 2017 Syping * * This program is free software: you can redistribute it and/or modify @@ -29,7 +29,8 @@ class UiModWidget : public QWidget Q_OBJECT public: UiModWidget(QWidget *parent = 0); - void setFilesMode(bool enabled); + void setFilesDropEnabled(bool enabled); + void setImageDropEnabled(bool enabled); ~UiModWidget(); protected: @@ -38,7 +39,8 @@ protected: void paintEvent(QPaintEvent *paintEvent); private: - bool filesMode; + bool filesDropEnabled; + bool imageDropEnabled; signals: void dropped(const QMimeData *mimeData); diff --git a/wrapper.h b/wrapper.h new file mode 100644 index 0000000..9ea0443 --- /dev/null +++ b/wrapper.h @@ -0,0 +1,30 @@ +/***************************************************************************** +* gta5view Grand Theft Auto V Profile Viewer +* Copyright (C) 2021 Syping +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +*****************************************************************************/ + +#ifndef WRAPPER_H +#define WRAPPER_H + +#if QT_VERSION < 0x050700 +#if __cplusplus > 201703L +#define qAsConst(x) std::as_const(x) +#else +#define qAsConst +#endif +#endif + +#endif // WRAPPER_H