You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
o3de/Code/Editor/Util/EditorUtils.h

707 lines
19 KiB
C++

/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
// Description : Utility classes used by Editor.
#ifndef CRYINCLUDE_EDITOR_UTIL_EDITORUTILS_H
#define CRYINCLUDE_EDITOR_UTIL_EDITORUTILS_H
#pragma once
#include <CryCommon/platform.h>
#include <IXml.h>
#include "Util/FileUtil.h"
#include <Cry_Color.h>
#include <CryCommon/ISystem.h>
//! Typedef for quaternion.
//typedef CryQuat Quat;
#include <QColor>
#include <QDataStream>
#include <QGuiApplication>
#include <QSet>
#include <Include/SandboxAPI.h>
#include <AzCore/Debug/TraceMessageBus.h>
#ifndef MIN
#define MIN(a, b) ((a) < (b) ? (a) : (b))
#endif
#ifndef MAX
#define MAX(a, b) ((a) > (b) ? (a) : (b))
#endif
#ifdef LoadCursor
#undef LoadCursor
#endif
#define LINE_EPS (0.00001f)
template <typename T, size_t N>
char (&ArraySizeHelper(T (&array)[N]))[N];
#define arraysize(array) (sizeof(ArraySizeHelper(array)))
/// Some preprocessor utils
/// http://altdevblogaday.com/2011/07/12/abusing-the-c-preprocessor/
#define JOIN(x, y) JOIN2(x, y)
#define JOIN2(x, y) x##y
#define LIST_0(x)
#define LIST_1(x) x##1
#define LIST_2(x) LIST_1(x), x##2
#define LIST_3(x) LIST_2(x), x##3
#define LIST_4(x) LIST_3(x), x##4
#define LIST_5(x) LIST_4(x), x##5
#define LIST_6(x) LIST_5(x), x##6
#define LIST_7(x) LIST_6(x), x##7
#define LIST_8(x) LIST_7(x), x##8
#define LIST(cnt, x) JOIN(LIST_, cnt)(x)
//! Checks heap for errors.
struct HeapCheck
{
//! Runs consistency checks on the heap.
static void Check(const char* file, int line);
};
#ifdef _DEBUG
#define HEAP_CHECK HeapCheck::Check(__FILE__, __LINE__);
#else
#define HEAP_CHECK
#endif
#define MAKE_SURE(x, action) { if (!(x)) { assert(0 && #x); action; } \
}
namespace EditorUtils
{
// Class to create scoped variable value.
template<typename TType>
class TScopedVariableValue
{
public:
//Relevant for containers, should not be used manually.
TScopedVariableValue()
: m_pVariable(nullptr)
{
}
// Main constructor.
TScopedVariableValue(TType& tVariable, const TType& tConstructValue, const TType& tDestructValue)
: m_pVariable(&tVariable)
, m_tConstructValue(tConstructValue)
, m_tDestructValue(tDestructValue)
{
*m_pVariable = m_tConstructValue;
}
// Transfers ownership.
TScopedVariableValue(TScopedVariableValue& tInput)
: m_pVariable(tInput.m_pVariable)
, m_tConstructValue(tInput.m_tConstructValue)
, m_tDestructValue(tInput.m_tDestructValue)
{
// I'm not sure if anyone should use this but for now I'm adding one.
tInput.m_pVariable = nullptr;
}
// Move constructor: needed to use CreateScopedVariable, and transfers ownership.
TScopedVariableValue(TScopedVariableValue&& tInput)
{
std::move(m_pVariable, tInput.m_tVariable);
std::move(m_tConstructValue, tInput.m_tConstructValue);
std::move(m_tDestructValue, tInput.m_tDestructtValue);
}
// Applies the scoping exit, if the variable is valid.
virtual ~TScopedVariableValue()
{
if (m_pVariable)
{
*m_pVariable = m_tDestructValue;
}
}
// Transfers ownership, if not self assignment.
TScopedVariableValue& operator=(TScopedVariableValue& tInput)
{
// I'm not sure if this makes sense to exist... but for now I'm adding one.
if (this != &tInput)
{
m_pVariable = tInput.m_pVariable;
m_tConstructValue = tInput.m_tConstructValue;
m_tDestructValue = tInput.m_tDestructValue;
tInput.m_pVariable = nullptr;
}
return *this;
}
protected:
TType* m_pVariable;
TType m_tConstructValue;
TType m_tDestructValue;
};
// Helper function to create scoped variable.
// Ideal usage: auto tMyVariable=CreateScoped(tContainedVariable,tConstructValue,tDestructValue);
template<typename TType>
TScopedVariableValue<TType> CreateScopedVariableValue(TType& tVariable, const TType& tConstructValue, const TType& tDestructValue)
{
return TScopedVariableValue<TType>(tVariable, tConstructValue, tDestructValue);
}
class AzWarningAbsorber
: public AZ::Debug::TraceMessageBus::Handler
{
public:
SANDBOX_API AzWarningAbsorber(const char* window);
SANDBOX_API ~AzWarningAbsorber();
bool OnPreWarning(const char* window, const char* fileName, int line, const char* func, const char* message) override;
AZStd::string m_window;
};
namespace LevelFile
{
//! Retrieve old cry level file extension (With prepending '.')
const char* GetOldCryFileExtension();
//! Retrieve default level file extension (With prepending '.')
const char* GetDefaultFileExtension();
}
};
//////////////////////////////////////////////////////////////////////////
// XML Helper functions.
//////////////////////////////////////////////////////////////////////////
namespace XmlHelpers
{
SANDBOX_API inline XmlNodeRef CreateXmlNode(const char* sTag)
{
return GetISystem()->CreateXmlNode(sTag);
}
inline bool SaveXmlNode(IFileUtil* pFileUtil, XmlNodeRef node, const char* filename)
{
if (!pFileUtil->OverwriteFile(filename))
{
return false;
}
return node->saveToFile(filename);
}
SANDBOX_API inline XmlNodeRef LoadXmlFromFile(const char* fileName)
{
return GetISystem()->LoadXmlFromFile(fileName);
}
SANDBOX_API inline XmlNodeRef LoadXmlFromBuffer(const char* buffer, size_t size, bool suppressWarnings = false)
{
return GetISystem()->LoadXmlFromBuffer(buffer, size, false, suppressWarnings);
}
}
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
// Drag Drop helper functions
//////////////////////////////////////////////////////////////////////////
namespace EditorDragDropHelpers
{
inline QString GetAnimationNameClipboardFormat()
{
return QStringLiteral("application/x-animation-browser-copy");
}
inline QString GetFragmentClipboardFormat()
{
return QStringLiteral("application/x-preview-fragment-properties");
}
}
//////////////////////////////////////////////////////////////////////////
/*!
* StdMap Wraps std::map to provide easier to use interface.
*/
template <class Key, class Value>
struct StdMap
{
private:
typedef std::map<Key, Value> Map;
Map m;
public:
typedef typename Map::iterator Iterator;
typedef typename Map::const_iterator ConstIterator;
void Insert(const Key& key, const Value& value) { m[key] = value; }
int GetCount() const { return m.size(); };
bool IsEmpty() const { return m.empty(); };
void Clear() { m.clear(); }
int Erase(const Key& key) { return m.erase(key); };
Value& operator[](const Key& key) { return m[key]; };
bool Find(const Key& key, Value& value) const
{
ConstIterator it = m.find(key);
if (it == m.end())
{
return false;
}
value = it->second;
return true;
}
Iterator Find(const Key& key) { return m.find(key); }
ConstIterator Find(const Key& key) const { return m.find(key); }
bool FindKeyByValue(const Value& value, Key& key) const
{
for (ConstIterator it = m.begin(); it != m.end(); ++it)
{
if (it->second == value)
{
key = it->first;
return true;
}
}
return false;
}
Iterator Begin() { return m.begin(); };
Iterator End() { return m.end(); };
ConstIterator Begin() const { return m.begin(); };
ConstIterator End() const { return m.end(); };
void GetAsVector(std::vector<Value>& array) const
{
array.resize(m.size());
int i = 0;
for (ConstIterator it = m.begin(); it != m.end(); ++it)
{
array[i++] = it->second;
}
}
};
//////////////////////////////////////////////////////////////////////////
//
// Convert String representation of color to RGB integer value.
//
//////////////////////////////////////////////////////////////////////////
inline QColor String2Color(const QString& val)
{
unsigned int r = 0, g = 0, b = 0;
int res = 0;
res = azsscanf(val.toUtf8().data(), "R:%d,G:%d,B:%d", &r, &g, &b);
if (res != 3)
{
res = azsscanf(val.toUtf8().data(), "R:%d G:%d B:%d", &r, &g, &b);
}
if (res != 3)
{
res = azsscanf(val.toUtf8().data(), "%d,%d,%d", &r, &g, &b);
}
if (res != 3)
{
res = azsscanf(val.toUtf8().data(), "%d %d %d", &r, &g, &b);
}
if (res != 3)
{
azsscanf(val.toUtf8().data(), "%x", &r);
return r;
}
return QColor(r, g, b);
}
// Converts QColor to Vector.
inline Vec3 Rgb2Vec(const QColor& color)
{
return Vec3(aznumeric_cast<float>(color.redF()), aznumeric_cast<float>(color.greenF()), aznumeric_cast<float>(color.blueF()));
}
// Converts QColor to ColorF.
inline ColorF Rgb2ColorF(const QColor& color)
{
return ColorF(aznumeric_cast<float>(color.redF()), aznumeric_cast<float>(color.greenF()), aznumeric_cast<float>(color.blueF()), 1.0f);
}
// Converts QColor to Vector.
inline QColor Vec2Rgb(const Vec3& color)
{
return QColor(aznumeric_cast<int>(color.x * 255), aznumeric_cast<int>(color.y * 255), aznumeric_cast<int>(color.z * 255));
}
// Converts ColorF to QColor.
inline QColor ColorF2Rgb(const ColorF& color)
{
return QColor(aznumeric_cast<int>(color.r * 255), aznumeric_cast<int>(color.g * 255), aznumeric_cast<int>(color.b * 255));
}
//////////////////////////////////////////////////////////////////////////
// Tokenize string.
//////////////////////////////////////////////////////////////////////////
inline QString TokenizeString(const QString& s, LPCSTR pszTokens, int& iStart)
{
assert(iStart >= 0);
QByteArray str = s.toUtf8();
if (pszTokens == nullptr)
{
return str;
}
auto pszPlace = str.begin() + iStart;
auto pszEnd = str.end();
if (pszPlace < pszEnd)
{
int nIncluding = (int)strspn(pszPlace, pszTokens);
;
if ((pszPlace + nIncluding) < pszEnd)
{
pszPlace += nIncluding;
int nExcluding = (int)strcspn(pszPlace, pszTokens);
int iFrom = iStart + nIncluding;
int nUntil = nExcluding;
iStart = iFrom + nUntil + 1;
return (str.mid(iFrom, nUntil));
}
}
// return empty string, done tokenizing
iStart = -1;
return "";
}
// This template function will join strings from a vector into a single string, using a separator char
template<class T>
inline void JoinStrings(const QList<T>& rStrings, QString& rDestStr, char aSeparator = ',')
{
for (size_t i = 0, iCount = rStrings.size(); i < iCount; ++i)
{
rDestStr += rStrings[i];
if (i < iCount - 1)
{
rDestStr += aSeparator;
}
}
}
// This function will split a string containing separated strings, into a vector of strings
// better version of TokenizeString
inline void SplitString(const QString& rSrcStr, QStringList& rDestStrings, char aSeparator = ',')
{
int crtPos = 0, lastPos = 0;
while (true)
{
crtPos = rSrcStr.indexOf(aSeparator, lastPos);
if (-1 == crtPos)
{
crtPos = rSrcStr.length();
if (crtPos != lastPos)
{
rDestStrings.push_back(rSrcStr.mid(lastPos, crtPos - lastPos));
}
break;
}
else
{
if (crtPos != lastPos)
{
rDestStrings.push_back(rSrcStr.mid(lastPos, crtPos - lastPos));
}
}
lastPos = crtPos + 1;
}
}
// Format unsigned number to string with 1000s separator
inline QString FormatWithThousandsSeperator(const unsigned int number)
{
QString string;
string = QString::number(number);
for (int p = string.length() - 3; p > 0; p -= 3)
{
string.insert(p, ',');
}
return string;
}
void FormatFloatForUI(QString& str, int significantDigits, double value);
//////////////////////////////////////////////////////////////////////////
// Simply sub string searching case insensitive.
//////////////////////////////////////////////////////////////////////////
inline const char* strstri(const char* pString, const char* pSubstring)
{
int i, j, k;
for (i = 0; pString[i]; i++)
{
for (j = i, k = 0; tolower(pString[j]) == tolower(pSubstring[k]); j++, k++)
{
if (!pSubstring[k + 1])
{
return (pString + i);
}
}
}
return nullptr;
}
inline bool CheckVirtualKey(Qt::MouseButton button)
{
return (qApp->property("pressedMouseButtons").toInt() & button) != 0;
}
inline bool CheckVirtualKey(Qt::Key virtualKey)
{
return qApp->property("pressedKeys").value<QSet<int>>().contains(virtualKey);
}
class QColor;
QColor ColorLinearToGamma(ColorF col);
ColorF ColorGammaToLinear(const QColor& col);
QColor ColorToQColor(uint32 color);
class QCursor;
class QPixmap;
template<typename T>
class QVector;
/*! Collection of Utility MFC functions.
*/
struct CMFCUtils
{
static QCursor LoadCursor(unsigned int nIDResource, int hotX = -1, int hotY = -1);
};
#ifndef _AFX
class CArchive : public QDataStream
{
public:
enum Mode
{
load,
store
};
CArchive(QIODevice* device, Mode mode)
: QDataStream(device)
, m_mode(mode)
{
setByteOrder(LittleEndian);
}
bool IsLoading() const
{
return m_mode == load;
}
bool IsStoring() const
{
return m_mode == store;
}
uint Read(void* buffer, uint size)
{
QDataStream::readRawData(reinterpret_cast<char*>(buffer), size);
return size;
}
uint Write(void* buffer, uint size)
{
// There is a bug in QT with writing files larger than 32MB. It separates
// the write into 32MB blocks, but doesn't write the last block correctly.
// To deal with this, we'll separate into blocks here so QT doesn't have to.
// QT bug in qfileengine_win.cpp line 434. Block size is calculated once and always
// used as the amount of data to write, but for the last block, unless there is exactly
// block size left to write, the actual remaining amount needs to be written, not the
// whole block size. This will cause WriteFile() to either write garbage to the file or
// attempt to get into memory it doesn't have access to.
const uint blockSize = 1024 * 1024 * 32; // This is the size QT uses for blocks.
uint totalBytesLeftToWrite = size;
uint totalBytesWritten = 0;
while (totalBytesLeftToWrite > 0)
{
uint bytesToWrite = AZ::GetMin(blockSize, totalBytesLeftToWrite);
uint bytesWritten = QDataStream::writeRawData(reinterpret_cast<char*>(buffer) + totalBytesWritten, bytesToWrite);
totalBytesLeftToWrite -= bytesWritten;
totalBytesWritten += bytesWritten;
// If something goes wrong, stop.
if (bytesWritten != bytesToWrite)
{
break;
}
}
return totalBytesWritten;
}
private:
Mode m_mode;
};
inline quint64 readStringLength(CArchive& ar, int& charSize)
{
// This is legacy MFC converted code. It used to use AfxReadStringLength() which has a complicated
// decoding pattern.
// The basic algorithm is that it reads in an 8 bit int, and if the length is less than 2^8,
// then that's the length. Next it reads in a 16 bit int, and if the length is less than 2^16,
// then that's the length. It does the same thing for 32 bit values and finally for 64 bit values.
// The 16 bit length also indicates whether or not it's a UCS2 / wide-char Windows string, if it's
// 0xfffe, but that comes after the first byte marker indicating there's a 16 bit length value.
// So, if the first 3 bytes are: 0xFF, 0xFF, 0xFE, it's a 2 byte string being read in, and the real
// length follows those 3 bytes (which may still be an 8, 16, or 32 bit length).
// default to one byte strings
charSize = 1;
quint8 len8;
ar >> len8;
if (len8 < 0xff)
{
return len8;
}
quint16 len16;
ar >> len16;
if (len16 == 0xfffe)
{
charSize = 2;
ar >> len8;
if (len8 < 0xff)
{
return len8;
}
ar >> len16;
}
if (len16 < 0xffff)
{
return len16;
}
quint32 len32;
ar >> len32;
if (len32 < 0xffffffff)
{
return len32;
}
quint64 len64;
ar >> len64;
return len64;
}
inline CArchive& operator>>(CArchive& ar, QString& str)
{
int charSize = 1;
auto length = readStringLength(ar, charSize);
QByteArray data = ar.device()->read(length * charSize);
if (charSize == 1)
{
str = QString::fromUtf8(data);
}
else
{
char* raw = data.data();
// check if it's short aligned; if it isn't, we need to copy to a temp buffer
if ((reinterpret_cast<uintptr_t>(raw) & 1) != 0)
{
ushort* shortAlignedData = new ushort[length];
memcpy(shortAlignedData, raw, length * 2);
str = QString::fromUtf16(shortAlignedData, aznumeric_cast<int>(length));
delete[] shortAlignedData;
}
else
{
str = QString::fromUtf16(reinterpret_cast<ushort*>(raw), aznumeric_cast<int>(length));
}
}
return ar;
}
inline CArchive& operator<<(CArchive& ar, const QString& str)
{
// This is written to mimic how MFC archiving worked, which was to
// write markers to indicate the size of the length -
// so a length that will fit into 8 bits takes 8 bits.
// A length that requires more than 8 bits, puts an 8 bit marker (0xff)
// to indicate that the length is greater, then 16 bits for the length.
// If the length requires 32 bits, there's an 8 bit marker (0xff), a
// 16 bit marker (0xffff) and then the 32 bit length.
// Note that the legacy code could also encode to 16 bit Windows wide character
// streams; that isn't necessary though, given that Qt supports Utf-8 out of the
// box and is much less ambiguous on other platforms.
QByteArray data = str.toUtf8();
int length = data.length();
if (length < 255)
{
ar << static_cast<quint8>(length);
}
else if (length < 0xfffe) // 0xfffe instead of 0xffff because 0xfffe indicated Windows wide character strings, which we aren't bothering with anymore
{
ar << static_cast<quint8>(0xff);
ar << static_cast<quint16>(length);
}
else
{
ar << static_cast<quint8>(0xff);
ar << static_cast<quint16>(0xffff);
ar << static_cast<quint32>(length);
}
ar.device()->write(data);
return ar;
}
#endif
#endif // CRYINCLUDE_EDITOR_UTIL_EDITORUTILS_H