Integrating latest 47acbe8
This commit is contained in:
@@ -11,3 +11,4 @@
|
||||
|
||||
add_subdirectory(detect_file_changes)
|
||||
add_subdirectory(commit_validation)
|
||||
add_subdirectory(project_manager)
|
||||
|
||||
@@ -3,43 +3,20 @@
|
||||
"3rdParty.txt": "#include",
|
||||
"AWS/AWSNativeSDK/1.7.167-az.2/**": "#include",
|
||||
"AWS/GameLift/3.4.0/**": "#include",
|
||||
"Blast/1.1.6-az.1/**": "#include",
|
||||
"benchmark/1.5.0/**": "#include",
|
||||
"cityhash/1.1-az.1/**": "#include",
|
||||
"civetweb/civetweb-20160922-az.2/**": "#include",
|
||||
"Clang/6.0.1-az/**": "#include",
|
||||
"DirectXShaderCompiler/1.0.1-az.1/**": "#include",
|
||||
"DirectXShaderCompiler/2020.08.07/**": "#include",
|
||||
"DirectXShaderCompiler/5.0.0-az/**": "#include",
|
||||
"dyad/0.2.0-17-amazon/**": "#include",
|
||||
"etc2comp/2017_04_24-az.2/**": "#include",
|
||||
"expat/2.1.0-pkg.3/**": "#include",
|
||||
"FbxSdk/2016.1.2-az.1/**": "#include",
|
||||
"glad/2.0.0-beta/**": "#include",
|
||||
"googletest/1.8.1-az.3/**": "#include",
|
||||
"jsmn/78b1dca/**": "#include",
|
||||
"libav/11.7/**": "#include",
|
||||
"LibTomCrypt/1.17-az.2/**": "#include",
|
||||
"LibTomMath/0.42.0-az.2/**": "#include",
|
||||
"lz4/r128-pkg.3/**": "#include",
|
||||
"Lzma/unknown-pkg.3/**": "#include",
|
||||
"LZSS/unknown-pkg.3/**": "#include",
|
||||
"md5/2.0-pkg.3/**": "#include",
|
||||
"mikkelsen/1.0.0-az.2/**": "#include",
|
||||
"nvapi/R361-developer_V2/**": "#include",
|
||||
"NvCloth/1.1.6-az.4/**": "#include",
|
||||
"OpenSSL/1.1.1b-noasm-az/**": "#include",
|
||||
"p4api/2019.1/**": "#include",
|
||||
"PhysX/4.1.0.25992954-az.1/**": "#include",
|
||||
"poly2tri/0.3.3-az.2/**": "#include",
|
||||
"PVRTexTool/2016_r1.1/**": "#include",
|
||||
|
||||
"Qt/5.15.1.2-az/**": "#include",
|
||||
"RadTelemetry/3.5.0.17/**": "#include",
|
||||
"rapidjson/rapidjson-1.1.0/**": "#include",
|
||||
"rapidxml/1.13-modified.1/**": "#include",
|
||||
"SQLite/v3.32.2/**": "#include",
|
||||
"tiff/3.9.5-az.3/**": "#include",
|
||||
"unwind/1.2.1/**": "#include",
|
||||
"Wwise/2019.2.8.7432/**": "#include",
|
||||
"xxhash/0.7.4/**": "#include",
|
||||
"zstd/1.35-pkg.1/**": "#include"
|
||||
"Wwise/2019.2.8.7432/**": "#include"
|
||||
}
|
||||
}
|
||||
@@ -14,16 +14,16 @@
|
||||
"FILE_LIST": "symbols.json",
|
||||
"FILE_LIST_TYPE": "All",
|
||||
"PACKAGE_NAME": "${PACKAGE_NAME_PATTERN}-windows-all-symbols-${BUILD_NUMBER}.zip"
|
||||
},
|
||||
{
|
||||
"FILE_LIST": "3rdParty.json",
|
||||
"FILE_LIST_TYPE": "Windows",
|
||||
"PACKAGE_NAME": "${PACKAGE_NAME_PATTERN}-windows-3rdparty-${BUILD_NUMBER}.zip"
|
||||
}
|
||||
],
|
||||
"BOOTSTRAP_CFG_GAME_FOLDER":"CMakeTestbed",
|
||||
"SKIP_BUILD": 0,
|
||||
"BUILD_TARGETS":[
|
||||
{
|
||||
"BUILD_CONFIG_FILENAME": "build_config.json",
|
||||
"PLATFORM": "Windows",
|
||||
"TYPE": "profile_vs2017"
|
||||
},
|
||||
{
|
||||
"BUILD_CONFIG_FILENAME": "build_config.json",
|
||||
"PLATFORM": "Windows",
|
||||
@@ -47,11 +47,6 @@
|
||||
"BOOTSTRAP_CFG_GAME_FOLDER":"AtomSampleViewer;AtomTest",
|
||||
"SKIP_BUILD": 1,
|
||||
"BUILD_TARGETS":[
|
||||
{
|
||||
"BUILD_CONFIG_FILENAME": "build_config.json",
|
||||
"PLATFORM": "Windows",
|
||||
"TYPE": "profile_vs2017_atom"
|
||||
},
|
||||
{
|
||||
"BUILD_CONFIG_FILENAME": "build_config.json",
|
||||
"PLATFORM": "Windows",
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
{
|
||||
"@3rdParty":{
|
||||
"3rdParty.txt":"#include",
|
||||
"AMD/AGS Lib/2.2":{
|
||||
"*":"#include",
|
||||
"inc/**":"#include",
|
||||
"lib/x64/**":"#include"
|
||||
},
|
||||
"AWS/AWSNativeSDK/1.7.167-az.2":{
|
||||
"*":"#include",
|
||||
"include/**":"#include",
|
||||
"LICENSE*":"#include",
|
||||
"lib/windows/**":"#include",
|
||||
"bin/windows/**":"#include",
|
||||
"lib/android/arm64-v8a/**":"#include",
|
||||
"bin/android/arm64-v8a/**":"#include",
|
||||
"bin/linux/**":"#include",
|
||||
"lib/linux/**":"#include"
|
||||
},
|
||||
"AWS/GameLift/3.4.0":{
|
||||
"*":"#include",
|
||||
"include/**":"#include",
|
||||
"bin/windows/**":"#include",
|
||||
"lib/linux/libstdcxx/**":"#include"
|
||||
},
|
||||
"civetweb/civetweb-20160922-az.2":{
|
||||
"src/**":"#include",
|
||||
"include/**":"#include",
|
||||
"lib/Windows/**":"#include",
|
||||
"*":"#include"
|
||||
},
|
||||
"DirectXShaderCompiler/1.0.1-az.1":{
|
||||
"*":"#include",
|
||||
"src/**":"#include",
|
||||
"bin/win_x64/**":"#include"
|
||||
},
|
||||
"DirectXShaderCompiler/2020.08.07":{
|
||||
"*":"#include",
|
||||
"bin/win_x64/**":"#include"
|
||||
},
|
||||
"DirectXShaderCompiler/5.0.0-az":{
|
||||
"*":"#include",
|
||||
"bin/win_x64/**":"#include"
|
||||
},
|
||||
"dyad/0.2.0-17-amazon":{
|
||||
"*":"#include",
|
||||
"doc/**":"#include",
|
||||
"example/**":"#include",
|
||||
"projects/**":"#include",
|
||||
"src/**":"#include",
|
||||
"lib/x64_v140_Debug/**":"#include",
|
||||
"lib/x64_v140_Release/**":"#include",
|
||||
"lib/linux_debug/**":"#include",
|
||||
"lib/linux_release/**":"#include"
|
||||
},
|
||||
"etc2comp/2017_04_24-az.2":{
|
||||
"EtcLib/Etc/**":"#include",
|
||||
"EtcLib/EtcCodec/**":"#include",
|
||||
"LICENSE":"#include",
|
||||
"EtcLib/Windows_x86_64/**":"#include",
|
||||
"EtcLib/Linux_x64_linux/**":"#include"
|
||||
},
|
||||
"expat/2.1.0-pkg.3":{
|
||||
"*":"#include",
|
||||
"amiga/**":"#include",
|
||||
"bcb5/**":"#include",
|
||||
"conftools/**":"#include",
|
||||
"doc/**":"#include",
|
||||
"examples/**":"#include",
|
||||
"lib/**":"#include",
|
||||
"m4/**":"#include",
|
||||
"tests/**":"#include",
|
||||
"vms/**":"#include",
|
||||
"win32/**":"#include",
|
||||
"xmlwf/**":"#include",
|
||||
"build/win_x64/vc140/**":"#include",
|
||||
"build/win_x64/android_ndk_r12/android-*/**":"#include",
|
||||
"build/linux/**":"#include"
|
||||
},
|
||||
"FreeType2/2.5.0.1-pkg.3":{
|
||||
"freetype-2.5.0.1/**":"#include",
|
||||
"dist/**":"#include",
|
||||
"vc140_x64/**":"#include",
|
||||
"build/win_x64/vc140/**":"#include",
|
||||
"android*/**":"#include",
|
||||
"build/win_x64/android_ndk_r12/android-*/**":"#include",
|
||||
"build/linux/clang-3.4/**":"#include"
|
||||
},
|
||||
"Redistributables/FbxSdk/2016.1.2":{
|
||||
"*win*":"#include",
|
||||
"*vs2013*":"#exclude"
|
||||
},
|
||||
"OpenSSL/1.1.1b-noasm-az":{
|
||||
"include/**":"#include",
|
||||
"ssl/**":"#include",
|
||||
"LICENSE":"#include",
|
||||
"bin/**":"#include",
|
||||
"lib/vc140_x64_debug/**":"#include",
|
||||
"lib/vc140_x64_release/**":"#include",
|
||||
"lib/android_ndk_r15c/android-*/**":"#include",
|
||||
"lib/linux-x86_64-clang-debug/**":"#include",
|
||||
"lib/linux-x86_64-clang-release/**":"#include"
|
||||
},
|
||||
"Qt/5.15.1.2-az":{
|
||||
"LICENSE":"#include",
|
||||
"LGPL_EXCEPTION.TXT":"#include",
|
||||
"LICENSE.GPLV3":"#include",
|
||||
"LICENSE.LGPLV3":"#include",
|
||||
"QT-NOTICE.TXT":"#include",
|
||||
"msvc*/**":"#include",
|
||||
"gcc_64/**":"#include"
|
||||
},
|
||||
"tiff/3.9.5-az.3":{
|
||||
"COPYRIGHT":"#include",
|
||||
"README":"#include",
|
||||
"RELEASE-DATE":"#include",
|
||||
"VERSION":"#include",
|
||||
"libtiff/buildLib32Lib64.bat":"#include",
|
||||
"libtiff/readme.txt":"#include",
|
||||
"include/libtiff/*.h":"#include",
|
||||
"libtiff/*vc140.lib":"#include",
|
||||
"libtiff/linux_gcc/**":"#include"
|
||||
}
|
||||
}
|
||||
}
|
||||
+6
-26
@@ -1,28 +1,13 @@
|
||||
{
|
||||
"@3rdParty": {
|
||||
"**/.owner": "#exclude",
|
||||
"3rdParty.txt": "#move:3rdParty",
|
||||
"OpenEXR/**": "#move:3rdParty",
|
||||
"CMake/3.19.1/**": "#move:3rdParty",
|
||||
"Redistributables":{
|
||||
"WwiseLTX": {
|
||||
"LTX_2018.1.2.6762": {
|
||||
"**": "#move:dev/Tools/Redistributables/WwiseLTX/LTX_2018.1.2.6762",
|
||||
"*.app.zip": "#exclude",
|
||||
"WwiseLauncher.pkg": "#exclude"
|
||||
}
|
||||
},
|
||||
"FbxSdk": {
|
||||
"2016.1.2": {
|
||||
"*win*": "#move:dev/Tools/Redistributables/FbxSdk/2016.1.2-az.1",
|
||||
"*vs2013*": "#exclude"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"@lyengine": {
|
||||
"**/*.pyc": "#exclude",
|
||||
"*": "#include",
|
||||
".gitattributes": "#exclude",
|
||||
".gitignore": "#exclude",
|
||||
".gitmodules": "#exclude",
|
||||
".lfsconfig": "#exclude",
|
||||
".p4ignore": "#exclude",
|
||||
".submodules": "#exclude",
|
||||
"AtomTest":
|
||||
{
|
||||
"**":"#include",
|
||||
@@ -167,11 +152,6 @@
|
||||
"3dsmax/**": "#include",
|
||||
"7za.exe": "#include",
|
||||
"7za_legal_notice.txt": "#include",
|
||||
"AWSNativeSDK": {
|
||||
"**": "#include",
|
||||
"Upgrader/restricted_platforms.py": "#exclude"
|
||||
},
|
||||
"AWSPythonSDK/**": "#include",
|
||||
"Crashpad/**": "#include",
|
||||
"CrySCompileServer/**": "#include",
|
||||
"PakShaders/**": "#include",
|
||||
@@ -22,6 +22,9 @@ except ImportError:
|
||||
from pathlib import Path
|
||||
|
||||
this_file_path = os.path.dirname(os.path.realpath(__file__))
|
||||
|
||||
# resolve symlinks and eliminate ".." components
|
||||
engine_root_path = Path(__file__).resolve().parents[3]
|
||||
|
||||
def convert_glob_pattern_to_regex_pattern(glob_pattern):
|
||||
# switch to forward slashes because way easier to pattern match against
|
||||
@@ -75,12 +78,13 @@ def generate_excludes_for_platform(root, platform):
|
||||
with open(platform_exclusions_filename, 'r') as platform_exclusions_file:
|
||||
platform_exclusions = json.load(platform_exclusions_file)
|
||||
else:
|
||||
# Use real path in case engine root is a symlink path
|
||||
# Use real path in case root is a symlink path
|
||||
if os.name == 'posix' and os.path.islink(root):
|
||||
root = os.readlink(root)
|
||||
cur_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
relative_folder = os.path.relpath(cur_dir, root)
|
||||
platform_exclusions_filename = os.path.join(root, 'restricted', platform, relative_folder, platform.lower() + '_exclusions.json')
|
||||
# "root" is the root of the folder structure we're validating
|
||||
# "engine_root_path" is the engine root where the restricted platform folder is linked
|
||||
relative_folder = os.path.relpath(this_file_path, engine_root_path)
|
||||
platform_exclusions_filename = os.path.join(engine_root_path, 'restricted', platform, relative_folder, platform.lower() + '_exclusions.json')
|
||||
with open(platform_exclusions_filename, 'r') as platform_exclusions_file:
|
||||
platform_exclusions = json.load(platform_exclusions_file)
|
||||
|
||||
@@ -100,11 +104,13 @@ def generate_include_exclude_regexes(package_platform, package_type, root, prohi
|
||||
# Search non-restricted platform first
|
||||
package_file_list = os.path.join(this_file_path, 'Platform', package_platform, 'package_filelists', f'{package_type}.json')
|
||||
if not os.path.exists(filelist):
|
||||
# Use real path in case engine root is a symlink path
|
||||
# Use real path in case root is a symlink path
|
||||
if os.name == 'posix' and os.path.islink(root):
|
||||
root = os.readlink(root)
|
||||
rel_path = os.path.relpath(cur_dir, root)
|
||||
package_file_list = os.path.join(root, 'restricted', package_platform, rel_path, 'package_filelists',
|
||||
# "root" is the root of the folder structure we're validating
|
||||
# "engine_root_path" is the engine root where the restricted platform folder is linked
|
||||
rel_path = os.path.relpath(this_file_path, engine_root_path)
|
||||
package_file_list = os.path.join(engine_root_path, 'restricted', package_platform, rel_path, 'package_filelists',
|
||||
f'{package_type}.json')
|
||||
with open(package_file_list, 'r') as package_file:
|
||||
package = json.load(package_file)
|
||||
|
||||
@@ -1,6 +1,13 @@
|
||||
{
|
||||
"@lyengine": {
|
||||
"**": "#include",
|
||||
".git/**": "#exclude",
|
||||
".gitattributes": "#exclude",
|
||||
".gitignore": "#exclude",
|
||||
".gitmodules": "#exclude",
|
||||
".lfsconfig": "#exclude",
|
||||
".p4ignore": "#exclude",
|
||||
".submodules": "#exclude",
|
||||
"**/*.pyc": "#exclude",
|
||||
"**/*.pdb": "#exclude"
|
||||
}
|
||||
|
||||
@@ -186,6 +186,5 @@ EXCLUDED_VALIDATION_PATTERNS = [
|
||||
'python/runtime',
|
||||
'restricted/*/Tools/*RemoteControl',
|
||||
'Tools/3dsmax',
|
||||
'Tools/AWSPythonSDK',
|
||||
'Tools/Crashpad',
|
||||
]
|
||||
|
||||
@@ -31,7 +31,6 @@
|
||||
*/Code/Framework/AzCore/Platform/Provo/AzCore/AzCore_Traits_Provo.h
|
||||
*/Code/Framework/AzCore/Platform/Salem/AzCore/AzCore_Traits_Salem.h
|
||||
*/Code/Framework/AzCore/Platform/Windows/AzCore/AzCore_Traits_Windows.h
|
||||
*/Code/Framework/AzCore/Platform/Xenia/AzCore/AzCore_Traits_Xenia.h
|
||||
*/Code/Framework/AzCore/Tests/AZStd/Examples.cpp
|
||||
*/Code/Framework/AzCore/Tests/Memory.cpp
|
||||
*/Code/Framework/AzFramework/AzFramework/Asset/AssetSystemComponentHelper.cpp
|
||||
|
||||
@@ -31,7 +31,6 @@ platform_macros = [
|
||||
'WIN32',
|
||||
'WIN32_LEAN_AND_MEAN',
|
||||
'WIN64',
|
||||
'XENIA',
|
||||
'_WIN32',
|
||||
'_WIN32_WINDOWS',
|
||||
'_WIN32_WINNT',
|
||||
|
||||
@@ -20,7 +20,7 @@ GOTO pythonDirNotFound
|
||||
:pythonPathAvailable
|
||||
SET PYTHON_EXECUTABLE=%PYTHON_DIRECTORY%\python.cmd
|
||||
IF NOT EXIST "%PYTHON_EXECUTABLE%" GOTO pythonExeNotFound
|
||||
CALL "%PYTHON_EXECUTABLE%" %BASE_PATH%\scripts\lmbr.py %*
|
||||
CALL "%PYTHON_EXECUTABLE%" %BASE_PATH%\scripts\o3de.py %*
|
||||
GOTO end
|
||||
:pythonDirNotFound
|
||||
ECHO Python directory not found: %PYTHON_DIRECTORY%
|
||||
@@ -21,12 +21,14 @@ if ROOT_DEV_PATH not in sys.path:
|
||||
from cmake.Tools import engine_template
|
||||
from cmake.Tools import current_project
|
||||
from cmake.Tools import add_remove_gem
|
||||
from cmake.Tools import registration
|
||||
|
||||
|
||||
def add_args(parser, subparsers) -> None:
|
||||
current_project.add_args(parser, subparsers)
|
||||
engine_template.add_args(parser, subparsers)
|
||||
add_remove_gem.add_args(parser, subparsers)
|
||||
registration.add_args(parser, subparsers)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
@@ -0,0 +1,36 @@
|
||||
#!/bin/sh
|
||||
|
||||
#
|
||||
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
||||
# its licensors.
|
||||
#
|
||||
# For complete copyright and license terms please see the LICENSE at the root of this
|
||||
# distribution (the "License"). All use of this software is governed by the License,
|
||||
# or, if provided, by the license below or the license accompanying this file. Do not
|
||||
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
#
|
||||
|
||||
#Use $(cd dirname $0) to get the absolute path of this scripts folder
|
||||
#Note this does not actually change the working directory
|
||||
SCRIPT_DIR=$(cd `dirname $0` && pwd)
|
||||
|
||||
#The engine root is always the parent of the scripts directory, so $(dirname "$SCRIPT_DIR") should get us engine root
|
||||
ENGINE_ROOT=$(dirname "$SCRIPT_DIR")
|
||||
|
||||
#The engines python is in the engineroot/python
|
||||
PYTHON_DIRECTORY="$ENGINE_ROOT/python"
|
||||
|
||||
#If engine python exists use it, if not try the system python
|
||||
if [ ! -d "$PYTHON_DIRECTORY" ]; then
|
||||
PYTHON_EXECUTABLE="python"
|
||||
else
|
||||
PYTHON_EXECUTABLE="$PYTHON_DIRECTORY/python.sh"
|
||||
fi
|
||||
if [ ! -f "$PYTHON_EXECUTABLE" ]; then
|
||||
echo "Python executable not found: $PYTHON_EXECUTABLE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
#run the o3de.py pass along the command
|
||||
$PYTHON_EXECUTABLE "$SCRIPT_DIR/o3de.py" $*
|
||||
@@ -1,5 +1,3 @@
|
||||
#!/bin/sh
|
||||
|
||||
#
|
||||
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
||||
# its licensors.
|
||||
@@ -11,20 +9,14 @@
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
#
|
||||
|
||||
CURRENT=$PWD
|
||||
cd "$PWD/.." || exit
|
||||
BASE_PATH=$PWD
|
||||
cd "$CURRENT" || exit
|
||||
PYTHON_DIRECTORY="${BASE_PATH}/python"
|
||||
if [ ! -d "$PYTHON_DIRECTORY" ]; then
|
||||
echo "Python dir not found: $PYTHON_DIRECTORY"
|
||||
exit 1
|
||||
fi
|
||||
PYTHON_EXECUTABLE="$PYTHON_DIRECTORY/python.sh"
|
||||
if [ ! -f "$PYTHON_EXECUTABLE" ]; then
|
||||
echo "Python executable not found: $PYTHON_EXECUTABLE"
|
||||
exit 1
|
||||
fi
|
||||
$PYTHON_EXECUTABLE "$BASE_PATH/scripts/lmbr.py" $*
|
||||
cd "$CURRENT" || exit
|
||||
ly_download_associated_package(pyside2)
|
||||
|
||||
ly_add_pytest(
|
||||
NAME test_pyside
|
||||
PATH ${CMAKE_CURRENT_LIST_DIR}/tests/test_pyside.py
|
||||
)
|
||||
|
||||
ly_add_pytest(
|
||||
NAME test_projects
|
||||
PATH ${CMAKE_CURRENT_LIST_DIR}/tests/test_projects.py
|
||||
)
|
||||
@@ -0,0 +1,10 @@
|
||||
#
|
||||
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
||||
# its licensors.
|
||||
#
|
||||
# For complete copyright and license terms please see the LICENSE at the root of this
|
||||
# distribution (the "License"). All use of this software is governed by the License,
|
||||
# or, if provided, by the license below or the license accompanying this file. Do not
|
||||
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
#
|
||||
@@ -0,0 +1,642 @@
|
||||
#
|
||||
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
||||
# its licensors.
|
||||
#
|
||||
# For complete copyright and license terms please see the LICENSE at the root of this
|
||||
# distribution (the "License"). All use of this software is governed by the License,
|
||||
# or, if provided, by the license below or the license accompanying this file. Do not
|
||||
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
#
|
||||
|
||||
# PySide project and gem selector GUI
|
||||
|
||||
import os
|
||||
import sys
|
||||
import argparse
|
||||
import json
|
||||
import logging
|
||||
import subprocess
|
||||
import threading
|
||||
import platform
|
||||
from logging.handlers import RotatingFileHandler
|
||||
|
||||
from typing import List
|
||||
|
||||
from pathlib import Path
|
||||
from pyside import add_pyside_environment, is_pyside_ready, uninstall_env
|
||||
|
||||
logger = logging.getLogger()
|
||||
|
||||
dev_path = os.path.realpath(os.path.join(os.path.dirname(__file__), '..', '..'))
|
||||
sys.path.append(dev_path)
|
||||
executable_path = ''
|
||||
|
||||
|
||||
def initialize_pyside_from_parser():
|
||||
# Parse arguments up top. We need to know the path to our binaries and QT libs in particular to load up
|
||||
# PySide
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('--executable_path', required=True, help='Path to Executable to launch with project')
|
||||
parser.add_argument('--binaries_path', default=None, help='Path to QT Binaries necessary for PySide. If not'
|
||||
'provided executable_path folder is assumed')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
global executable_path
|
||||
executable_path = args.executable_path
|
||||
binaries_path = args.binaries_path or os.path.dirname(executable_path)
|
||||
|
||||
# Initialize PySide before imports below. This adds both PySide python modules to the python system interpreter
|
||||
# path and adds the necessary paths to binaries for the DLLs to be found and load their dependencies
|
||||
add_pyside_environment(binaries_path)
|
||||
|
||||
|
||||
if not is_pyside_ready():
|
||||
initialize_pyside_from_parser()
|
||||
|
||||
try:
|
||||
from PySide2.QtWidgets import QApplication, QDialogButtonBox, QPushButton, QComboBox, QMessageBox, QFileDialog
|
||||
from PySide2.QtWidgets import QListView, QLabel
|
||||
from PySide2.QtUiTools import QUiLoader
|
||||
from PySide2.QtCore import QFile, QObject, Qt, Signal, Slot
|
||||
from PySide2.QtGui import QIcon, QStandardItemModel, QStandardItem
|
||||
except ImportError as e:
|
||||
logger.error(f"Failed to import PySide2 with error {e}")
|
||||
|
||||
from cmake.Tools import engine_template
|
||||
from cmake.Tools import add_remove_gem
|
||||
|
||||
ui_path = os.path.join(os.path.join(os.path.dirname(__file__), 'ui'))
|
||||
ui_file = 'projects.ui'
|
||||
ui_icon_file = 'projects.ico'
|
||||
manage_gems_file = 'manage_gems.ui'
|
||||
create_project_file = 'create_project.ui'
|
||||
|
||||
mru_file_name = 'o3de_projects.json'
|
||||
default_settings_folder = os.path.join(Path.home(), '.o3de')
|
||||
|
||||
# Used to indicate a folder which appears to be a valid o3de project
|
||||
project_marker_file = 'project.json'
|
||||
|
||||
|
||||
class DialogLoggerSignaller(QObject):
|
||||
send_to_dialog = Signal(str)
|
||||
|
||||
def __init__(self, dialog_logger):
|
||||
super(DialogLoggerSignaller, self).__init__()
|
||||
|
||||
self.dialog_logger = dialog_logger
|
||||
|
||||
|
||||
# Independent class to handle log forwarding. Logger and qt signals both use emit method.
|
||||
# This class's job is to receive the logger record and then emit the formatted message through
|
||||
# DialogLoggerSignaller which is what the ProjectDialog handler listens for
|
||||
class DialogLogger(logging.Handler):
|
||||
|
||||
def __init__(self, log_dialog, log_level=logging.INFO, forward_log_level=logging.WARNING,
|
||||
message_box_log_level=logging.ERROR):
|
||||
super(DialogLogger, self).__init__()
|
||||
|
||||
self.log_dialog = log_dialog
|
||||
self.log_level = log_level
|
||||
self.forward_log_level = forward_log_level
|
||||
self.message_box_log_level = message_box_log_level
|
||||
self.log_records = []
|
||||
self.formatter = logging.Formatter('%(levelname)s : %(message)s')
|
||||
self.setFormatter(self.formatter)
|
||||
self.signaller = DialogLoggerSignaller(self)
|
||||
|
||||
def emit(self, record):
|
||||
self.log_records.append(record)
|
||||
if record.levelno >= self.message_box_log_level:
|
||||
QMessageBox.warning(None, record.levelname, record.message)
|
||||
elif record.levelno >= self.forward_log_level:
|
||||
self.signaller.send_to_dialog.emit(self.format(record))
|
||||
|
||||
|
||||
|
||||
class ProjectDialog(QObject):
|
||||
"""
|
||||
Main project dialog class responsible for displaying the project selection list
|
||||
"""
|
||||
|
||||
def __init__(self, parent=None, my_ui_path=ui_path, settings_folder=default_settings_folder):
|
||||
super(ProjectDialog, self).__init__(parent)
|
||||
|
||||
self.log_display = None
|
||||
self.dialog_logger = DialogLogger(self)
|
||||
logger.addHandler(self.dialog_logger)
|
||||
logger.setLevel(logging.INFO)
|
||||
|
||||
log_path = os.path.join(os.path.dirname(__file__), "logs")
|
||||
if not os.path.isdir(log_path):
|
||||
os.makedirs(log_path)
|
||||
log_path = os.path.join(log_path, "project_manager.log")
|
||||
log_file_handler = RotatingFileHandler(filename=log_path, maxBytes=1024 * 1024, backupCount=1)
|
||||
formatter = logging.Formatter('%(asctime)s | %(levelname)s : %(message)s')
|
||||
log_file_handler.setFormatter(formatter)
|
||||
logger.addHandler(log_file_handler)
|
||||
|
||||
logger.info("Starting Project Manager")
|
||||
|
||||
self.dialog_logger.signaller.send_to_dialog.connect(self.handle_log_message)
|
||||
self.displayed_projects = []
|
||||
|
||||
self.mru_file_path = os.path.join(settings_folder, mru_file_name)
|
||||
|
||||
self.my_ui_file_path = os.path.join(my_ui_path, ui_file)
|
||||
self.my_ui_icon_path = os.path.join(my_ui_path, ui_icon_file)
|
||||
self.my_ui_manage_gems_path = os.path.join(my_ui_path, manage_gems_file)
|
||||
self.my_ui_create_project_path = os.path.join(my_ui_path, create_project_file)
|
||||
self.ui_dir = my_ui_path
|
||||
self.ui_file = QFile(self.my_ui_file_path)
|
||||
self.ui_file.open(QFile.ReadOnly)
|
||||
|
||||
loader = QUiLoader()
|
||||
self.dialog = loader.load(self.ui_file)
|
||||
self.dialog.setWindowIcon(QIcon(self.my_ui_icon_path))
|
||||
self.dialog.setFixedSize(self.dialog.size())
|
||||
self.browse_projects_button = self.dialog.findChild(QPushButton, 'browseProjectsButton')
|
||||
self.browse_projects_button.clicked.connect(self.browse_projects_handler)
|
||||
|
||||
self.log_display = self.dialog.findChild(QLabel, 'logDisplay')
|
||||
|
||||
self.create_project_button = self.dialog.findChild(QPushButton, 'createProjectButton')
|
||||
self.create_project_button.clicked.connect(self.create_project_handler)
|
||||
|
||||
self.ok_cancel_button = self.dialog.findChild(QDialogButtonBox, 'okCancel')
|
||||
self.ok_cancel_button.accepted.connect(self.accepted_handler)
|
||||
|
||||
self.manage_gems_button = self.dialog.findChild(QPushButton, 'manageGemsButton')
|
||||
self.manage_gems_button.clicked.connect(self.manage_gems_handler)
|
||||
|
||||
self.project_list_box = self.dialog.findChild(QComboBox, 'projectListBox')
|
||||
self.add_projects(self.get_mru_list())
|
||||
self.project_gem_list = []
|
||||
|
||||
self.dialog.show()
|
||||
|
||||
self.load_thread = None
|
||||
self.gems_list = []
|
||||
self.load_gems()
|
||||
|
||||
def update_gems(self) -> None:
|
||||
"""
|
||||
Perform a full refresh of active project and available gems. Loads both from
|
||||
data on disk and refreshes UI
|
||||
:return: None
|
||||
"""
|
||||
project_path = self.path_for_selection()
|
||||
if not project_path:
|
||||
self.project_gem_list = set()
|
||||
else:
|
||||
self.project_gem_list = add_remove_gem.get_project_gem_list(self.path_for_selection())
|
||||
project_gem_model = QStandardItemModel()
|
||||
for item in sorted(self.project_gem_list):
|
||||
project_gem_model.appendRow(QStandardItem(item))
|
||||
self.project_gems.setModel(project_gem_model)
|
||||
|
||||
add_gem_model = QStandardItemModel()
|
||||
for item in self.gems_list:
|
||||
if item.get('Name') in self.project_gem_list:
|
||||
continue
|
||||
model_item = QStandardItem(item.get('Name'))
|
||||
model_item.setData(item.get('Path'), Qt.UserRole)
|
||||
add_gem_model.appendRow(model_item)
|
||||
self.add_gems_list.setModel(add_gem_model)
|
||||
|
||||
def get_selected_project_name(self) -> str:
|
||||
return os.path.basename(self.path_for_selection())
|
||||
|
||||
def get_launch_project(self) -> str:
|
||||
# We can't currently pass an absolute path, so we need to assume the folder lives under dev and just pass
|
||||
# the base name of the selected project
|
||||
return os.path.basename(os.path.normpath(self.path_for_selection()))
|
||||
|
||||
def get_executable_launch_params(self) -> list:
|
||||
"""
|
||||
Retrieve the necessary launch parameters to make the subprocess launch call with - this is the path
|
||||
to the executable such as the Editor and the path to the selected project
|
||||
:return: list of params
|
||||
"""
|
||||
launch_params = [executable_path,
|
||||
f'-regset="/Amazon/AzCore/Bootstrap/sys_game_folder={self.get_launch_project()}"']
|
||||
return launch_params
|
||||
|
||||
def load_gems(self) -> None:
|
||||
"""
|
||||
Starts another thread to discover available gems. This requires file discovery/parsing and would likely
|
||||
cause a noticeable pause if it were not on another thread
|
||||
:return: None
|
||||
"""
|
||||
self.load_thread = threading.Thread(target=self.load_gems_thread_func)
|
||||
self.load_thread.start()
|
||||
|
||||
def load_gems_thread_func(self) -> None:
|
||||
"""
|
||||
Actual load function for load_gems thread. Blocking call to lower level find method to fill out gems_list
|
||||
:return: None
|
||||
"""
|
||||
self.gems_list = add_remove_gem.find_all_gems([os.path.join(dev_path, 'Gems')])
|
||||
|
||||
def load_template_list(self) -> None:
|
||||
"""
|
||||
Search for available templates to fill out template list
|
||||
:return: None
|
||||
"""
|
||||
self.project_templates = engine_template.find_all_project_templates([os.path.join(dev_path, 'Templates')])
|
||||
|
||||
def get_gem_info(self, gem_name: str) -> str:
|
||||
"""
|
||||
Provided a known gem name provides gem info
|
||||
:param gem_name: Name of known gem. Names are based on Gem.<Gemname> module names in CMakeLists.txt rather
|
||||
than folders a Gem lives in.
|
||||
:return: Dictionary with data about gem if known
|
||||
"""
|
||||
for this_gem in self.gems_list:
|
||||
if this_gem.get('Name') == gem_name:
|
||||
this_gem
|
||||
return None
|
||||
|
||||
def get_gem_info_by_path(self, gem_path: str) -> str:
|
||||
"""
|
||||
Provided a gem path returns the gem info if known
|
||||
:param gem_path: Path to gem
|
||||
:return: Dictionary with data about gem if known
|
||||
"""
|
||||
for this_gem in self.gems_list:
|
||||
if this_gem.get('Path') == gem_path:
|
||||
return this_gem
|
||||
return None
|
||||
|
||||
def path_for_gem(self, gem_name: str) -> str:
|
||||
"""
|
||||
Provided a known gem name provides the full path on disk
|
||||
:param gem_name: Name of known gem. Names are based on Gem.<Gemname> module names in CMakeLists.txt rather
|
||||
than folders a Gem lives in.
|
||||
:return: Path to CMakeLists.txt containing the Gem modules
|
||||
"""
|
||||
for this_gem in self.gems_list:
|
||||
if this_gem.get('Name') == gem_name:
|
||||
return this_gem.get('Path')
|
||||
return ''
|
||||
|
||||
def open_project(self, project_path: str) -> None:
|
||||
"""
|
||||
Launch the desired application given the selected project
|
||||
:param project_path: Path to currently selected project
|
||||
:return: None
|
||||
"""
|
||||
logger.info(f'Attempting to open {project_path}')
|
||||
self.update_mru_list(project_path)
|
||||
launch_params = self.get_executable_launch_params()
|
||||
try:
|
||||
logger.info(f'Launching with params {launch_params}')
|
||||
subprocess.Popen(launch_params, env=uninstall_env())
|
||||
quit(0)
|
||||
except subprocess.CalledProcessError as e:
|
||||
logger.error(f'Failed to start executable with launch params {launch_params} error {e}')
|
||||
|
||||
def path_for_selection(self) -> str:
|
||||
"""
|
||||
Retrive the full path to the project the user currently has selected in the drop down
|
||||
:return: str path to project
|
||||
"""
|
||||
if self.project_list_box.currentIndex() == -1:
|
||||
logger.warning("No project selected")
|
||||
return ""
|
||||
return self.project_list_box.itemData(self.project_list_box.currentIndex(), Qt.ToolTipRole)
|
||||
|
||||
def accepted_handler(self) -> None:
|
||||
"""
|
||||
Override for handling "Ok" on main project dialog to first check whether the user has selected a project and
|
||||
prompt them to if not. If a project is selected will attempt to open it.
|
||||
:return: None
|
||||
"""
|
||||
if not self.project_list_box.currentText():
|
||||
msg_box = QMessageBox(parent=self.dialog)
|
||||
msg_box.setWindowTitle("O3DE")
|
||||
msg_box.setText("Please select a project")
|
||||
msg_box.exec()
|
||||
return
|
||||
self.open_project(self.path_for_selection())
|
||||
|
||||
def is_project_folder(self, project_path: str) -> bool:
|
||||
"""
|
||||
Checks whether the supplied path appears to be a canonical project folder.
|
||||
Root of a valid project should contain the canonical file
|
||||
:param project_path:
|
||||
:return:
|
||||
"""
|
||||
return os.path.isfile(os.path.join(project_path, project_marker_file))
|
||||
|
||||
def add_new_project(self, project_folder, update_mru=True, reset_selected=True, validate=True):
|
||||
"""
|
||||
Handle request to add a new project to our display and mru lists. Validation checks whether the folder
|
||||
appears to be a project. Duplicates should never be added with or without validation.
|
||||
:param project_folder: Absolute path to project folder
|
||||
:param update_mru: Should the project also be promoted to "most recent" in our mru list
|
||||
:param reset_selected: Update our drop down to show this as our current selection
|
||||
:param validate: Verify the folder appears to be a valid project folder
|
||||
:return:
|
||||
"""
|
||||
if validate:
|
||||
if not self.is_project_folder(project_folder):
|
||||
QMessageBox.warning(self.dialog, "Invalid Project Folder",
|
||||
f"{project_folder} does not contain a {project_marker_file}"
|
||||
f" and does not appear to be a valid project")
|
||||
return
|
||||
self.add_projects([project_folder])
|
||||
if reset_selected:
|
||||
self.project_list_box.setCurrentIndex(self.project_list_box.count() - 1)
|
||||
if update_mru:
|
||||
self.update_mru_list(project_folder)
|
||||
|
||||
def browse_projects_handler(self):
|
||||
"""
|
||||
Open a file search dialog looking for a folder which contains a valid project marker. If valid
|
||||
will update the mru list with the new entry, if invalid will warn the user.
|
||||
:return: None
|
||||
"""
|
||||
project_folder = QFileDialog.getExistingDirectory(self.dialog, "Select Project Folder", dev_path)
|
||||
if project_folder:
|
||||
self.add_new_project(project_folder)
|
||||
|
||||
return
|
||||
|
||||
def get_selected_project_gems(self) -> list:
|
||||
"""
|
||||
:return: List of (GemName, GemPath) of currently selected gems in the project gems list
|
||||
"""
|
||||
selected_items = self.project_gems.selectionModel().selectedRows()
|
||||
return [self.project_gems.model().data(item) for item in selected_items]
|
||||
|
||||
def remove_gems_handler(self):
|
||||
"""
|
||||
Finds the currently selected gems in the active gems list and attempts to remove each and updates the UI.
|
||||
:return: None
|
||||
"""
|
||||
remove_gems = self.get_selected_project_gems()
|
||||
for this_gem in remove_gems:
|
||||
gem_path = self.path_for_gem(this_gem)
|
||||
add_remove_gem.add_remove_gem(False, dev_path, gem_path or os.path.join(dev_path, 'Gems', this_gem),
|
||||
self.path_for_selection(), gem_name=this_gem)
|
||||
|
||||
self.update_gems()
|
||||
|
||||
def manage_gems_handler(self):
|
||||
"""
|
||||
Opens the Gem management pane. Waits for the load thread to complete if still running and displays all
|
||||
active gems for the current project as well as all available gems which aren't currently active.
|
||||
:return: None
|
||||
"""
|
||||
|
||||
if not self.path_for_selection():
|
||||
msg_box = QMessageBox(parent=self.dialog)
|
||||
msg_box.setWindowTitle("O3DE")
|
||||
msg_box.setText("Please select a project")
|
||||
msg_box.exec()
|
||||
return
|
||||
|
||||
self.load_thread.join()
|
||||
loader = QUiLoader()
|
||||
self.manage_gems_file = QFile(self.my_ui_manage_gems_path)
|
||||
|
||||
if not self.manage_gems_file:
|
||||
logger.error(f'Failed to load gems UI file at {self.manage_gems_file}')
|
||||
return
|
||||
|
||||
self.manage_gems_dialog = loader.load(self.manage_gems_file)
|
||||
|
||||
if not self.manage_gems_dialog:
|
||||
logger.error(f'Failed to load gems dialog file at {self.manage_gems_file}')
|
||||
return
|
||||
|
||||
self.manage_gems_dialog.setWindowTitle(f"Manage Gems for {self.get_selected_project_name()}")
|
||||
|
||||
self.add_gems_button = self.manage_gems_dialog.findChild(QPushButton, 'addGemsButton')
|
||||
self.add_gems_button.clicked.connect(self.add_gems_handler)
|
||||
|
||||
self.add_gems_list = self.manage_gems_dialog.findChild(QListView, 'addGemsList')
|
||||
|
||||
self.remove_gems_button = self.manage_gems_dialog.findChild(QPushButton, 'removeGemsButton')
|
||||
self.remove_gems_button.clicked.connect(self.remove_gems_handler)
|
||||
|
||||
self.project_gems = self.manage_gems_dialog.findChild(QListView, 'projectGems')
|
||||
self.update_gems()
|
||||
|
||||
self.manage_gems_dialog.exec()
|
||||
|
||||
def get_selected_add_gems(self) -> list:
|
||||
"""
|
||||
Find returns a list of currently selected gems in the add listas (GemName, GemPath)
|
||||
:return:
|
||||
"""
|
||||
selected_items = self.add_gems_list.selectionModel().selectedRows()
|
||||
return [(self.add_gems_list.model().data(item), self.add_gems_list.model().data(item, Qt.UserRole)) for
|
||||
item in selected_items]
|
||||
|
||||
def add_gems_handler(self) -> None:
|
||||
"""
|
||||
Searches the available gems list for selected gems and attempts to add each one to the current project.
|
||||
Updates UI after completion.
|
||||
:return: None
|
||||
"""
|
||||
add_gems_list = self.get_selected_add_gems()
|
||||
for this_gem in add_gems_list:
|
||||
gem_info = self.get_gem_info_by_path(this_gem[1])
|
||||
if not gem_info:
|
||||
logger.error(f'Unknown gem {this_gem}!')
|
||||
continue
|
||||
add_remove_gem.add_remove_gem(True, dev_path, this_gem[1], self.path_for_selection(),
|
||||
gem_name=gem_info.get('Name'),
|
||||
runtime_dependency=gem_info.get('Runtime', False),
|
||||
tool_dependency=gem_info.get('Tools', False))
|
||||
self.update_gems()
|
||||
|
||||
def create_project_handler(self):
|
||||
"""
|
||||
Opens the Create Project pane. Retrieves a list of available templates for display
|
||||
:return: None
|
||||
"""
|
||||
loader = QUiLoader()
|
||||
self.create_project_file = QFile(self.my_ui_create_project_path)
|
||||
|
||||
if not self.create_project_file:
|
||||
logger.error(f'Failed to create project UI file at {self.create_project_file}')
|
||||
return
|
||||
|
||||
self.create_project_dialog = loader.load(self.create_project_file)
|
||||
|
||||
if not self.create_project_dialog:
|
||||
logger.error(f'Failed to load create project dialog file at {self.create_project_file}')
|
||||
return
|
||||
|
||||
self.create_project_ok_button = self.create_project_dialog.findChild(QDialogButtonBox, 'okCancel')
|
||||
self.create_project_ok_button.accepted.connect(self.create_project_accepted_handler)
|
||||
|
||||
self.project_template_list = self.create_project_dialog.findChild(QListView, 'projectTemplates')
|
||||
|
||||
self.load_template_list()
|
||||
|
||||
self.load_template_model = QStandardItemModel()
|
||||
for item in self.project_templates:
|
||||
model_item = QStandardItem(item[0])
|
||||
model_item.setData(item[1], Qt.UserRole)
|
||||
self.load_template_model.appendRow(model_item)
|
||||
|
||||
self.project_template_list.setModel(self.load_template_model)
|
||||
|
||||
self.create_project_dialog.exec()
|
||||
|
||||
def get_selected_project_template(self) -> tuple:
|
||||
"""
|
||||
Get the current pair templatename, path to template selecte dby the user
|
||||
:return: pair
|
||||
"""
|
||||
|
||||
selected_item = self.project_template_list.selectionModel().currentIndex()
|
||||
if not selected_item.isValid():
|
||||
logger.warning("Select a template to create from")
|
||||
return None
|
||||
|
||||
create_project_item = (self.project_template_list.model().data(selected_item),
|
||||
self.project_template_list.model().data(selected_item, Qt.UserRole))
|
||||
return create_project_item
|
||||
|
||||
def create_project_accepted_handler(self) -> None:
|
||||
"""
|
||||
Searches the available gems list for selected gems and attempts to add each one to the current project.
|
||||
Updates UI after completion.
|
||||
:return: None
|
||||
"""
|
||||
create_project_item = self.get_selected_project_template()
|
||||
if not create_project_item:
|
||||
return
|
||||
|
||||
folder_dialog = QFileDialog(self.dialog, "Select a Folder and Enter a New Project Name", dev_path)
|
||||
folder_dialog.setFileMode(QFileDialog.AnyFile)
|
||||
folder_dialog.setOptions(QFileDialog.ShowDirsOnly)
|
||||
project_count = 0
|
||||
project_name = "MyNewProject"
|
||||
while os.path.exists(os.path.join(dev_path, project_name)):
|
||||
project_name = f"MyNewProject{project_count}"
|
||||
project_count += 1
|
||||
folder_dialog.selectFile(project_name)
|
||||
project_folder = None
|
||||
if folder_dialog.exec():
|
||||
project_folder = folder_dialog.selectedFiles()
|
||||
if project_folder:
|
||||
if engine_template.create_project(dev_path, project_folder[0], create_project_item[1]) == 0:
|
||||
# Success
|
||||
self.add_new_project(project_folder[0], validate=False)
|
||||
msg_box = QMessageBox(parent=self.dialog)
|
||||
msg_box.setWindowTitle("O3DE")
|
||||
msg_box.setText(f"Project {os.path.basename(os.path.normpath(project_folder[0]))} created."
|
||||
" Build your\nnew project before hitting OK to launch.")
|
||||
msg_box.exec()
|
||||
return
|
||||
|
||||
def get_display_name(self, project_path: str) -> str:
|
||||
"""
|
||||
Returns the project path in the format to be displayed in the projects MRU list
|
||||
:param project_path: Path to the project folder
|
||||
:return: Formatted path
|
||||
"""
|
||||
return f'{os.path.basename(os.path.normpath(project_path))} ({project_path})'
|
||||
|
||||
def add_projects(self, new_list: List[str]) -> None:
|
||||
"""
|
||||
Attempt to add a list of known projects. Performs validation first that the supplied folder appears valid and
|
||||
silently drops invalid folders - these can simply be folders which were previously valid and are in the MRU list
|
||||
but have been moved or deleted. Used both when loading the MRU list or when a user browses or creates a new
|
||||
project
|
||||
:param new_list: List of full paths to projects to add
|
||||
:return: None
|
||||
"""
|
||||
new_display_items = []
|
||||
new_display_paths = []
|
||||
for this_item in new_list:
|
||||
if self.is_project_folder(this_item) and this_item not in self.displayed_projects:
|
||||
self.displayed_projects.append(this_item)
|
||||
new_display_items.append(self.get_display_name(this_item))
|
||||
new_display_paths.append(this_item)
|
||||
# Storing the full path in the tooltip, if this is altered we need to store this elsewhere
|
||||
# as it's used when selecting a project to open
|
||||
|
||||
for this_slot in range(len(new_display_items)):
|
||||
self.project_list_box.addItem(new_display_items[this_slot])
|
||||
self.project_list_box.setItemData(self.project_list_box.count() - 1, new_display_paths[this_slot],
|
||||
Qt.ToolTipRole)
|
||||
|
||||
def update_mru_list(self, used_project: str) -> None:
|
||||
"""
|
||||
Promote a supplied project name to the "most recent" in a given MRU list.
|
||||
:param used_project: path to project to promote
|
||||
:param file_path: path to mru list file
|
||||
:return: None
|
||||
"""
|
||||
used_project = os.path.normpath(used_project)
|
||||
if not os.path.exists(os.path.dirname(self.mru_file_path)):
|
||||
os.makedirs(os.path.dirname(self.mru_file_path), exist_ok=True)
|
||||
mru_data = {}
|
||||
try:
|
||||
with open(self.mru_file_path, 'r') as mru_file:
|
||||
mru_data = json.loads(mru_file.read())
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
except json.JSONDecodeError:
|
||||
pass
|
||||
|
||||
recent_list = mru_data.get('Projects', [])
|
||||
recent_list = [item for item in recent_list if item.get('Path') != used_project and
|
||||
self.is_project_folder(item.get('Path'))]
|
||||
|
||||
new_list = [{'Path': used_project}]
|
||||
new_list.extend(recent_list)
|
||||
|
||||
mru_data['Projects'] = new_list
|
||||
try:
|
||||
with open(self.mru_file_path, 'w') as mru_file:
|
||||
mru_file.write(json.dumps(mru_data, indent=1))
|
||||
except PermissionError as e:
|
||||
logger.warning(f"Failed to write {self.mru_file_path} with error {e}")
|
||||
|
||||
def get_mru_list(self) -> List[str]:
|
||||
"""
|
||||
Retrive the current MRU list. Does not perform validation that the projects still appear valid
|
||||
:return: list of full path strings to project folders
|
||||
"""
|
||||
if not os.path.exists(os.path.dirname(self.mru_file_path)):
|
||||
return []
|
||||
try:
|
||||
with open(self.mru_file_path, 'r') as mru_file:
|
||||
mru_data = json.loads(mru_file.read())
|
||||
except FileNotFoundError:
|
||||
return []
|
||||
except json.JSONDecodeError:
|
||||
logger.error(f'MRU list at {self.mru_file_path} is not valid JSON')
|
||||
return []
|
||||
|
||||
recent_list = mru_data.get('Projects', [])
|
||||
return [item.get('Path') for item in recent_list if item.get('Path') is not None]
|
||||
|
||||
@Slot(str)
|
||||
def handle_log_message(self, message: str) -> None:
|
||||
"""
|
||||
Signal handler for messages from the logger. Displays the most recent warning/error
|
||||
:param message: formatted log message from DialogLoggerSignaller
|
||||
:return:
|
||||
"""
|
||||
if not self.log_display:
|
||||
return
|
||||
self.log_display.setText(message)
|
||||
self.log_display.setToolTip(message)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
dialog_app = QApplication(sys.argv)
|
||||
my_dialog = ProjectDialog()
|
||||
dialog_app.exec_()
|
||||
sys.exit(0)
|
||||
@@ -0,0 +1,60 @@
|
||||
#
|
||||
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
||||
# its licensors.
|
||||
#
|
||||
# For complete copyright and license terms please see the LICENSE at the root of this
|
||||
# distribution (the "License"). All use of this software is governed by the License,
|
||||
# or, if provided, by the license below or the license accompanying this file. Do not
|
||||
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
#
|
||||
|
||||
import os
|
||||
import logging
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
logger = logging.getLogger()
|
||||
|
||||
pyside_initialized = False
|
||||
old_env = os.environ.copy()
|
||||
|
||||
# Helper to extend OS PATH for pyside to locate our QT binaries based on our build folder
|
||||
def add_pyside_environment(bin_path):
|
||||
if is_pyside_ready():
|
||||
# No need to reinitialize currently
|
||||
logger.info("Pyside environment already initialized")
|
||||
return
|
||||
global old_env
|
||||
old_env = os.environ.copy()
|
||||
binaries_path = Path(os.path.normpath(bin_path))
|
||||
platforms_path = binaries_path.joinpath("platforms")
|
||||
os.environ["QT_QPA_PLATFORM_PLUGIN_PATH"] = str(platforms_path)
|
||||
|
||||
path = os.environ['PATH']
|
||||
|
||||
new_path = os.pathsep.join([str(binaries_path), str(platforms_path), path])
|
||||
os.environ['PATH'] = new_path
|
||||
|
||||
global pyside_initialized
|
||||
pyside_initialized = True
|
||||
|
||||
|
||||
def is_pyside_ready():
|
||||
return pyside_initialized
|
||||
|
||||
|
||||
def is_configuration_valid(workspace):
|
||||
return os.path.basename(workspace.paths.build_directory()) != "debug"
|
||||
|
||||
|
||||
def uninstall_env():
|
||||
if not is_pyside_ready():
|
||||
logger.warning("Pyside not initialized")
|
||||
return os.environ
|
||||
|
||||
global old_env
|
||||
if old_env.get("QT_QPA_PLATFORM_PLUGIN_PATH"):
|
||||
old_env.pop("QT_QPA_PLATFORM_PLUGIN_PATH")
|
||||
os.environ = old_env
|
||||
return old_env
|
||||
@@ -0,0 +1,10 @@
|
||||
#
|
||||
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
||||
# its licensors.
|
||||
#
|
||||
# For complete copyright and license terms please see the LICENSE at the root of this
|
||||
# distribution (the "License"). All use of this software is governed by the License,
|
||||
# or, if provided, by the license below or the license accompanying this file. Do not
|
||||
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
#
|
||||
@@ -0,0 +1,234 @@
|
||||
#
|
||||
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
||||
# its licensors.
|
||||
#
|
||||
# For complete copyright and license terms please see the LICENSE at the root of this
|
||||
# distribution (the "License"). All use of this software is governed by the License,
|
||||
# or, if provided, by the license below or the license accompanying this file. Do not
|
||||
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
#
|
||||
|
||||
import pytest
|
||||
import os
|
||||
import sys
|
||||
import tempfile
|
||||
import logging
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
logger = logging.getLogger()
|
||||
|
||||
# Code lives one folder above
|
||||
projects_path = os.path.realpath(os.path.join(os.path.dirname(__file__), '..'))
|
||||
sys.path.append(projects_path)
|
||||
|
||||
from pyside import add_pyside_environment, is_configuration_valid
|
||||
from ly_test_tools import WINDOWS
|
||||
|
||||
project_marker_file = "project.json"
|
||||
|
||||
class ProjectHelper:
|
||||
def __init__(self):
|
||||
self._temp_directory = tempfile.TemporaryDirectory()
|
||||
self.temp_project_root = self._temp_directory.name
|
||||
self.temp_file_dir = os.path.join(self.temp_project_root, "o3de")
|
||||
|
||||
self.application = None
|
||||
self.dialog = None
|
||||
|
||||
if not os.path.exists(self.temp_file_dir):
|
||||
os.makedirs(self.temp_file_dir)
|
||||
|
||||
def create_empty_projects(self):
|
||||
self.project_1_dir = os.path.join(self.temp_project_root, "Project1")
|
||||
if not os.path.exists(self.project_1_dir):
|
||||
os.makedirs(self.project_1_dir)
|
||||
with open(os.path.join(self.project_1_dir,project_marker_file), 'w') as marker_file:
|
||||
marker_file.write("{}")
|
||||
self.project_2_dir = os.path.join(self.temp_project_root, "Project2")
|
||||
if not os.path.exists(self.project_2_dir):
|
||||
os.makedirs(self.project_2_dir)
|
||||
with open(os.path.join(self.project_2_dir, project_marker_file), 'w') as marker_file:
|
||||
marker_file.write("{}")
|
||||
self.project_3_dir = os.path.join(self.temp_project_root, "Project3")
|
||||
if not os.path.exists(self.project_3_dir):
|
||||
os.makedirs(self.project_3_dir)
|
||||
with open(os.path.join(self.project_3_dir, project_marker_file), 'w') as marker_file:
|
||||
marker_file.write("{}")
|
||||
self.invalid_project_dir = os.path.join(self.temp_project_root, "InvalidProject")
|
||||
|
||||
|
||||
def setup_dialog_test(self, workspace):
|
||||
add_pyside_environment(workspace.paths.build_directory())
|
||||
|
||||
if not is_configuration_valid(workspace):
|
||||
# This is essentially skipif debug. Our debug tests use our profile version of python, but that means we'd
|
||||
# need to use the profile version of PySide which works with the profile QT libs which aren't in the debug
|
||||
# folder we've built.
|
||||
return None
|
||||
from PySide2.QtWidgets import QApplication, QMessageBox
|
||||
|
||||
if QApplication.instance():
|
||||
self.application = QApplication.instance()
|
||||
else:
|
||||
self.application = QApplication(sys.argv)
|
||||
assert self.application
|
||||
|
||||
from projects import ProjectDialog
|
||||
|
||||
try:
|
||||
self.dialog = ProjectDialog(settings_folder=self.temp_file_dir)
|
||||
return self.dialog
|
||||
except Exception as e:
|
||||
logger.error(f'Failed to create ProjectDialog with error {e}')
|
||||
return None
|
||||
|
||||
def create_project_from_template(self, project_name) -> bool:
|
||||
"""
|
||||
Uses the dialog to create a temporary project based on the first template found
|
||||
:param project_name: Name of project to create. Will be created under temp_project_root
|
||||
:return: True for Success, False for failure
|
||||
"""
|
||||
from PySide2.QtWidgets import QWidget, QFileDialog
|
||||
from projects import ProjectDialog
|
||||
|
||||
QWidget.exec = MagicMock()
|
||||
self.dialog.create_project_handler()
|
||||
QWidget.exec.assert_called_once()
|
||||
|
||||
assert len(self.dialog.project_templates), 'Failed to find any project templates'
|
||||
ProjectDialog.get_selected_project_template = MagicMock(return_value=self.dialog.project_templates[0])
|
||||
|
||||
QFileDialog.exec = MagicMock()
|
||||
create_project_dir = os.path.join(self.temp_project_root, project_name)
|
||||
QFileDialog.selectedFiles = MagicMock(return_value=[create_project_dir])
|
||||
self.dialog.create_project_accepted_handler()
|
||||
assert os.path.isdir(create_project_dir), f"Expected project folder not found at {create_project_dir}"
|
||||
assert QWidget.exec.call_count == 2, "Message box confirming project creation failed to show"
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def project_helper():
|
||||
return ProjectHelper()
|
||||
|
||||
|
||||
@pytest.mark.skipif(not WINDOWS, reason="PySide2 only works on windows currently")
|
||||
@pytest.mark.parametrize('project', ['']) # Workspace wants a project, but this test is not project dependent
|
||||
def test_logger_handler(workspace, project_helper):
|
||||
my_dialog = project_helper.setup_dialog_test(workspace)
|
||||
if not my_dialog:
|
||||
return
|
||||
|
||||
from PySide2.QtWidgets import QMessageBox
|
||||
QMessageBox.warning = MagicMock()
|
||||
logger.error(f'Testing logger')
|
||||
QMessageBox.warning.assert_called_once()
|
||||
|
||||
|
||||
@pytest.mark.skipif(not WINDOWS, reason="PySide2 only works on windows currently")
|
||||
@pytest.mark.parametrize('project', ['']) # Workspace wants a project, but this test is not project dependent
|
||||
def test_mru_list(workspace, project_helper):
|
||||
my_dialog = project_helper.setup_dialog_test(workspace)
|
||||
if not my_dialog:
|
||||
return
|
||||
|
||||
project_helper.create_empty_projects()
|
||||
|
||||
from PySide2.QtWidgets import QMessageBox
|
||||
|
||||
mru_list = my_dialog.get_mru_list()
|
||||
assert len(mru_list) == 0, f'MRU list unexpectedly had entries: {mru_list}'
|
||||
|
||||
QMessageBox.warning = MagicMock()
|
||||
my_dialog.add_new_project('TestProjectInvalid')
|
||||
mru_list = my_dialog.get_mru_list()
|
||||
assert len(mru_list) == 0, f'MRU list unexpectedly added an invalid project : {mru_list}'
|
||||
QMessageBox.warning.assert_called_once()
|
||||
|
||||
my_dialog.add_new_project(project_helper.project_1_dir)
|
||||
mru_list = my_dialog.get_mru_list()
|
||||
assert len(mru_list) == 1, f'MRU list failed to add project at {project_helper.project_1_dir}'
|
||||
|
||||
my_dialog.add_new_project(project_helper.project_1_dir)
|
||||
mru_list = my_dialog.get_mru_list()
|
||||
assert len(mru_list) == 1, f'MRU list added project at {project_helper.project_1_dir} a second time : {mru_list}'
|
||||
|
||||
my_dialog.update_mru_list(project_helper.project_1_dir)
|
||||
mru_list = my_dialog.get_mru_list()
|
||||
assert len(mru_list) == 1, f'MRU list added project at {project_helper.project_1_dir} a second time : {mru_list}'
|
||||
|
||||
my_dialog.add_new_project(project_helper.project_2_dir)
|
||||
mru_list = my_dialog.get_mru_list()
|
||||
assert len(mru_list) == 2, f'MRU list failed to add project at {project_helper.project_2_dir}'
|
||||
|
||||
assert mru_list[0] == project_helper.project_2_dir, f"{project_helper.project_2_dir} wasn't first item"
|
||||
assert mru_list[1] == project_helper.project_1_dir, f"{project_helper.project_1_dir} wasn't second item"
|
||||
|
||||
my_dialog.update_mru_list(project_helper.project_1_dir)
|
||||
mru_list = my_dialog.get_mru_list()
|
||||
assert len(mru_list) == 2, f'MRU list added wrong items {mru_list}'
|
||||
assert mru_list[0] == project_helper.project_1_dir, f"{project_helper.project_1_dir} wasn't first item"
|
||||
assert mru_list[1] == project_helper.project_2_dir, f"{project_helper.project_2_dir} wasn't second item"
|
||||
|
||||
my_dialog.add_new_project(project_helper.invalid_project_dir)
|
||||
mru_list = my_dialog.get_mru_list()
|
||||
assert len(mru_list) == 2, f'MRU list added invalid item {mru_list}'
|
||||
assert mru_list[0] == project_helper.project_1_dir, f"{project_helper.project_1_dir} wasn't first item"
|
||||
assert mru_list[1] == project_helper.project_2_dir, f"{project_helper.project_2_dir} wasn't second item"
|
||||
|
||||
my_dialog.add_new_project(project_helper.project_3_dir)
|
||||
mru_list = my_dialog.get_mru_list()
|
||||
assert len(mru_list) == 3, f'MRU list failed to add {project_helper.project_3_dir} : {mru_list}'
|
||||
assert mru_list[0] == project_helper.project_3_dir, f"{project_helper.project_3_dir} wasn't first item"
|
||||
assert mru_list[1] == project_helper.project_1_dir, f"{project_helper.project_1_dir} wasn't second item"
|
||||
assert mru_list[2] == project_helper.project_2_dir, f"{project_helper.project_2_dir} wasn't third item"
|
||||
|
||||
|
||||
@pytest.mark.skipif(not WINDOWS, reason="PySide2 only works on windows currently")
|
||||
@pytest.mark.parametrize('project', ['']) # Workspace wants a project, but this test is not project dependent
|
||||
def test_create_project(workspace, project_helper):
|
||||
my_dialog = project_helper.setup_dialog_test(workspace)
|
||||
if not my_dialog:
|
||||
return
|
||||
|
||||
project_helper.create_project_from_template("TestCreateProject")
|
||||
|
||||
|
||||
@pytest.mark.skipif(not WINDOWS, reason="PySide2 only works on windows currently")
|
||||
@pytest.mark.parametrize('project', ['']) # Workspace wants a project, but this test is not project dependent
|
||||
def test_add_remove_gems(workspace, project_helper):
|
||||
my_dialog = project_helper.setup_dialog_test(workspace)
|
||||
if not my_dialog:
|
||||
return
|
||||
|
||||
my_project_name = "TestAddRemoveGems"
|
||||
|
||||
project_helper.create_project_from_template(my_project_name)
|
||||
my_project_path = os.path.join(project_helper.temp_project_root, my_project_name)
|
||||
|
||||
from PySide2.QtWidgets import QWidget, QFileDialog
|
||||
from projects import ProjectDialog
|
||||
|
||||
assert my_dialog.path_for_selection() == my_project_path, "Gems project not selected"
|
||||
QWidget.exec = MagicMock()
|
||||
my_dialog.manage_gems_handler()
|
||||
assert my_dialog.manage_gems_dialog, "No gem management dialog created"
|
||||
QWidget.exec.assert_called_once()
|
||||
|
||||
assert len(my_dialog.gems_list), 'Failed to find any gems'
|
||||
my_test_gem = my_dialog.gems_list[0]
|
||||
my_test_gem_name = my_test_gem.get("Name")
|
||||
my_test_gem_path = my_test_gem.get("Path")
|
||||
my_test_gem_selection = (my_test_gem_name, my_test_gem_path)
|
||||
ProjectDialog.get_selected_add_gems = MagicMock(return_value=[my_test_gem_selection])
|
||||
|
||||
|
||||
assert my_test_gem_name, "No Name set in test gem"
|
||||
assert my_test_gem_name not in my_dialog.project_gem_list, f'Gem {my_test_gem_name} already in project gem list'
|
||||
my_dialog.add_gems_handler()
|
||||
assert my_test_gem_name in my_dialog.project_gem_list, f'Gem {my_test_gem_name} failed to add to gem list'
|
||||
ProjectDialog.get_selected_project_gems = MagicMock(return_value=[my_test_gem_name])
|
||||
my_dialog.remove_gems_handler()
|
||||
assert my_test_gem_name not in my_dialog.project_gem_list, f'Gem {my_test_gem_name} still in project gem list'
|
||||
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
#
|
||||
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
||||
# its licensors.
|
||||
#
|
||||
# For complete copyright and license terms please see the LICENSE at the root of this
|
||||
# distribution (the "License"). All use of this software is governed by the License,
|
||||
# or, if provided, by the license below or the license accompanying this file. Do not
|
||||
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
#
|
||||
|
||||
import pytest
|
||||
import sys
|
||||
import os
|
||||
|
||||
pyside_path = os.path.realpath(os.path.join(os.path.dirname(__file__), '..'))
|
||||
sys.path.append(pyside_path)
|
||||
|
||||
from pyside import add_pyside_environment, is_pyside_ready, is_configuration_valid
|
||||
|
||||
from ly_test_tools import WINDOWS
|
||||
|
||||
import logging
|
||||
logger = logging.getLogger()
|
||||
|
||||
@pytest.mark.skipif(not WINDOWS, reason="PySide2 only works on windows currently")
|
||||
@pytest.mark.parametrize('project', ['']) # Workspace wants a project, but this test is not project dependent
|
||||
def test_add_pyside_environment(workspace):
|
||||
import_failed = False
|
||||
try:
|
||||
import PySide2
|
||||
except ImportError:
|
||||
import_failed = True
|
||||
|
||||
if not import_failed:
|
||||
cur_path = sys.path
|
||||
logger.warning(f"Expected to fail initial import but passed. Sys path was {cur_path}")
|
||||
|
||||
assert is_pyside_ready() is False, "Expected pyside not to be initialized yet"
|
||||
add_pyside_environment(workspace.paths.build_directory())
|
||||
assert is_pyside_ready() is True, "Expected pyside to be initialized yet"
|
||||
|
||||
try:
|
||||
import PySide2
|
||||
if not is_configuration_valid(workspace):
|
||||
return
|
||||
from PySide2.QtWidgets import QApplication
|
||||
except ImportError as e:
|
||||
assert False, f"Failed to import PySide2 with error {e}"
|
||||
try:
|
||||
from PySide2.QtWidgets import QApplication
|
||||
except ImportError as e:
|
||||
assert False, f"Failed to import QApplication from PySide2.QtWidgets with error {e}"
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>createProjectDialog</class>
|
||||
<widget class="QDialog" name="createProjectDialog">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>226</width>
|
||||
<height>288</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Create Project</string>
|
||||
</property>
|
||||
<widget class="QDialogButtonBox" name="okCancel">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>50</x>
|
||||
<y>250</y>
|
||||
<width>171</width>
|
||||
<height>32</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QListView" name="projectTemplates">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>10</x>
|
||||
<y>20</y>
|
||||
<width>211</width>
|
||||
<height>221</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="selectionMode">
|
||||
<enum>QAbstractItemView::SingleSelection</enum>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>10</x>
|
||||
<y>0</y>
|
||||
<width>101</width>
|
||||
<height>16</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Available Templates</string>
|
||||
</property>
|
||||
</widget>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>okCancel</sender>
|
||||
<signal>accepted()</signal>
|
||||
<receiver>createProjectDialog</receiver>
|
||||
<slot>accept()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>248</x>
|
||||
<y>254</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>157</x>
|
||||
<y>274</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>okCancel</sender>
|
||||
<signal>rejected()</signal>
|
||||
<receiver>createProjectDialog</receiver>
|
||||
<slot>reject()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>316</x>
|
||||
<y>260</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>286</x>
|
||||
<y>274</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
</ui>
|
||||
@@ -0,0 +1,165 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>addGemsDialog</class>
|
||||
<widget class="QDialog" name="addGemsDialog">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>635</width>
|
||||
<height>327</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Manage Gems</string>
|
||||
</property>
|
||||
<widget class="QDialogButtonBox" name="close">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>460</x>
|
||||
<y>290</y>
|
||||
<width>171</width>
|
||||
<height>32</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Close</set>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QListView" name="addGemsList">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>380</x>
|
||||
<y>50</y>
|
||||
<width>250</width>
|
||||
<height>221</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="selectionMode">
|
||||
<enum>QAbstractItemView::ExtendedSelection</enum>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QListView" name="projectGems">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>10</x>
|
||||
<y>50</y>
|
||||
<width>250</width>
|
||||
<height>221</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="selectionMode">
|
||||
<enum>QAbstractItemView::ExtendedSelection</enum>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>380</x>
|
||||
<y>30</y>
|
||||
<width>91</width>
|
||||
<height>16</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Available Gems</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>10</x>
|
||||
<y>30</y>
|
||||
<width>71</width>
|
||||
<height>16</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Enabled Gems</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QPushButton" name="removeGemsButton">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>267</x>
|
||||
<y>130</y>
|
||||
<width>105</width>
|
||||
<height>23</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Remove Gems >></string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QPushButton" name="addGemsButton">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>267</x>
|
||||
<y>100</y>
|
||||
<width>105</width>
|
||||
<height>23</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string><< Add Gems</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>10</x>
|
||||
<y>5</y>
|
||||
<width>241</width>
|
||||
<height>16</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Adding new Gems may require rebuilding</string>
|
||||
</property>
|
||||
</widget>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>close</sender>
|
||||
<signal>accepted()</signal>
|
||||
<receiver>addGemsDialog</receiver>
|
||||
<slot>accept()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>248</x>
|
||||
<y>254</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>157</x>
|
||||
<y>274</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>close</sender>
|
||||
<signal>rejected()</signal>
|
||||
<receiver>addGemsDialog</receiver>
|
||||
<slot>reject()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>316</x>
|
||||
<y>260</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>286</x>
|
||||
<y>274</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
</ui>
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:113be7ded1969e0d535722382b6d3730eed052a10430cf23804ae7f20414b999
|
||||
size 108278
|
||||
@@ -0,0 +1,167 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>Dialog</class>
|
||||
<widget class="QDialog" name="Dialog">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>464</width>
|
||||
<height>136</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
||||
<horstretch>1</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>O3DE</string>
|
||||
</property>
|
||||
<property name="whatsThis">
|
||||
<string>Select and manage your projects for O3DE</string>
|
||||
</property>
|
||||
<widget class="QDialogButtonBox" name="okCancel">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>290</x>
|
||||
<y>100</y>
|
||||
<width>171</width>
|
||||
<height>31</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QComboBox" name="projectListBox">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>10</x>
|
||||
<y>30</y>
|
||||
<width>451</width>
|
||||
<height>31</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="whatsThis">
|
||||
<string>Current project to launch or manage gems for.</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QPushButton" name="createProjectButton">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>10</x>
|
||||
<y>70</y>
|
||||
<width>91</width>
|
||||
<height>23</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="whatsThis">
|
||||
<string>Create a new O3DE project from a pre configured template.</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Create New</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>10</x>
|
||||
<y>10</y>
|
||||
<width>47</width>
|
||||
<height>13</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Project</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QPushButton" name="browseProjectsButton">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>110</x>
|
||||
<y>70</y>
|
||||
<width>91</width>
|
||||
<height>23</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="whatsThis">
|
||||
<string>Browse for an existing O3DE project.</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Browse</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QPushButton" name="manageGemsButton">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>350</x>
|
||||
<y>70</y>
|
||||
<width>91</width>
|
||||
<height>23</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="whatsThis">
|
||||
<string>Add or remove gems from your selected project. Gems add and remove additional assets and features to projects.</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Manage Gems</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QLabel" name="logDisplay">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>5</x>
|
||||
<y>110</y>
|
||||
<width>275</width>
|
||||
<height>16</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="textInteractionFlags">
|
||||
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
|
||||
</property>
|
||||
</widget>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>okCancel</sender>
|
||||
<signal>accepted()</signal>
|
||||
<receiver>Dialog</receiver>
|
||||
<slot>accept()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>248</x>
|
||||
<y>254</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>157</x>
|
||||
<y>274</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>okCancel</sender>
|
||||
<signal>rejected()</signal>
|
||||
<receiver>Dialog</receiver>
|
||||
<slot>reject()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>316</x>
|
||||
<y>260</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>286</x>
|
||||
<y>274</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
</ui>
|
||||
Reference in New Issue
Block a user