/* * 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. * */ // Original file Copyright Crytek GMBH or its affiliates, used under license. // Description : Utility classes used by Editor. #ifndef CRYINCLUDE_EDITOR_UTIL_EDITORUTILS_H #define CRYINCLUDE_EDITOR_UTIL_EDITORUTILS_H #pragma once #include <IXml.h> #include "Util/FileUtil.h" #include <Cry_Color.h> //! Typedef for quaternion. //typedef CryQuat Quat; #include <QColor> #include <QDataStream> #include <QGuiApplication> #include <QSet> #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: AzWarningAbsorber(const char* window); ~AzWarningAbsorber(); bool OnPreWarning(const char* window, const char* fileName, int line, const char* func, const char* message) override; AZStd::string m_window; }; }; ////////////////////////////////////////////////////////////////////////// // 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 == NULL) { 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 NULL; } 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; }; static 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; } static 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; } static 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