diff --git a/Framework/Array.h b/Framework/Array.h new file mode 100644 index 0000000..ab83e77 --- /dev/null +++ b/Framework/Array.h @@ -0,0 +1,98 @@ +#ifndef ARRAY_H +#define ARRAY_H + + +#include "Utilities.h" + + +// std::vector replacement that is cut down but familiar. +// It uses the custom allocators, and is restricted to POD elements only +template +struct PODArrayPOD // A dynamic array of POD type elements which is itself a POD type (so can have multi-dimensional arrays of these). +{ + static_assert(std::is_pod::value, "T must be a POD type."); + + void init() { m_magic = 0x01020304; m_capacity = m_size = 0; m_data = nullptr; } + void deinit() { if (m_data != nullptr) YQ_FREE(m_data); } + + const T& at(size_t a_pos) const { checkInited(); checkIndex(a_pos); return m_data[a_pos]; } + T& at(size_t a_pos) { checkInited(); checkIndex(a_pos); return m_data[a_pos]; } + const T& operator[](size_t a_pos) const { checkInited(); checkIndex(a_pos); return m_data[a_pos]; } + void pop_back() { checkInited(); checkIndex(1); m_size--; } + const T& back() const { checkInited(); return m_data[m_size - 1]; } + T& back() { checkInited(); return m_data[m_size - 1]; } + void push_back(const T& a_val) { checkInited(); if (m_size == m_capacity) ensureSpaceFor(1); m_data[m_size++] = a_val; } + void push(const T& a_val) { checkInited(); push_back(a_val); } + T pop() { checkInited(); pop_back(); return m_data[m_size]; } + void reserve(size_t a_count) { checkInited(); m_capacity = a_count; m_data = YQ_REALLOC_ARRAY(m_data, T, a_count); } + size_t capacity() const { checkInited(); return m_capacity; } + size_t size() const { checkInited(); return m_size; } + T* data() const { checkInited(); return m_data; } + void clear() { checkInited(); m_size = 0; } +//private: // Actually making private would make this no longer a POD type + yqResult checkInited() const { if (BT_IsDebug) YQ_API_PRECONDITION(m_magic == 0x01020304, R_Uninitialized); return R_Okay; } + yqResult checkIndex(size_t a_pos) const { YQ_API_PRECONDITION(a_pos < m_size, R_IndexOutOfRange); return R_Okay; } + void ensureSpaceFor(size_t a_count) { reserve(std::max(m_size + a_count, m_capacity + m_capacity / 2)); } // try to grow by 50% + T* m_data; // Because of this pointer it is not safe to copy DynamicArray objects around. Making it a shared_ptr though stops this being a POD type + size_t m_capacity; + size_t m_size; + uint32_t m_magic; +}; + + +static_assert(std::is_pod >::value, "PODArrayPOD must be a POD type."); + + +// A dynamic array of POD type elements. +// It isn't POD itself, but is instead reference counted so safer to deal with. +template +struct ArrayPOD +{ + static_assert(std::is_pod::value, "T must be a POD type."); + + // Actually not reference counted yet, but safe as objects of this type can't be copied + ArrayPOD& operator=(const ArrayPOD&) = delete; + ArrayPOD(const ArrayPOD&) = delete; + ArrayPOD() { m_capacity = m_size = 0; m_data = nullptr; } + ~ArrayPOD() { if (m_data != nullptr) YQ_FREE(m_data); } + + const T& at(size_t a_pos) const { checkIndex(a_pos); return m_data[a_pos]; } + T& at(size_t a_pos) { checkIndex(a_pos); return m_data[a_pos]; } + const T& operator[](size_t a_pos) const { checkIndex(a_pos); return m_data[a_pos]; } + void pop_back() { checkIndex(1); m_size--; } + const T& back() const { return m_data[m_size - 1]; } + T& back() { return m_data[m_size - 1]; } + void push_back(const T& a_val) { if (m_size == m_capacity) ensureSpaceFor(1); m_data[m_size++] = a_val; } + void push(const T& a_val) { push_back(a_val); } + T pop() { pop_back(); return m_data[m_size]; } + void reserve(size_t a_count) { m_capacity = a_count; m_data = YQ_REALLOC_ARRAY(m_data, T, a_count); } + size_t capacity() const { return m_capacity; } + size_t size() const { return m_size; } + T* data() const { return m_data; } + void clear() { m_size = 0; } +private: + yqResult checkIndex(size_t a_pos) const { YQ_API_PRECONDITION(a_pos < m_size, R_IndexOutOfRange); return R_Okay; } + void ensureSpaceFor(size_t a_count) { reserve(std::max(m_size + a_count, m_capacity + m_capacity / 2)); } // try to grow by 50% + T* m_data; // Because of this pointer it is not safe to copy DynamicArray objects around. Making it a shared_ptr though stops this being a POD type + size_t m_capacity; + size_t m_size; +}; + + +struct PODString +{ +public: + void init() { m_data.init(); } + void deinit() { m_data.deinit(); } + void operator=(const char* a_str) { m_data.clear(); operator+=(a_str); } + void operator+=(const char* a_str) { while (*a_str) m_data.push_back(*a_str++); m_data.push(0); m_data.pop(); } + char* c_str() { return m_data.data(); } +//private: // Actually making private would make this no longer a POD type + PODArrayPOD m_data; +}; + + +static_assert(std::is_pod::value, "PODString must be a POD type."); + + +#endif // ARRAY_H diff --git a/Framework/Array.h b/Framework/Array.h new file mode 100644 index 0000000..ab83e77 --- /dev/null +++ b/Framework/Array.h @@ -0,0 +1,98 @@ +#ifndef ARRAY_H +#define ARRAY_H + + +#include "Utilities.h" + + +// std::vector replacement that is cut down but familiar. +// It uses the custom allocators, and is restricted to POD elements only +template +struct PODArrayPOD // A dynamic array of POD type elements which is itself a POD type (so can have multi-dimensional arrays of these). +{ + static_assert(std::is_pod::value, "T must be a POD type."); + + void init() { m_magic = 0x01020304; m_capacity = m_size = 0; m_data = nullptr; } + void deinit() { if (m_data != nullptr) YQ_FREE(m_data); } + + const T& at(size_t a_pos) const { checkInited(); checkIndex(a_pos); return m_data[a_pos]; } + T& at(size_t a_pos) { checkInited(); checkIndex(a_pos); return m_data[a_pos]; } + const T& operator[](size_t a_pos) const { checkInited(); checkIndex(a_pos); return m_data[a_pos]; } + void pop_back() { checkInited(); checkIndex(1); m_size--; } + const T& back() const { checkInited(); return m_data[m_size - 1]; } + T& back() { checkInited(); return m_data[m_size - 1]; } + void push_back(const T& a_val) { checkInited(); if (m_size == m_capacity) ensureSpaceFor(1); m_data[m_size++] = a_val; } + void push(const T& a_val) { checkInited(); push_back(a_val); } + T pop() { checkInited(); pop_back(); return m_data[m_size]; } + void reserve(size_t a_count) { checkInited(); m_capacity = a_count; m_data = YQ_REALLOC_ARRAY(m_data, T, a_count); } + size_t capacity() const { checkInited(); return m_capacity; } + size_t size() const { checkInited(); return m_size; } + T* data() const { checkInited(); return m_data; } + void clear() { checkInited(); m_size = 0; } +//private: // Actually making private would make this no longer a POD type + yqResult checkInited() const { if (BT_IsDebug) YQ_API_PRECONDITION(m_magic == 0x01020304, R_Uninitialized); return R_Okay; } + yqResult checkIndex(size_t a_pos) const { YQ_API_PRECONDITION(a_pos < m_size, R_IndexOutOfRange); return R_Okay; } + void ensureSpaceFor(size_t a_count) { reserve(std::max(m_size + a_count, m_capacity + m_capacity / 2)); } // try to grow by 50% + T* m_data; // Because of this pointer it is not safe to copy DynamicArray objects around. Making it a shared_ptr though stops this being a POD type + size_t m_capacity; + size_t m_size; + uint32_t m_magic; +}; + + +static_assert(std::is_pod >::value, "PODArrayPOD must be a POD type."); + + +// A dynamic array of POD type elements. +// It isn't POD itself, but is instead reference counted so safer to deal with. +template +struct ArrayPOD +{ + static_assert(std::is_pod::value, "T must be a POD type."); + + // Actually not reference counted yet, but safe as objects of this type can't be copied + ArrayPOD& operator=(const ArrayPOD&) = delete; + ArrayPOD(const ArrayPOD&) = delete; + ArrayPOD() { m_capacity = m_size = 0; m_data = nullptr; } + ~ArrayPOD() { if (m_data != nullptr) YQ_FREE(m_data); } + + const T& at(size_t a_pos) const { checkIndex(a_pos); return m_data[a_pos]; } + T& at(size_t a_pos) { checkIndex(a_pos); return m_data[a_pos]; } + const T& operator[](size_t a_pos) const { checkIndex(a_pos); return m_data[a_pos]; } + void pop_back() { checkIndex(1); m_size--; } + const T& back() const { return m_data[m_size - 1]; } + T& back() { return m_data[m_size - 1]; } + void push_back(const T& a_val) { if (m_size == m_capacity) ensureSpaceFor(1); m_data[m_size++] = a_val; } + void push(const T& a_val) { push_back(a_val); } + T pop() { pop_back(); return m_data[m_size]; } + void reserve(size_t a_count) { m_capacity = a_count; m_data = YQ_REALLOC_ARRAY(m_data, T, a_count); } + size_t capacity() const { return m_capacity; } + size_t size() const { return m_size; } + T* data() const { return m_data; } + void clear() { m_size = 0; } +private: + yqResult checkIndex(size_t a_pos) const { YQ_API_PRECONDITION(a_pos < m_size, R_IndexOutOfRange); return R_Okay; } + void ensureSpaceFor(size_t a_count) { reserve(std::max(m_size + a_count, m_capacity + m_capacity / 2)); } // try to grow by 50% + T* m_data; // Because of this pointer it is not safe to copy DynamicArray objects around. Making it a shared_ptr though stops this being a POD type + size_t m_capacity; + size_t m_size; +}; + + +struct PODString +{ +public: + void init() { m_data.init(); } + void deinit() { m_data.deinit(); } + void operator=(const char* a_str) { m_data.clear(); operator+=(a_str); } + void operator+=(const char* a_str) { while (*a_str) m_data.push_back(*a_str++); m_data.push(0); m_data.pop(); } + char* c_str() { return m_data.data(); } +//private: // Actually making private would make this no longer a POD type + PODArrayPOD m_data; +}; + + +static_assert(std::is_pod::value, "PODString must be a POD type."); + + +#endif // ARRAY_H diff --git a/Framework/Macros.h b/Framework/Macros.h new file mode 100644 index 0000000..6ca8c86 --- /dev/null +++ b/Framework/Macros.h @@ -0,0 +1,111 @@ +#ifndef MACROS_H +#define MACROS_H + + +// +// Various common macros +// +#define YQ_ARRAY_SIZE(x) (sizeof(x)/sizeof((x)[0])) + +#define YQ_EXPAND(x) x + +#define YQ_STRINGIZE_IMPL(val) #val +#define YQ_STRINGIZE(val) YQ_STRINGIZE_IMPL(val) + +#define YQ_CONCATENATE_IMPL(x, y) x##y +#define YQ_CONCATENATE(x, y) YQ_CONCATENATE_IMPL(x, y) + +// If 0 args, it mis-reports a count of 1. Also it only counts up to 10 args. +#define YQ_ARG_COUNT_IMPL(X,A,B,C,D,E,F,G,H,I,J,N,...) N +#define YQ_ARG_COUNT(...) (YQ_EXPAND(YQ_ARG_COUNT_IMPL(_,__VA_ARGS__, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0))) + +#ifdef __COUNTER__ +# define YQ_ANONYMOUS_VARIABLE(str) YQ_CONCATENATE(str, __COUNTER__) +#else +# define YQ_ANONYMOUS_VARIABLE(str) YQ_CONCATENATE(str, __LINE__) +#endif + +#define YQ_UNUSED(x) std::ignore = (x) + +#ifdef __MINGW32__ +// Avoid callbacks +# define YQ_CALLBACK __cdecl +# define YQ_FUNCTION_NAME __PRETTY_FUNCTION__ // __func__ +#elif defined(_WIN32) +# define YQ_CALLBACK __cdecl +# define YQ_FUNCTION_NAME __FUNCTION__ +#else +# define YQ_CALLBACK +# define YQ_FUNCTION_NAME __PRETTY_FUNCTION__ // __func__ +#endif + +// Helper marcos for doing a variable number of null checks +#define YQ_NULL_CHECK_IMPL_1(arg) if (arg == nullptr) YQ_API_LEAVE(R_InvalidParameter); +#define YQ_NULL_CHECK_IMPL_2(arg,...) YQ_NULL_CHECK_IMPL_1(arg) YQ_NULL_CHECK_IMPL_1(__VA_ARGS__) +#define YQ_NULL_CHECK_IMPL_3(arg,...) YQ_NULL_CHECK_IMPL_1(arg) YQ_NULL_CHECK_IMPL_2(__VA_ARGS__) +#define YQ_NULL_CHECK_IMPL_4(arg,...) YQ_NULL_CHECK_IMPL_1(arg) YQ_NULL_CHECK_IMPL_3(__VA_ARGS__) +#define YQ_NULL_CHECK_IMPL_5(arg,...) YQ_NULL_CHECK_IMPL_1(arg) YQ_NULL_CHECK_IMPL_4(__VA_ARGS__) +#define YQ_NULL_CHECK_IMPL_N3(N, ...) YQ_NULL_CHECK_IMPL_ ## N(__VA_ARGS__) +#define YQ_NULL_CHECK_IMPL_N2(N, ...) YQ_NULL_CHECK_IMPL_N3(N, __VA_ARGS__) +#define YQ_NULL_CHECK_IMPL_N(...) YQ_NULL_CHECK_IMPL_N2(YQ_ARG_COUNT(__VA_ARGS__), __VA_ARGS__) + +// File name, line numbes, and function name are statically concatenated at compile time, with a reduced string sharing trade off +#define YQ_LOCATION __FILE__ "(" YQ_STRINGIZE(__LINE__) "): " + +// Macros to log messages for debug builds. +// For release builds these will not output anything and no code or data will be generate (except for error logging) +#define YQ_LOG_WARNING(a_fmt, ...) ((BT_IsRelease) ? ((void)0) : yqLogMessage(LL_Warning, YQ_LOCATION, YQ_FUNCTION_NAME, "warning: " a_fmt, ##__VA_ARGS__)) +#define YQ_LOG_DEBUG(a_fmt, ...) ((BT_IsRelease) ? ((void)0) : yqLogMessage(LL_Debug, YQ_LOCATION, YQ_FUNCTION_NAME, "debug: " a_fmt, ##__VA_ARGS__)) +#define YQ_LOG_INFO(a_fmt, ...) ((BT_IsRelease) ? ((void)0) : yqLogMessage(LL_Info, YQ_LOCATION, YQ_FUNCTION_NAME, "info: " a_fmt, ##__VA_ARGS__)) +#define YQ_LOG_ERROR(a_fmt, ...) yqLogMessage(LL_Error, YQ_LOCATION, YQ_FUNCTION_NAME, "error: " a_fmt, ##__VA_ARGS__) + +// Macros that only generate log code in verbose debug builds. The logging logs the entry to API calls and the exit from them and what return code is being returned. +// In debug and release, on errors, the code always calls the installed or default error handling routine +enum { YQ_VERBOSE_DEBUGGING = 0 }; +#define YQ_API_VERBOSE(msg) ((YQ_VERBOSE_DEBUGGING) ? YQ_LOG_DEBUG(msg) : ((void)0)) +#define YQ_API_ERROR(result, ...) (yqHandleError(result, YQ_LOCATION, YQ_FUNCTION_NAME, __VA_ARGS__), YQ_LOG_ERROR("Leave: " #result " (%i)", result), result) +#define YQ_API_ENTER() YQ_API_VERBOSE("Enter") +#define YQ_API_LEAVE(result, ...) return ((result == R_Okay) ? (YQ_API_VERBOSE("Leave"), result) : YQ_API_ERROR(result, "" __VA_ARGS__)) + +// Macros to do common parameter and precondition checking for API function calls +// This macro checks the pre-condition is met, if it doesn't then returns the error result and executes the provided cleanup code +// See also 'finally'. Probably during pre-condition checking, there shouldn't be any cleanup yet as no work should be done at this stage. +//#define YQ_API_PRECONDITION_CLEANUP(condition,result,cleanup) do { if (!(condition)) { cleanup; YQ_API_LEAVE(result); } } while (0,0) +#define YQ_API_PRECONDITION(condition,result) do { if (!(condition)) { YQ_API_LEAVE(result, #condition); } } while (0,0)//YQ_API_PRECONDITION_CLEANUP(condition,result, ((void)0)) +#define YQ_API_PRECONDITION_NULL(...) YQ_NULL_CHECK_IMPL_N(__VA_ARGS__) + +// Can redirect to the user heap manager routines if provided +#define YQ_ALLOC(type) ((type*)(yqAllocateMemory(sizeof(type)))) +#define YQ_ALLOC_ARRAY(type,siz) ((type*)(yqAllocateMemory(sizeof(type)*(siz)))) +#define YQ_REALLOC_ARRAY(ptr,type,siz) ((type*)(yqReallocateMemory(ptr,sizeof(type)*(siz)))) +#define YQ_FREE(ptr) (((ptr)) ? (void)(yqFreeMemory((void*)(ptr)), (ptr) = nullptr) : (void)0) + +// C++ wrappers for using the custom allocators to replace use of new / delete (does placement new with custom allocated memory) +// Note: The custom allocators are expected to return null if the allocation fails and not throw. The macros below do not check this. +#define YQ_NEW(type) (new(YQ_ALLOC(type)) type) +#define YQ_NEW_PARAMS(type, params) (new(YQ_ALLOC(type)) type params) +#define YQ_DELETE(obj,type) ((obj) ? (void)((obj)->~type(), YQ_FREE(obj)) : (void)0) + + +/* + +Example usage: + +Result SomeFunction(T1 **Param1, T2 *Param2, T3 *Param3, int Param4) +{ + YQ_API_ENTER(); + + // Check pre-conditions + YQ_API_PRECONDITION_NULL(Param1, Param2, Param3); + YQ_API_PRECONDITION(Param4 == 42, R_InvalidParameter); + + if (errorHappened) + YQ_API_LEAVE(R_Failure); + + YQ_API_LEAVE(R_Okay); +} + +*/ + + +#endif // MACROS_H diff --git a/Framework/Array.h b/Framework/Array.h new file mode 100644 index 0000000..ab83e77 --- /dev/null +++ b/Framework/Array.h @@ -0,0 +1,98 @@ +#ifndef ARRAY_H +#define ARRAY_H + + +#include "Utilities.h" + + +// std::vector replacement that is cut down but familiar. +// It uses the custom allocators, and is restricted to POD elements only +template +struct PODArrayPOD // A dynamic array of POD type elements which is itself a POD type (so can have multi-dimensional arrays of these). +{ + static_assert(std::is_pod::value, "T must be a POD type."); + + void init() { m_magic = 0x01020304; m_capacity = m_size = 0; m_data = nullptr; } + void deinit() { if (m_data != nullptr) YQ_FREE(m_data); } + + const T& at(size_t a_pos) const { checkInited(); checkIndex(a_pos); return m_data[a_pos]; } + T& at(size_t a_pos) { checkInited(); checkIndex(a_pos); return m_data[a_pos]; } + const T& operator[](size_t a_pos) const { checkInited(); checkIndex(a_pos); return m_data[a_pos]; } + void pop_back() { checkInited(); checkIndex(1); m_size--; } + const T& back() const { checkInited(); return m_data[m_size - 1]; } + T& back() { checkInited(); return m_data[m_size - 1]; } + void push_back(const T& a_val) { checkInited(); if (m_size == m_capacity) ensureSpaceFor(1); m_data[m_size++] = a_val; } + void push(const T& a_val) { checkInited(); push_back(a_val); } + T pop() { checkInited(); pop_back(); return m_data[m_size]; } + void reserve(size_t a_count) { checkInited(); m_capacity = a_count; m_data = YQ_REALLOC_ARRAY(m_data, T, a_count); } + size_t capacity() const { checkInited(); return m_capacity; } + size_t size() const { checkInited(); return m_size; } + T* data() const { checkInited(); return m_data; } + void clear() { checkInited(); m_size = 0; } +//private: // Actually making private would make this no longer a POD type + yqResult checkInited() const { if (BT_IsDebug) YQ_API_PRECONDITION(m_magic == 0x01020304, R_Uninitialized); return R_Okay; } + yqResult checkIndex(size_t a_pos) const { YQ_API_PRECONDITION(a_pos < m_size, R_IndexOutOfRange); return R_Okay; } + void ensureSpaceFor(size_t a_count) { reserve(std::max(m_size + a_count, m_capacity + m_capacity / 2)); } // try to grow by 50% + T* m_data; // Because of this pointer it is not safe to copy DynamicArray objects around. Making it a shared_ptr though stops this being a POD type + size_t m_capacity; + size_t m_size; + uint32_t m_magic; +}; + + +static_assert(std::is_pod >::value, "PODArrayPOD must be a POD type."); + + +// A dynamic array of POD type elements. +// It isn't POD itself, but is instead reference counted so safer to deal with. +template +struct ArrayPOD +{ + static_assert(std::is_pod::value, "T must be a POD type."); + + // Actually not reference counted yet, but safe as objects of this type can't be copied + ArrayPOD& operator=(const ArrayPOD&) = delete; + ArrayPOD(const ArrayPOD&) = delete; + ArrayPOD() { m_capacity = m_size = 0; m_data = nullptr; } + ~ArrayPOD() { if (m_data != nullptr) YQ_FREE(m_data); } + + const T& at(size_t a_pos) const { checkIndex(a_pos); return m_data[a_pos]; } + T& at(size_t a_pos) { checkIndex(a_pos); return m_data[a_pos]; } + const T& operator[](size_t a_pos) const { checkIndex(a_pos); return m_data[a_pos]; } + void pop_back() { checkIndex(1); m_size--; } + const T& back() const { return m_data[m_size - 1]; } + T& back() { return m_data[m_size - 1]; } + void push_back(const T& a_val) { if (m_size == m_capacity) ensureSpaceFor(1); m_data[m_size++] = a_val; } + void push(const T& a_val) { push_back(a_val); } + T pop() { pop_back(); return m_data[m_size]; } + void reserve(size_t a_count) { m_capacity = a_count; m_data = YQ_REALLOC_ARRAY(m_data, T, a_count); } + size_t capacity() const { return m_capacity; } + size_t size() const { return m_size; } + T* data() const { return m_data; } + void clear() { m_size = 0; } +private: + yqResult checkIndex(size_t a_pos) const { YQ_API_PRECONDITION(a_pos < m_size, R_IndexOutOfRange); return R_Okay; } + void ensureSpaceFor(size_t a_count) { reserve(std::max(m_size + a_count, m_capacity + m_capacity / 2)); } // try to grow by 50% + T* m_data; // Because of this pointer it is not safe to copy DynamicArray objects around. Making it a shared_ptr though stops this being a POD type + size_t m_capacity; + size_t m_size; +}; + + +struct PODString +{ +public: + void init() { m_data.init(); } + void deinit() { m_data.deinit(); } + void operator=(const char* a_str) { m_data.clear(); operator+=(a_str); } + void operator+=(const char* a_str) { while (*a_str) m_data.push_back(*a_str++); m_data.push(0); m_data.pop(); } + char* c_str() { return m_data.data(); } +//private: // Actually making private would make this no longer a POD type + PODArrayPOD m_data; +}; + + +static_assert(std::is_pod::value, "PODString must be a POD type."); + + +#endif // ARRAY_H diff --git a/Framework/Macros.h b/Framework/Macros.h new file mode 100644 index 0000000..6ca8c86 --- /dev/null +++ b/Framework/Macros.h @@ -0,0 +1,111 @@ +#ifndef MACROS_H +#define MACROS_H + + +// +// Various common macros +// +#define YQ_ARRAY_SIZE(x) (sizeof(x)/sizeof((x)[0])) + +#define YQ_EXPAND(x) x + +#define YQ_STRINGIZE_IMPL(val) #val +#define YQ_STRINGIZE(val) YQ_STRINGIZE_IMPL(val) + +#define YQ_CONCATENATE_IMPL(x, y) x##y +#define YQ_CONCATENATE(x, y) YQ_CONCATENATE_IMPL(x, y) + +// If 0 args, it mis-reports a count of 1. Also it only counts up to 10 args. +#define YQ_ARG_COUNT_IMPL(X,A,B,C,D,E,F,G,H,I,J,N,...) N +#define YQ_ARG_COUNT(...) (YQ_EXPAND(YQ_ARG_COUNT_IMPL(_,__VA_ARGS__, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0))) + +#ifdef __COUNTER__ +# define YQ_ANONYMOUS_VARIABLE(str) YQ_CONCATENATE(str, __COUNTER__) +#else +# define YQ_ANONYMOUS_VARIABLE(str) YQ_CONCATENATE(str, __LINE__) +#endif + +#define YQ_UNUSED(x) std::ignore = (x) + +#ifdef __MINGW32__ +// Avoid callbacks +# define YQ_CALLBACK __cdecl +# define YQ_FUNCTION_NAME __PRETTY_FUNCTION__ // __func__ +#elif defined(_WIN32) +# define YQ_CALLBACK __cdecl +# define YQ_FUNCTION_NAME __FUNCTION__ +#else +# define YQ_CALLBACK +# define YQ_FUNCTION_NAME __PRETTY_FUNCTION__ // __func__ +#endif + +// Helper marcos for doing a variable number of null checks +#define YQ_NULL_CHECK_IMPL_1(arg) if (arg == nullptr) YQ_API_LEAVE(R_InvalidParameter); +#define YQ_NULL_CHECK_IMPL_2(arg,...) YQ_NULL_CHECK_IMPL_1(arg) YQ_NULL_CHECK_IMPL_1(__VA_ARGS__) +#define YQ_NULL_CHECK_IMPL_3(arg,...) YQ_NULL_CHECK_IMPL_1(arg) YQ_NULL_CHECK_IMPL_2(__VA_ARGS__) +#define YQ_NULL_CHECK_IMPL_4(arg,...) YQ_NULL_CHECK_IMPL_1(arg) YQ_NULL_CHECK_IMPL_3(__VA_ARGS__) +#define YQ_NULL_CHECK_IMPL_5(arg,...) YQ_NULL_CHECK_IMPL_1(arg) YQ_NULL_CHECK_IMPL_4(__VA_ARGS__) +#define YQ_NULL_CHECK_IMPL_N3(N, ...) YQ_NULL_CHECK_IMPL_ ## N(__VA_ARGS__) +#define YQ_NULL_CHECK_IMPL_N2(N, ...) YQ_NULL_CHECK_IMPL_N3(N, __VA_ARGS__) +#define YQ_NULL_CHECK_IMPL_N(...) YQ_NULL_CHECK_IMPL_N2(YQ_ARG_COUNT(__VA_ARGS__), __VA_ARGS__) + +// File name, line numbes, and function name are statically concatenated at compile time, with a reduced string sharing trade off +#define YQ_LOCATION __FILE__ "(" YQ_STRINGIZE(__LINE__) "): " + +// Macros to log messages for debug builds. +// For release builds these will not output anything and no code or data will be generate (except for error logging) +#define YQ_LOG_WARNING(a_fmt, ...) ((BT_IsRelease) ? ((void)0) : yqLogMessage(LL_Warning, YQ_LOCATION, YQ_FUNCTION_NAME, "warning: " a_fmt, ##__VA_ARGS__)) +#define YQ_LOG_DEBUG(a_fmt, ...) ((BT_IsRelease) ? ((void)0) : yqLogMessage(LL_Debug, YQ_LOCATION, YQ_FUNCTION_NAME, "debug: " a_fmt, ##__VA_ARGS__)) +#define YQ_LOG_INFO(a_fmt, ...) ((BT_IsRelease) ? ((void)0) : yqLogMessage(LL_Info, YQ_LOCATION, YQ_FUNCTION_NAME, "info: " a_fmt, ##__VA_ARGS__)) +#define YQ_LOG_ERROR(a_fmt, ...) yqLogMessage(LL_Error, YQ_LOCATION, YQ_FUNCTION_NAME, "error: " a_fmt, ##__VA_ARGS__) + +// Macros that only generate log code in verbose debug builds. The logging logs the entry to API calls and the exit from them and what return code is being returned. +// In debug and release, on errors, the code always calls the installed or default error handling routine +enum { YQ_VERBOSE_DEBUGGING = 0 }; +#define YQ_API_VERBOSE(msg) ((YQ_VERBOSE_DEBUGGING) ? YQ_LOG_DEBUG(msg) : ((void)0)) +#define YQ_API_ERROR(result, ...) (yqHandleError(result, YQ_LOCATION, YQ_FUNCTION_NAME, __VA_ARGS__), YQ_LOG_ERROR("Leave: " #result " (%i)", result), result) +#define YQ_API_ENTER() YQ_API_VERBOSE("Enter") +#define YQ_API_LEAVE(result, ...) return ((result == R_Okay) ? (YQ_API_VERBOSE("Leave"), result) : YQ_API_ERROR(result, "" __VA_ARGS__)) + +// Macros to do common parameter and precondition checking for API function calls +// This macro checks the pre-condition is met, if it doesn't then returns the error result and executes the provided cleanup code +// See also 'finally'. Probably during pre-condition checking, there shouldn't be any cleanup yet as no work should be done at this stage. +//#define YQ_API_PRECONDITION_CLEANUP(condition,result,cleanup) do { if (!(condition)) { cleanup; YQ_API_LEAVE(result); } } while (0,0) +#define YQ_API_PRECONDITION(condition,result) do { if (!(condition)) { YQ_API_LEAVE(result, #condition); } } while (0,0)//YQ_API_PRECONDITION_CLEANUP(condition,result, ((void)0)) +#define YQ_API_PRECONDITION_NULL(...) YQ_NULL_CHECK_IMPL_N(__VA_ARGS__) + +// Can redirect to the user heap manager routines if provided +#define YQ_ALLOC(type) ((type*)(yqAllocateMemory(sizeof(type)))) +#define YQ_ALLOC_ARRAY(type,siz) ((type*)(yqAllocateMemory(sizeof(type)*(siz)))) +#define YQ_REALLOC_ARRAY(ptr,type,siz) ((type*)(yqReallocateMemory(ptr,sizeof(type)*(siz)))) +#define YQ_FREE(ptr) (((ptr)) ? (void)(yqFreeMemory((void*)(ptr)), (ptr) = nullptr) : (void)0) + +// C++ wrappers for using the custom allocators to replace use of new / delete (does placement new with custom allocated memory) +// Note: The custom allocators are expected to return null if the allocation fails and not throw. The macros below do not check this. +#define YQ_NEW(type) (new(YQ_ALLOC(type)) type) +#define YQ_NEW_PARAMS(type, params) (new(YQ_ALLOC(type)) type params) +#define YQ_DELETE(obj,type) ((obj) ? (void)((obj)->~type(), YQ_FREE(obj)) : (void)0) + + +/* + +Example usage: + +Result SomeFunction(T1 **Param1, T2 *Param2, T3 *Param3, int Param4) +{ + YQ_API_ENTER(); + + // Check pre-conditions + YQ_API_PRECONDITION_NULL(Param1, Param2, Param3); + YQ_API_PRECONDITION(Param4 == 42, R_InvalidParameter); + + if (errorHappened) + YQ_API_LEAVE(R_Failure); + + YQ_API_LEAVE(R_Okay); +} + +*/ + + +#endif // MACROS_H diff --git a/Framework/NonCopyable.h b/Framework/NonCopyable.h index 3881321..b8cb274 100644 --- a/Framework/NonCopyable.h +++ b/Framework/NonCopyable.h @@ -3,6 +3,62 @@ /// XXX: Not sure but maybe there is a C++11 std::non_copyable that does the same as this +/// Actually C++11 has the additional ability to add '= delete' to a member to say it can be used. + +#ifdef HAVE_CPP11 +# define DELETE = delete +#else +# define DELETE +#endif + + +/// This way has the advantage of not adding to the size of the base class if that is important [1] +/// [1] Ref: http://trac.webkit.org/changeset/68414 +#define MAKE_NONCOPYABLE(ClassName) \ + private: \ + ClassName(const ClassName&) DELETE; \ + ClassName& operator=(const ClassName&) DELETE; \ + + +/* Utility classes */ +struct NoCopy +{ +public: + NoCopy() {} +private: + NoCopy(const NoCopy &) DELETE; +}; + +struct NoAssign +{ +private: + NoAssign &operator=(const NoAssign &) DELETE; +}; + +struct NonInstantiable +{ +private: + NonInstantiable() DELETE; +}; + +struct NoCopyAssign : NoCopy, NoAssign +{ +}; + +typedef NoCopyAssign NoAssignCopy; + +// Not tested +struct NoMove +{ +public: + NoMove() {} +private: + NoMove(const NoMove &&) DELETE; +}; + +struct NoMoveCopyAssign : NoMove, NoCopy, NoAssign +{ +}; /// @@ -10,9 +66,9 @@ /// /// Making a class NonCopyable means preventing objects of the class being copied. This is useful if /// the class manages a resource that can only be held by one object such that it can't be reference -/// counted, or it is the underlying object that becomes reference counted. +/// counted, or it is the underlying object that becomes reference counted. Idea from here [1]. /// -/// Idea comes from here: http://stackoverflow.com/questions/9458741/with-explicitly-deleted-member-functions-in-c11-is-it-still-worthwhile-to-inh +/// [1] Ref: http://stackoverflow.com/questions/9458741/with-explicitly-deleted-member-functions-in-c11-is-it-still-worthwhile-to-inh /// class NonCopyable { @@ -34,12 +90,19 @@ MyNonCopyableClass() {} }; + MyNonCopyableClass create() + { + MyNonCopyableClass var; + return var; + } + Now it is not possible to copy objects of this type without a compiler error: - void function() + void function() { - MyNonCopyableClass obj1; - MyNonCopyableClass obj2; + MyNonCopyableClass obj1; + MyNonCopyableClass obj2; + MyNonCopyableClass obj3 = create(); // Allowed due to move semantics in c++11, use NoMoveCopyAssign to prevent // These attempts to make a copy of obj1 will cause a compiler error MyNonCopyableClass obj3(obj1); diff --git a/Framework/Array.h b/Framework/Array.h new file mode 100644 index 0000000..ab83e77 --- /dev/null +++ b/Framework/Array.h @@ -0,0 +1,98 @@ +#ifndef ARRAY_H +#define ARRAY_H + + +#include "Utilities.h" + + +// std::vector replacement that is cut down but familiar. +// It uses the custom allocators, and is restricted to POD elements only +template +struct PODArrayPOD // A dynamic array of POD type elements which is itself a POD type (so can have multi-dimensional arrays of these). +{ + static_assert(std::is_pod::value, "T must be a POD type."); + + void init() { m_magic = 0x01020304; m_capacity = m_size = 0; m_data = nullptr; } + void deinit() { if (m_data != nullptr) YQ_FREE(m_data); } + + const T& at(size_t a_pos) const { checkInited(); checkIndex(a_pos); return m_data[a_pos]; } + T& at(size_t a_pos) { checkInited(); checkIndex(a_pos); return m_data[a_pos]; } + const T& operator[](size_t a_pos) const { checkInited(); checkIndex(a_pos); return m_data[a_pos]; } + void pop_back() { checkInited(); checkIndex(1); m_size--; } + const T& back() const { checkInited(); return m_data[m_size - 1]; } + T& back() { checkInited(); return m_data[m_size - 1]; } + void push_back(const T& a_val) { checkInited(); if (m_size == m_capacity) ensureSpaceFor(1); m_data[m_size++] = a_val; } + void push(const T& a_val) { checkInited(); push_back(a_val); } + T pop() { checkInited(); pop_back(); return m_data[m_size]; } + void reserve(size_t a_count) { checkInited(); m_capacity = a_count; m_data = YQ_REALLOC_ARRAY(m_data, T, a_count); } + size_t capacity() const { checkInited(); return m_capacity; } + size_t size() const { checkInited(); return m_size; } + T* data() const { checkInited(); return m_data; } + void clear() { checkInited(); m_size = 0; } +//private: // Actually making private would make this no longer a POD type + yqResult checkInited() const { if (BT_IsDebug) YQ_API_PRECONDITION(m_magic == 0x01020304, R_Uninitialized); return R_Okay; } + yqResult checkIndex(size_t a_pos) const { YQ_API_PRECONDITION(a_pos < m_size, R_IndexOutOfRange); return R_Okay; } + void ensureSpaceFor(size_t a_count) { reserve(std::max(m_size + a_count, m_capacity + m_capacity / 2)); } // try to grow by 50% + T* m_data; // Because of this pointer it is not safe to copy DynamicArray objects around. Making it a shared_ptr though stops this being a POD type + size_t m_capacity; + size_t m_size; + uint32_t m_magic; +}; + + +static_assert(std::is_pod >::value, "PODArrayPOD must be a POD type."); + + +// A dynamic array of POD type elements. +// It isn't POD itself, but is instead reference counted so safer to deal with. +template +struct ArrayPOD +{ + static_assert(std::is_pod::value, "T must be a POD type."); + + // Actually not reference counted yet, but safe as objects of this type can't be copied + ArrayPOD& operator=(const ArrayPOD&) = delete; + ArrayPOD(const ArrayPOD&) = delete; + ArrayPOD() { m_capacity = m_size = 0; m_data = nullptr; } + ~ArrayPOD() { if (m_data != nullptr) YQ_FREE(m_data); } + + const T& at(size_t a_pos) const { checkIndex(a_pos); return m_data[a_pos]; } + T& at(size_t a_pos) { checkIndex(a_pos); return m_data[a_pos]; } + const T& operator[](size_t a_pos) const { checkIndex(a_pos); return m_data[a_pos]; } + void pop_back() { checkIndex(1); m_size--; } + const T& back() const { return m_data[m_size - 1]; } + T& back() { return m_data[m_size - 1]; } + void push_back(const T& a_val) { if (m_size == m_capacity) ensureSpaceFor(1); m_data[m_size++] = a_val; } + void push(const T& a_val) { push_back(a_val); } + T pop() { pop_back(); return m_data[m_size]; } + void reserve(size_t a_count) { m_capacity = a_count; m_data = YQ_REALLOC_ARRAY(m_data, T, a_count); } + size_t capacity() const { return m_capacity; } + size_t size() const { return m_size; } + T* data() const { return m_data; } + void clear() { m_size = 0; } +private: + yqResult checkIndex(size_t a_pos) const { YQ_API_PRECONDITION(a_pos < m_size, R_IndexOutOfRange); return R_Okay; } + void ensureSpaceFor(size_t a_count) { reserve(std::max(m_size + a_count, m_capacity + m_capacity / 2)); } // try to grow by 50% + T* m_data; // Because of this pointer it is not safe to copy DynamicArray objects around. Making it a shared_ptr though stops this being a POD type + size_t m_capacity; + size_t m_size; +}; + + +struct PODString +{ +public: + void init() { m_data.init(); } + void deinit() { m_data.deinit(); } + void operator=(const char* a_str) { m_data.clear(); operator+=(a_str); } + void operator+=(const char* a_str) { while (*a_str) m_data.push_back(*a_str++); m_data.push(0); m_data.pop(); } + char* c_str() { return m_data.data(); } +//private: // Actually making private would make this no longer a POD type + PODArrayPOD m_data; +}; + + +static_assert(std::is_pod::value, "PODString must be a POD type."); + + +#endif // ARRAY_H diff --git a/Framework/Macros.h b/Framework/Macros.h new file mode 100644 index 0000000..6ca8c86 --- /dev/null +++ b/Framework/Macros.h @@ -0,0 +1,111 @@ +#ifndef MACROS_H +#define MACROS_H + + +// +// Various common macros +// +#define YQ_ARRAY_SIZE(x) (sizeof(x)/sizeof((x)[0])) + +#define YQ_EXPAND(x) x + +#define YQ_STRINGIZE_IMPL(val) #val +#define YQ_STRINGIZE(val) YQ_STRINGIZE_IMPL(val) + +#define YQ_CONCATENATE_IMPL(x, y) x##y +#define YQ_CONCATENATE(x, y) YQ_CONCATENATE_IMPL(x, y) + +// If 0 args, it mis-reports a count of 1. Also it only counts up to 10 args. +#define YQ_ARG_COUNT_IMPL(X,A,B,C,D,E,F,G,H,I,J,N,...) N +#define YQ_ARG_COUNT(...) (YQ_EXPAND(YQ_ARG_COUNT_IMPL(_,__VA_ARGS__, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0))) + +#ifdef __COUNTER__ +# define YQ_ANONYMOUS_VARIABLE(str) YQ_CONCATENATE(str, __COUNTER__) +#else +# define YQ_ANONYMOUS_VARIABLE(str) YQ_CONCATENATE(str, __LINE__) +#endif + +#define YQ_UNUSED(x) std::ignore = (x) + +#ifdef __MINGW32__ +// Avoid callbacks +# define YQ_CALLBACK __cdecl +# define YQ_FUNCTION_NAME __PRETTY_FUNCTION__ // __func__ +#elif defined(_WIN32) +# define YQ_CALLBACK __cdecl +# define YQ_FUNCTION_NAME __FUNCTION__ +#else +# define YQ_CALLBACK +# define YQ_FUNCTION_NAME __PRETTY_FUNCTION__ // __func__ +#endif + +// Helper marcos for doing a variable number of null checks +#define YQ_NULL_CHECK_IMPL_1(arg) if (arg == nullptr) YQ_API_LEAVE(R_InvalidParameter); +#define YQ_NULL_CHECK_IMPL_2(arg,...) YQ_NULL_CHECK_IMPL_1(arg) YQ_NULL_CHECK_IMPL_1(__VA_ARGS__) +#define YQ_NULL_CHECK_IMPL_3(arg,...) YQ_NULL_CHECK_IMPL_1(arg) YQ_NULL_CHECK_IMPL_2(__VA_ARGS__) +#define YQ_NULL_CHECK_IMPL_4(arg,...) YQ_NULL_CHECK_IMPL_1(arg) YQ_NULL_CHECK_IMPL_3(__VA_ARGS__) +#define YQ_NULL_CHECK_IMPL_5(arg,...) YQ_NULL_CHECK_IMPL_1(arg) YQ_NULL_CHECK_IMPL_4(__VA_ARGS__) +#define YQ_NULL_CHECK_IMPL_N3(N, ...) YQ_NULL_CHECK_IMPL_ ## N(__VA_ARGS__) +#define YQ_NULL_CHECK_IMPL_N2(N, ...) YQ_NULL_CHECK_IMPL_N3(N, __VA_ARGS__) +#define YQ_NULL_CHECK_IMPL_N(...) YQ_NULL_CHECK_IMPL_N2(YQ_ARG_COUNT(__VA_ARGS__), __VA_ARGS__) + +// File name, line numbes, and function name are statically concatenated at compile time, with a reduced string sharing trade off +#define YQ_LOCATION __FILE__ "(" YQ_STRINGIZE(__LINE__) "): " + +// Macros to log messages for debug builds. +// For release builds these will not output anything and no code or data will be generate (except for error logging) +#define YQ_LOG_WARNING(a_fmt, ...) ((BT_IsRelease) ? ((void)0) : yqLogMessage(LL_Warning, YQ_LOCATION, YQ_FUNCTION_NAME, "warning: " a_fmt, ##__VA_ARGS__)) +#define YQ_LOG_DEBUG(a_fmt, ...) ((BT_IsRelease) ? ((void)0) : yqLogMessage(LL_Debug, YQ_LOCATION, YQ_FUNCTION_NAME, "debug: " a_fmt, ##__VA_ARGS__)) +#define YQ_LOG_INFO(a_fmt, ...) ((BT_IsRelease) ? ((void)0) : yqLogMessage(LL_Info, YQ_LOCATION, YQ_FUNCTION_NAME, "info: " a_fmt, ##__VA_ARGS__)) +#define YQ_LOG_ERROR(a_fmt, ...) yqLogMessage(LL_Error, YQ_LOCATION, YQ_FUNCTION_NAME, "error: " a_fmt, ##__VA_ARGS__) + +// Macros that only generate log code in verbose debug builds. The logging logs the entry to API calls and the exit from them and what return code is being returned. +// In debug and release, on errors, the code always calls the installed or default error handling routine +enum { YQ_VERBOSE_DEBUGGING = 0 }; +#define YQ_API_VERBOSE(msg) ((YQ_VERBOSE_DEBUGGING) ? YQ_LOG_DEBUG(msg) : ((void)0)) +#define YQ_API_ERROR(result, ...) (yqHandleError(result, YQ_LOCATION, YQ_FUNCTION_NAME, __VA_ARGS__), YQ_LOG_ERROR("Leave: " #result " (%i)", result), result) +#define YQ_API_ENTER() YQ_API_VERBOSE("Enter") +#define YQ_API_LEAVE(result, ...) return ((result == R_Okay) ? (YQ_API_VERBOSE("Leave"), result) : YQ_API_ERROR(result, "" __VA_ARGS__)) + +// Macros to do common parameter and precondition checking for API function calls +// This macro checks the pre-condition is met, if it doesn't then returns the error result and executes the provided cleanup code +// See also 'finally'. Probably during pre-condition checking, there shouldn't be any cleanup yet as no work should be done at this stage. +//#define YQ_API_PRECONDITION_CLEANUP(condition,result,cleanup) do { if (!(condition)) { cleanup; YQ_API_LEAVE(result); } } while (0,0) +#define YQ_API_PRECONDITION(condition,result) do { if (!(condition)) { YQ_API_LEAVE(result, #condition); } } while (0,0)//YQ_API_PRECONDITION_CLEANUP(condition,result, ((void)0)) +#define YQ_API_PRECONDITION_NULL(...) YQ_NULL_CHECK_IMPL_N(__VA_ARGS__) + +// Can redirect to the user heap manager routines if provided +#define YQ_ALLOC(type) ((type*)(yqAllocateMemory(sizeof(type)))) +#define YQ_ALLOC_ARRAY(type,siz) ((type*)(yqAllocateMemory(sizeof(type)*(siz)))) +#define YQ_REALLOC_ARRAY(ptr,type,siz) ((type*)(yqReallocateMemory(ptr,sizeof(type)*(siz)))) +#define YQ_FREE(ptr) (((ptr)) ? (void)(yqFreeMemory((void*)(ptr)), (ptr) = nullptr) : (void)0) + +// C++ wrappers for using the custom allocators to replace use of new / delete (does placement new with custom allocated memory) +// Note: The custom allocators are expected to return null if the allocation fails and not throw. The macros below do not check this. +#define YQ_NEW(type) (new(YQ_ALLOC(type)) type) +#define YQ_NEW_PARAMS(type, params) (new(YQ_ALLOC(type)) type params) +#define YQ_DELETE(obj,type) ((obj) ? (void)((obj)->~type(), YQ_FREE(obj)) : (void)0) + + +/* + +Example usage: + +Result SomeFunction(T1 **Param1, T2 *Param2, T3 *Param3, int Param4) +{ + YQ_API_ENTER(); + + // Check pre-conditions + YQ_API_PRECONDITION_NULL(Param1, Param2, Param3); + YQ_API_PRECONDITION(Param4 == 42, R_InvalidParameter); + + if (errorHappened) + YQ_API_LEAVE(R_Failure); + + YQ_API_LEAVE(R_Okay); +} + +*/ + + +#endif // MACROS_H diff --git a/Framework/NonCopyable.h b/Framework/NonCopyable.h index 3881321..b8cb274 100644 --- a/Framework/NonCopyable.h +++ b/Framework/NonCopyable.h @@ -3,6 +3,62 @@ /// XXX: Not sure but maybe there is a C++11 std::non_copyable that does the same as this +/// Actually C++11 has the additional ability to add '= delete' to a member to say it can be used. + +#ifdef HAVE_CPP11 +# define DELETE = delete +#else +# define DELETE +#endif + + +/// This way has the advantage of not adding to the size of the base class if that is important [1] +/// [1] Ref: http://trac.webkit.org/changeset/68414 +#define MAKE_NONCOPYABLE(ClassName) \ + private: \ + ClassName(const ClassName&) DELETE; \ + ClassName& operator=(const ClassName&) DELETE; \ + + +/* Utility classes */ +struct NoCopy +{ +public: + NoCopy() {} +private: + NoCopy(const NoCopy &) DELETE; +}; + +struct NoAssign +{ +private: + NoAssign &operator=(const NoAssign &) DELETE; +}; + +struct NonInstantiable +{ +private: + NonInstantiable() DELETE; +}; + +struct NoCopyAssign : NoCopy, NoAssign +{ +}; + +typedef NoCopyAssign NoAssignCopy; + +// Not tested +struct NoMove +{ +public: + NoMove() {} +private: + NoMove(const NoMove &&) DELETE; +}; + +struct NoMoveCopyAssign : NoMove, NoCopy, NoAssign +{ +}; /// @@ -10,9 +66,9 @@ /// /// Making a class NonCopyable means preventing objects of the class being copied. This is useful if /// the class manages a resource that can only be held by one object such that it can't be reference -/// counted, or it is the underlying object that becomes reference counted. +/// counted, or it is the underlying object that becomes reference counted. Idea from here [1]. /// -/// Idea comes from here: http://stackoverflow.com/questions/9458741/with-explicitly-deleted-member-functions-in-c11-is-it-still-worthwhile-to-inh +/// [1] Ref: http://stackoverflow.com/questions/9458741/with-explicitly-deleted-member-functions-in-c11-is-it-still-worthwhile-to-inh /// class NonCopyable { @@ -34,12 +90,19 @@ MyNonCopyableClass() {} }; + MyNonCopyableClass create() + { + MyNonCopyableClass var; + return var; + } + Now it is not possible to copy objects of this type without a compiler error: - void function() + void function() { - MyNonCopyableClass obj1; - MyNonCopyableClass obj2; + MyNonCopyableClass obj1; + MyNonCopyableClass obj2; + MyNonCopyableClass obj3 = create(); // Allowed due to move semantics in c++11, use NoMoveCopyAssign to prevent // These attempts to make a copy of obj1 will cause a compiler error MyNonCopyableClass obj3(obj1); diff --git a/Framework/ObjectFilter.cpp b/Framework/ObjectFilter.cpp new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/Framework/ObjectFilter.cpp diff --git a/Framework/Array.h b/Framework/Array.h new file mode 100644 index 0000000..ab83e77 --- /dev/null +++ b/Framework/Array.h @@ -0,0 +1,98 @@ +#ifndef ARRAY_H +#define ARRAY_H + + +#include "Utilities.h" + + +// std::vector replacement that is cut down but familiar. +// It uses the custom allocators, and is restricted to POD elements only +template +struct PODArrayPOD // A dynamic array of POD type elements which is itself a POD type (so can have multi-dimensional arrays of these). +{ + static_assert(std::is_pod::value, "T must be a POD type."); + + void init() { m_magic = 0x01020304; m_capacity = m_size = 0; m_data = nullptr; } + void deinit() { if (m_data != nullptr) YQ_FREE(m_data); } + + const T& at(size_t a_pos) const { checkInited(); checkIndex(a_pos); return m_data[a_pos]; } + T& at(size_t a_pos) { checkInited(); checkIndex(a_pos); return m_data[a_pos]; } + const T& operator[](size_t a_pos) const { checkInited(); checkIndex(a_pos); return m_data[a_pos]; } + void pop_back() { checkInited(); checkIndex(1); m_size--; } + const T& back() const { checkInited(); return m_data[m_size - 1]; } + T& back() { checkInited(); return m_data[m_size - 1]; } + void push_back(const T& a_val) { checkInited(); if (m_size == m_capacity) ensureSpaceFor(1); m_data[m_size++] = a_val; } + void push(const T& a_val) { checkInited(); push_back(a_val); } + T pop() { checkInited(); pop_back(); return m_data[m_size]; } + void reserve(size_t a_count) { checkInited(); m_capacity = a_count; m_data = YQ_REALLOC_ARRAY(m_data, T, a_count); } + size_t capacity() const { checkInited(); return m_capacity; } + size_t size() const { checkInited(); return m_size; } + T* data() const { checkInited(); return m_data; } + void clear() { checkInited(); m_size = 0; } +//private: // Actually making private would make this no longer a POD type + yqResult checkInited() const { if (BT_IsDebug) YQ_API_PRECONDITION(m_magic == 0x01020304, R_Uninitialized); return R_Okay; } + yqResult checkIndex(size_t a_pos) const { YQ_API_PRECONDITION(a_pos < m_size, R_IndexOutOfRange); return R_Okay; } + void ensureSpaceFor(size_t a_count) { reserve(std::max(m_size + a_count, m_capacity + m_capacity / 2)); } // try to grow by 50% + T* m_data; // Because of this pointer it is not safe to copy DynamicArray objects around. Making it a shared_ptr though stops this being a POD type + size_t m_capacity; + size_t m_size; + uint32_t m_magic; +}; + + +static_assert(std::is_pod >::value, "PODArrayPOD must be a POD type."); + + +// A dynamic array of POD type elements. +// It isn't POD itself, but is instead reference counted so safer to deal with. +template +struct ArrayPOD +{ + static_assert(std::is_pod::value, "T must be a POD type."); + + // Actually not reference counted yet, but safe as objects of this type can't be copied + ArrayPOD& operator=(const ArrayPOD&) = delete; + ArrayPOD(const ArrayPOD&) = delete; + ArrayPOD() { m_capacity = m_size = 0; m_data = nullptr; } + ~ArrayPOD() { if (m_data != nullptr) YQ_FREE(m_data); } + + const T& at(size_t a_pos) const { checkIndex(a_pos); return m_data[a_pos]; } + T& at(size_t a_pos) { checkIndex(a_pos); return m_data[a_pos]; } + const T& operator[](size_t a_pos) const { checkIndex(a_pos); return m_data[a_pos]; } + void pop_back() { checkIndex(1); m_size--; } + const T& back() const { return m_data[m_size - 1]; } + T& back() { return m_data[m_size - 1]; } + void push_back(const T& a_val) { if (m_size == m_capacity) ensureSpaceFor(1); m_data[m_size++] = a_val; } + void push(const T& a_val) { push_back(a_val); } + T pop() { pop_back(); return m_data[m_size]; } + void reserve(size_t a_count) { m_capacity = a_count; m_data = YQ_REALLOC_ARRAY(m_data, T, a_count); } + size_t capacity() const { return m_capacity; } + size_t size() const { return m_size; } + T* data() const { return m_data; } + void clear() { m_size = 0; } +private: + yqResult checkIndex(size_t a_pos) const { YQ_API_PRECONDITION(a_pos < m_size, R_IndexOutOfRange); return R_Okay; } + void ensureSpaceFor(size_t a_count) { reserve(std::max(m_size + a_count, m_capacity + m_capacity / 2)); } // try to grow by 50% + T* m_data; // Because of this pointer it is not safe to copy DynamicArray objects around. Making it a shared_ptr though stops this being a POD type + size_t m_capacity; + size_t m_size; +}; + + +struct PODString +{ +public: + void init() { m_data.init(); } + void deinit() { m_data.deinit(); } + void operator=(const char* a_str) { m_data.clear(); operator+=(a_str); } + void operator+=(const char* a_str) { while (*a_str) m_data.push_back(*a_str++); m_data.push(0); m_data.pop(); } + char* c_str() { return m_data.data(); } +//private: // Actually making private would make this no longer a POD type + PODArrayPOD m_data; +}; + + +static_assert(std::is_pod::value, "PODString must be a POD type."); + + +#endif // ARRAY_H diff --git a/Framework/Macros.h b/Framework/Macros.h new file mode 100644 index 0000000..6ca8c86 --- /dev/null +++ b/Framework/Macros.h @@ -0,0 +1,111 @@ +#ifndef MACROS_H +#define MACROS_H + + +// +// Various common macros +// +#define YQ_ARRAY_SIZE(x) (sizeof(x)/sizeof((x)[0])) + +#define YQ_EXPAND(x) x + +#define YQ_STRINGIZE_IMPL(val) #val +#define YQ_STRINGIZE(val) YQ_STRINGIZE_IMPL(val) + +#define YQ_CONCATENATE_IMPL(x, y) x##y +#define YQ_CONCATENATE(x, y) YQ_CONCATENATE_IMPL(x, y) + +// If 0 args, it mis-reports a count of 1. Also it only counts up to 10 args. +#define YQ_ARG_COUNT_IMPL(X,A,B,C,D,E,F,G,H,I,J,N,...) N +#define YQ_ARG_COUNT(...) (YQ_EXPAND(YQ_ARG_COUNT_IMPL(_,__VA_ARGS__, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0))) + +#ifdef __COUNTER__ +# define YQ_ANONYMOUS_VARIABLE(str) YQ_CONCATENATE(str, __COUNTER__) +#else +# define YQ_ANONYMOUS_VARIABLE(str) YQ_CONCATENATE(str, __LINE__) +#endif + +#define YQ_UNUSED(x) std::ignore = (x) + +#ifdef __MINGW32__ +// Avoid callbacks +# define YQ_CALLBACK __cdecl +# define YQ_FUNCTION_NAME __PRETTY_FUNCTION__ // __func__ +#elif defined(_WIN32) +# define YQ_CALLBACK __cdecl +# define YQ_FUNCTION_NAME __FUNCTION__ +#else +# define YQ_CALLBACK +# define YQ_FUNCTION_NAME __PRETTY_FUNCTION__ // __func__ +#endif + +// Helper marcos for doing a variable number of null checks +#define YQ_NULL_CHECK_IMPL_1(arg) if (arg == nullptr) YQ_API_LEAVE(R_InvalidParameter); +#define YQ_NULL_CHECK_IMPL_2(arg,...) YQ_NULL_CHECK_IMPL_1(arg) YQ_NULL_CHECK_IMPL_1(__VA_ARGS__) +#define YQ_NULL_CHECK_IMPL_3(arg,...) YQ_NULL_CHECK_IMPL_1(arg) YQ_NULL_CHECK_IMPL_2(__VA_ARGS__) +#define YQ_NULL_CHECK_IMPL_4(arg,...) YQ_NULL_CHECK_IMPL_1(arg) YQ_NULL_CHECK_IMPL_3(__VA_ARGS__) +#define YQ_NULL_CHECK_IMPL_5(arg,...) YQ_NULL_CHECK_IMPL_1(arg) YQ_NULL_CHECK_IMPL_4(__VA_ARGS__) +#define YQ_NULL_CHECK_IMPL_N3(N, ...) YQ_NULL_CHECK_IMPL_ ## N(__VA_ARGS__) +#define YQ_NULL_CHECK_IMPL_N2(N, ...) YQ_NULL_CHECK_IMPL_N3(N, __VA_ARGS__) +#define YQ_NULL_CHECK_IMPL_N(...) YQ_NULL_CHECK_IMPL_N2(YQ_ARG_COUNT(__VA_ARGS__), __VA_ARGS__) + +// File name, line numbes, and function name are statically concatenated at compile time, with a reduced string sharing trade off +#define YQ_LOCATION __FILE__ "(" YQ_STRINGIZE(__LINE__) "): " + +// Macros to log messages for debug builds. +// For release builds these will not output anything and no code or data will be generate (except for error logging) +#define YQ_LOG_WARNING(a_fmt, ...) ((BT_IsRelease) ? ((void)0) : yqLogMessage(LL_Warning, YQ_LOCATION, YQ_FUNCTION_NAME, "warning: " a_fmt, ##__VA_ARGS__)) +#define YQ_LOG_DEBUG(a_fmt, ...) ((BT_IsRelease) ? ((void)0) : yqLogMessage(LL_Debug, YQ_LOCATION, YQ_FUNCTION_NAME, "debug: " a_fmt, ##__VA_ARGS__)) +#define YQ_LOG_INFO(a_fmt, ...) ((BT_IsRelease) ? ((void)0) : yqLogMessage(LL_Info, YQ_LOCATION, YQ_FUNCTION_NAME, "info: " a_fmt, ##__VA_ARGS__)) +#define YQ_LOG_ERROR(a_fmt, ...) yqLogMessage(LL_Error, YQ_LOCATION, YQ_FUNCTION_NAME, "error: " a_fmt, ##__VA_ARGS__) + +// Macros that only generate log code in verbose debug builds. The logging logs the entry to API calls and the exit from them and what return code is being returned. +// In debug and release, on errors, the code always calls the installed or default error handling routine +enum { YQ_VERBOSE_DEBUGGING = 0 }; +#define YQ_API_VERBOSE(msg) ((YQ_VERBOSE_DEBUGGING) ? YQ_LOG_DEBUG(msg) : ((void)0)) +#define YQ_API_ERROR(result, ...) (yqHandleError(result, YQ_LOCATION, YQ_FUNCTION_NAME, __VA_ARGS__), YQ_LOG_ERROR("Leave: " #result " (%i)", result), result) +#define YQ_API_ENTER() YQ_API_VERBOSE("Enter") +#define YQ_API_LEAVE(result, ...) return ((result == R_Okay) ? (YQ_API_VERBOSE("Leave"), result) : YQ_API_ERROR(result, "" __VA_ARGS__)) + +// Macros to do common parameter and precondition checking for API function calls +// This macro checks the pre-condition is met, if it doesn't then returns the error result and executes the provided cleanup code +// See also 'finally'. Probably during pre-condition checking, there shouldn't be any cleanup yet as no work should be done at this stage. +//#define YQ_API_PRECONDITION_CLEANUP(condition,result,cleanup) do { if (!(condition)) { cleanup; YQ_API_LEAVE(result); } } while (0,0) +#define YQ_API_PRECONDITION(condition,result) do { if (!(condition)) { YQ_API_LEAVE(result, #condition); } } while (0,0)//YQ_API_PRECONDITION_CLEANUP(condition,result, ((void)0)) +#define YQ_API_PRECONDITION_NULL(...) YQ_NULL_CHECK_IMPL_N(__VA_ARGS__) + +// Can redirect to the user heap manager routines if provided +#define YQ_ALLOC(type) ((type*)(yqAllocateMemory(sizeof(type)))) +#define YQ_ALLOC_ARRAY(type,siz) ((type*)(yqAllocateMemory(sizeof(type)*(siz)))) +#define YQ_REALLOC_ARRAY(ptr,type,siz) ((type*)(yqReallocateMemory(ptr,sizeof(type)*(siz)))) +#define YQ_FREE(ptr) (((ptr)) ? (void)(yqFreeMemory((void*)(ptr)), (ptr) = nullptr) : (void)0) + +// C++ wrappers for using the custom allocators to replace use of new / delete (does placement new with custom allocated memory) +// Note: The custom allocators are expected to return null if the allocation fails and not throw. The macros below do not check this. +#define YQ_NEW(type) (new(YQ_ALLOC(type)) type) +#define YQ_NEW_PARAMS(type, params) (new(YQ_ALLOC(type)) type params) +#define YQ_DELETE(obj,type) ((obj) ? (void)((obj)->~type(), YQ_FREE(obj)) : (void)0) + + +/* + +Example usage: + +Result SomeFunction(T1 **Param1, T2 *Param2, T3 *Param3, int Param4) +{ + YQ_API_ENTER(); + + // Check pre-conditions + YQ_API_PRECONDITION_NULL(Param1, Param2, Param3); + YQ_API_PRECONDITION(Param4 == 42, R_InvalidParameter); + + if (errorHappened) + YQ_API_LEAVE(R_Failure); + + YQ_API_LEAVE(R_Okay); +} + +*/ + + +#endif // MACROS_H diff --git a/Framework/NonCopyable.h b/Framework/NonCopyable.h index 3881321..b8cb274 100644 --- a/Framework/NonCopyable.h +++ b/Framework/NonCopyable.h @@ -3,6 +3,62 @@ /// XXX: Not sure but maybe there is a C++11 std::non_copyable that does the same as this +/// Actually C++11 has the additional ability to add '= delete' to a member to say it can be used. + +#ifdef HAVE_CPP11 +# define DELETE = delete +#else +# define DELETE +#endif + + +/// This way has the advantage of not adding to the size of the base class if that is important [1] +/// [1] Ref: http://trac.webkit.org/changeset/68414 +#define MAKE_NONCOPYABLE(ClassName) \ + private: \ + ClassName(const ClassName&) DELETE; \ + ClassName& operator=(const ClassName&) DELETE; \ + + +/* Utility classes */ +struct NoCopy +{ +public: + NoCopy() {} +private: + NoCopy(const NoCopy &) DELETE; +}; + +struct NoAssign +{ +private: + NoAssign &operator=(const NoAssign &) DELETE; +}; + +struct NonInstantiable +{ +private: + NonInstantiable() DELETE; +}; + +struct NoCopyAssign : NoCopy, NoAssign +{ +}; + +typedef NoCopyAssign NoAssignCopy; + +// Not tested +struct NoMove +{ +public: + NoMove() {} +private: + NoMove(const NoMove &&) DELETE; +}; + +struct NoMoveCopyAssign : NoMove, NoCopy, NoAssign +{ +}; /// @@ -10,9 +66,9 @@ /// /// Making a class NonCopyable means preventing objects of the class being copied. This is useful if /// the class manages a resource that can only be held by one object such that it can't be reference -/// counted, or it is the underlying object that becomes reference counted. +/// counted, or it is the underlying object that becomes reference counted. Idea from here [1]. /// -/// Idea comes from here: http://stackoverflow.com/questions/9458741/with-explicitly-deleted-member-functions-in-c11-is-it-still-worthwhile-to-inh +/// [1] Ref: http://stackoverflow.com/questions/9458741/with-explicitly-deleted-member-functions-in-c11-is-it-still-worthwhile-to-inh /// class NonCopyable { @@ -34,12 +90,19 @@ MyNonCopyableClass() {} }; + MyNonCopyableClass create() + { + MyNonCopyableClass var; + return var; + } + Now it is not possible to copy objects of this type without a compiler error: - void function() + void function() { - MyNonCopyableClass obj1; - MyNonCopyableClass obj2; + MyNonCopyableClass obj1; + MyNonCopyableClass obj2; + MyNonCopyableClass obj3 = create(); // Allowed due to move semantics in c++11, use NoMoveCopyAssign to prevent // These attempts to make a copy of obj1 will cause a compiler error MyNonCopyableClass obj3(obj1); diff --git a/Framework/ObjectFilter.cpp b/Framework/ObjectFilter.cpp new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/Framework/ObjectFilter.cpp diff --git a/Framework/ObjectInspector.cpp b/Framework/ObjectInspector.cpp new file mode 100644 index 0000000..91b6726 --- /dev/null +++ b/Framework/ObjectInspector.cpp @@ -0,0 +1,90 @@ +#include "ObjectInspector.h" + +#include "ObjectPropertyModel.h" +#include "lib/ObjectProxy.h" + +#include +#include +#include +#include +#include +#include + +ObjectInspector::ObjectInspector(QWidget* parent) +: QWidget(parent) +{ + QVBoxLayout* layout = new QVBoxLayout(this); + layout->setMargin(0); + + m_nameLabel = new QLabel(this); + layout->addWidget(m_nameLabel); + + m_propertyView = new QTableView(this); + m_propertyView->verticalHeader()->setVisible(false); + m_propertyView->horizontalHeader()->setStretchLastSection(true); + + QHBoxLayout* filterLayout = new QHBoxLayout; + QLabel* filterLabel = new QLabel(tr("Filter:"),this); + m_propertyFilterEdit = new QLineEdit(this); + m_propertyFilterEdit->setToolTip(tr("String or regular expression to filter property list with")); + + connect(m_propertyFilterEdit,SIGNAL(textChanged(QString)), + this,SLOT(changeFilter(QString))); + filterLayout->addWidget(filterLabel); + filterLayout->addWidget(m_propertyFilterEdit); + + m_model = new ObjectPropertyModel(this); + m_propertySortModel = new QSortFilterProxyModel(this); + m_propertySortModel->setSourceModel(m_model); + + m_propertyView->setModel(m_propertySortModel); + + layout->addWidget(m_propertyView); + layout->addLayout(filterLayout); + + setObject(ObjectProxy::Pointer()); +} + +QString ObjectInspector::formatAddress(void* ptr) +{ + quintptr intPtr = reinterpret_cast(ptr); + return QString("0x%1").arg(QString(QByteArray::number(intPtr,16))); +} + +void ObjectInspector::refresh() +{ + setObject(m_currentObject); +} + +void ObjectInspector::setObject(ObjectProxy::Pointer object) +{ + QString labelText; + + if (object) + { + labelText = QString("%1 [%2] (%3)") + .arg(object->className()) + .arg(object->objectName()) + .arg(formatAddress(reinterpret_cast(object->address()))); + } + else + { + labelText = tr("[No Object Selected]"); + } + + m_nameLabel->setText(labelText); + m_currentObject = object; + m_model->setObject(object); + m_propertySortModel->sort(0,Qt::AscendingOrder); +} + +ObjectProxy::Pointer ObjectInspector::object() const +{ + return m_currentObject; +} + +void ObjectInspector::changeFilter(const QString& pattern) +{ + m_propertySortModel->setFilterRegExp(QRegExp(pattern)); +} + diff --git a/Framework/Array.h b/Framework/Array.h new file mode 100644 index 0000000..ab83e77 --- /dev/null +++ b/Framework/Array.h @@ -0,0 +1,98 @@ +#ifndef ARRAY_H +#define ARRAY_H + + +#include "Utilities.h" + + +// std::vector replacement that is cut down but familiar. +// It uses the custom allocators, and is restricted to POD elements only +template +struct PODArrayPOD // A dynamic array of POD type elements which is itself a POD type (so can have multi-dimensional arrays of these). +{ + static_assert(std::is_pod::value, "T must be a POD type."); + + void init() { m_magic = 0x01020304; m_capacity = m_size = 0; m_data = nullptr; } + void deinit() { if (m_data != nullptr) YQ_FREE(m_data); } + + const T& at(size_t a_pos) const { checkInited(); checkIndex(a_pos); return m_data[a_pos]; } + T& at(size_t a_pos) { checkInited(); checkIndex(a_pos); return m_data[a_pos]; } + const T& operator[](size_t a_pos) const { checkInited(); checkIndex(a_pos); return m_data[a_pos]; } + void pop_back() { checkInited(); checkIndex(1); m_size--; } + const T& back() const { checkInited(); return m_data[m_size - 1]; } + T& back() { checkInited(); return m_data[m_size - 1]; } + void push_back(const T& a_val) { checkInited(); if (m_size == m_capacity) ensureSpaceFor(1); m_data[m_size++] = a_val; } + void push(const T& a_val) { checkInited(); push_back(a_val); } + T pop() { checkInited(); pop_back(); return m_data[m_size]; } + void reserve(size_t a_count) { checkInited(); m_capacity = a_count; m_data = YQ_REALLOC_ARRAY(m_data, T, a_count); } + size_t capacity() const { checkInited(); return m_capacity; } + size_t size() const { checkInited(); return m_size; } + T* data() const { checkInited(); return m_data; } + void clear() { checkInited(); m_size = 0; } +//private: // Actually making private would make this no longer a POD type + yqResult checkInited() const { if (BT_IsDebug) YQ_API_PRECONDITION(m_magic == 0x01020304, R_Uninitialized); return R_Okay; } + yqResult checkIndex(size_t a_pos) const { YQ_API_PRECONDITION(a_pos < m_size, R_IndexOutOfRange); return R_Okay; } + void ensureSpaceFor(size_t a_count) { reserve(std::max(m_size + a_count, m_capacity + m_capacity / 2)); } // try to grow by 50% + T* m_data; // Because of this pointer it is not safe to copy DynamicArray objects around. Making it a shared_ptr though stops this being a POD type + size_t m_capacity; + size_t m_size; + uint32_t m_magic; +}; + + +static_assert(std::is_pod >::value, "PODArrayPOD must be a POD type."); + + +// A dynamic array of POD type elements. +// It isn't POD itself, but is instead reference counted so safer to deal with. +template +struct ArrayPOD +{ + static_assert(std::is_pod::value, "T must be a POD type."); + + // Actually not reference counted yet, but safe as objects of this type can't be copied + ArrayPOD& operator=(const ArrayPOD&) = delete; + ArrayPOD(const ArrayPOD&) = delete; + ArrayPOD() { m_capacity = m_size = 0; m_data = nullptr; } + ~ArrayPOD() { if (m_data != nullptr) YQ_FREE(m_data); } + + const T& at(size_t a_pos) const { checkIndex(a_pos); return m_data[a_pos]; } + T& at(size_t a_pos) { checkIndex(a_pos); return m_data[a_pos]; } + const T& operator[](size_t a_pos) const { checkIndex(a_pos); return m_data[a_pos]; } + void pop_back() { checkIndex(1); m_size--; } + const T& back() const { return m_data[m_size - 1]; } + T& back() { return m_data[m_size - 1]; } + void push_back(const T& a_val) { if (m_size == m_capacity) ensureSpaceFor(1); m_data[m_size++] = a_val; } + void push(const T& a_val) { push_back(a_val); } + T pop() { pop_back(); return m_data[m_size]; } + void reserve(size_t a_count) { m_capacity = a_count; m_data = YQ_REALLOC_ARRAY(m_data, T, a_count); } + size_t capacity() const { return m_capacity; } + size_t size() const { return m_size; } + T* data() const { return m_data; } + void clear() { m_size = 0; } +private: + yqResult checkIndex(size_t a_pos) const { YQ_API_PRECONDITION(a_pos < m_size, R_IndexOutOfRange); return R_Okay; } + void ensureSpaceFor(size_t a_count) { reserve(std::max(m_size + a_count, m_capacity + m_capacity / 2)); } // try to grow by 50% + T* m_data; // Because of this pointer it is not safe to copy DynamicArray objects around. Making it a shared_ptr though stops this being a POD type + size_t m_capacity; + size_t m_size; +}; + + +struct PODString +{ +public: + void init() { m_data.init(); } + void deinit() { m_data.deinit(); } + void operator=(const char* a_str) { m_data.clear(); operator+=(a_str); } + void operator+=(const char* a_str) { while (*a_str) m_data.push_back(*a_str++); m_data.push(0); m_data.pop(); } + char* c_str() { return m_data.data(); } +//private: // Actually making private would make this no longer a POD type + PODArrayPOD m_data; +}; + + +static_assert(std::is_pod::value, "PODString must be a POD type."); + + +#endif // ARRAY_H diff --git a/Framework/Macros.h b/Framework/Macros.h new file mode 100644 index 0000000..6ca8c86 --- /dev/null +++ b/Framework/Macros.h @@ -0,0 +1,111 @@ +#ifndef MACROS_H +#define MACROS_H + + +// +// Various common macros +// +#define YQ_ARRAY_SIZE(x) (sizeof(x)/sizeof((x)[0])) + +#define YQ_EXPAND(x) x + +#define YQ_STRINGIZE_IMPL(val) #val +#define YQ_STRINGIZE(val) YQ_STRINGIZE_IMPL(val) + +#define YQ_CONCATENATE_IMPL(x, y) x##y +#define YQ_CONCATENATE(x, y) YQ_CONCATENATE_IMPL(x, y) + +// If 0 args, it mis-reports a count of 1. Also it only counts up to 10 args. +#define YQ_ARG_COUNT_IMPL(X,A,B,C,D,E,F,G,H,I,J,N,...) N +#define YQ_ARG_COUNT(...) (YQ_EXPAND(YQ_ARG_COUNT_IMPL(_,__VA_ARGS__, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0))) + +#ifdef __COUNTER__ +# define YQ_ANONYMOUS_VARIABLE(str) YQ_CONCATENATE(str, __COUNTER__) +#else +# define YQ_ANONYMOUS_VARIABLE(str) YQ_CONCATENATE(str, __LINE__) +#endif + +#define YQ_UNUSED(x) std::ignore = (x) + +#ifdef __MINGW32__ +// Avoid callbacks +# define YQ_CALLBACK __cdecl +# define YQ_FUNCTION_NAME __PRETTY_FUNCTION__ // __func__ +#elif defined(_WIN32) +# define YQ_CALLBACK __cdecl +# define YQ_FUNCTION_NAME __FUNCTION__ +#else +# define YQ_CALLBACK +# define YQ_FUNCTION_NAME __PRETTY_FUNCTION__ // __func__ +#endif + +// Helper marcos for doing a variable number of null checks +#define YQ_NULL_CHECK_IMPL_1(arg) if (arg == nullptr) YQ_API_LEAVE(R_InvalidParameter); +#define YQ_NULL_CHECK_IMPL_2(arg,...) YQ_NULL_CHECK_IMPL_1(arg) YQ_NULL_CHECK_IMPL_1(__VA_ARGS__) +#define YQ_NULL_CHECK_IMPL_3(arg,...) YQ_NULL_CHECK_IMPL_1(arg) YQ_NULL_CHECK_IMPL_2(__VA_ARGS__) +#define YQ_NULL_CHECK_IMPL_4(arg,...) YQ_NULL_CHECK_IMPL_1(arg) YQ_NULL_CHECK_IMPL_3(__VA_ARGS__) +#define YQ_NULL_CHECK_IMPL_5(arg,...) YQ_NULL_CHECK_IMPL_1(arg) YQ_NULL_CHECK_IMPL_4(__VA_ARGS__) +#define YQ_NULL_CHECK_IMPL_N3(N, ...) YQ_NULL_CHECK_IMPL_ ## N(__VA_ARGS__) +#define YQ_NULL_CHECK_IMPL_N2(N, ...) YQ_NULL_CHECK_IMPL_N3(N, __VA_ARGS__) +#define YQ_NULL_CHECK_IMPL_N(...) YQ_NULL_CHECK_IMPL_N2(YQ_ARG_COUNT(__VA_ARGS__), __VA_ARGS__) + +// File name, line numbes, and function name are statically concatenated at compile time, with a reduced string sharing trade off +#define YQ_LOCATION __FILE__ "(" YQ_STRINGIZE(__LINE__) "): " + +// Macros to log messages for debug builds. +// For release builds these will not output anything and no code or data will be generate (except for error logging) +#define YQ_LOG_WARNING(a_fmt, ...) ((BT_IsRelease) ? ((void)0) : yqLogMessage(LL_Warning, YQ_LOCATION, YQ_FUNCTION_NAME, "warning: " a_fmt, ##__VA_ARGS__)) +#define YQ_LOG_DEBUG(a_fmt, ...) ((BT_IsRelease) ? ((void)0) : yqLogMessage(LL_Debug, YQ_LOCATION, YQ_FUNCTION_NAME, "debug: " a_fmt, ##__VA_ARGS__)) +#define YQ_LOG_INFO(a_fmt, ...) ((BT_IsRelease) ? ((void)0) : yqLogMessage(LL_Info, YQ_LOCATION, YQ_FUNCTION_NAME, "info: " a_fmt, ##__VA_ARGS__)) +#define YQ_LOG_ERROR(a_fmt, ...) yqLogMessage(LL_Error, YQ_LOCATION, YQ_FUNCTION_NAME, "error: " a_fmt, ##__VA_ARGS__) + +// Macros that only generate log code in verbose debug builds. The logging logs the entry to API calls and the exit from them and what return code is being returned. +// In debug and release, on errors, the code always calls the installed or default error handling routine +enum { YQ_VERBOSE_DEBUGGING = 0 }; +#define YQ_API_VERBOSE(msg) ((YQ_VERBOSE_DEBUGGING) ? YQ_LOG_DEBUG(msg) : ((void)0)) +#define YQ_API_ERROR(result, ...) (yqHandleError(result, YQ_LOCATION, YQ_FUNCTION_NAME, __VA_ARGS__), YQ_LOG_ERROR("Leave: " #result " (%i)", result), result) +#define YQ_API_ENTER() YQ_API_VERBOSE("Enter") +#define YQ_API_LEAVE(result, ...) return ((result == R_Okay) ? (YQ_API_VERBOSE("Leave"), result) : YQ_API_ERROR(result, "" __VA_ARGS__)) + +// Macros to do common parameter and precondition checking for API function calls +// This macro checks the pre-condition is met, if it doesn't then returns the error result and executes the provided cleanup code +// See also 'finally'. Probably during pre-condition checking, there shouldn't be any cleanup yet as no work should be done at this stage. +//#define YQ_API_PRECONDITION_CLEANUP(condition,result,cleanup) do { if (!(condition)) { cleanup; YQ_API_LEAVE(result); } } while (0,0) +#define YQ_API_PRECONDITION(condition,result) do { if (!(condition)) { YQ_API_LEAVE(result, #condition); } } while (0,0)//YQ_API_PRECONDITION_CLEANUP(condition,result, ((void)0)) +#define YQ_API_PRECONDITION_NULL(...) YQ_NULL_CHECK_IMPL_N(__VA_ARGS__) + +// Can redirect to the user heap manager routines if provided +#define YQ_ALLOC(type) ((type*)(yqAllocateMemory(sizeof(type)))) +#define YQ_ALLOC_ARRAY(type,siz) ((type*)(yqAllocateMemory(sizeof(type)*(siz)))) +#define YQ_REALLOC_ARRAY(ptr,type,siz) ((type*)(yqReallocateMemory(ptr,sizeof(type)*(siz)))) +#define YQ_FREE(ptr) (((ptr)) ? (void)(yqFreeMemory((void*)(ptr)), (ptr) = nullptr) : (void)0) + +// C++ wrappers for using the custom allocators to replace use of new / delete (does placement new with custom allocated memory) +// Note: The custom allocators are expected to return null if the allocation fails and not throw. The macros below do not check this. +#define YQ_NEW(type) (new(YQ_ALLOC(type)) type) +#define YQ_NEW_PARAMS(type, params) (new(YQ_ALLOC(type)) type params) +#define YQ_DELETE(obj,type) ((obj) ? (void)((obj)->~type(), YQ_FREE(obj)) : (void)0) + + +/* + +Example usage: + +Result SomeFunction(T1 **Param1, T2 *Param2, T3 *Param3, int Param4) +{ + YQ_API_ENTER(); + + // Check pre-conditions + YQ_API_PRECONDITION_NULL(Param1, Param2, Param3); + YQ_API_PRECONDITION(Param4 == 42, R_InvalidParameter); + + if (errorHappened) + YQ_API_LEAVE(R_Failure); + + YQ_API_LEAVE(R_Okay); +} + +*/ + + +#endif // MACROS_H diff --git a/Framework/NonCopyable.h b/Framework/NonCopyable.h index 3881321..b8cb274 100644 --- a/Framework/NonCopyable.h +++ b/Framework/NonCopyable.h @@ -3,6 +3,62 @@ /// XXX: Not sure but maybe there is a C++11 std::non_copyable that does the same as this +/// Actually C++11 has the additional ability to add '= delete' to a member to say it can be used. + +#ifdef HAVE_CPP11 +# define DELETE = delete +#else +# define DELETE +#endif + + +/// This way has the advantage of not adding to the size of the base class if that is important [1] +/// [1] Ref: http://trac.webkit.org/changeset/68414 +#define MAKE_NONCOPYABLE(ClassName) \ + private: \ + ClassName(const ClassName&) DELETE; \ + ClassName& operator=(const ClassName&) DELETE; \ + + +/* Utility classes */ +struct NoCopy +{ +public: + NoCopy() {} +private: + NoCopy(const NoCopy &) DELETE; +}; + +struct NoAssign +{ +private: + NoAssign &operator=(const NoAssign &) DELETE; +}; + +struct NonInstantiable +{ +private: + NonInstantiable() DELETE; +}; + +struct NoCopyAssign : NoCopy, NoAssign +{ +}; + +typedef NoCopyAssign NoAssignCopy; + +// Not tested +struct NoMove +{ +public: + NoMove() {} +private: + NoMove(const NoMove &&) DELETE; +}; + +struct NoMoveCopyAssign : NoMove, NoCopy, NoAssign +{ +}; /// @@ -10,9 +66,9 @@ /// /// Making a class NonCopyable means preventing objects of the class being copied. This is useful if /// the class manages a resource that can only be held by one object such that it can't be reference -/// counted, or it is the underlying object that becomes reference counted. +/// counted, or it is the underlying object that becomes reference counted. Idea from here [1]. /// -/// Idea comes from here: http://stackoverflow.com/questions/9458741/with-explicitly-deleted-member-functions-in-c11-is-it-still-worthwhile-to-inh +/// [1] Ref: http://stackoverflow.com/questions/9458741/with-explicitly-deleted-member-functions-in-c11-is-it-still-worthwhile-to-inh /// class NonCopyable { @@ -34,12 +90,19 @@ MyNonCopyableClass() {} }; + MyNonCopyableClass create() + { + MyNonCopyableClass var; + return var; + } + Now it is not possible to copy objects of this type without a compiler error: - void function() + void function() { - MyNonCopyableClass obj1; - MyNonCopyableClass obj2; + MyNonCopyableClass obj1; + MyNonCopyableClass obj2; + MyNonCopyableClass obj3 = create(); // Allowed due to move semantics in c++11, use NoMoveCopyAssign to prevent // These attempts to make a copy of obj1 will cause a compiler error MyNonCopyableClass obj3(obj1); diff --git a/Framework/ObjectFilter.cpp b/Framework/ObjectFilter.cpp new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/Framework/ObjectFilter.cpp diff --git a/Framework/ObjectInspector.cpp b/Framework/ObjectInspector.cpp new file mode 100644 index 0000000..91b6726 --- /dev/null +++ b/Framework/ObjectInspector.cpp @@ -0,0 +1,90 @@ +#include "ObjectInspector.h" + +#include "ObjectPropertyModel.h" +#include "lib/ObjectProxy.h" + +#include +#include +#include +#include +#include +#include + +ObjectInspector::ObjectInspector(QWidget* parent) +: QWidget(parent) +{ + QVBoxLayout* layout = new QVBoxLayout(this); + layout->setMargin(0); + + m_nameLabel = new QLabel(this); + layout->addWidget(m_nameLabel); + + m_propertyView = new QTableView(this); + m_propertyView->verticalHeader()->setVisible(false); + m_propertyView->horizontalHeader()->setStretchLastSection(true); + + QHBoxLayout* filterLayout = new QHBoxLayout; + QLabel* filterLabel = new QLabel(tr("Filter:"),this); + m_propertyFilterEdit = new QLineEdit(this); + m_propertyFilterEdit->setToolTip(tr("String or regular expression to filter property list with")); + + connect(m_propertyFilterEdit,SIGNAL(textChanged(QString)), + this,SLOT(changeFilter(QString))); + filterLayout->addWidget(filterLabel); + filterLayout->addWidget(m_propertyFilterEdit); + + m_model = new ObjectPropertyModel(this); + m_propertySortModel = new QSortFilterProxyModel(this); + m_propertySortModel->setSourceModel(m_model); + + m_propertyView->setModel(m_propertySortModel); + + layout->addWidget(m_propertyView); + layout->addLayout(filterLayout); + + setObject(ObjectProxy::Pointer()); +} + +QString ObjectInspector::formatAddress(void* ptr) +{ + quintptr intPtr = reinterpret_cast(ptr); + return QString("0x%1").arg(QString(QByteArray::number(intPtr,16))); +} + +void ObjectInspector::refresh() +{ + setObject(m_currentObject); +} + +void ObjectInspector::setObject(ObjectProxy::Pointer object) +{ + QString labelText; + + if (object) + { + labelText = QString("%1 [%2] (%3)") + .arg(object->className()) + .arg(object->objectName()) + .arg(formatAddress(reinterpret_cast(object->address()))); + } + else + { + labelText = tr("[No Object Selected]"); + } + + m_nameLabel->setText(labelText); + m_currentObject = object; + m_model->setObject(object); + m_propertySortModel->sort(0,Qt::AscendingOrder); +} + +ObjectProxy::Pointer ObjectInspector::object() const +{ + return m_currentObject; +} + +void ObjectInspector::changeFilter(const QString& pattern) +{ + m_propertySortModel->setFilterRegExp(QRegExp(pattern)); +} + diff --git a/Framework/ObjectInspector.h b/Framework/ObjectInspector.h new file mode 100644 index 0000000..e1ce3ac --- /dev/null +++ b/Framework/ObjectInspector.h @@ -0,0 +1,40 @@ +#pragma once + +#include "lib/ObjectProxy.h" + +#include + +class QLabel; +class QLineEdit; +class QTableView; +class QSortFilterProxyModel; + +class ObjectPropertyModel; + +class ObjectInspector : public QWidget +{ + Q_OBJECT + + public: + ObjectInspector(QWidget* parent); + + void setObject(ObjectProxy::Pointer object); + ObjectProxy::Pointer object() const; + + static QString formatAddress(void* ptr); + + public Q_SLOTS: + void refresh(); + + private Q_SLOTS: + void changeFilter(const QString& pattern); + + private: + QLabel* m_nameLabel; + QTableView* m_propertyView; + ObjectProxy::Pointer m_currentObject; + ObjectPropertyModel* m_model; + QSortFilterProxyModel* m_propertySortModel; + QLineEdit* m_propertyFilterEdit; +}; + diff --git a/Framework/Array.h b/Framework/Array.h new file mode 100644 index 0000000..ab83e77 --- /dev/null +++ b/Framework/Array.h @@ -0,0 +1,98 @@ +#ifndef ARRAY_H +#define ARRAY_H + + +#include "Utilities.h" + + +// std::vector replacement that is cut down but familiar. +// It uses the custom allocators, and is restricted to POD elements only +template +struct PODArrayPOD // A dynamic array of POD type elements which is itself a POD type (so can have multi-dimensional arrays of these). +{ + static_assert(std::is_pod::value, "T must be a POD type."); + + void init() { m_magic = 0x01020304; m_capacity = m_size = 0; m_data = nullptr; } + void deinit() { if (m_data != nullptr) YQ_FREE(m_data); } + + const T& at(size_t a_pos) const { checkInited(); checkIndex(a_pos); return m_data[a_pos]; } + T& at(size_t a_pos) { checkInited(); checkIndex(a_pos); return m_data[a_pos]; } + const T& operator[](size_t a_pos) const { checkInited(); checkIndex(a_pos); return m_data[a_pos]; } + void pop_back() { checkInited(); checkIndex(1); m_size--; } + const T& back() const { checkInited(); return m_data[m_size - 1]; } + T& back() { checkInited(); return m_data[m_size - 1]; } + void push_back(const T& a_val) { checkInited(); if (m_size == m_capacity) ensureSpaceFor(1); m_data[m_size++] = a_val; } + void push(const T& a_val) { checkInited(); push_back(a_val); } + T pop() { checkInited(); pop_back(); return m_data[m_size]; } + void reserve(size_t a_count) { checkInited(); m_capacity = a_count; m_data = YQ_REALLOC_ARRAY(m_data, T, a_count); } + size_t capacity() const { checkInited(); return m_capacity; } + size_t size() const { checkInited(); return m_size; } + T* data() const { checkInited(); return m_data; } + void clear() { checkInited(); m_size = 0; } +//private: // Actually making private would make this no longer a POD type + yqResult checkInited() const { if (BT_IsDebug) YQ_API_PRECONDITION(m_magic == 0x01020304, R_Uninitialized); return R_Okay; } + yqResult checkIndex(size_t a_pos) const { YQ_API_PRECONDITION(a_pos < m_size, R_IndexOutOfRange); return R_Okay; } + void ensureSpaceFor(size_t a_count) { reserve(std::max(m_size + a_count, m_capacity + m_capacity / 2)); } // try to grow by 50% + T* m_data; // Because of this pointer it is not safe to copy DynamicArray objects around. Making it a shared_ptr though stops this being a POD type + size_t m_capacity; + size_t m_size; + uint32_t m_magic; +}; + + +static_assert(std::is_pod >::value, "PODArrayPOD must be a POD type."); + + +// A dynamic array of POD type elements. +// It isn't POD itself, but is instead reference counted so safer to deal with. +template +struct ArrayPOD +{ + static_assert(std::is_pod::value, "T must be a POD type."); + + // Actually not reference counted yet, but safe as objects of this type can't be copied + ArrayPOD& operator=(const ArrayPOD&) = delete; + ArrayPOD(const ArrayPOD&) = delete; + ArrayPOD() { m_capacity = m_size = 0; m_data = nullptr; } + ~ArrayPOD() { if (m_data != nullptr) YQ_FREE(m_data); } + + const T& at(size_t a_pos) const { checkIndex(a_pos); return m_data[a_pos]; } + T& at(size_t a_pos) { checkIndex(a_pos); return m_data[a_pos]; } + const T& operator[](size_t a_pos) const { checkIndex(a_pos); return m_data[a_pos]; } + void pop_back() { checkIndex(1); m_size--; } + const T& back() const { return m_data[m_size - 1]; } + T& back() { return m_data[m_size - 1]; } + void push_back(const T& a_val) { if (m_size == m_capacity) ensureSpaceFor(1); m_data[m_size++] = a_val; } + void push(const T& a_val) { push_back(a_val); } + T pop() { pop_back(); return m_data[m_size]; } + void reserve(size_t a_count) { m_capacity = a_count; m_data = YQ_REALLOC_ARRAY(m_data, T, a_count); } + size_t capacity() const { return m_capacity; } + size_t size() const { return m_size; } + T* data() const { return m_data; } + void clear() { m_size = 0; } +private: + yqResult checkIndex(size_t a_pos) const { YQ_API_PRECONDITION(a_pos < m_size, R_IndexOutOfRange); return R_Okay; } + void ensureSpaceFor(size_t a_count) { reserve(std::max(m_size + a_count, m_capacity + m_capacity / 2)); } // try to grow by 50% + T* m_data; // Because of this pointer it is not safe to copy DynamicArray objects around. Making it a shared_ptr though stops this being a POD type + size_t m_capacity; + size_t m_size; +}; + + +struct PODString +{ +public: + void init() { m_data.init(); } + void deinit() { m_data.deinit(); } + void operator=(const char* a_str) { m_data.clear(); operator+=(a_str); } + void operator+=(const char* a_str) { while (*a_str) m_data.push_back(*a_str++); m_data.push(0); m_data.pop(); } + char* c_str() { return m_data.data(); } +//private: // Actually making private would make this no longer a POD type + PODArrayPOD m_data; +}; + + +static_assert(std::is_pod::value, "PODString must be a POD type."); + + +#endif // ARRAY_H diff --git a/Framework/Macros.h b/Framework/Macros.h new file mode 100644 index 0000000..6ca8c86 --- /dev/null +++ b/Framework/Macros.h @@ -0,0 +1,111 @@ +#ifndef MACROS_H +#define MACROS_H + + +// +// Various common macros +// +#define YQ_ARRAY_SIZE(x) (sizeof(x)/sizeof((x)[0])) + +#define YQ_EXPAND(x) x + +#define YQ_STRINGIZE_IMPL(val) #val +#define YQ_STRINGIZE(val) YQ_STRINGIZE_IMPL(val) + +#define YQ_CONCATENATE_IMPL(x, y) x##y +#define YQ_CONCATENATE(x, y) YQ_CONCATENATE_IMPL(x, y) + +// If 0 args, it mis-reports a count of 1. Also it only counts up to 10 args. +#define YQ_ARG_COUNT_IMPL(X,A,B,C,D,E,F,G,H,I,J,N,...) N +#define YQ_ARG_COUNT(...) (YQ_EXPAND(YQ_ARG_COUNT_IMPL(_,__VA_ARGS__, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0))) + +#ifdef __COUNTER__ +# define YQ_ANONYMOUS_VARIABLE(str) YQ_CONCATENATE(str, __COUNTER__) +#else +# define YQ_ANONYMOUS_VARIABLE(str) YQ_CONCATENATE(str, __LINE__) +#endif + +#define YQ_UNUSED(x) std::ignore = (x) + +#ifdef __MINGW32__ +// Avoid callbacks +# define YQ_CALLBACK __cdecl +# define YQ_FUNCTION_NAME __PRETTY_FUNCTION__ // __func__ +#elif defined(_WIN32) +# define YQ_CALLBACK __cdecl +# define YQ_FUNCTION_NAME __FUNCTION__ +#else +# define YQ_CALLBACK +# define YQ_FUNCTION_NAME __PRETTY_FUNCTION__ // __func__ +#endif + +// Helper marcos for doing a variable number of null checks +#define YQ_NULL_CHECK_IMPL_1(arg) if (arg == nullptr) YQ_API_LEAVE(R_InvalidParameter); +#define YQ_NULL_CHECK_IMPL_2(arg,...) YQ_NULL_CHECK_IMPL_1(arg) YQ_NULL_CHECK_IMPL_1(__VA_ARGS__) +#define YQ_NULL_CHECK_IMPL_3(arg,...) YQ_NULL_CHECK_IMPL_1(arg) YQ_NULL_CHECK_IMPL_2(__VA_ARGS__) +#define YQ_NULL_CHECK_IMPL_4(arg,...) YQ_NULL_CHECK_IMPL_1(arg) YQ_NULL_CHECK_IMPL_3(__VA_ARGS__) +#define YQ_NULL_CHECK_IMPL_5(arg,...) YQ_NULL_CHECK_IMPL_1(arg) YQ_NULL_CHECK_IMPL_4(__VA_ARGS__) +#define YQ_NULL_CHECK_IMPL_N3(N, ...) YQ_NULL_CHECK_IMPL_ ## N(__VA_ARGS__) +#define YQ_NULL_CHECK_IMPL_N2(N, ...) YQ_NULL_CHECK_IMPL_N3(N, __VA_ARGS__) +#define YQ_NULL_CHECK_IMPL_N(...) YQ_NULL_CHECK_IMPL_N2(YQ_ARG_COUNT(__VA_ARGS__), __VA_ARGS__) + +// File name, line numbes, and function name are statically concatenated at compile time, with a reduced string sharing trade off +#define YQ_LOCATION __FILE__ "(" YQ_STRINGIZE(__LINE__) "): " + +// Macros to log messages for debug builds. +// For release builds these will not output anything and no code or data will be generate (except for error logging) +#define YQ_LOG_WARNING(a_fmt, ...) ((BT_IsRelease) ? ((void)0) : yqLogMessage(LL_Warning, YQ_LOCATION, YQ_FUNCTION_NAME, "warning: " a_fmt, ##__VA_ARGS__)) +#define YQ_LOG_DEBUG(a_fmt, ...) ((BT_IsRelease) ? ((void)0) : yqLogMessage(LL_Debug, YQ_LOCATION, YQ_FUNCTION_NAME, "debug: " a_fmt, ##__VA_ARGS__)) +#define YQ_LOG_INFO(a_fmt, ...) ((BT_IsRelease) ? ((void)0) : yqLogMessage(LL_Info, YQ_LOCATION, YQ_FUNCTION_NAME, "info: " a_fmt, ##__VA_ARGS__)) +#define YQ_LOG_ERROR(a_fmt, ...) yqLogMessage(LL_Error, YQ_LOCATION, YQ_FUNCTION_NAME, "error: " a_fmt, ##__VA_ARGS__) + +// Macros that only generate log code in verbose debug builds. The logging logs the entry to API calls and the exit from them and what return code is being returned. +// In debug and release, on errors, the code always calls the installed or default error handling routine +enum { YQ_VERBOSE_DEBUGGING = 0 }; +#define YQ_API_VERBOSE(msg) ((YQ_VERBOSE_DEBUGGING) ? YQ_LOG_DEBUG(msg) : ((void)0)) +#define YQ_API_ERROR(result, ...) (yqHandleError(result, YQ_LOCATION, YQ_FUNCTION_NAME, __VA_ARGS__), YQ_LOG_ERROR("Leave: " #result " (%i)", result), result) +#define YQ_API_ENTER() YQ_API_VERBOSE("Enter") +#define YQ_API_LEAVE(result, ...) return ((result == R_Okay) ? (YQ_API_VERBOSE("Leave"), result) : YQ_API_ERROR(result, "" __VA_ARGS__)) + +// Macros to do common parameter and precondition checking for API function calls +// This macro checks the pre-condition is met, if it doesn't then returns the error result and executes the provided cleanup code +// See also 'finally'. Probably during pre-condition checking, there shouldn't be any cleanup yet as no work should be done at this stage. +//#define YQ_API_PRECONDITION_CLEANUP(condition,result,cleanup) do { if (!(condition)) { cleanup; YQ_API_LEAVE(result); } } while (0,0) +#define YQ_API_PRECONDITION(condition,result) do { if (!(condition)) { YQ_API_LEAVE(result, #condition); } } while (0,0)//YQ_API_PRECONDITION_CLEANUP(condition,result, ((void)0)) +#define YQ_API_PRECONDITION_NULL(...) YQ_NULL_CHECK_IMPL_N(__VA_ARGS__) + +// Can redirect to the user heap manager routines if provided +#define YQ_ALLOC(type) ((type*)(yqAllocateMemory(sizeof(type)))) +#define YQ_ALLOC_ARRAY(type,siz) ((type*)(yqAllocateMemory(sizeof(type)*(siz)))) +#define YQ_REALLOC_ARRAY(ptr,type,siz) ((type*)(yqReallocateMemory(ptr,sizeof(type)*(siz)))) +#define YQ_FREE(ptr) (((ptr)) ? (void)(yqFreeMemory((void*)(ptr)), (ptr) = nullptr) : (void)0) + +// C++ wrappers for using the custom allocators to replace use of new / delete (does placement new with custom allocated memory) +// Note: The custom allocators are expected to return null if the allocation fails and not throw. The macros below do not check this. +#define YQ_NEW(type) (new(YQ_ALLOC(type)) type) +#define YQ_NEW_PARAMS(type, params) (new(YQ_ALLOC(type)) type params) +#define YQ_DELETE(obj,type) ((obj) ? (void)((obj)->~type(), YQ_FREE(obj)) : (void)0) + + +/* + +Example usage: + +Result SomeFunction(T1 **Param1, T2 *Param2, T3 *Param3, int Param4) +{ + YQ_API_ENTER(); + + // Check pre-conditions + YQ_API_PRECONDITION_NULL(Param1, Param2, Param3); + YQ_API_PRECONDITION(Param4 == 42, R_InvalidParameter); + + if (errorHappened) + YQ_API_LEAVE(R_Failure); + + YQ_API_LEAVE(R_Okay); +} + +*/ + + +#endif // MACROS_H diff --git a/Framework/NonCopyable.h b/Framework/NonCopyable.h index 3881321..b8cb274 100644 --- a/Framework/NonCopyable.h +++ b/Framework/NonCopyable.h @@ -3,6 +3,62 @@ /// XXX: Not sure but maybe there is a C++11 std::non_copyable that does the same as this +/// Actually C++11 has the additional ability to add '= delete' to a member to say it can be used. + +#ifdef HAVE_CPP11 +# define DELETE = delete +#else +# define DELETE +#endif + + +/// This way has the advantage of not adding to the size of the base class if that is important [1] +/// [1] Ref: http://trac.webkit.org/changeset/68414 +#define MAKE_NONCOPYABLE(ClassName) \ + private: \ + ClassName(const ClassName&) DELETE; \ + ClassName& operator=(const ClassName&) DELETE; \ + + +/* Utility classes */ +struct NoCopy +{ +public: + NoCopy() {} +private: + NoCopy(const NoCopy &) DELETE; +}; + +struct NoAssign +{ +private: + NoAssign &operator=(const NoAssign &) DELETE; +}; + +struct NonInstantiable +{ +private: + NonInstantiable() DELETE; +}; + +struct NoCopyAssign : NoCopy, NoAssign +{ +}; + +typedef NoCopyAssign NoAssignCopy; + +// Not tested +struct NoMove +{ +public: + NoMove() {} +private: + NoMove(const NoMove &&) DELETE; +}; + +struct NoMoveCopyAssign : NoMove, NoCopy, NoAssign +{ +}; /// @@ -10,9 +66,9 @@ /// /// Making a class NonCopyable means preventing objects of the class being copied. This is useful if /// the class manages a resource that can only be held by one object such that it can't be reference -/// counted, or it is the underlying object that becomes reference counted. +/// counted, or it is the underlying object that becomes reference counted. Idea from here [1]. /// -/// Idea comes from here: http://stackoverflow.com/questions/9458741/with-explicitly-deleted-member-functions-in-c11-is-it-still-worthwhile-to-inh +/// [1] Ref: http://stackoverflow.com/questions/9458741/with-explicitly-deleted-member-functions-in-c11-is-it-still-worthwhile-to-inh /// class NonCopyable { @@ -34,12 +90,19 @@ MyNonCopyableClass() {} }; + MyNonCopyableClass create() + { + MyNonCopyableClass var; + return var; + } + Now it is not possible to copy objects of this type without a compiler error: - void function() + void function() { - MyNonCopyableClass obj1; - MyNonCopyableClass obj2; + MyNonCopyableClass obj1; + MyNonCopyableClass obj2; + MyNonCopyableClass obj3 = create(); // Allowed due to move semantics in c++11, use NoMoveCopyAssign to prevent // These attempts to make a copy of obj1 will cause a compiler error MyNonCopyableClass obj3(obj1); diff --git a/Framework/ObjectFilter.cpp b/Framework/ObjectFilter.cpp new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/Framework/ObjectFilter.cpp diff --git a/Framework/ObjectInspector.cpp b/Framework/ObjectInspector.cpp new file mode 100644 index 0000000..91b6726 --- /dev/null +++ b/Framework/ObjectInspector.cpp @@ -0,0 +1,90 @@ +#include "ObjectInspector.h" + +#include "ObjectPropertyModel.h" +#include "lib/ObjectProxy.h" + +#include +#include +#include +#include +#include +#include + +ObjectInspector::ObjectInspector(QWidget* parent) +: QWidget(parent) +{ + QVBoxLayout* layout = new QVBoxLayout(this); + layout->setMargin(0); + + m_nameLabel = new QLabel(this); + layout->addWidget(m_nameLabel); + + m_propertyView = new QTableView(this); + m_propertyView->verticalHeader()->setVisible(false); + m_propertyView->horizontalHeader()->setStretchLastSection(true); + + QHBoxLayout* filterLayout = new QHBoxLayout; + QLabel* filterLabel = new QLabel(tr("Filter:"),this); + m_propertyFilterEdit = new QLineEdit(this); + m_propertyFilterEdit->setToolTip(tr("String or regular expression to filter property list with")); + + connect(m_propertyFilterEdit,SIGNAL(textChanged(QString)), + this,SLOT(changeFilter(QString))); + filterLayout->addWidget(filterLabel); + filterLayout->addWidget(m_propertyFilterEdit); + + m_model = new ObjectPropertyModel(this); + m_propertySortModel = new QSortFilterProxyModel(this); + m_propertySortModel->setSourceModel(m_model); + + m_propertyView->setModel(m_propertySortModel); + + layout->addWidget(m_propertyView); + layout->addLayout(filterLayout); + + setObject(ObjectProxy::Pointer()); +} + +QString ObjectInspector::formatAddress(void* ptr) +{ + quintptr intPtr = reinterpret_cast(ptr); + return QString("0x%1").arg(QString(QByteArray::number(intPtr,16))); +} + +void ObjectInspector::refresh() +{ + setObject(m_currentObject); +} + +void ObjectInspector::setObject(ObjectProxy::Pointer object) +{ + QString labelText; + + if (object) + { + labelText = QString("%1 [%2] (%3)") + .arg(object->className()) + .arg(object->objectName()) + .arg(formatAddress(reinterpret_cast(object->address()))); + } + else + { + labelText = tr("[No Object Selected]"); + } + + m_nameLabel->setText(labelText); + m_currentObject = object; + m_model->setObject(object); + m_propertySortModel->sort(0,Qt::AscendingOrder); +} + +ObjectProxy::Pointer ObjectInspector::object() const +{ + return m_currentObject; +} + +void ObjectInspector::changeFilter(const QString& pattern) +{ + m_propertySortModel->setFilterRegExp(QRegExp(pattern)); +} + diff --git a/Framework/ObjectInspector.h b/Framework/ObjectInspector.h new file mode 100644 index 0000000..e1ce3ac --- /dev/null +++ b/Framework/ObjectInspector.h @@ -0,0 +1,40 @@ +#pragma once + +#include "lib/ObjectProxy.h" + +#include + +class QLabel; +class QLineEdit; +class QTableView; +class QSortFilterProxyModel; + +class ObjectPropertyModel; + +class ObjectInspector : public QWidget +{ + Q_OBJECT + + public: + ObjectInspector(QWidget* parent); + + void setObject(ObjectProxy::Pointer object); + ObjectProxy::Pointer object() const; + + static QString formatAddress(void* ptr); + + public Q_SLOTS: + void refresh(); + + private Q_SLOTS: + void changeFilter(const QString& pattern); + + private: + QLabel* m_nameLabel; + QTableView* m_propertyView; + ObjectProxy::Pointer m_currentObject; + ObjectPropertyModel* m_model; + QSortFilterProxyModel* m_propertySortModel; + QLineEdit* m_propertyFilterEdit; +}; + diff --git a/Framework/ObjectPropertyModel.cpp b/Framework/ObjectPropertyModel.cpp new file mode 100644 index 0000000..6e4d4f7 --- /dev/null +++ b/Framework/ObjectPropertyModel.cpp @@ -0,0 +1,91 @@ +#include "ObjectPropertyModel.h" + +#include "lib/ObjectProxy.h" + +#include +#include + +ObjectPropertyModel::ObjectPropertyModel(QObject* parent) +: QStandardItemModel(parent) +{ + setColumnCount(2); + setObject(ObjectProxy::Pointer()); + + connect(this,SIGNAL(itemChanged(QStandardItem*)), + this,SLOT(updatePropertyValue(QStandardItem*))); +} + +QVariant ObjectPropertyModel::toEditValue(const QVariant& value) +{ + // TODO - Find existing code which can handle display and editing + // of common value types. + switch (value.userType()) + { + case QVariant::Size: + case QVariant::SizeF: + { + QSize size = value.toSize(); + return QString("%1 x %2").arg(size.width()).arg(size.height()); + } + case QVariant::Rect: + case QVariant::RectF: + { + QRect rect = value.toRect(); + return QString("(%1,%2) (%3,%4)").arg(rect.left()).arg(rect.top()).arg(rect.width()).arg(rect.height()); + } + } + return value; +} + +void ObjectPropertyModel::updatePropertyValue(QStandardItem* item) +{ + QStandardItem* propertyItem = this->item(item->row(),0); + QString propertyName = propertyItem->text(); + + if (m_object) + { + QVariant newValue = item->data(Qt::EditRole); + qDebug() << "setting property" << propertyName << "to" << newValue; + m_object->writeProperty(propertyName,newValue); + } +} + +void ObjectPropertyModel::setObject(ObjectProxy::Pointer object) +{ + clear(); + + setHorizontalHeaderLabels( + QStringList() << tr("Property") << tr("Value") + ); + + m_object = object; + + if (object) + { + object->refresh(); + int rowCount = 0; + QListIterator iter(object->properties()); + while (iter.hasNext()) + { + ObjectProxy::Property property = iter.next(); + QVariant value = property.value; + QVariant editValue = toEditValue(value); + + QStandardItem* nameItem = new QStandardItem(property.name); + nameItem->setEditable(false); + + QStandardItem* valueItem = new QStandardItem; + valueItem->setData(editValue,Qt::EditRole); + valueItem->setEditable(property.isWritable); + + insertRow(rowCount,QList() << nameItem << valueItem); + ++rowCount; + } + } +} + +ObjectProxy::Pointer ObjectPropertyModel::object() const +{ + return m_object; +} + diff --git a/Framework/Array.h b/Framework/Array.h new file mode 100644 index 0000000..ab83e77 --- /dev/null +++ b/Framework/Array.h @@ -0,0 +1,98 @@ +#ifndef ARRAY_H +#define ARRAY_H + + +#include "Utilities.h" + + +// std::vector replacement that is cut down but familiar. +// It uses the custom allocators, and is restricted to POD elements only +template +struct PODArrayPOD // A dynamic array of POD type elements which is itself a POD type (so can have multi-dimensional arrays of these). +{ + static_assert(std::is_pod::value, "T must be a POD type."); + + void init() { m_magic = 0x01020304; m_capacity = m_size = 0; m_data = nullptr; } + void deinit() { if (m_data != nullptr) YQ_FREE(m_data); } + + const T& at(size_t a_pos) const { checkInited(); checkIndex(a_pos); return m_data[a_pos]; } + T& at(size_t a_pos) { checkInited(); checkIndex(a_pos); return m_data[a_pos]; } + const T& operator[](size_t a_pos) const { checkInited(); checkIndex(a_pos); return m_data[a_pos]; } + void pop_back() { checkInited(); checkIndex(1); m_size--; } + const T& back() const { checkInited(); return m_data[m_size - 1]; } + T& back() { checkInited(); return m_data[m_size - 1]; } + void push_back(const T& a_val) { checkInited(); if (m_size == m_capacity) ensureSpaceFor(1); m_data[m_size++] = a_val; } + void push(const T& a_val) { checkInited(); push_back(a_val); } + T pop() { checkInited(); pop_back(); return m_data[m_size]; } + void reserve(size_t a_count) { checkInited(); m_capacity = a_count; m_data = YQ_REALLOC_ARRAY(m_data, T, a_count); } + size_t capacity() const { checkInited(); return m_capacity; } + size_t size() const { checkInited(); return m_size; } + T* data() const { checkInited(); return m_data; } + void clear() { checkInited(); m_size = 0; } +//private: // Actually making private would make this no longer a POD type + yqResult checkInited() const { if (BT_IsDebug) YQ_API_PRECONDITION(m_magic == 0x01020304, R_Uninitialized); return R_Okay; } + yqResult checkIndex(size_t a_pos) const { YQ_API_PRECONDITION(a_pos < m_size, R_IndexOutOfRange); return R_Okay; } + void ensureSpaceFor(size_t a_count) { reserve(std::max(m_size + a_count, m_capacity + m_capacity / 2)); } // try to grow by 50% + T* m_data; // Because of this pointer it is not safe to copy DynamicArray objects around. Making it a shared_ptr though stops this being a POD type + size_t m_capacity; + size_t m_size; + uint32_t m_magic; +}; + + +static_assert(std::is_pod >::value, "PODArrayPOD must be a POD type."); + + +// A dynamic array of POD type elements. +// It isn't POD itself, but is instead reference counted so safer to deal with. +template +struct ArrayPOD +{ + static_assert(std::is_pod::value, "T must be a POD type."); + + // Actually not reference counted yet, but safe as objects of this type can't be copied + ArrayPOD& operator=(const ArrayPOD&) = delete; + ArrayPOD(const ArrayPOD&) = delete; + ArrayPOD() { m_capacity = m_size = 0; m_data = nullptr; } + ~ArrayPOD() { if (m_data != nullptr) YQ_FREE(m_data); } + + const T& at(size_t a_pos) const { checkIndex(a_pos); return m_data[a_pos]; } + T& at(size_t a_pos) { checkIndex(a_pos); return m_data[a_pos]; } + const T& operator[](size_t a_pos) const { checkIndex(a_pos); return m_data[a_pos]; } + void pop_back() { checkIndex(1); m_size--; } + const T& back() const { return m_data[m_size - 1]; } + T& back() { return m_data[m_size - 1]; } + void push_back(const T& a_val) { if (m_size == m_capacity) ensureSpaceFor(1); m_data[m_size++] = a_val; } + void push(const T& a_val) { push_back(a_val); } + T pop() { pop_back(); return m_data[m_size]; } + void reserve(size_t a_count) { m_capacity = a_count; m_data = YQ_REALLOC_ARRAY(m_data, T, a_count); } + size_t capacity() const { return m_capacity; } + size_t size() const { return m_size; } + T* data() const { return m_data; } + void clear() { m_size = 0; } +private: + yqResult checkIndex(size_t a_pos) const { YQ_API_PRECONDITION(a_pos < m_size, R_IndexOutOfRange); return R_Okay; } + void ensureSpaceFor(size_t a_count) { reserve(std::max(m_size + a_count, m_capacity + m_capacity / 2)); } // try to grow by 50% + T* m_data; // Because of this pointer it is not safe to copy DynamicArray objects around. Making it a shared_ptr though stops this being a POD type + size_t m_capacity; + size_t m_size; +}; + + +struct PODString +{ +public: + void init() { m_data.init(); } + void deinit() { m_data.deinit(); } + void operator=(const char* a_str) { m_data.clear(); operator+=(a_str); } + void operator+=(const char* a_str) { while (*a_str) m_data.push_back(*a_str++); m_data.push(0); m_data.pop(); } + char* c_str() { return m_data.data(); } +//private: // Actually making private would make this no longer a POD type + PODArrayPOD m_data; +}; + + +static_assert(std::is_pod::value, "PODString must be a POD type."); + + +#endif // ARRAY_H diff --git a/Framework/Macros.h b/Framework/Macros.h new file mode 100644 index 0000000..6ca8c86 --- /dev/null +++ b/Framework/Macros.h @@ -0,0 +1,111 @@ +#ifndef MACROS_H +#define MACROS_H + + +// +// Various common macros +// +#define YQ_ARRAY_SIZE(x) (sizeof(x)/sizeof((x)[0])) + +#define YQ_EXPAND(x) x + +#define YQ_STRINGIZE_IMPL(val) #val +#define YQ_STRINGIZE(val) YQ_STRINGIZE_IMPL(val) + +#define YQ_CONCATENATE_IMPL(x, y) x##y +#define YQ_CONCATENATE(x, y) YQ_CONCATENATE_IMPL(x, y) + +// If 0 args, it mis-reports a count of 1. Also it only counts up to 10 args. +#define YQ_ARG_COUNT_IMPL(X,A,B,C,D,E,F,G,H,I,J,N,...) N +#define YQ_ARG_COUNT(...) (YQ_EXPAND(YQ_ARG_COUNT_IMPL(_,__VA_ARGS__, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0))) + +#ifdef __COUNTER__ +# define YQ_ANONYMOUS_VARIABLE(str) YQ_CONCATENATE(str, __COUNTER__) +#else +# define YQ_ANONYMOUS_VARIABLE(str) YQ_CONCATENATE(str, __LINE__) +#endif + +#define YQ_UNUSED(x) std::ignore = (x) + +#ifdef __MINGW32__ +// Avoid callbacks +# define YQ_CALLBACK __cdecl +# define YQ_FUNCTION_NAME __PRETTY_FUNCTION__ // __func__ +#elif defined(_WIN32) +# define YQ_CALLBACK __cdecl +# define YQ_FUNCTION_NAME __FUNCTION__ +#else +# define YQ_CALLBACK +# define YQ_FUNCTION_NAME __PRETTY_FUNCTION__ // __func__ +#endif + +// Helper marcos for doing a variable number of null checks +#define YQ_NULL_CHECK_IMPL_1(arg) if (arg == nullptr) YQ_API_LEAVE(R_InvalidParameter); +#define YQ_NULL_CHECK_IMPL_2(arg,...) YQ_NULL_CHECK_IMPL_1(arg) YQ_NULL_CHECK_IMPL_1(__VA_ARGS__) +#define YQ_NULL_CHECK_IMPL_3(arg,...) YQ_NULL_CHECK_IMPL_1(arg) YQ_NULL_CHECK_IMPL_2(__VA_ARGS__) +#define YQ_NULL_CHECK_IMPL_4(arg,...) YQ_NULL_CHECK_IMPL_1(arg) YQ_NULL_CHECK_IMPL_3(__VA_ARGS__) +#define YQ_NULL_CHECK_IMPL_5(arg,...) YQ_NULL_CHECK_IMPL_1(arg) YQ_NULL_CHECK_IMPL_4(__VA_ARGS__) +#define YQ_NULL_CHECK_IMPL_N3(N, ...) YQ_NULL_CHECK_IMPL_ ## N(__VA_ARGS__) +#define YQ_NULL_CHECK_IMPL_N2(N, ...) YQ_NULL_CHECK_IMPL_N3(N, __VA_ARGS__) +#define YQ_NULL_CHECK_IMPL_N(...) YQ_NULL_CHECK_IMPL_N2(YQ_ARG_COUNT(__VA_ARGS__), __VA_ARGS__) + +// File name, line numbes, and function name are statically concatenated at compile time, with a reduced string sharing trade off +#define YQ_LOCATION __FILE__ "(" YQ_STRINGIZE(__LINE__) "): " + +// Macros to log messages for debug builds. +// For release builds these will not output anything and no code or data will be generate (except for error logging) +#define YQ_LOG_WARNING(a_fmt, ...) ((BT_IsRelease) ? ((void)0) : yqLogMessage(LL_Warning, YQ_LOCATION, YQ_FUNCTION_NAME, "warning: " a_fmt, ##__VA_ARGS__)) +#define YQ_LOG_DEBUG(a_fmt, ...) ((BT_IsRelease) ? ((void)0) : yqLogMessage(LL_Debug, YQ_LOCATION, YQ_FUNCTION_NAME, "debug: " a_fmt, ##__VA_ARGS__)) +#define YQ_LOG_INFO(a_fmt, ...) ((BT_IsRelease) ? ((void)0) : yqLogMessage(LL_Info, YQ_LOCATION, YQ_FUNCTION_NAME, "info: " a_fmt, ##__VA_ARGS__)) +#define YQ_LOG_ERROR(a_fmt, ...) yqLogMessage(LL_Error, YQ_LOCATION, YQ_FUNCTION_NAME, "error: " a_fmt, ##__VA_ARGS__) + +// Macros that only generate log code in verbose debug builds. The logging logs the entry to API calls and the exit from them and what return code is being returned. +// In debug and release, on errors, the code always calls the installed or default error handling routine +enum { YQ_VERBOSE_DEBUGGING = 0 }; +#define YQ_API_VERBOSE(msg) ((YQ_VERBOSE_DEBUGGING) ? YQ_LOG_DEBUG(msg) : ((void)0)) +#define YQ_API_ERROR(result, ...) (yqHandleError(result, YQ_LOCATION, YQ_FUNCTION_NAME, __VA_ARGS__), YQ_LOG_ERROR("Leave: " #result " (%i)", result), result) +#define YQ_API_ENTER() YQ_API_VERBOSE("Enter") +#define YQ_API_LEAVE(result, ...) return ((result == R_Okay) ? (YQ_API_VERBOSE("Leave"), result) : YQ_API_ERROR(result, "" __VA_ARGS__)) + +// Macros to do common parameter and precondition checking for API function calls +// This macro checks the pre-condition is met, if it doesn't then returns the error result and executes the provided cleanup code +// See also 'finally'. Probably during pre-condition checking, there shouldn't be any cleanup yet as no work should be done at this stage. +//#define YQ_API_PRECONDITION_CLEANUP(condition,result,cleanup) do { if (!(condition)) { cleanup; YQ_API_LEAVE(result); } } while (0,0) +#define YQ_API_PRECONDITION(condition,result) do { if (!(condition)) { YQ_API_LEAVE(result, #condition); } } while (0,0)//YQ_API_PRECONDITION_CLEANUP(condition,result, ((void)0)) +#define YQ_API_PRECONDITION_NULL(...) YQ_NULL_CHECK_IMPL_N(__VA_ARGS__) + +// Can redirect to the user heap manager routines if provided +#define YQ_ALLOC(type) ((type*)(yqAllocateMemory(sizeof(type)))) +#define YQ_ALLOC_ARRAY(type,siz) ((type*)(yqAllocateMemory(sizeof(type)*(siz)))) +#define YQ_REALLOC_ARRAY(ptr,type,siz) ((type*)(yqReallocateMemory(ptr,sizeof(type)*(siz)))) +#define YQ_FREE(ptr) (((ptr)) ? (void)(yqFreeMemory((void*)(ptr)), (ptr) = nullptr) : (void)0) + +// C++ wrappers for using the custom allocators to replace use of new / delete (does placement new with custom allocated memory) +// Note: The custom allocators are expected to return null if the allocation fails and not throw. The macros below do not check this. +#define YQ_NEW(type) (new(YQ_ALLOC(type)) type) +#define YQ_NEW_PARAMS(type, params) (new(YQ_ALLOC(type)) type params) +#define YQ_DELETE(obj,type) ((obj) ? (void)((obj)->~type(), YQ_FREE(obj)) : (void)0) + + +/* + +Example usage: + +Result SomeFunction(T1 **Param1, T2 *Param2, T3 *Param3, int Param4) +{ + YQ_API_ENTER(); + + // Check pre-conditions + YQ_API_PRECONDITION_NULL(Param1, Param2, Param3); + YQ_API_PRECONDITION(Param4 == 42, R_InvalidParameter); + + if (errorHappened) + YQ_API_LEAVE(R_Failure); + + YQ_API_LEAVE(R_Okay); +} + +*/ + + +#endif // MACROS_H diff --git a/Framework/NonCopyable.h b/Framework/NonCopyable.h index 3881321..b8cb274 100644 --- a/Framework/NonCopyable.h +++ b/Framework/NonCopyable.h @@ -3,6 +3,62 @@ /// XXX: Not sure but maybe there is a C++11 std::non_copyable that does the same as this +/// Actually C++11 has the additional ability to add '= delete' to a member to say it can be used. + +#ifdef HAVE_CPP11 +# define DELETE = delete +#else +# define DELETE +#endif + + +/// This way has the advantage of not adding to the size of the base class if that is important [1] +/// [1] Ref: http://trac.webkit.org/changeset/68414 +#define MAKE_NONCOPYABLE(ClassName) \ + private: \ + ClassName(const ClassName&) DELETE; \ + ClassName& operator=(const ClassName&) DELETE; \ + + +/* Utility classes */ +struct NoCopy +{ +public: + NoCopy() {} +private: + NoCopy(const NoCopy &) DELETE; +}; + +struct NoAssign +{ +private: + NoAssign &operator=(const NoAssign &) DELETE; +}; + +struct NonInstantiable +{ +private: + NonInstantiable() DELETE; +}; + +struct NoCopyAssign : NoCopy, NoAssign +{ +}; + +typedef NoCopyAssign NoAssignCopy; + +// Not tested +struct NoMove +{ +public: + NoMove() {} +private: + NoMove(const NoMove &&) DELETE; +}; + +struct NoMoveCopyAssign : NoMove, NoCopy, NoAssign +{ +}; /// @@ -10,9 +66,9 @@ /// /// Making a class NonCopyable means preventing objects of the class being copied. This is useful if /// the class manages a resource that can only be held by one object such that it can't be reference -/// counted, or it is the underlying object that becomes reference counted. +/// counted, or it is the underlying object that becomes reference counted. Idea from here [1]. /// -/// Idea comes from here: http://stackoverflow.com/questions/9458741/with-explicitly-deleted-member-functions-in-c11-is-it-still-worthwhile-to-inh +/// [1] Ref: http://stackoverflow.com/questions/9458741/with-explicitly-deleted-member-functions-in-c11-is-it-still-worthwhile-to-inh /// class NonCopyable { @@ -34,12 +90,19 @@ MyNonCopyableClass() {} }; + MyNonCopyableClass create() + { + MyNonCopyableClass var; + return var; + } + Now it is not possible to copy objects of this type without a compiler error: - void function() + void function() { - MyNonCopyableClass obj1; - MyNonCopyableClass obj2; + MyNonCopyableClass obj1; + MyNonCopyableClass obj2; + MyNonCopyableClass obj3 = create(); // Allowed due to move semantics in c++11, use NoMoveCopyAssign to prevent // These attempts to make a copy of obj1 will cause a compiler error MyNonCopyableClass obj3(obj1); diff --git a/Framework/ObjectFilter.cpp b/Framework/ObjectFilter.cpp new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/Framework/ObjectFilter.cpp diff --git a/Framework/ObjectInspector.cpp b/Framework/ObjectInspector.cpp new file mode 100644 index 0000000..91b6726 --- /dev/null +++ b/Framework/ObjectInspector.cpp @@ -0,0 +1,90 @@ +#include "ObjectInspector.h" + +#include "ObjectPropertyModel.h" +#include "lib/ObjectProxy.h" + +#include +#include +#include +#include +#include +#include + +ObjectInspector::ObjectInspector(QWidget* parent) +: QWidget(parent) +{ + QVBoxLayout* layout = new QVBoxLayout(this); + layout->setMargin(0); + + m_nameLabel = new QLabel(this); + layout->addWidget(m_nameLabel); + + m_propertyView = new QTableView(this); + m_propertyView->verticalHeader()->setVisible(false); + m_propertyView->horizontalHeader()->setStretchLastSection(true); + + QHBoxLayout* filterLayout = new QHBoxLayout; + QLabel* filterLabel = new QLabel(tr("Filter:"),this); + m_propertyFilterEdit = new QLineEdit(this); + m_propertyFilterEdit->setToolTip(tr("String or regular expression to filter property list with")); + + connect(m_propertyFilterEdit,SIGNAL(textChanged(QString)), + this,SLOT(changeFilter(QString))); + filterLayout->addWidget(filterLabel); + filterLayout->addWidget(m_propertyFilterEdit); + + m_model = new ObjectPropertyModel(this); + m_propertySortModel = new QSortFilterProxyModel(this); + m_propertySortModel->setSourceModel(m_model); + + m_propertyView->setModel(m_propertySortModel); + + layout->addWidget(m_propertyView); + layout->addLayout(filterLayout); + + setObject(ObjectProxy::Pointer()); +} + +QString ObjectInspector::formatAddress(void* ptr) +{ + quintptr intPtr = reinterpret_cast(ptr); + return QString("0x%1").arg(QString(QByteArray::number(intPtr,16))); +} + +void ObjectInspector::refresh() +{ + setObject(m_currentObject); +} + +void ObjectInspector::setObject(ObjectProxy::Pointer object) +{ + QString labelText; + + if (object) + { + labelText = QString("%1 [%2] (%3)") + .arg(object->className()) + .arg(object->objectName()) + .arg(formatAddress(reinterpret_cast(object->address()))); + } + else + { + labelText = tr("[No Object Selected]"); + } + + m_nameLabel->setText(labelText); + m_currentObject = object; + m_model->setObject(object); + m_propertySortModel->sort(0,Qt::AscendingOrder); +} + +ObjectProxy::Pointer ObjectInspector::object() const +{ + return m_currentObject; +} + +void ObjectInspector::changeFilter(const QString& pattern) +{ + m_propertySortModel->setFilterRegExp(QRegExp(pattern)); +} + diff --git a/Framework/ObjectInspector.h b/Framework/ObjectInspector.h new file mode 100644 index 0000000..e1ce3ac --- /dev/null +++ b/Framework/ObjectInspector.h @@ -0,0 +1,40 @@ +#pragma once + +#include "lib/ObjectProxy.h" + +#include + +class QLabel; +class QLineEdit; +class QTableView; +class QSortFilterProxyModel; + +class ObjectPropertyModel; + +class ObjectInspector : public QWidget +{ + Q_OBJECT + + public: + ObjectInspector(QWidget* parent); + + void setObject(ObjectProxy::Pointer object); + ObjectProxy::Pointer object() const; + + static QString formatAddress(void* ptr); + + public Q_SLOTS: + void refresh(); + + private Q_SLOTS: + void changeFilter(const QString& pattern); + + private: + QLabel* m_nameLabel; + QTableView* m_propertyView; + ObjectProxy::Pointer m_currentObject; + ObjectPropertyModel* m_model; + QSortFilterProxyModel* m_propertySortModel; + QLineEdit* m_propertyFilterEdit; +}; + diff --git a/Framework/ObjectPropertyModel.cpp b/Framework/ObjectPropertyModel.cpp new file mode 100644 index 0000000..6e4d4f7 --- /dev/null +++ b/Framework/ObjectPropertyModel.cpp @@ -0,0 +1,91 @@ +#include "ObjectPropertyModel.h" + +#include "lib/ObjectProxy.h" + +#include +#include + +ObjectPropertyModel::ObjectPropertyModel(QObject* parent) +: QStandardItemModel(parent) +{ + setColumnCount(2); + setObject(ObjectProxy::Pointer()); + + connect(this,SIGNAL(itemChanged(QStandardItem*)), + this,SLOT(updatePropertyValue(QStandardItem*))); +} + +QVariant ObjectPropertyModel::toEditValue(const QVariant& value) +{ + // TODO - Find existing code which can handle display and editing + // of common value types. + switch (value.userType()) + { + case QVariant::Size: + case QVariant::SizeF: + { + QSize size = value.toSize(); + return QString("%1 x %2").arg(size.width()).arg(size.height()); + } + case QVariant::Rect: + case QVariant::RectF: + { + QRect rect = value.toRect(); + return QString("(%1,%2) (%3,%4)").arg(rect.left()).arg(rect.top()).arg(rect.width()).arg(rect.height()); + } + } + return value; +} + +void ObjectPropertyModel::updatePropertyValue(QStandardItem* item) +{ + QStandardItem* propertyItem = this->item(item->row(),0); + QString propertyName = propertyItem->text(); + + if (m_object) + { + QVariant newValue = item->data(Qt::EditRole); + qDebug() << "setting property" << propertyName << "to" << newValue; + m_object->writeProperty(propertyName,newValue); + } +} + +void ObjectPropertyModel::setObject(ObjectProxy::Pointer object) +{ + clear(); + + setHorizontalHeaderLabels( + QStringList() << tr("Property") << tr("Value") + ); + + m_object = object; + + if (object) + { + object->refresh(); + int rowCount = 0; + QListIterator iter(object->properties()); + while (iter.hasNext()) + { + ObjectProxy::Property property = iter.next(); + QVariant value = property.value; + QVariant editValue = toEditValue(value); + + QStandardItem* nameItem = new QStandardItem(property.name); + nameItem->setEditable(false); + + QStandardItem* valueItem = new QStandardItem; + valueItem->setData(editValue,Qt::EditRole); + valueItem->setEditable(property.isWritable); + + insertRow(rowCount,QList() << nameItem << valueItem); + ++rowCount; + } + } +} + +ObjectProxy::Pointer ObjectPropertyModel::object() const +{ + return m_object; +} + diff --git a/Framework/ObjectPropertyModel.h b/Framework/ObjectPropertyModel.h new file mode 100644 index 0000000..69e176c --- /dev/null +++ b/Framework/ObjectPropertyModel.h @@ -0,0 +1,27 @@ +#pragma once + +#include "lib/ObjectProxy.h" + +#include +#include + +class ObjectPropertyModel : public QStandardItemModel +{ + Q_OBJECT + + public: + ObjectPropertyModel(QObject* parent); + + void setObject(ObjectProxy::Pointer object); + ObjectProxy::Pointer object() const; + + static QVariant toEditValue(const QVariant& value); + + private Q_SLOTS: + void updatePropertyValue(QStandardItem* item); + + private: + ObjectProxy::Pointer m_object; +}; + + diff --git a/Framework/Array.h b/Framework/Array.h new file mode 100644 index 0000000..ab83e77 --- /dev/null +++ b/Framework/Array.h @@ -0,0 +1,98 @@ +#ifndef ARRAY_H +#define ARRAY_H + + +#include "Utilities.h" + + +// std::vector replacement that is cut down but familiar. +// It uses the custom allocators, and is restricted to POD elements only +template +struct PODArrayPOD // A dynamic array of POD type elements which is itself a POD type (so can have multi-dimensional arrays of these). +{ + static_assert(std::is_pod::value, "T must be a POD type."); + + void init() { m_magic = 0x01020304; m_capacity = m_size = 0; m_data = nullptr; } + void deinit() { if (m_data != nullptr) YQ_FREE(m_data); } + + const T& at(size_t a_pos) const { checkInited(); checkIndex(a_pos); return m_data[a_pos]; } + T& at(size_t a_pos) { checkInited(); checkIndex(a_pos); return m_data[a_pos]; } + const T& operator[](size_t a_pos) const { checkInited(); checkIndex(a_pos); return m_data[a_pos]; } + void pop_back() { checkInited(); checkIndex(1); m_size--; } + const T& back() const { checkInited(); return m_data[m_size - 1]; } + T& back() { checkInited(); return m_data[m_size - 1]; } + void push_back(const T& a_val) { checkInited(); if (m_size == m_capacity) ensureSpaceFor(1); m_data[m_size++] = a_val; } + void push(const T& a_val) { checkInited(); push_back(a_val); } + T pop() { checkInited(); pop_back(); return m_data[m_size]; } + void reserve(size_t a_count) { checkInited(); m_capacity = a_count; m_data = YQ_REALLOC_ARRAY(m_data, T, a_count); } + size_t capacity() const { checkInited(); return m_capacity; } + size_t size() const { checkInited(); return m_size; } + T* data() const { checkInited(); return m_data; } + void clear() { checkInited(); m_size = 0; } +//private: // Actually making private would make this no longer a POD type + yqResult checkInited() const { if (BT_IsDebug) YQ_API_PRECONDITION(m_magic == 0x01020304, R_Uninitialized); return R_Okay; } + yqResult checkIndex(size_t a_pos) const { YQ_API_PRECONDITION(a_pos < m_size, R_IndexOutOfRange); return R_Okay; } + void ensureSpaceFor(size_t a_count) { reserve(std::max(m_size + a_count, m_capacity + m_capacity / 2)); } // try to grow by 50% + T* m_data; // Because of this pointer it is not safe to copy DynamicArray objects around. Making it a shared_ptr though stops this being a POD type + size_t m_capacity; + size_t m_size; + uint32_t m_magic; +}; + + +static_assert(std::is_pod >::value, "PODArrayPOD must be a POD type."); + + +// A dynamic array of POD type elements. +// It isn't POD itself, but is instead reference counted so safer to deal with. +template +struct ArrayPOD +{ + static_assert(std::is_pod::value, "T must be a POD type."); + + // Actually not reference counted yet, but safe as objects of this type can't be copied + ArrayPOD& operator=(const ArrayPOD&) = delete; + ArrayPOD(const ArrayPOD&) = delete; + ArrayPOD() { m_capacity = m_size = 0; m_data = nullptr; } + ~ArrayPOD() { if (m_data != nullptr) YQ_FREE(m_data); } + + const T& at(size_t a_pos) const { checkIndex(a_pos); return m_data[a_pos]; } + T& at(size_t a_pos) { checkIndex(a_pos); return m_data[a_pos]; } + const T& operator[](size_t a_pos) const { checkIndex(a_pos); return m_data[a_pos]; } + void pop_back() { checkIndex(1); m_size--; } + const T& back() const { return m_data[m_size - 1]; } + T& back() { return m_data[m_size - 1]; } + void push_back(const T& a_val) { if (m_size == m_capacity) ensureSpaceFor(1); m_data[m_size++] = a_val; } + void push(const T& a_val) { push_back(a_val); } + T pop() { pop_back(); return m_data[m_size]; } + void reserve(size_t a_count) { m_capacity = a_count; m_data = YQ_REALLOC_ARRAY(m_data, T, a_count); } + size_t capacity() const { return m_capacity; } + size_t size() const { return m_size; } + T* data() const { return m_data; } + void clear() { m_size = 0; } +private: + yqResult checkIndex(size_t a_pos) const { YQ_API_PRECONDITION(a_pos < m_size, R_IndexOutOfRange); return R_Okay; } + void ensureSpaceFor(size_t a_count) { reserve(std::max(m_size + a_count, m_capacity + m_capacity / 2)); } // try to grow by 50% + T* m_data; // Because of this pointer it is not safe to copy DynamicArray objects around. Making it a shared_ptr though stops this being a POD type + size_t m_capacity; + size_t m_size; +}; + + +struct PODString +{ +public: + void init() { m_data.init(); } + void deinit() { m_data.deinit(); } + void operator=(const char* a_str) { m_data.clear(); operator+=(a_str); } + void operator+=(const char* a_str) { while (*a_str) m_data.push_back(*a_str++); m_data.push(0); m_data.pop(); } + char* c_str() { return m_data.data(); } +//private: // Actually making private would make this no longer a POD type + PODArrayPOD m_data; +}; + + +static_assert(std::is_pod::value, "PODString must be a POD type."); + + +#endif // ARRAY_H diff --git a/Framework/Macros.h b/Framework/Macros.h new file mode 100644 index 0000000..6ca8c86 --- /dev/null +++ b/Framework/Macros.h @@ -0,0 +1,111 @@ +#ifndef MACROS_H +#define MACROS_H + + +// +// Various common macros +// +#define YQ_ARRAY_SIZE(x) (sizeof(x)/sizeof((x)[0])) + +#define YQ_EXPAND(x) x + +#define YQ_STRINGIZE_IMPL(val) #val +#define YQ_STRINGIZE(val) YQ_STRINGIZE_IMPL(val) + +#define YQ_CONCATENATE_IMPL(x, y) x##y +#define YQ_CONCATENATE(x, y) YQ_CONCATENATE_IMPL(x, y) + +// If 0 args, it mis-reports a count of 1. Also it only counts up to 10 args. +#define YQ_ARG_COUNT_IMPL(X,A,B,C,D,E,F,G,H,I,J,N,...) N +#define YQ_ARG_COUNT(...) (YQ_EXPAND(YQ_ARG_COUNT_IMPL(_,__VA_ARGS__, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0))) + +#ifdef __COUNTER__ +# define YQ_ANONYMOUS_VARIABLE(str) YQ_CONCATENATE(str, __COUNTER__) +#else +# define YQ_ANONYMOUS_VARIABLE(str) YQ_CONCATENATE(str, __LINE__) +#endif + +#define YQ_UNUSED(x) std::ignore = (x) + +#ifdef __MINGW32__ +// Avoid callbacks +# define YQ_CALLBACK __cdecl +# define YQ_FUNCTION_NAME __PRETTY_FUNCTION__ // __func__ +#elif defined(_WIN32) +# define YQ_CALLBACK __cdecl +# define YQ_FUNCTION_NAME __FUNCTION__ +#else +# define YQ_CALLBACK +# define YQ_FUNCTION_NAME __PRETTY_FUNCTION__ // __func__ +#endif + +// Helper marcos for doing a variable number of null checks +#define YQ_NULL_CHECK_IMPL_1(arg) if (arg == nullptr) YQ_API_LEAVE(R_InvalidParameter); +#define YQ_NULL_CHECK_IMPL_2(arg,...) YQ_NULL_CHECK_IMPL_1(arg) YQ_NULL_CHECK_IMPL_1(__VA_ARGS__) +#define YQ_NULL_CHECK_IMPL_3(arg,...) YQ_NULL_CHECK_IMPL_1(arg) YQ_NULL_CHECK_IMPL_2(__VA_ARGS__) +#define YQ_NULL_CHECK_IMPL_4(arg,...) YQ_NULL_CHECK_IMPL_1(arg) YQ_NULL_CHECK_IMPL_3(__VA_ARGS__) +#define YQ_NULL_CHECK_IMPL_5(arg,...) YQ_NULL_CHECK_IMPL_1(arg) YQ_NULL_CHECK_IMPL_4(__VA_ARGS__) +#define YQ_NULL_CHECK_IMPL_N3(N, ...) YQ_NULL_CHECK_IMPL_ ## N(__VA_ARGS__) +#define YQ_NULL_CHECK_IMPL_N2(N, ...) YQ_NULL_CHECK_IMPL_N3(N, __VA_ARGS__) +#define YQ_NULL_CHECK_IMPL_N(...) YQ_NULL_CHECK_IMPL_N2(YQ_ARG_COUNT(__VA_ARGS__), __VA_ARGS__) + +// File name, line numbes, and function name are statically concatenated at compile time, with a reduced string sharing trade off +#define YQ_LOCATION __FILE__ "(" YQ_STRINGIZE(__LINE__) "): " + +// Macros to log messages for debug builds. +// For release builds these will not output anything and no code or data will be generate (except for error logging) +#define YQ_LOG_WARNING(a_fmt, ...) ((BT_IsRelease) ? ((void)0) : yqLogMessage(LL_Warning, YQ_LOCATION, YQ_FUNCTION_NAME, "warning: " a_fmt, ##__VA_ARGS__)) +#define YQ_LOG_DEBUG(a_fmt, ...) ((BT_IsRelease) ? ((void)0) : yqLogMessage(LL_Debug, YQ_LOCATION, YQ_FUNCTION_NAME, "debug: " a_fmt, ##__VA_ARGS__)) +#define YQ_LOG_INFO(a_fmt, ...) ((BT_IsRelease) ? ((void)0) : yqLogMessage(LL_Info, YQ_LOCATION, YQ_FUNCTION_NAME, "info: " a_fmt, ##__VA_ARGS__)) +#define YQ_LOG_ERROR(a_fmt, ...) yqLogMessage(LL_Error, YQ_LOCATION, YQ_FUNCTION_NAME, "error: " a_fmt, ##__VA_ARGS__) + +// Macros that only generate log code in verbose debug builds. The logging logs the entry to API calls and the exit from them and what return code is being returned. +// In debug and release, on errors, the code always calls the installed or default error handling routine +enum { YQ_VERBOSE_DEBUGGING = 0 }; +#define YQ_API_VERBOSE(msg) ((YQ_VERBOSE_DEBUGGING) ? YQ_LOG_DEBUG(msg) : ((void)0)) +#define YQ_API_ERROR(result, ...) (yqHandleError(result, YQ_LOCATION, YQ_FUNCTION_NAME, __VA_ARGS__), YQ_LOG_ERROR("Leave: " #result " (%i)", result), result) +#define YQ_API_ENTER() YQ_API_VERBOSE("Enter") +#define YQ_API_LEAVE(result, ...) return ((result == R_Okay) ? (YQ_API_VERBOSE("Leave"), result) : YQ_API_ERROR(result, "" __VA_ARGS__)) + +// Macros to do common parameter and precondition checking for API function calls +// This macro checks the pre-condition is met, if it doesn't then returns the error result and executes the provided cleanup code +// See also 'finally'. Probably during pre-condition checking, there shouldn't be any cleanup yet as no work should be done at this stage. +//#define YQ_API_PRECONDITION_CLEANUP(condition,result,cleanup) do { if (!(condition)) { cleanup; YQ_API_LEAVE(result); } } while (0,0) +#define YQ_API_PRECONDITION(condition,result) do { if (!(condition)) { YQ_API_LEAVE(result, #condition); } } while (0,0)//YQ_API_PRECONDITION_CLEANUP(condition,result, ((void)0)) +#define YQ_API_PRECONDITION_NULL(...) YQ_NULL_CHECK_IMPL_N(__VA_ARGS__) + +// Can redirect to the user heap manager routines if provided +#define YQ_ALLOC(type) ((type*)(yqAllocateMemory(sizeof(type)))) +#define YQ_ALLOC_ARRAY(type,siz) ((type*)(yqAllocateMemory(sizeof(type)*(siz)))) +#define YQ_REALLOC_ARRAY(ptr,type,siz) ((type*)(yqReallocateMemory(ptr,sizeof(type)*(siz)))) +#define YQ_FREE(ptr) (((ptr)) ? (void)(yqFreeMemory((void*)(ptr)), (ptr) = nullptr) : (void)0) + +// C++ wrappers for using the custom allocators to replace use of new / delete (does placement new with custom allocated memory) +// Note: The custom allocators are expected to return null if the allocation fails and not throw. The macros below do not check this. +#define YQ_NEW(type) (new(YQ_ALLOC(type)) type) +#define YQ_NEW_PARAMS(type, params) (new(YQ_ALLOC(type)) type params) +#define YQ_DELETE(obj,type) ((obj) ? (void)((obj)->~type(), YQ_FREE(obj)) : (void)0) + + +/* + +Example usage: + +Result SomeFunction(T1 **Param1, T2 *Param2, T3 *Param3, int Param4) +{ + YQ_API_ENTER(); + + // Check pre-conditions + YQ_API_PRECONDITION_NULL(Param1, Param2, Param3); + YQ_API_PRECONDITION(Param4 == 42, R_InvalidParameter); + + if (errorHappened) + YQ_API_LEAVE(R_Failure); + + YQ_API_LEAVE(R_Okay); +} + +*/ + + +#endif // MACROS_H diff --git a/Framework/NonCopyable.h b/Framework/NonCopyable.h index 3881321..b8cb274 100644 --- a/Framework/NonCopyable.h +++ b/Framework/NonCopyable.h @@ -3,6 +3,62 @@ /// XXX: Not sure but maybe there is a C++11 std::non_copyable that does the same as this +/// Actually C++11 has the additional ability to add '= delete' to a member to say it can be used. + +#ifdef HAVE_CPP11 +# define DELETE = delete +#else +# define DELETE +#endif + + +/// This way has the advantage of not adding to the size of the base class if that is important [1] +/// [1] Ref: http://trac.webkit.org/changeset/68414 +#define MAKE_NONCOPYABLE(ClassName) \ + private: \ + ClassName(const ClassName&) DELETE; \ + ClassName& operator=(const ClassName&) DELETE; \ + + +/* Utility classes */ +struct NoCopy +{ +public: + NoCopy() {} +private: + NoCopy(const NoCopy &) DELETE; +}; + +struct NoAssign +{ +private: + NoAssign &operator=(const NoAssign &) DELETE; +}; + +struct NonInstantiable +{ +private: + NonInstantiable() DELETE; +}; + +struct NoCopyAssign : NoCopy, NoAssign +{ +}; + +typedef NoCopyAssign NoAssignCopy; + +// Not tested +struct NoMove +{ +public: + NoMove() {} +private: + NoMove(const NoMove &&) DELETE; +}; + +struct NoMoveCopyAssign : NoMove, NoCopy, NoAssign +{ +}; /// @@ -10,9 +66,9 @@ /// /// Making a class NonCopyable means preventing objects of the class being copied. This is useful if /// the class manages a resource that can only be held by one object such that it can't be reference -/// counted, or it is the underlying object that becomes reference counted. +/// counted, or it is the underlying object that becomes reference counted. Idea from here [1]. /// -/// Idea comes from here: http://stackoverflow.com/questions/9458741/with-explicitly-deleted-member-functions-in-c11-is-it-still-worthwhile-to-inh +/// [1] Ref: http://stackoverflow.com/questions/9458741/with-explicitly-deleted-member-functions-in-c11-is-it-still-worthwhile-to-inh /// class NonCopyable { @@ -34,12 +90,19 @@ MyNonCopyableClass() {} }; + MyNonCopyableClass create() + { + MyNonCopyableClass var; + return var; + } + Now it is not possible to copy objects of this type without a compiler error: - void function() + void function() { - MyNonCopyableClass obj1; - MyNonCopyableClass obj2; + MyNonCopyableClass obj1; + MyNonCopyableClass obj2; + MyNonCopyableClass obj3 = create(); // Allowed due to move semantics in c++11, use NoMoveCopyAssign to prevent // These attempts to make a copy of obj1 will cause a compiler error MyNonCopyableClass obj3(obj1); diff --git a/Framework/ObjectFilter.cpp b/Framework/ObjectFilter.cpp new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/Framework/ObjectFilter.cpp diff --git a/Framework/ObjectInspector.cpp b/Framework/ObjectInspector.cpp new file mode 100644 index 0000000..91b6726 --- /dev/null +++ b/Framework/ObjectInspector.cpp @@ -0,0 +1,90 @@ +#include "ObjectInspector.h" + +#include "ObjectPropertyModel.h" +#include "lib/ObjectProxy.h" + +#include +#include +#include +#include +#include +#include + +ObjectInspector::ObjectInspector(QWidget* parent) +: QWidget(parent) +{ + QVBoxLayout* layout = new QVBoxLayout(this); + layout->setMargin(0); + + m_nameLabel = new QLabel(this); + layout->addWidget(m_nameLabel); + + m_propertyView = new QTableView(this); + m_propertyView->verticalHeader()->setVisible(false); + m_propertyView->horizontalHeader()->setStretchLastSection(true); + + QHBoxLayout* filterLayout = new QHBoxLayout; + QLabel* filterLabel = new QLabel(tr("Filter:"),this); + m_propertyFilterEdit = new QLineEdit(this); + m_propertyFilterEdit->setToolTip(tr("String or regular expression to filter property list with")); + + connect(m_propertyFilterEdit,SIGNAL(textChanged(QString)), + this,SLOT(changeFilter(QString))); + filterLayout->addWidget(filterLabel); + filterLayout->addWidget(m_propertyFilterEdit); + + m_model = new ObjectPropertyModel(this); + m_propertySortModel = new QSortFilterProxyModel(this); + m_propertySortModel->setSourceModel(m_model); + + m_propertyView->setModel(m_propertySortModel); + + layout->addWidget(m_propertyView); + layout->addLayout(filterLayout); + + setObject(ObjectProxy::Pointer()); +} + +QString ObjectInspector::formatAddress(void* ptr) +{ + quintptr intPtr = reinterpret_cast(ptr); + return QString("0x%1").arg(QString(QByteArray::number(intPtr,16))); +} + +void ObjectInspector::refresh() +{ + setObject(m_currentObject); +} + +void ObjectInspector::setObject(ObjectProxy::Pointer object) +{ + QString labelText; + + if (object) + { + labelText = QString("%1 [%2] (%3)") + .arg(object->className()) + .arg(object->objectName()) + .arg(formatAddress(reinterpret_cast(object->address()))); + } + else + { + labelText = tr("[No Object Selected]"); + } + + m_nameLabel->setText(labelText); + m_currentObject = object; + m_model->setObject(object); + m_propertySortModel->sort(0,Qt::AscendingOrder); +} + +ObjectProxy::Pointer ObjectInspector::object() const +{ + return m_currentObject; +} + +void ObjectInspector::changeFilter(const QString& pattern) +{ + m_propertySortModel->setFilterRegExp(QRegExp(pattern)); +} + diff --git a/Framework/ObjectInspector.h b/Framework/ObjectInspector.h new file mode 100644 index 0000000..e1ce3ac --- /dev/null +++ b/Framework/ObjectInspector.h @@ -0,0 +1,40 @@ +#pragma once + +#include "lib/ObjectProxy.h" + +#include + +class QLabel; +class QLineEdit; +class QTableView; +class QSortFilterProxyModel; + +class ObjectPropertyModel; + +class ObjectInspector : public QWidget +{ + Q_OBJECT + + public: + ObjectInspector(QWidget* parent); + + void setObject(ObjectProxy::Pointer object); + ObjectProxy::Pointer object() const; + + static QString formatAddress(void* ptr); + + public Q_SLOTS: + void refresh(); + + private Q_SLOTS: + void changeFilter(const QString& pattern); + + private: + QLabel* m_nameLabel; + QTableView* m_propertyView; + ObjectProxy::Pointer m_currentObject; + ObjectPropertyModel* m_model; + QSortFilterProxyModel* m_propertySortModel; + QLineEdit* m_propertyFilterEdit; +}; + diff --git a/Framework/ObjectPropertyModel.cpp b/Framework/ObjectPropertyModel.cpp new file mode 100644 index 0000000..6e4d4f7 --- /dev/null +++ b/Framework/ObjectPropertyModel.cpp @@ -0,0 +1,91 @@ +#include "ObjectPropertyModel.h" + +#include "lib/ObjectProxy.h" + +#include +#include + +ObjectPropertyModel::ObjectPropertyModel(QObject* parent) +: QStandardItemModel(parent) +{ + setColumnCount(2); + setObject(ObjectProxy::Pointer()); + + connect(this,SIGNAL(itemChanged(QStandardItem*)), + this,SLOT(updatePropertyValue(QStandardItem*))); +} + +QVariant ObjectPropertyModel::toEditValue(const QVariant& value) +{ + // TODO - Find existing code which can handle display and editing + // of common value types. + switch (value.userType()) + { + case QVariant::Size: + case QVariant::SizeF: + { + QSize size = value.toSize(); + return QString("%1 x %2").arg(size.width()).arg(size.height()); + } + case QVariant::Rect: + case QVariant::RectF: + { + QRect rect = value.toRect(); + return QString("(%1,%2) (%3,%4)").arg(rect.left()).arg(rect.top()).arg(rect.width()).arg(rect.height()); + } + } + return value; +} + +void ObjectPropertyModel::updatePropertyValue(QStandardItem* item) +{ + QStandardItem* propertyItem = this->item(item->row(),0); + QString propertyName = propertyItem->text(); + + if (m_object) + { + QVariant newValue = item->data(Qt::EditRole); + qDebug() << "setting property" << propertyName << "to" << newValue; + m_object->writeProperty(propertyName,newValue); + } +} + +void ObjectPropertyModel::setObject(ObjectProxy::Pointer object) +{ + clear(); + + setHorizontalHeaderLabels( + QStringList() << tr("Property") << tr("Value") + ); + + m_object = object; + + if (object) + { + object->refresh(); + int rowCount = 0; + QListIterator iter(object->properties()); + while (iter.hasNext()) + { + ObjectProxy::Property property = iter.next(); + QVariant value = property.value; + QVariant editValue = toEditValue(value); + + QStandardItem* nameItem = new QStandardItem(property.name); + nameItem->setEditable(false); + + QStandardItem* valueItem = new QStandardItem; + valueItem->setData(editValue,Qt::EditRole); + valueItem->setEditable(property.isWritable); + + insertRow(rowCount,QList() << nameItem << valueItem); + ++rowCount; + } + } +} + +ObjectProxy::Pointer ObjectPropertyModel::object() const +{ + return m_object; +} + diff --git a/Framework/ObjectPropertyModel.h b/Framework/ObjectPropertyModel.h new file mode 100644 index 0000000..69e176c --- /dev/null +++ b/Framework/ObjectPropertyModel.h @@ -0,0 +1,27 @@ +#pragma once + +#include "lib/ObjectProxy.h" + +#include +#include + +class ObjectPropertyModel : public QStandardItemModel +{ + Q_OBJECT + + public: + ObjectPropertyModel(QObject* parent); + + void setObject(ObjectProxy::Pointer object); + ObjectProxy::Pointer object() const; + + static QVariant toEditValue(const QVariant& value); + + private Q_SLOTS: + void updatePropertyValue(QStandardItem* item); + + private: + ObjectProxy::Pointer m_object; +}; + + diff --git a/Framework/ObjectTreeModel.cpp b/Framework/ObjectTreeModel.cpp new file mode 100644 index 0000000..5d7bbfc --- /dev/null +++ b/Framework/ObjectTreeModel.cpp @@ -0,0 +1,135 @@ +#include "ObjectTreeModel.h" +#include + + +ObjectTreeModel::ObjectTreeModel(QObject* parent) +: QAbstractItemModel(parent) +{ +} + +int ObjectTreeModel::columnCount(const QModelIndex&) const +{ + return 1; +} + +int ObjectTreeModel::rowCount(const QModelIndex& parent) const +{ + QObject* obj = static_cast(parent.internalPointer()); + return (obj) ? obj->children().count() : m_roots.count(); +} + +void ObjectTreeModel::setRootObjects(const QList& roots) +{ + beginResetModel(); + m_roots = roots; + endResetModel(); +} + +QModelIndex ObjectTreeModel::index(int row, int column, const QModelIndex& parent) const +{ + QObject* parentItem = static_cast(parent.internalPointer()); + const QObjectList& children = (parentItem) ? parentItem->children() : m_roots; + bool outOfBounds = (row < 0 || column != 0 || row >= children.count()); + return outOfBounds ? QModelIndex() : createIndex(row,column,children.at(row)); +} + +int ObjectTreeModel::indexInParent(QObject* item) const +{ + QObject* parent = item->parent(); + return (parent && !m_roots.contains(item)) ? parent->children().indexOf(item) : m_roots.indexOf(item); +} + +QModelIndex ObjectTreeModel::parent(const QModelIndex& index) const +{ + QObject* item = static_cast(index.internalPointer()); + QObject* parent = (item && !m_roots.contains(item)) ? item->parent() : 0; + return (parent) ? createIndex(indexInParent(parent),index.column(),parent) : QModelIndex(); +} + +QVariant ObjectTreeModel::data(const QModelIndex& index, int role) const +{ + QObject* item = static_cast(index.internalPointer()); + return (role == Qt::DisplayRole) ? ((item) ? displayText(item) : "[deleted]") : QVariant(); +} + +QString ObjectTreeModel::displayText(QObject* object) const +{ + QString text = object->metaObject()->className(); + if (!object->objectName().isEmpty()) + { + text += QString("[%1]").arg(object->objectName()); + } + return text; +} + +QVariant ObjectTreeModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (section == 0 && orientation == Qt::Horizontal && role == Qt::DisplayRole) + { + return tr("Object"); + } + return QAbstractItemModel::headerData(section,orientation,role); +} + +QObject* ObjectTreeModel::index(ObjPtr object, const QList& items) const +{ + //inline QList findChildren(const QRegularExpression &re, Qt::FindChildOptions options = Qt::FindChildrenRecursively) const + Q_FOREACH(QObject* item, items) + { + if (item == object) + return item; + QObject* match = index(object, item->children()); + if (match) + return match; + } + return 0; +} + +QModelIndex ObjectTreeModel::index(ObjPtr object) const +{ + QObject* item = index(object, m_roots); + return (item) ? createIndex(indexInParent(item),0,item) : QModelIndex(); +} + + + + +/* +bool ObjectTreeModel::matches(ObjPtr object, const QString& query) const +{ + return displayText(object).contains(query,Qt::CaseInsensitive); +} + +void ObjectTreeModel::search(QList* hits, ObjectItem* item, const QString& query) const +{ + ObjPtr object = item;//->object; + if (!object) + { + return; + } + if (matches(object,query)) + { + *hits << object; + } + Q_FOREACH(ObjectItem* child, item->children()) + { + search(hits,child,query); + } +} + +QList ObjectTreeModel::search(const QString& query) const +{ + if (query.isEmpty()) + { + return QList(); + } + + QList matches; + Q_FOREACH(ObjectItem* item, m_roots) + { + search(&matches,item,query); + } + return matches; +} + +*/ diff --git a/Framework/Array.h b/Framework/Array.h new file mode 100644 index 0000000..ab83e77 --- /dev/null +++ b/Framework/Array.h @@ -0,0 +1,98 @@ +#ifndef ARRAY_H +#define ARRAY_H + + +#include "Utilities.h" + + +// std::vector replacement that is cut down but familiar. +// It uses the custom allocators, and is restricted to POD elements only +template +struct PODArrayPOD // A dynamic array of POD type elements which is itself a POD type (so can have multi-dimensional arrays of these). +{ + static_assert(std::is_pod::value, "T must be a POD type."); + + void init() { m_magic = 0x01020304; m_capacity = m_size = 0; m_data = nullptr; } + void deinit() { if (m_data != nullptr) YQ_FREE(m_data); } + + const T& at(size_t a_pos) const { checkInited(); checkIndex(a_pos); return m_data[a_pos]; } + T& at(size_t a_pos) { checkInited(); checkIndex(a_pos); return m_data[a_pos]; } + const T& operator[](size_t a_pos) const { checkInited(); checkIndex(a_pos); return m_data[a_pos]; } + void pop_back() { checkInited(); checkIndex(1); m_size--; } + const T& back() const { checkInited(); return m_data[m_size - 1]; } + T& back() { checkInited(); return m_data[m_size - 1]; } + void push_back(const T& a_val) { checkInited(); if (m_size == m_capacity) ensureSpaceFor(1); m_data[m_size++] = a_val; } + void push(const T& a_val) { checkInited(); push_back(a_val); } + T pop() { checkInited(); pop_back(); return m_data[m_size]; } + void reserve(size_t a_count) { checkInited(); m_capacity = a_count; m_data = YQ_REALLOC_ARRAY(m_data, T, a_count); } + size_t capacity() const { checkInited(); return m_capacity; } + size_t size() const { checkInited(); return m_size; } + T* data() const { checkInited(); return m_data; } + void clear() { checkInited(); m_size = 0; } +//private: // Actually making private would make this no longer a POD type + yqResult checkInited() const { if (BT_IsDebug) YQ_API_PRECONDITION(m_magic == 0x01020304, R_Uninitialized); return R_Okay; } + yqResult checkIndex(size_t a_pos) const { YQ_API_PRECONDITION(a_pos < m_size, R_IndexOutOfRange); return R_Okay; } + void ensureSpaceFor(size_t a_count) { reserve(std::max(m_size + a_count, m_capacity + m_capacity / 2)); } // try to grow by 50% + T* m_data; // Because of this pointer it is not safe to copy DynamicArray objects around. Making it a shared_ptr though stops this being a POD type + size_t m_capacity; + size_t m_size; + uint32_t m_magic; +}; + + +static_assert(std::is_pod >::value, "PODArrayPOD must be a POD type."); + + +// A dynamic array of POD type elements. +// It isn't POD itself, but is instead reference counted so safer to deal with. +template +struct ArrayPOD +{ + static_assert(std::is_pod::value, "T must be a POD type."); + + // Actually not reference counted yet, but safe as objects of this type can't be copied + ArrayPOD& operator=(const ArrayPOD&) = delete; + ArrayPOD(const ArrayPOD&) = delete; + ArrayPOD() { m_capacity = m_size = 0; m_data = nullptr; } + ~ArrayPOD() { if (m_data != nullptr) YQ_FREE(m_data); } + + const T& at(size_t a_pos) const { checkIndex(a_pos); return m_data[a_pos]; } + T& at(size_t a_pos) { checkIndex(a_pos); return m_data[a_pos]; } + const T& operator[](size_t a_pos) const { checkIndex(a_pos); return m_data[a_pos]; } + void pop_back() { checkIndex(1); m_size--; } + const T& back() const { return m_data[m_size - 1]; } + T& back() { return m_data[m_size - 1]; } + void push_back(const T& a_val) { if (m_size == m_capacity) ensureSpaceFor(1); m_data[m_size++] = a_val; } + void push(const T& a_val) { push_back(a_val); } + T pop() { pop_back(); return m_data[m_size]; } + void reserve(size_t a_count) { m_capacity = a_count; m_data = YQ_REALLOC_ARRAY(m_data, T, a_count); } + size_t capacity() const { return m_capacity; } + size_t size() const { return m_size; } + T* data() const { return m_data; } + void clear() { m_size = 0; } +private: + yqResult checkIndex(size_t a_pos) const { YQ_API_PRECONDITION(a_pos < m_size, R_IndexOutOfRange); return R_Okay; } + void ensureSpaceFor(size_t a_count) { reserve(std::max(m_size + a_count, m_capacity + m_capacity / 2)); } // try to grow by 50% + T* m_data; // Because of this pointer it is not safe to copy DynamicArray objects around. Making it a shared_ptr though stops this being a POD type + size_t m_capacity; + size_t m_size; +}; + + +struct PODString +{ +public: + void init() { m_data.init(); } + void deinit() { m_data.deinit(); } + void operator=(const char* a_str) { m_data.clear(); operator+=(a_str); } + void operator+=(const char* a_str) { while (*a_str) m_data.push_back(*a_str++); m_data.push(0); m_data.pop(); } + char* c_str() { return m_data.data(); } +//private: // Actually making private would make this no longer a POD type + PODArrayPOD m_data; +}; + + +static_assert(std::is_pod::value, "PODString must be a POD type."); + + +#endif // ARRAY_H diff --git a/Framework/Macros.h b/Framework/Macros.h new file mode 100644 index 0000000..6ca8c86 --- /dev/null +++ b/Framework/Macros.h @@ -0,0 +1,111 @@ +#ifndef MACROS_H +#define MACROS_H + + +// +// Various common macros +// +#define YQ_ARRAY_SIZE(x) (sizeof(x)/sizeof((x)[0])) + +#define YQ_EXPAND(x) x + +#define YQ_STRINGIZE_IMPL(val) #val +#define YQ_STRINGIZE(val) YQ_STRINGIZE_IMPL(val) + +#define YQ_CONCATENATE_IMPL(x, y) x##y +#define YQ_CONCATENATE(x, y) YQ_CONCATENATE_IMPL(x, y) + +// If 0 args, it mis-reports a count of 1. Also it only counts up to 10 args. +#define YQ_ARG_COUNT_IMPL(X,A,B,C,D,E,F,G,H,I,J,N,...) N +#define YQ_ARG_COUNT(...) (YQ_EXPAND(YQ_ARG_COUNT_IMPL(_,__VA_ARGS__, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0))) + +#ifdef __COUNTER__ +# define YQ_ANONYMOUS_VARIABLE(str) YQ_CONCATENATE(str, __COUNTER__) +#else +# define YQ_ANONYMOUS_VARIABLE(str) YQ_CONCATENATE(str, __LINE__) +#endif + +#define YQ_UNUSED(x) std::ignore = (x) + +#ifdef __MINGW32__ +// Avoid callbacks +# define YQ_CALLBACK __cdecl +# define YQ_FUNCTION_NAME __PRETTY_FUNCTION__ // __func__ +#elif defined(_WIN32) +# define YQ_CALLBACK __cdecl +# define YQ_FUNCTION_NAME __FUNCTION__ +#else +# define YQ_CALLBACK +# define YQ_FUNCTION_NAME __PRETTY_FUNCTION__ // __func__ +#endif + +// Helper marcos for doing a variable number of null checks +#define YQ_NULL_CHECK_IMPL_1(arg) if (arg == nullptr) YQ_API_LEAVE(R_InvalidParameter); +#define YQ_NULL_CHECK_IMPL_2(arg,...) YQ_NULL_CHECK_IMPL_1(arg) YQ_NULL_CHECK_IMPL_1(__VA_ARGS__) +#define YQ_NULL_CHECK_IMPL_3(arg,...) YQ_NULL_CHECK_IMPL_1(arg) YQ_NULL_CHECK_IMPL_2(__VA_ARGS__) +#define YQ_NULL_CHECK_IMPL_4(arg,...) YQ_NULL_CHECK_IMPL_1(arg) YQ_NULL_CHECK_IMPL_3(__VA_ARGS__) +#define YQ_NULL_CHECK_IMPL_5(arg,...) YQ_NULL_CHECK_IMPL_1(arg) YQ_NULL_CHECK_IMPL_4(__VA_ARGS__) +#define YQ_NULL_CHECK_IMPL_N3(N, ...) YQ_NULL_CHECK_IMPL_ ## N(__VA_ARGS__) +#define YQ_NULL_CHECK_IMPL_N2(N, ...) YQ_NULL_CHECK_IMPL_N3(N, __VA_ARGS__) +#define YQ_NULL_CHECK_IMPL_N(...) YQ_NULL_CHECK_IMPL_N2(YQ_ARG_COUNT(__VA_ARGS__), __VA_ARGS__) + +// File name, line numbes, and function name are statically concatenated at compile time, with a reduced string sharing trade off +#define YQ_LOCATION __FILE__ "(" YQ_STRINGIZE(__LINE__) "): " + +// Macros to log messages for debug builds. +// For release builds these will not output anything and no code or data will be generate (except for error logging) +#define YQ_LOG_WARNING(a_fmt, ...) ((BT_IsRelease) ? ((void)0) : yqLogMessage(LL_Warning, YQ_LOCATION, YQ_FUNCTION_NAME, "warning: " a_fmt, ##__VA_ARGS__)) +#define YQ_LOG_DEBUG(a_fmt, ...) ((BT_IsRelease) ? ((void)0) : yqLogMessage(LL_Debug, YQ_LOCATION, YQ_FUNCTION_NAME, "debug: " a_fmt, ##__VA_ARGS__)) +#define YQ_LOG_INFO(a_fmt, ...) ((BT_IsRelease) ? ((void)0) : yqLogMessage(LL_Info, YQ_LOCATION, YQ_FUNCTION_NAME, "info: " a_fmt, ##__VA_ARGS__)) +#define YQ_LOG_ERROR(a_fmt, ...) yqLogMessage(LL_Error, YQ_LOCATION, YQ_FUNCTION_NAME, "error: " a_fmt, ##__VA_ARGS__) + +// Macros that only generate log code in verbose debug builds. The logging logs the entry to API calls and the exit from them and what return code is being returned. +// In debug and release, on errors, the code always calls the installed or default error handling routine +enum { YQ_VERBOSE_DEBUGGING = 0 }; +#define YQ_API_VERBOSE(msg) ((YQ_VERBOSE_DEBUGGING) ? YQ_LOG_DEBUG(msg) : ((void)0)) +#define YQ_API_ERROR(result, ...) (yqHandleError(result, YQ_LOCATION, YQ_FUNCTION_NAME, __VA_ARGS__), YQ_LOG_ERROR("Leave: " #result " (%i)", result), result) +#define YQ_API_ENTER() YQ_API_VERBOSE("Enter") +#define YQ_API_LEAVE(result, ...) return ((result == R_Okay) ? (YQ_API_VERBOSE("Leave"), result) : YQ_API_ERROR(result, "" __VA_ARGS__)) + +// Macros to do common parameter and precondition checking for API function calls +// This macro checks the pre-condition is met, if it doesn't then returns the error result and executes the provided cleanup code +// See also 'finally'. Probably during pre-condition checking, there shouldn't be any cleanup yet as no work should be done at this stage. +//#define YQ_API_PRECONDITION_CLEANUP(condition,result,cleanup) do { if (!(condition)) { cleanup; YQ_API_LEAVE(result); } } while (0,0) +#define YQ_API_PRECONDITION(condition,result) do { if (!(condition)) { YQ_API_LEAVE(result, #condition); } } while (0,0)//YQ_API_PRECONDITION_CLEANUP(condition,result, ((void)0)) +#define YQ_API_PRECONDITION_NULL(...) YQ_NULL_CHECK_IMPL_N(__VA_ARGS__) + +// Can redirect to the user heap manager routines if provided +#define YQ_ALLOC(type) ((type*)(yqAllocateMemory(sizeof(type)))) +#define YQ_ALLOC_ARRAY(type,siz) ((type*)(yqAllocateMemory(sizeof(type)*(siz)))) +#define YQ_REALLOC_ARRAY(ptr,type,siz) ((type*)(yqReallocateMemory(ptr,sizeof(type)*(siz)))) +#define YQ_FREE(ptr) (((ptr)) ? (void)(yqFreeMemory((void*)(ptr)), (ptr) = nullptr) : (void)0) + +// C++ wrappers for using the custom allocators to replace use of new / delete (does placement new with custom allocated memory) +// Note: The custom allocators are expected to return null if the allocation fails and not throw. The macros below do not check this. +#define YQ_NEW(type) (new(YQ_ALLOC(type)) type) +#define YQ_NEW_PARAMS(type, params) (new(YQ_ALLOC(type)) type params) +#define YQ_DELETE(obj,type) ((obj) ? (void)((obj)->~type(), YQ_FREE(obj)) : (void)0) + + +/* + +Example usage: + +Result SomeFunction(T1 **Param1, T2 *Param2, T3 *Param3, int Param4) +{ + YQ_API_ENTER(); + + // Check pre-conditions + YQ_API_PRECONDITION_NULL(Param1, Param2, Param3); + YQ_API_PRECONDITION(Param4 == 42, R_InvalidParameter); + + if (errorHappened) + YQ_API_LEAVE(R_Failure); + + YQ_API_LEAVE(R_Okay); +} + +*/ + + +#endif // MACROS_H diff --git a/Framework/NonCopyable.h b/Framework/NonCopyable.h index 3881321..b8cb274 100644 --- a/Framework/NonCopyable.h +++ b/Framework/NonCopyable.h @@ -3,6 +3,62 @@ /// XXX: Not sure but maybe there is a C++11 std::non_copyable that does the same as this +/// Actually C++11 has the additional ability to add '= delete' to a member to say it can be used. + +#ifdef HAVE_CPP11 +# define DELETE = delete +#else +# define DELETE +#endif + + +/// This way has the advantage of not adding to the size of the base class if that is important [1] +/// [1] Ref: http://trac.webkit.org/changeset/68414 +#define MAKE_NONCOPYABLE(ClassName) \ + private: \ + ClassName(const ClassName&) DELETE; \ + ClassName& operator=(const ClassName&) DELETE; \ + + +/* Utility classes */ +struct NoCopy +{ +public: + NoCopy() {} +private: + NoCopy(const NoCopy &) DELETE; +}; + +struct NoAssign +{ +private: + NoAssign &operator=(const NoAssign &) DELETE; +}; + +struct NonInstantiable +{ +private: + NonInstantiable() DELETE; +}; + +struct NoCopyAssign : NoCopy, NoAssign +{ +}; + +typedef NoCopyAssign NoAssignCopy; + +// Not tested +struct NoMove +{ +public: + NoMove() {} +private: + NoMove(const NoMove &&) DELETE; +}; + +struct NoMoveCopyAssign : NoMove, NoCopy, NoAssign +{ +}; /// @@ -10,9 +66,9 @@ /// /// Making a class NonCopyable means preventing objects of the class being copied. This is useful if /// the class manages a resource that can only be held by one object such that it can't be reference -/// counted, or it is the underlying object that becomes reference counted. +/// counted, or it is the underlying object that becomes reference counted. Idea from here [1]. /// -/// Idea comes from here: http://stackoverflow.com/questions/9458741/with-explicitly-deleted-member-functions-in-c11-is-it-still-worthwhile-to-inh +/// [1] Ref: http://stackoverflow.com/questions/9458741/with-explicitly-deleted-member-functions-in-c11-is-it-still-worthwhile-to-inh /// class NonCopyable { @@ -34,12 +90,19 @@ MyNonCopyableClass() {} }; + MyNonCopyableClass create() + { + MyNonCopyableClass var; + return var; + } + Now it is not possible to copy objects of this type without a compiler error: - void function() + void function() { - MyNonCopyableClass obj1; - MyNonCopyableClass obj2; + MyNonCopyableClass obj1; + MyNonCopyableClass obj2; + MyNonCopyableClass obj3 = create(); // Allowed due to move semantics in c++11, use NoMoveCopyAssign to prevent // These attempts to make a copy of obj1 will cause a compiler error MyNonCopyableClass obj3(obj1); diff --git a/Framework/ObjectFilter.cpp b/Framework/ObjectFilter.cpp new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/Framework/ObjectFilter.cpp diff --git a/Framework/ObjectInspector.cpp b/Framework/ObjectInspector.cpp new file mode 100644 index 0000000..91b6726 --- /dev/null +++ b/Framework/ObjectInspector.cpp @@ -0,0 +1,90 @@ +#include "ObjectInspector.h" + +#include "ObjectPropertyModel.h" +#include "lib/ObjectProxy.h" + +#include +#include +#include +#include +#include +#include + +ObjectInspector::ObjectInspector(QWidget* parent) +: QWidget(parent) +{ + QVBoxLayout* layout = new QVBoxLayout(this); + layout->setMargin(0); + + m_nameLabel = new QLabel(this); + layout->addWidget(m_nameLabel); + + m_propertyView = new QTableView(this); + m_propertyView->verticalHeader()->setVisible(false); + m_propertyView->horizontalHeader()->setStretchLastSection(true); + + QHBoxLayout* filterLayout = new QHBoxLayout; + QLabel* filterLabel = new QLabel(tr("Filter:"),this); + m_propertyFilterEdit = new QLineEdit(this); + m_propertyFilterEdit->setToolTip(tr("String or regular expression to filter property list with")); + + connect(m_propertyFilterEdit,SIGNAL(textChanged(QString)), + this,SLOT(changeFilter(QString))); + filterLayout->addWidget(filterLabel); + filterLayout->addWidget(m_propertyFilterEdit); + + m_model = new ObjectPropertyModel(this); + m_propertySortModel = new QSortFilterProxyModel(this); + m_propertySortModel->setSourceModel(m_model); + + m_propertyView->setModel(m_propertySortModel); + + layout->addWidget(m_propertyView); + layout->addLayout(filterLayout); + + setObject(ObjectProxy::Pointer()); +} + +QString ObjectInspector::formatAddress(void* ptr) +{ + quintptr intPtr = reinterpret_cast(ptr); + return QString("0x%1").arg(QString(QByteArray::number(intPtr,16))); +} + +void ObjectInspector::refresh() +{ + setObject(m_currentObject); +} + +void ObjectInspector::setObject(ObjectProxy::Pointer object) +{ + QString labelText; + + if (object) + { + labelText = QString("%1 [%2] (%3)") + .arg(object->className()) + .arg(object->objectName()) + .arg(formatAddress(reinterpret_cast(object->address()))); + } + else + { + labelText = tr("[No Object Selected]"); + } + + m_nameLabel->setText(labelText); + m_currentObject = object; + m_model->setObject(object); + m_propertySortModel->sort(0,Qt::AscendingOrder); +} + +ObjectProxy::Pointer ObjectInspector::object() const +{ + return m_currentObject; +} + +void ObjectInspector::changeFilter(const QString& pattern) +{ + m_propertySortModel->setFilterRegExp(QRegExp(pattern)); +} + diff --git a/Framework/ObjectInspector.h b/Framework/ObjectInspector.h new file mode 100644 index 0000000..e1ce3ac --- /dev/null +++ b/Framework/ObjectInspector.h @@ -0,0 +1,40 @@ +#pragma once + +#include "lib/ObjectProxy.h" + +#include + +class QLabel; +class QLineEdit; +class QTableView; +class QSortFilterProxyModel; + +class ObjectPropertyModel; + +class ObjectInspector : public QWidget +{ + Q_OBJECT + + public: + ObjectInspector(QWidget* parent); + + void setObject(ObjectProxy::Pointer object); + ObjectProxy::Pointer object() const; + + static QString formatAddress(void* ptr); + + public Q_SLOTS: + void refresh(); + + private Q_SLOTS: + void changeFilter(const QString& pattern); + + private: + QLabel* m_nameLabel; + QTableView* m_propertyView; + ObjectProxy::Pointer m_currentObject; + ObjectPropertyModel* m_model; + QSortFilterProxyModel* m_propertySortModel; + QLineEdit* m_propertyFilterEdit; +}; + diff --git a/Framework/ObjectPropertyModel.cpp b/Framework/ObjectPropertyModel.cpp new file mode 100644 index 0000000..6e4d4f7 --- /dev/null +++ b/Framework/ObjectPropertyModel.cpp @@ -0,0 +1,91 @@ +#include "ObjectPropertyModel.h" + +#include "lib/ObjectProxy.h" + +#include +#include + +ObjectPropertyModel::ObjectPropertyModel(QObject* parent) +: QStandardItemModel(parent) +{ + setColumnCount(2); + setObject(ObjectProxy::Pointer()); + + connect(this,SIGNAL(itemChanged(QStandardItem*)), + this,SLOT(updatePropertyValue(QStandardItem*))); +} + +QVariant ObjectPropertyModel::toEditValue(const QVariant& value) +{ + // TODO - Find existing code which can handle display and editing + // of common value types. + switch (value.userType()) + { + case QVariant::Size: + case QVariant::SizeF: + { + QSize size = value.toSize(); + return QString("%1 x %2").arg(size.width()).arg(size.height()); + } + case QVariant::Rect: + case QVariant::RectF: + { + QRect rect = value.toRect(); + return QString("(%1,%2) (%3,%4)").arg(rect.left()).arg(rect.top()).arg(rect.width()).arg(rect.height()); + } + } + return value; +} + +void ObjectPropertyModel::updatePropertyValue(QStandardItem* item) +{ + QStandardItem* propertyItem = this->item(item->row(),0); + QString propertyName = propertyItem->text(); + + if (m_object) + { + QVariant newValue = item->data(Qt::EditRole); + qDebug() << "setting property" << propertyName << "to" << newValue; + m_object->writeProperty(propertyName,newValue); + } +} + +void ObjectPropertyModel::setObject(ObjectProxy::Pointer object) +{ + clear(); + + setHorizontalHeaderLabels( + QStringList() << tr("Property") << tr("Value") + ); + + m_object = object; + + if (object) + { + object->refresh(); + int rowCount = 0; + QListIterator iter(object->properties()); + while (iter.hasNext()) + { + ObjectProxy::Property property = iter.next(); + QVariant value = property.value; + QVariant editValue = toEditValue(value); + + QStandardItem* nameItem = new QStandardItem(property.name); + nameItem->setEditable(false); + + QStandardItem* valueItem = new QStandardItem; + valueItem->setData(editValue,Qt::EditRole); + valueItem->setEditable(property.isWritable); + + insertRow(rowCount,QList() << nameItem << valueItem); + ++rowCount; + } + } +} + +ObjectProxy::Pointer ObjectPropertyModel::object() const +{ + return m_object; +} + diff --git a/Framework/ObjectPropertyModel.h b/Framework/ObjectPropertyModel.h new file mode 100644 index 0000000..69e176c --- /dev/null +++ b/Framework/ObjectPropertyModel.h @@ -0,0 +1,27 @@ +#pragma once + +#include "lib/ObjectProxy.h" + +#include +#include + +class ObjectPropertyModel : public QStandardItemModel +{ + Q_OBJECT + + public: + ObjectPropertyModel(QObject* parent); + + void setObject(ObjectProxy::Pointer object); + ObjectProxy::Pointer object() const; + + static QVariant toEditValue(const QVariant& value); + + private Q_SLOTS: + void updatePropertyValue(QStandardItem* item); + + private: + ObjectProxy::Pointer m_object; +}; + + diff --git a/Framework/ObjectTreeModel.cpp b/Framework/ObjectTreeModel.cpp new file mode 100644 index 0000000..5d7bbfc --- /dev/null +++ b/Framework/ObjectTreeModel.cpp @@ -0,0 +1,135 @@ +#include "ObjectTreeModel.h" +#include + + +ObjectTreeModel::ObjectTreeModel(QObject* parent) +: QAbstractItemModel(parent) +{ +} + +int ObjectTreeModel::columnCount(const QModelIndex&) const +{ + return 1; +} + +int ObjectTreeModel::rowCount(const QModelIndex& parent) const +{ + QObject* obj = static_cast(parent.internalPointer()); + return (obj) ? obj->children().count() : m_roots.count(); +} + +void ObjectTreeModel::setRootObjects(const QList& roots) +{ + beginResetModel(); + m_roots = roots; + endResetModel(); +} + +QModelIndex ObjectTreeModel::index(int row, int column, const QModelIndex& parent) const +{ + QObject* parentItem = static_cast(parent.internalPointer()); + const QObjectList& children = (parentItem) ? parentItem->children() : m_roots; + bool outOfBounds = (row < 0 || column != 0 || row >= children.count()); + return outOfBounds ? QModelIndex() : createIndex(row,column,children.at(row)); +} + +int ObjectTreeModel::indexInParent(QObject* item) const +{ + QObject* parent = item->parent(); + return (parent && !m_roots.contains(item)) ? parent->children().indexOf(item) : m_roots.indexOf(item); +} + +QModelIndex ObjectTreeModel::parent(const QModelIndex& index) const +{ + QObject* item = static_cast(index.internalPointer()); + QObject* parent = (item && !m_roots.contains(item)) ? item->parent() : 0; + return (parent) ? createIndex(indexInParent(parent),index.column(),parent) : QModelIndex(); +} + +QVariant ObjectTreeModel::data(const QModelIndex& index, int role) const +{ + QObject* item = static_cast(index.internalPointer()); + return (role == Qt::DisplayRole) ? ((item) ? displayText(item) : "[deleted]") : QVariant(); +} + +QString ObjectTreeModel::displayText(QObject* object) const +{ + QString text = object->metaObject()->className(); + if (!object->objectName().isEmpty()) + { + text += QString("[%1]").arg(object->objectName()); + } + return text; +} + +QVariant ObjectTreeModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (section == 0 && orientation == Qt::Horizontal && role == Qt::DisplayRole) + { + return tr("Object"); + } + return QAbstractItemModel::headerData(section,orientation,role); +} + +QObject* ObjectTreeModel::index(ObjPtr object, const QList& items) const +{ + //inline QList findChildren(const QRegularExpression &re, Qt::FindChildOptions options = Qt::FindChildrenRecursively) const + Q_FOREACH(QObject* item, items) + { + if (item == object) + return item; + QObject* match = index(object, item->children()); + if (match) + return match; + } + return 0; +} + +QModelIndex ObjectTreeModel::index(ObjPtr object) const +{ + QObject* item = index(object, m_roots); + return (item) ? createIndex(indexInParent(item),0,item) : QModelIndex(); +} + + + + +/* +bool ObjectTreeModel::matches(ObjPtr object, const QString& query) const +{ + return displayText(object).contains(query,Qt::CaseInsensitive); +} + +void ObjectTreeModel::search(QList* hits, ObjectItem* item, const QString& query) const +{ + ObjPtr object = item;//->object; + if (!object) + { + return; + } + if (matches(object,query)) + { + *hits << object; + } + Q_FOREACH(ObjectItem* child, item->children()) + { + search(hits,child,query); + } +} + +QList ObjectTreeModel::search(const QString& query) const +{ + if (query.isEmpty()) + { + return QList(); + } + + QList matches; + Q_FOREACH(ObjectItem* item, m_roots) + { + search(&matches,item,query); + } + return matches; +} + +*/ diff --git a/Framework/ObjectTreeModel.h b/Framework/ObjectTreeModel.h new file mode 100644 index 0000000..4b50d8a --- /dev/null +++ b/Framework/ObjectTreeModel.h @@ -0,0 +1,47 @@ +#pragma once + +#include +#include +#include + + +class TrackedObject +{ +public: + TrackedObject() {} + TrackedObject(QObject* a_obj) : m_object(a_obj) {} + operator bool() { return !m_object.isNull(); } + operator QObject*() { return m_object.data(); } +private: + QPointer m_object; +}; + + +typedef QObject* ObjPtr; +//typedef TrackedObject ObjPtr; + + +class ObjectTreeModel : public QAbstractItemModel +{ + Q_OBJECT +public: + ObjectTreeModel(QObject* parent); + //void setFilter(ObjectFilter *filter); + //QList search(const QString& query) const; + void setRootObjects(const QList& roots); + QModelIndex index(ObjPtr object) const; + virtual QVariant headerData(int section, Qt::Orientation orientation, int role) const; + virtual int columnCount(const QModelIndex& parent) const; + virtual int rowCount(const QModelIndex& parent) const; + virtual QModelIndex index(int row, int column, const QModelIndex& parent) const; + virtual QModelIndex parent(const QModelIndex& index) const; + virtual QVariant data(const QModelIndex& index, int role) const; +private: + int indexInParent(QObject* item) const; + QObject* index(ObjPtr object, const QList& items) const; + //bool matches(ObjPtr object, const QString& query) const; + //void search(QList* matches, ObjectItem* item, const QString& query) const; + QString displayText(QObject* object) const; + QList m_roots; +}; + diff --git a/Framework/Array.h b/Framework/Array.h new file mode 100644 index 0000000..ab83e77 --- /dev/null +++ b/Framework/Array.h @@ -0,0 +1,98 @@ +#ifndef ARRAY_H +#define ARRAY_H + + +#include "Utilities.h" + + +// std::vector replacement that is cut down but familiar. +// It uses the custom allocators, and is restricted to POD elements only +template +struct PODArrayPOD // A dynamic array of POD type elements which is itself a POD type (so can have multi-dimensional arrays of these). +{ + static_assert(std::is_pod::value, "T must be a POD type."); + + void init() { m_magic = 0x01020304; m_capacity = m_size = 0; m_data = nullptr; } + void deinit() { if (m_data != nullptr) YQ_FREE(m_data); } + + const T& at(size_t a_pos) const { checkInited(); checkIndex(a_pos); return m_data[a_pos]; } + T& at(size_t a_pos) { checkInited(); checkIndex(a_pos); return m_data[a_pos]; } + const T& operator[](size_t a_pos) const { checkInited(); checkIndex(a_pos); return m_data[a_pos]; } + void pop_back() { checkInited(); checkIndex(1); m_size--; } + const T& back() const { checkInited(); return m_data[m_size - 1]; } + T& back() { checkInited(); return m_data[m_size - 1]; } + void push_back(const T& a_val) { checkInited(); if (m_size == m_capacity) ensureSpaceFor(1); m_data[m_size++] = a_val; } + void push(const T& a_val) { checkInited(); push_back(a_val); } + T pop() { checkInited(); pop_back(); return m_data[m_size]; } + void reserve(size_t a_count) { checkInited(); m_capacity = a_count; m_data = YQ_REALLOC_ARRAY(m_data, T, a_count); } + size_t capacity() const { checkInited(); return m_capacity; } + size_t size() const { checkInited(); return m_size; } + T* data() const { checkInited(); return m_data; } + void clear() { checkInited(); m_size = 0; } +//private: // Actually making private would make this no longer a POD type + yqResult checkInited() const { if (BT_IsDebug) YQ_API_PRECONDITION(m_magic == 0x01020304, R_Uninitialized); return R_Okay; } + yqResult checkIndex(size_t a_pos) const { YQ_API_PRECONDITION(a_pos < m_size, R_IndexOutOfRange); return R_Okay; } + void ensureSpaceFor(size_t a_count) { reserve(std::max(m_size + a_count, m_capacity + m_capacity / 2)); } // try to grow by 50% + T* m_data; // Because of this pointer it is not safe to copy DynamicArray objects around. Making it a shared_ptr though stops this being a POD type + size_t m_capacity; + size_t m_size; + uint32_t m_magic; +}; + + +static_assert(std::is_pod >::value, "PODArrayPOD must be a POD type."); + + +// A dynamic array of POD type elements. +// It isn't POD itself, but is instead reference counted so safer to deal with. +template +struct ArrayPOD +{ + static_assert(std::is_pod::value, "T must be a POD type."); + + // Actually not reference counted yet, but safe as objects of this type can't be copied + ArrayPOD& operator=(const ArrayPOD&) = delete; + ArrayPOD(const ArrayPOD&) = delete; + ArrayPOD() { m_capacity = m_size = 0; m_data = nullptr; } + ~ArrayPOD() { if (m_data != nullptr) YQ_FREE(m_data); } + + const T& at(size_t a_pos) const { checkIndex(a_pos); return m_data[a_pos]; } + T& at(size_t a_pos) { checkIndex(a_pos); return m_data[a_pos]; } + const T& operator[](size_t a_pos) const { checkIndex(a_pos); return m_data[a_pos]; } + void pop_back() { checkIndex(1); m_size--; } + const T& back() const { return m_data[m_size - 1]; } + T& back() { return m_data[m_size - 1]; } + void push_back(const T& a_val) { if (m_size == m_capacity) ensureSpaceFor(1); m_data[m_size++] = a_val; } + void push(const T& a_val) { push_back(a_val); } + T pop() { pop_back(); return m_data[m_size]; } + void reserve(size_t a_count) { m_capacity = a_count; m_data = YQ_REALLOC_ARRAY(m_data, T, a_count); } + size_t capacity() const { return m_capacity; } + size_t size() const { return m_size; } + T* data() const { return m_data; } + void clear() { m_size = 0; } +private: + yqResult checkIndex(size_t a_pos) const { YQ_API_PRECONDITION(a_pos < m_size, R_IndexOutOfRange); return R_Okay; } + void ensureSpaceFor(size_t a_count) { reserve(std::max(m_size + a_count, m_capacity + m_capacity / 2)); } // try to grow by 50% + T* m_data; // Because of this pointer it is not safe to copy DynamicArray objects around. Making it a shared_ptr though stops this being a POD type + size_t m_capacity; + size_t m_size; +}; + + +struct PODString +{ +public: + void init() { m_data.init(); } + void deinit() { m_data.deinit(); } + void operator=(const char* a_str) { m_data.clear(); operator+=(a_str); } + void operator+=(const char* a_str) { while (*a_str) m_data.push_back(*a_str++); m_data.push(0); m_data.pop(); } + char* c_str() { return m_data.data(); } +//private: // Actually making private would make this no longer a POD type + PODArrayPOD m_data; +}; + + +static_assert(std::is_pod::value, "PODString must be a POD type."); + + +#endif // ARRAY_H diff --git a/Framework/Macros.h b/Framework/Macros.h new file mode 100644 index 0000000..6ca8c86 --- /dev/null +++ b/Framework/Macros.h @@ -0,0 +1,111 @@ +#ifndef MACROS_H +#define MACROS_H + + +// +// Various common macros +// +#define YQ_ARRAY_SIZE(x) (sizeof(x)/sizeof((x)[0])) + +#define YQ_EXPAND(x) x + +#define YQ_STRINGIZE_IMPL(val) #val +#define YQ_STRINGIZE(val) YQ_STRINGIZE_IMPL(val) + +#define YQ_CONCATENATE_IMPL(x, y) x##y +#define YQ_CONCATENATE(x, y) YQ_CONCATENATE_IMPL(x, y) + +// If 0 args, it mis-reports a count of 1. Also it only counts up to 10 args. +#define YQ_ARG_COUNT_IMPL(X,A,B,C,D,E,F,G,H,I,J,N,...) N +#define YQ_ARG_COUNT(...) (YQ_EXPAND(YQ_ARG_COUNT_IMPL(_,__VA_ARGS__, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0))) + +#ifdef __COUNTER__ +# define YQ_ANONYMOUS_VARIABLE(str) YQ_CONCATENATE(str, __COUNTER__) +#else +# define YQ_ANONYMOUS_VARIABLE(str) YQ_CONCATENATE(str, __LINE__) +#endif + +#define YQ_UNUSED(x) std::ignore = (x) + +#ifdef __MINGW32__ +// Avoid callbacks +# define YQ_CALLBACK __cdecl +# define YQ_FUNCTION_NAME __PRETTY_FUNCTION__ // __func__ +#elif defined(_WIN32) +# define YQ_CALLBACK __cdecl +# define YQ_FUNCTION_NAME __FUNCTION__ +#else +# define YQ_CALLBACK +# define YQ_FUNCTION_NAME __PRETTY_FUNCTION__ // __func__ +#endif + +// Helper marcos for doing a variable number of null checks +#define YQ_NULL_CHECK_IMPL_1(arg) if (arg == nullptr) YQ_API_LEAVE(R_InvalidParameter); +#define YQ_NULL_CHECK_IMPL_2(arg,...) YQ_NULL_CHECK_IMPL_1(arg) YQ_NULL_CHECK_IMPL_1(__VA_ARGS__) +#define YQ_NULL_CHECK_IMPL_3(arg,...) YQ_NULL_CHECK_IMPL_1(arg) YQ_NULL_CHECK_IMPL_2(__VA_ARGS__) +#define YQ_NULL_CHECK_IMPL_4(arg,...) YQ_NULL_CHECK_IMPL_1(arg) YQ_NULL_CHECK_IMPL_3(__VA_ARGS__) +#define YQ_NULL_CHECK_IMPL_5(arg,...) YQ_NULL_CHECK_IMPL_1(arg) YQ_NULL_CHECK_IMPL_4(__VA_ARGS__) +#define YQ_NULL_CHECK_IMPL_N3(N, ...) YQ_NULL_CHECK_IMPL_ ## N(__VA_ARGS__) +#define YQ_NULL_CHECK_IMPL_N2(N, ...) YQ_NULL_CHECK_IMPL_N3(N, __VA_ARGS__) +#define YQ_NULL_CHECK_IMPL_N(...) YQ_NULL_CHECK_IMPL_N2(YQ_ARG_COUNT(__VA_ARGS__), __VA_ARGS__) + +// File name, line numbes, and function name are statically concatenated at compile time, with a reduced string sharing trade off +#define YQ_LOCATION __FILE__ "(" YQ_STRINGIZE(__LINE__) "): " + +// Macros to log messages for debug builds. +// For release builds these will not output anything and no code or data will be generate (except for error logging) +#define YQ_LOG_WARNING(a_fmt, ...) ((BT_IsRelease) ? ((void)0) : yqLogMessage(LL_Warning, YQ_LOCATION, YQ_FUNCTION_NAME, "warning: " a_fmt, ##__VA_ARGS__)) +#define YQ_LOG_DEBUG(a_fmt, ...) ((BT_IsRelease) ? ((void)0) : yqLogMessage(LL_Debug, YQ_LOCATION, YQ_FUNCTION_NAME, "debug: " a_fmt, ##__VA_ARGS__)) +#define YQ_LOG_INFO(a_fmt, ...) ((BT_IsRelease) ? ((void)0) : yqLogMessage(LL_Info, YQ_LOCATION, YQ_FUNCTION_NAME, "info: " a_fmt, ##__VA_ARGS__)) +#define YQ_LOG_ERROR(a_fmt, ...) yqLogMessage(LL_Error, YQ_LOCATION, YQ_FUNCTION_NAME, "error: " a_fmt, ##__VA_ARGS__) + +// Macros that only generate log code in verbose debug builds. The logging logs the entry to API calls and the exit from them and what return code is being returned. +// In debug and release, on errors, the code always calls the installed or default error handling routine +enum { YQ_VERBOSE_DEBUGGING = 0 }; +#define YQ_API_VERBOSE(msg) ((YQ_VERBOSE_DEBUGGING) ? YQ_LOG_DEBUG(msg) : ((void)0)) +#define YQ_API_ERROR(result, ...) (yqHandleError(result, YQ_LOCATION, YQ_FUNCTION_NAME, __VA_ARGS__), YQ_LOG_ERROR("Leave: " #result " (%i)", result), result) +#define YQ_API_ENTER() YQ_API_VERBOSE("Enter") +#define YQ_API_LEAVE(result, ...) return ((result == R_Okay) ? (YQ_API_VERBOSE("Leave"), result) : YQ_API_ERROR(result, "" __VA_ARGS__)) + +// Macros to do common parameter and precondition checking for API function calls +// This macro checks the pre-condition is met, if it doesn't then returns the error result and executes the provided cleanup code +// See also 'finally'. Probably during pre-condition checking, there shouldn't be any cleanup yet as no work should be done at this stage. +//#define YQ_API_PRECONDITION_CLEANUP(condition,result,cleanup) do { if (!(condition)) { cleanup; YQ_API_LEAVE(result); } } while (0,0) +#define YQ_API_PRECONDITION(condition,result) do { if (!(condition)) { YQ_API_LEAVE(result, #condition); } } while (0,0)//YQ_API_PRECONDITION_CLEANUP(condition,result, ((void)0)) +#define YQ_API_PRECONDITION_NULL(...) YQ_NULL_CHECK_IMPL_N(__VA_ARGS__) + +// Can redirect to the user heap manager routines if provided +#define YQ_ALLOC(type) ((type*)(yqAllocateMemory(sizeof(type)))) +#define YQ_ALLOC_ARRAY(type,siz) ((type*)(yqAllocateMemory(sizeof(type)*(siz)))) +#define YQ_REALLOC_ARRAY(ptr,type,siz) ((type*)(yqReallocateMemory(ptr,sizeof(type)*(siz)))) +#define YQ_FREE(ptr) (((ptr)) ? (void)(yqFreeMemory((void*)(ptr)), (ptr) = nullptr) : (void)0) + +// C++ wrappers for using the custom allocators to replace use of new / delete (does placement new with custom allocated memory) +// Note: The custom allocators are expected to return null if the allocation fails and not throw. The macros below do not check this. +#define YQ_NEW(type) (new(YQ_ALLOC(type)) type) +#define YQ_NEW_PARAMS(type, params) (new(YQ_ALLOC(type)) type params) +#define YQ_DELETE(obj,type) ((obj) ? (void)((obj)->~type(), YQ_FREE(obj)) : (void)0) + + +/* + +Example usage: + +Result SomeFunction(T1 **Param1, T2 *Param2, T3 *Param3, int Param4) +{ + YQ_API_ENTER(); + + // Check pre-conditions + YQ_API_PRECONDITION_NULL(Param1, Param2, Param3); + YQ_API_PRECONDITION(Param4 == 42, R_InvalidParameter); + + if (errorHappened) + YQ_API_LEAVE(R_Failure); + + YQ_API_LEAVE(R_Okay); +} + +*/ + + +#endif // MACROS_H diff --git a/Framework/NonCopyable.h b/Framework/NonCopyable.h index 3881321..b8cb274 100644 --- a/Framework/NonCopyable.h +++ b/Framework/NonCopyable.h @@ -3,6 +3,62 @@ /// XXX: Not sure but maybe there is a C++11 std::non_copyable that does the same as this +/// Actually C++11 has the additional ability to add '= delete' to a member to say it can be used. + +#ifdef HAVE_CPP11 +# define DELETE = delete +#else +# define DELETE +#endif + + +/// This way has the advantage of not adding to the size of the base class if that is important [1] +/// [1] Ref: http://trac.webkit.org/changeset/68414 +#define MAKE_NONCOPYABLE(ClassName) \ + private: \ + ClassName(const ClassName&) DELETE; \ + ClassName& operator=(const ClassName&) DELETE; \ + + +/* Utility classes */ +struct NoCopy +{ +public: + NoCopy() {} +private: + NoCopy(const NoCopy &) DELETE; +}; + +struct NoAssign +{ +private: + NoAssign &operator=(const NoAssign &) DELETE; +}; + +struct NonInstantiable +{ +private: + NonInstantiable() DELETE; +}; + +struct NoCopyAssign : NoCopy, NoAssign +{ +}; + +typedef NoCopyAssign NoAssignCopy; + +// Not tested +struct NoMove +{ +public: + NoMove() {} +private: + NoMove(const NoMove &&) DELETE; +}; + +struct NoMoveCopyAssign : NoMove, NoCopy, NoAssign +{ +}; /// @@ -10,9 +66,9 @@ /// /// Making a class NonCopyable means preventing objects of the class being copied. This is useful if /// the class manages a resource that can only be held by one object such that it can't be reference -/// counted, or it is the underlying object that becomes reference counted. +/// counted, or it is the underlying object that becomes reference counted. Idea from here [1]. /// -/// Idea comes from here: http://stackoverflow.com/questions/9458741/with-explicitly-deleted-member-functions-in-c11-is-it-still-worthwhile-to-inh +/// [1] Ref: http://stackoverflow.com/questions/9458741/with-explicitly-deleted-member-functions-in-c11-is-it-still-worthwhile-to-inh /// class NonCopyable { @@ -34,12 +90,19 @@ MyNonCopyableClass() {} }; + MyNonCopyableClass create() + { + MyNonCopyableClass var; + return var; + } + Now it is not possible to copy objects of this type without a compiler error: - void function() + void function() { - MyNonCopyableClass obj1; - MyNonCopyableClass obj2; + MyNonCopyableClass obj1; + MyNonCopyableClass obj2; + MyNonCopyableClass obj3 = create(); // Allowed due to move semantics in c++11, use NoMoveCopyAssign to prevent // These attempts to make a copy of obj1 will cause a compiler error MyNonCopyableClass obj3(obj1); diff --git a/Framework/ObjectFilter.cpp b/Framework/ObjectFilter.cpp new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/Framework/ObjectFilter.cpp diff --git a/Framework/ObjectInspector.cpp b/Framework/ObjectInspector.cpp new file mode 100644 index 0000000..91b6726 --- /dev/null +++ b/Framework/ObjectInspector.cpp @@ -0,0 +1,90 @@ +#include "ObjectInspector.h" + +#include "ObjectPropertyModel.h" +#include "lib/ObjectProxy.h" + +#include +#include +#include +#include +#include +#include + +ObjectInspector::ObjectInspector(QWidget* parent) +: QWidget(parent) +{ + QVBoxLayout* layout = new QVBoxLayout(this); + layout->setMargin(0); + + m_nameLabel = new QLabel(this); + layout->addWidget(m_nameLabel); + + m_propertyView = new QTableView(this); + m_propertyView->verticalHeader()->setVisible(false); + m_propertyView->horizontalHeader()->setStretchLastSection(true); + + QHBoxLayout* filterLayout = new QHBoxLayout; + QLabel* filterLabel = new QLabel(tr("Filter:"),this); + m_propertyFilterEdit = new QLineEdit(this); + m_propertyFilterEdit->setToolTip(tr("String or regular expression to filter property list with")); + + connect(m_propertyFilterEdit,SIGNAL(textChanged(QString)), + this,SLOT(changeFilter(QString))); + filterLayout->addWidget(filterLabel); + filterLayout->addWidget(m_propertyFilterEdit); + + m_model = new ObjectPropertyModel(this); + m_propertySortModel = new QSortFilterProxyModel(this); + m_propertySortModel->setSourceModel(m_model); + + m_propertyView->setModel(m_propertySortModel); + + layout->addWidget(m_propertyView); + layout->addLayout(filterLayout); + + setObject(ObjectProxy::Pointer()); +} + +QString ObjectInspector::formatAddress(void* ptr) +{ + quintptr intPtr = reinterpret_cast(ptr); + return QString("0x%1").arg(QString(QByteArray::number(intPtr,16))); +} + +void ObjectInspector::refresh() +{ + setObject(m_currentObject); +} + +void ObjectInspector::setObject(ObjectProxy::Pointer object) +{ + QString labelText; + + if (object) + { + labelText = QString("%1 [%2] (%3)") + .arg(object->className()) + .arg(object->objectName()) + .arg(formatAddress(reinterpret_cast(object->address()))); + } + else + { + labelText = tr("[No Object Selected]"); + } + + m_nameLabel->setText(labelText); + m_currentObject = object; + m_model->setObject(object); + m_propertySortModel->sort(0,Qt::AscendingOrder); +} + +ObjectProxy::Pointer ObjectInspector::object() const +{ + return m_currentObject; +} + +void ObjectInspector::changeFilter(const QString& pattern) +{ + m_propertySortModel->setFilterRegExp(QRegExp(pattern)); +} + diff --git a/Framework/ObjectInspector.h b/Framework/ObjectInspector.h new file mode 100644 index 0000000..e1ce3ac --- /dev/null +++ b/Framework/ObjectInspector.h @@ -0,0 +1,40 @@ +#pragma once + +#include "lib/ObjectProxy.h" + +#include + +class QLabel; +class QLineEdit; +class QTableView; +class QSortFilterProxyModel; + +class ObjectPropertyModel; + +class ObjectInspector : public QWidget +{ + Q_OBJECT + + public: + ObjectInspector(QWidget* parent); + + void setObject(ObjectProxy::Pointer object); + ObjectProxy::Pointer object() const; + + static QString formatAddress(void* ptr); + + public Q_SLOTS: + void refresh(); + + private Q_SLOTS: + void changeFilter(const QString& pattern); + + private: + QLabel* m_nameLabel; + QTableView* m_propertyView; + ObjectProxy::Pointer m_currentObject; + ObjectPropertyModel* m_model; + QSortFilterProxyModel* m_propertySortModel; + QLineEdit* m_propertyFilterEdit; +}; + diff --git a/Framework/ObjectPropertyModel.cpp b/Framework/ObjectPropertyModel.cpp new file mode 100644 index 0000000..6e4d4f7 --- /dev/null +++ b/Framework/ObjectPropertyModel.cpp @@ -0,0 +1,91 @@ +#include "ObjectPropertyModel.h" + +#include "lib/ObjectProxy.h" + +#include +#include + +ObjectPropertyModel::ObjectPropertyModel(QObject* parent) +: QStandardItemModel(parent) +{ + setColumnCount(2); + setObject(ObjectProxy::Pointer()); + + connect(this,SIGNAL(itemChanged(QStandardItem*)), + this,SLOT(updatePropertyValue(QStandardItem*))); +} + +QVariant ObjectPropertyModel::toEditValue(const QVariant& value) +{ + // TODO - Find existing code which can handle display and editing + // of common value types. + switch (value.userType()) + { + case QVariant::Size: + case QVariant::SizeF: + { + QSize size = value.toSize(); + return QString("%1 x %2").arg(size.width()).arg(size.height()); + } + case QVariant::Rect: + case QVariant::RectF: + { + QRect rect = value.toRect(); + return QString("(%1,%2) (%3,%4)").arg(rect.left()).arg(rect.top()).arg(rect.width()).arg(rect.height()); + } + } + return value; +} + +void ObjectPropertyModel::updatePropertyValue(QStandardItem* item) +{ + QStandardItem* propertyItem = this->item(item->row(),0); + QString propertyName = propertyItem->text(); + + if (m_object) + { + QVariant newValue = item->data(Qt::EditRole); + qDebug() << "setting property" << propertyName << "to" << newValue; + m_object->writeProperty(propertyName,newValue); + } +} + +void ObjectPropertyModel::setObject(ObjectProxy::Pointer object) +{ + clear(); + + setHorizontalHeaderLabels( + QStringList() << tr("Property") << tr("Value") + ); + + m_object = object; + + if (object) + { + object->refresh(); + int rowCount = 0; + QListIterator iter(object->properties()); + while (iter.hasNext()) + { + ObjectProxy::Property property = iter.next(); + QVariant value = property.value; + QVariant editValue = toEditValue(value); + + QStandardItem* nameItem = new QStandardItem(property.name); + nameItem->setEditable(false); + + QStandardItem* valueItem = new QStandardItem; + valueItem->setData(editValue,Qt::EditRole); + valueItem->setEditable(property.isWritable); + + insertRow(rowCount,QList() << nameItem << valueItem); + ++rowCount; + } + } +} + +ObjectProxy::Pointer ObjectPropertyModel::object() const +{ + return m_object; +} + diff --git a/Framework/ObjectPropertyModel.h b/Framework/ObjectPropertyModel.h new file mode 100644 index 0000000..69e176c --- /dev/null +++ b/Framework/ObjectPropertyModel.h @@ -0,0 +1,27 @@ +#pragma once + +#include "lib/ObjectProxy.h" + +#include +#include + +class ObjectPropertyModel : public QStandardItemModel +{ + Q_OBJECT + + public: + ObjectPropertyModel(QObject* parent); + + void setObject(ObjectProxy::Pointer object); + ObjectProxy::Pointer object() const; + + static QVariant toEditValue(const QVariant& value); + + private Q_SLOTS: + void updatePropertyValue(QStandardItem* item); + + private: + ObjectProxy::Pointer m_object; +}; + + diff --git a/Framework/ObjectTreeModel.cpp b/Framework/ObjectTreeModel.cpp new file mode 100644 index 0000000..5d7bbfc --- /dev/null +++ b/Framework/ObjectTreeModel.cpp @@ -0,0 +1,135 @@ +#include "ObjectTreeModel.h" +#include + + +ObjectTreeModel::ObjectTreeModel(QObject* parent) +: QAbstractItemModel(parent) +{ +} + +int ObjectTreeModel::columnCount(const QModelIndex&) const +{ + return 1; +} + +int ObjectTreeModel::rowCount(const QModelIndex& parent) const +{ + QObject* obj = static_cast(parent.internalPointer()); + return (obj) ? obj->children().count() : m_roots.count(); +} + +void ObjectTreeModel::setRootObjects(const QList& roots) +{ + beginResetModel(); + m_roots = roots; + endResetModel(); +} + +QModelIndex ObjectTreeModel::index(int row, int column, const QModelIndex& parent) const +{ + QObject* parentItem = static_cast(parent.internalPointer()); + const QObjectList& children = (parentItem) ? parentItem->children() : m_roots; + bool outOfBounds = (row < 0 || column != 0 || row >= children.count()); + return outOfBounds ? QModelIndex() : createIndex(row,column,children.at(row)); +} + +int ObjectTreeModel::indexInParent(QObject* item) const +{ + QObject* parent = item->parent(); + return (parent && !m_roots.contains(item)) ? parent->children().indexOf(item) : m_roots.indexOf(item); +} + +QModelIndex ObjectTreeModel::parent(const QModelIndex& index) const +{ + QObject* item = static_cast(index.internalPointer()); + QObject* parent = (item && !m_roots.contains(item)) ? item->parent() : 0; + return (parent) ? createIndex(indexInParent(parent),index.column(),parent) : QModelIndex(); +} + +QVariant ObjectTreeModel::data(const QModelIndex& index, int role) const +{ + QObject* item = static_cast(index.internalPointer()); + return (role == Qt::DisplayRole) ? ((item) ? displayText(item) : "[deleted]") : QVariant(); +} + +QString ObjectTreeModel::displayText(QObject* object) const +{ + QString text = object->metaObject()->className(); + if (!object->objectName().isEmpty()) + { + text += QString("[%1]").arg(object->objectName()); + } + return text; +} + +QVariant ObjectTreeModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (section == 0 && orientation == Qt::Horizontal && role == Qt::DisplayRole) + { + return tr("Object"); + } + return QAbstractItemModel::headerData(section,orientation,role); +} + +QObject* ObjectTreeModel::index(ObjPtr object, const QList& items) const +{ + //inline QList findChildren(const QRegularExpression &re, Qt::FindChildOptions options = Qt::FindChildrenRecursively) const + Q_FOREACH(QObject* item, items) + { + if (item == object) + return item; + QObject* match = index(object, item->children()); + if (match) + return match; + } + return 0; +} + +QModelIndex ObjectTreeModel::index(ObjPtr object) const +{ + QObject* item = index(object, m_roots); + return (item) ? createIndex(indexInParent(item),0,item) : QModelIndex(); +} + + + + +/* +bool ObjectTreeModel::matches(ObjPtr object, const QString& query) const +{ + return displayText(object).contains(query,Qt::CaseInsensitive); +} + +void ObjectTreeModel::search(QList* hits, ObjectItem* item, const QString& query) const +{ + ObjPtr object = item;//->object; + if (!object) + { + return; + } + if (matches(object,query)) + { + *hits << object; + } + Q_FOREACH(ObjectItem* child, item->children()) + { + search(hits,child,query); + } +} + +QList ObjectTreeModel::search(const QString& query) const +{ + if (query.isEmpty()) + { + return QList(); + } + + QList matches; + Q_FOREACH(ObjectItem* item, m_roots) + { + search(&matches,item,query); + } + return matches; +} + +*/ diff --git a/Framework/ObjectTreeModel.h b/Framework/ObjectTreeModel.h new file mode 100644 index 0000000..4b50d8a --- /dev/null +++ b/Framework/ObjectTreeModel.h @@ -0,0 +1,47 @@ +#pragma once + +#include +#include +#include + + +class TrackedObject +{ +public: + TrackedObject() {} + TrackedObject(QObject* a_obj) : m_object(a_obj) {} + operator bool() { return !m_object.isNull(); } + operator QObject*() { return m_object.data(); } +private: + QPointer m_object; +}; + + +typedef QObject* ObjPtr; +//typedef TrackedObject ObjPtr; + + +class ObjectTreeModel : public QAbstractItemModel +{ + Q_OBJECT +public: + ObjectTreeModel(QObject* parent); + //void setFilter(ObjectFilter *filter); + //QList search(const QString& query) const; + void setRootObjects(const QList& roots); + QModelIndex index(ObjPtr object) const; + virtual QVariant headerData(int section, Qt::Orientation orientation, int role) const; + virtual int columnCount(const QModelIndex& parent) const; + virtual int rowCount(const QModelIndex& parent) const; + virtual QModelIndex index(int row, int column, const QModelIndex& parent) const; + virtual QModelIndex parent(const QModelIndex& index) const; + virtual QVariant data(const QModelIndex& index, int role) const; +private: + int indexInParent(QObject* item) const; + QObject* index(ObjPtr object, const QList& items) const; + //bool matches(ObjPtr object, const QString& query) const; + //void search(QList* matches, ObjectItem* item, const QString& query) const; + QString displayText(QObject* object) const; + QList m_roots; +}; + diff --git a/Framework/SystemInfomation.cpp b/Framework/SystemInfomation.cpp new file mode 100644 index 0000000..3f09248 --- /dev/null +++ b/Framework/SystemInfomation.cpp @@ -0,0 +1,132 @@ +#include "SystemInfomation.h" +#include "../Util.h" +#include +#include +#include + + +#ifdef __APPLE__ +# include "TargetConditionals.h" +#elif defined(_WIN32) +# include +#else +# include +# include +# include +# include +#endif + + +void getSystemInfo(SystemInfo& a_info) +{ + a_info.m_cpuType = "unknown"; + a_info.m_osType = "unknown"; + a_info.m_osVersion = "unknown"; + +#ifdef _WIN32 + a_info.m_osType = "Windows"; + + // Get RAM size + MEMORYSTATUSEX status; + status.dwLength = sizeof(status); + GlobalMemoryStatusEx(&status); + a_info.m_ramSize = status.ullTotalPhys; + + // Get OS version + const OSVERSIONINFO osver = winOsVersion(); + a_info.m_osVersion.reserve(512); + snprintf(a_info.m_osVersion.data(), "%i.%i.%i", osver.dwMajorVersion, osver.dwMinorVersion, osver.dwBuildNumber); + + // Get OS arch + SYSTEM_INFO info; + GetNativeSystemInfo(&info); +# ifdef _WIN64 + //if (info.wProcessorArchitecture == ???) + a_info.m_cpuType = "x86_64"; +# else + a_info.m_cpuType = "x86"; +# endif +#else + long pages = sysconf(_SC_PHYS_PAGES); + long page_size = sysconf(_SC_PAGE_SIZE); + a_info.m_ramSize = pages * page_size; + struct utsname u; + long ret = uname(&u); + if (ret != -1) + { + a_info.m_cpuType = u.machine; + a_info.m_osVersion = u.sysname + std::string(" ") + u.release; + } +# ifdef __APPLE__ + // Get RAM Size + int mib[2] = { CTL_HW, HW_MEMSIZE }; + size_t len = sizeof(uint64_t); + sysctl(mib, sizeof(mib) / sizeof(mib[0]), &u.ramSize, &len, NULL, 0); +# if TARGET_OS_IPHONE + a_info.m_osType = "iOS"; +# elif TARGET_OS_MAC + const char *macCodeName = ""; + switch (int(MacintoshVersion)) { + case MV_CHEETAH: macCodeName = " Cheetah"; break; + case MV_PUMA: macCodeName = " Puma"; break; + case MV_JAGUAR: macCodeName = " Jaguar"; break; + case MV_PANTHER: macCodeName = " Panther"; break; + case MV_TIGER: macCodeName = " Tiger"; break; + case MV_LEOPARD: macCodeName = " Leopard"; break; + case MV_SNOWLEOPARD: macCodeName = " Snow Leopard"; break; + case MV_LION: macCodeName = " Lion"; break; + case MV_MOUNTAINLION:macCodeName = " Mountain Lion"; break; + case MV_MAVERICKS: macCodeName = " Mavericks"; break; + case MV_YOSEMITE: macCodeName = " Yosemite"; break; + } + a_info.m_osType = "Mac OS X" + std::string(macCodeName); +# endif +# elif __ANDROID__ + a_info.m_osType = "Android"; +# elif __linux + // system("lsb_release -sd"); + FILE* f = fopen("/etc/lsb-release", "rt"); + char dist[64] = { 0 }; + if (f) { + char buf[128]; + while (fgets(buf, 128, f) != 0) + if (sscanf(buf, "DISTRIB_DESCRIPTION=%64c", dist) == 1) + break; + if (dist[0]) { + std::vector strBits = split(dist, '"'); + a_info.m_osType = strBits[(strBits.size()==3)?1:0]; + } else { + a_info.m_osType = "non-specific LSB"; + } + fclose(f); + } else { + a_info.m_osType = "non-LSB"; + } +# else + a_info.m_osType = "Generic UNIX"; +# endif +#endif + + // TODO: improve the CPU detection using libcpuid + + // Make human readable RAM size string + const char* humanUnitStrings[] = { " bytes", " KB", " MB", " GB", " TB" }; + int ramUnit = 0; + uint64_t ramSize = a_info.m_ramSize; + while (ramSize > 1024 && ramUnit < (YQ_ARRAY_SIZE(humanUnitStrings)-1)) + ramUnit++, ramSize /= 1000; + a_info.m_ramSizeStr = std::to_string(ramSize) + humanUnitStrings[ramUnit]; + + // Obtain the screen properties + QScreen *s = QApplication::screens()[0]; + int refreshRate = (s) ? s->refreshRate() : 0; + int depth = (s) ? s->depth() : 0; + a_info.m_resolutionWidth = (s) ? s->geometry().width() : 0; + a_info.m_resolutionHeight = (s) ? s->geometry().height() : 0; + a_info.m_resolutionStr = std::to_string(a_info.m_resolutionWidth) + "x" + + std::to_string(a_info.m_resolutionHeight) + " " + + std::to_string(depth) + "bit " + + "@" + std::to_string(refreshRate) + "Hz"; + ; +} + diff --git a/Framework/Array.h b/Framework/Array.h new file mode 100644 index 0000000..ab83e77 --- /dev/null +++ b/Framework/Array.h @@ -0,0 +1,98 @@ +#ifndef ARRAY_H +#define ARRAY_H + + +#include "Utilities.h" + + +// std::vector replacement that is cut down but familiar. +// It uses the custom allocators, and is restricted to POD elements only +template +struct PODArrayPOD // A dynamic array of POD type elements which is itself a POD type (so can have multi-dimensional arrays of these). +{ + static_assert(std::is_pod::value, "T must be a POD type."); + + void init() { m_magic = 0x01020304; m_capacity = m_size = 0; m_data = nullptr; } + void deinit() { if (m_data != nullptr) YQ_FREE(m_data); } + + const T& at(size_t a_pos) const { checkInited(); checkIndex(a_pos); return m_data[a_pos]; } + T& at(size_t a_pos) { checkInited(); checkIndex(a_pos); return m_data[a_pos]; } + const T& operator[](size_t a_pos) const { checkInited(); checkIndex(a_pos); return m_data[a_pos]; } + void pop_back() { checkInited(); checkIndex(1); m_size--; } + const T& back() const { checkInited(); return m_data[m_size - 1]; } + T& back() { checkInited(); return m_data[m_size - 1]; } + void push_back(const T& a_val) { checkInited(); if (m_size == m_capacity) ensureSpaceFor(1); m_data[m_size++] = a_val; } + void push(const T& a_val) { checkInited(); push_back(a_val); } + T pop() { checkInited(); pop_back(); return m_data[m_size]; } + void reserve(size_t a_count) { checkInited(); m_capacity = a_count; m_data = YQ_REALLOC_ARRAY(m_data, T, a_count); } + size_t capacity() const { checkInited(); return m_capacity; } + size_t size() const { checkInited(); return m_size; } + T* data() const { checkInited(); return m_data; } + void clear() { checkInited(); m_size = 0; } +//private: // Actually making private would make this no longer a POD type + yqResult checkInited() const { if (BT_IsDebug) YQ_API_PRECONDITION(m_magic == 0x01020304, R_Uninitialized); return R_Okay; } + yqResult checkIndex(size_t a_pos) const { YQ_API_PRECONDITION(a_pos < m_size, R_IndexOutOfRange); return R_Okay; } + void ensureSpaceFor(size_t a_count) { reserve(std::max(m_size + a_count, m_capacity + m_capacity / 2)); } // try to grow by 50% + T* m_data; // Because of this pointer it is not safe to copy DynamicArray objects around. Making it a shared_ptr though stops this being a POD type + size_t m_capacity; + size_t m_size; + uint32_t m_magic; +}; + + +static_assert(std::is_pod >::value, "PODArrayPOD must be a POD type."); + + +// A dynamic array of POD type elements. +// It isn't POD itself, but is instead reference counted so safer to deal with. +template +struct ArrayPOD +{ + static_assert(std::is_pod::value, "T must be a POD type."); + + // Actually not reference counted yet, but safe as objects of this type can't be copied + ArrayPOD& operator=(const ArrayPOD&) = delete; + ArrayPOD(const ArrayPOD&) = delete; + ArrayPOD() { m_capacity = m_size = 0; m_data = nullptr; } + ~ArrayPOD() { if (m_data != nullptr) YQ_FREE(m_data); } + + const T& at(size_t a_pos) const { checkIndex(a_pos); return m_data[a_pos]; } + T& at(size_t a_pos) { checkIndex(a_pos); return m_data[a_pos]; } + const T& operator[](size_t a_pos) const { checkIndex(a_pos); return m_data[a_pos]; } + void pop_back() { checkIndex(1); m_size--; } + const T& back() const { return m_data[m_size - 1]; } + T& back() { return m_data[m_size - 1]; } + void push_back(const T& a_val) { if (m_size == m_capacity) ensureSpaceFor(1); m_data[m_size++] = a_val; } + void push(const T& a_val) { push_back(a_val); } + T pop() { pop_back(); return m_data[m_size]; } + void reserve(size_t a_count) { m_capacity = a_count; m_data = YQ_REALLOC_ARRAY(m_data, T, a_count); } + size_t capacity() const { return m_capacity; } + size_t size() const { return m_size; } + T* data() const { return m_data; } + void clear() { m_size = 0; } +private: + yqResult checkIndex(size_t a_pos) const { YQ_API_PRECONDITION(a_pos < m_size, R_IndexOutOfRange); return R_Okay; } + void ensureSpaceFor(size_t a_count) { reserve(std::max(m_size + a_count, m_capacity + m_capacity / 2)); } // try to grow by 50% + T* m_data; // Because of this pointer it is not safe to copy DynamicArray objects around. Making it a shared_ptr though stops this being a POD type + size_t m_capacity; + size_t m_size; +}; + + +struct PODString +{ +public: + void init() { m_data.init(); } + void deinit() { m_data.deinit(); } + void operator=(const char* a_str) { m_data.clear(); operator+=(a_str); } + void operator+=(const char* a_str) { while (*a_str) m_data.push_back(*a_str++); m_data.push(0); m_data.pop(); } + char* c_str() { return m_data.data(); } +//private: // Actually making private would make this no longer a POD type + PODArrayPOD m_data; +}; + + +static_assert(std::is_pod::value, "PODString must be a POD type."); + + +#endif // ARRAY_H diff --git a/Framework/Macros.h b/Framework/Macros.h new file mode 100644 index 0000000..6ca8c86 --- /dev/null +++ b/Framework/Macros.h @@ -0,0 +1,111 @@ +#ifndef MACROS_H +#define MACROS_H + + +// +// Various common macros +// +#define YQ_ARRAY_SIZE(x) (sizeof(x)/sizeof((x)[0])) + +#define YQ_EXPAND(x) x + +#define YQ_STRINGIZE_IMPL(val) #val +#define YQ_STRINGIZE(val) YQ_STRINGIZE_IMPL(val) + +#define YQ_CONCATENATE_IMPL(x, y) x##y +#define YQ_CONCATENATE(x, y) YQ_CONCATENATE_IMPL(x, y) + +// If 0 args, it mis-reports a count of 1. Also it only counts up to 10 args. +#define YQ_ARG_COUNT_IMPL(X,A,B,C,D,E,F,G,H,I,J,N,...) N +#define YQ_ARG_COUNT(...) (YQ_EXPAND(YQ_ARG_COUNT_IMPL(_,__VA_ARGS__, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0))) + +#ifdef __COUNTER__ +# define YQ_ANONYMOUS_VARIABLE(str) YQ_CONCATENATE(str, __COUNTER__) +#else +# define YQ_ANONYMOUS_VARIABLE(str) YQ_CONCATENATE(str, __LINE__) +#endif + +#define YQ_UNUSED(x) std::ignore = (x) + +#ifdef __MINGW32__ +// Avoid callbacks +# define YQ_CALLBACK __cdecl +# define YQ_FUNCTION_NAME __PRETTY_FUNCTION__ // __func__ +#elif defined(_WIN32) +# define YQ_CALLBACK __cdecl +# define YQ_FUNCTION_NAME __FUNCTION__ +#else +# define YQ_CALLBACK +# define YQ_FUNCTION_NAME __PRETTY_FUNCTION__ // __func__ +#endif + +// Helper marcos for doing a variable number of null checks +#define YQ_NULL_CHECK_IMPL_1(arg) if (arg == nullptr) YQ_API_LEAVE(R_InvalidParameter); +#define YQ_NULL_CHECK_IMPL_2(arg,...) YQ_NULL_CHECK_IMPL_1(arg) YQ_NULL_CHECK_IMPL_1(__VA_ARGS__) +#define YQ_NULL_CHECK_IMPL_3(arg,...) YQ_NULL_CHECK_IMPL_1(arg) YQ_NULL_CHECK_IMPL_2(__VA_ARGS__) +#define YQ_NULL_CHECK_IMPL_4(arg,...) YQ_NULL_CHECK_IMPL_1(arg) YQ_NULL_CHECK_IMPL_3(__VA_ARGS__) +#define YQ_NULL_CHECK_IMPL_5(arg,...) YQ_NULL_CHECK_IMPL_1(arg) YQ_NULL_CHECK_IMPL_4(__VA_ARGS__) +#define YQ_NULL_CHECK_IMPL_N3(N, ...) YQ_NULL_CHECK_IMPL_ ## N(__VA_ARGS__) +#define YQ_NULL_CHECK_IMPL_N2(N, ...) YQ_NULL_CHECK_IMPL_N3(N, __VA_ARGS__) +#define YQ_NULL_CHECK_IMPL_N(...) YQ_NULL_CHECK_IMPL_N2(YQ_ARG_COUNT(__VA_ARGS__), __VA_ARGS__) + +// File name, line numbes, and function name are statically concatenated at compile time, with a reduced string sharing trade off +#define YQ_LOCATION __FILE__ "(" YQ_STRINGIZE(__LINE__) "): " + +// Macros to log messages for debug builds. +// For release builds these will not output anything and no code or data will be generate (except for error logging) +#define YQ_LOG_WARNING(a_fmt, ...) ((BT_IsRelease) ? ((void)0) : yqLogMessage(LL_Warning, YQ_LOCATION, YQ_FUNCTION_NAME, "warning: " a_fmt, ##__VA_ARGS__)) +#define YQ_LOG_DEBUG(a_fmt, ...) ((BT_IsRelease) ? ((void)0) : yqLogMessage(LL_Debug, YQ_LOCATION, YQ_FUNCTION_NAME, "debug: " a_fmt, ##__VA_ARGS__)) +#define YQ_LOG_INFO(a_fmt, ...) ((BT_IsRelease) ? ((void)0) : yqLogMessage(LL_Info, YQ_LOCATION, YQ_FUNCTION_NAME, "info: " a_fmt, ##__VA_ARGS__)) +#define YQ_LOG_ERROR(a_fmt, ...) yqLogMessage(LL_Error, YQ_LOCATION, YQ_FUNCTION_NAME, "error: " a_fmt, ##__VA_ARGS__) + +// Macros that only generate log code in verbose debug builds. The logging logs the entry to API calls and the exit from them and what return code is being returned. +// In debug and release, on errors, the code always calls the installed or default error handling routine +enum { YQ_VERBOSE_DEBUGGING = 0 }; +#define YQ_API_VERBOSE(msg) ((YQ_VERBOSE_DEBUGGING) ? YQ_LOG_DEBUG(msg) : ((void)0)) +#define YQ_API_ERROR(result, ...) (yqHandleError(result, YQ_LOCATION, YQ_FUNCTION_NAME, __VA_ARGS__), YQ_LOG_ERROR("Leave: " #result " (%i)", result), result) +#define YQ_API_ENTER() YQ_API_VERBOSE("Enter") +#define YQ_API_LEAVE(result, ...) return ((result == R_Okay) ? (YQ_API_VERBOSE("Leave"), result) : YQ_API_ERROR(result, "" __VA_ARGS__)) + +// Macros to do common parameter and precondition checking for API function calls +// This macro checks the pre-condition is met, if it doesn't then returns the error result and executes the provided cleanup code +// See also 'finally'. Probably during pre-condition checking, there shouldn't be any cleanup yet as no work should be done at this stage. +//#define YQ_API_PRECONDITION_CLEANUP(condition,result,cleanup) do { if (!(condition)) { cleanup; YQ_API_LEAVE(result); } } while (0,0) +#define YQ_API_PRECONDITION(condition,result) do { if (!(condition)) { YQ_API_LEAVE(result, #condition); } } while (0,0)//YQ_API_PRECONDITION_CLEANUP(condition,result, ((void)0)) +#define YQ_API_PRECONDITION_NULL(...) YQ_NULL_CHECK_IMPL_N(__VA_ARGS__) + +// Can redirect to the user heap manager routines if provided +#define YQ_ALLOC(type) ((type*)(yqAllocateMemory(sizeof(type)))) +#define YQ_ALLOC_ARRAY(type,siz) ((type*)(yqAllocateMemory(sizeof(type)*(siz)))) +#define YQ_REALLOC_ARRAY(ptr,type,siz) ((type*)(yqReallocateMemory(ptr,sizeof(type)*(siz)))) +#define YQ_FREE(ptr) (((ptr)) ? (void)(yqFreeMemory((void*)(ptr)), (ptr) = nullptr) : (void)0) + +// C++ wrappers for using the custom allocators to replace use of new / delete (does placement new with custom allocated memory) +// Note: The custom allocators are expected to return null if the allocation fails and not throw. The macros below do not check this. +#define YQ_NEW(type) (new(YQ_ALLOC(type)) type) +#define YQ_NEW_PARAMS(type, params) (new(YQ_ALLOC(type)) type params) +#define YQ_DELETE(obj,type) ((obj) ? (void)((obj)->~type(), YQ_FREE(obj)) : (void)0) + + +/* + +Example usage: + +Result SomeFunction(T1 **Param1, T2 *Param2, T3 *Param3, int Param4) +{ + YQ_API_ENTER(); + + // Check pre-conditions + YQ_API_PRECONDITION_NULL(Param1, Param2, Param3); + YQ_API_PRECONDITION(Param4 == 42, R_InvalidParameter); + + if (errorHappened) + YQ_API_LEAVE(R_Failure); + + YQ_API_LEAVE(R_Okay); +} + +*/ + + +#endif // MACROS_H diff --git a/Framework/NonCopyable.h b/Framework/NonCopyable.h index 3881321..b8cb274 100644 --- a/Framework/NonCopyable.h +++ b/Framework/NonCopyable.h @@ -3,6 +3,62 @@ /// XXX: Not sure but maybe there is a C++11 std::non_copyable that does the same as this +/// Actually C++11 has the additional ability to add '= delete' to a member to say it can be used. + +#ifdef HAVE_CPP11 +# define DELETE = delete +#else +# define DELETE +#endif + + +/// This way has the advantage of not adding to the size of the base class if that is important [1] +/// [1] Ref: http://trac.webkit.org/changeset/68414 +#define MAKE_NONCOPYABLE(ClassName) \ + private: \ + ClassName(const ClassName&) DELETE; \ + ClassName& operator=(const ClassName&) DELETE; \ + + +/* Utility classes */ +struct NoCopy +{ +public: + NoCopy() {} +private: + NoCopy(const NoCopy &) DELETE; +}; + +struct NoAssign +{ +private: + NoAssign &operator=(const NoAssign &) DELETE; +}; + +struct NonInstantiable +{ +private: + NonInstantiable() DELETE; +}; + +struct NoCopyAssign : NoCopy, NoAssign +{ +}; + +typedef NoCopyAssign NoAssignCopy; + +// Not tested +struct NoMove +{ +public: + NoMove() {} +private: + NoMove(const NoMove &&) DELETE; +}; + +struct NoMoveCopyAssign : NoMove, NoCopy, NoAssign +{ +}; /// @@ -10,9 +66,9 @@ /// /// Making a class NonCopyable means preventing objects of the class being copied. This is useful if /// the class manages a resource that can only be held by one object such that it can't be reference -/// counted, or it is the underlying object that becomes reference counted. +/// counted, or it is the underlying object that becomes reference counted. Idea from here [1]. /// -/// Idea comes from here: http://stackoverflow.com/questions/9458741/with-explicitly-deleted-member-functions-in-c11-is-it-still-worthwhile-to-inh +/// [1] Ref: http://stackoverflow.com/questions/9458741/with-explicitly-deleted-member-functions-in-c11-is-it-still-worthwhile-to-inh /// class NonCopyable { @@ -34,12 +90,19 @@ MyNonCopyableClass() {} }; + MyNonCopyableClass create() + { + MyNonCopyableClass var; + return var; + } + Now it is not possible to copy objects of this type without a compiler error: - void function() + void function() { - MyNonCopyableClass obj1; - MyNonCopyableClass obj2; + MyNonCopyableClass obj1; + MyNonCopyableClass obj2; + MyNonCopyableClass obj3 = create(); // Allowed due to move semantics in c++11, use NoMoveCopyAssign to prevent // These attempts to make a copy of obj1 will cause a compiler error MyNonCopyableClass obj3(obj1); diff --git a/Framework/ObjectFilter.cpp b/Framework/ObjectFilter.cpp new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/Framework/ObjectFilter.cpp diff --git a/Framework/ObjectInspector.cpp b/Framework/ObjectInspector.cpp new file mode 100644 index 0000000..91b6726 --- /dev/null +++ b/Framework/ObjectInspector.cpp @@ -0,0 +1,90 @@ +#include "ObjectInspector.h" + +#include "ObjectPropertyModel.h" +#include "lib/ObjectProxy.h" + +#include +#include +#include +#include +#include +#include + +ObjectInspector::ObjectInspector(QWidget* parent) +: QWidget(parent) +{ + QVBoxLayout* layout = new QVBoxLayout(this); + layout->setMargin(0); + + m_nameLabel = new QLabel(this); + layout->addWidget(m_nameLabel); + + m_propertyView = new QTableView(this); + m_propertyView->verticalHeader()->setVisible(false); + m_propertyView->horizontalHeader()->setStretchLastSection(true); + + QHBoxLayout* filterLayout = new QHBoxLayout; + QLabel* filterLabel = new QLabel(tr("Filter:"),this); + m_propertyFilterEdit = new QLineEdit(this); + m_propertyFilterEdit->setToolTip(tr("String or regular expression to filter property list with")); + + connect(m_propertyFilterEdit,SIGNAL(textChanged(QString)), + this,SLOT(changeFilter(QString))); + filterLayout->addWidget(filterLabel); + filterLayout->addWidget(m_propertyFilterEdit); + + m_model = new ObjectPropertyModel(this); + m_propertySortModel = new QSortFilterProxyModel(this); + m_propertySortModel->setSourceModel(m_model); + + m_propertyView->setModel(m_propertySortModel); + + layout->addWidget(m_propertyView); + layout->addLayout(filterLayout); + + setObject(ObjectProxy::Pointer()); +} + +QString ObjectInspector::formatAddress(void* ptr) +{ + quintptr intPtr = reinterpret_cast(ptr); + return QString("0x%1").arg(QString(QByteArray::number(intPtr,16))); +} + +void ObjectInspector::refresh() +{ + setObject(m_currentObject); +} + +void ObjectInspector::setObject(ObjectProxy::Pointer object) +{ + QString labelText; + + if (object) + { + labelText = QString("%1 [%2] (%3)") + .arg(object->className()) + .arg(object->objectName()) + .arg(formatAddress(reinterpret_cast(object->address()))); + } + else + { + labelText = tr("[No Object Selected]"); + } + + m_nameLabel->setText(labelText); + m_currentObject = object; + m_model->setObject(object); + m_propertySortModel->sort(0,Qt::AscendingOrder); +} + +ObjectProxy::Pointer ObjectInspector::object() const +{ + return m_currentObject; +} + +void ObjectInspector::changeFilter(const QString& pattern) +{ + m_propertySortModel->setFilterRegExp(QRegExp(pattern)); +} + diff --git a/Framework/ObjectInspector.h b/Framework/ObjectInspector.h new file mode 100644 index 0000000..e1ce3ac --- /dev/null +++ b/Framework/ObjectInspector.h @@ -0,0 +1,40 @@ +#pragma once + +#include "lib/ObjectProxy.h" + +#include + +class QLabel; +class QLineEdit; +class QTableView; +class QSortFilterProxyModel; + +class ObjectPropertyModel; + +class ObjectInspector : public QWidget +{ + Q_OBJECT + + public: + ObjectInspector(QWidget* parent); + + void setObject(ObjectProxy::Pointer object); + ObjectProxy::Pointer object() const; + + static QString formatAddress(void* ptr); + + public Q_SLOTS: + void refresh(); + + private Q_SLOTS: + void changeFilter(const QString& pattern); + + private: + QLabel* m_nameLabel; + QTableView* m_propertyView; + ObjectProxy::Pointer m_currentObject; + ObjectPropertyModel* m_model; + QSortFilterProxyModel* m_propertySortModel; + QLineEdit* m_propertyFilterEdit; +}; + diff --git a/Framework/ObjectPropertyModel.cpp b/Framework/ObjectPropertyModel.cpp new file mode 100644 index 0000000..6e4d4f7 --- /dev/null +++ b/Framework/ObjectPropertyModel.cpp @@ -0,0 +1,91 @@ +#include "ObjectPropertyModel.h" + +#include "lib/ObjectProxy.h" + +#include +#include + +ObjectPropertyModel::ObjectPropertyModel(QObject* parent) +: QStandardItemModel(parent) +{ + setColumnCount(2); + setObject(ObjectProxy::Pointer()); + + connect(this,SIGNAL(itemChanged(QStandardItem*)), + this,SLOT(updatePropertyValue(QStandardItem*))); +} + +QVariant ObjectPropertyModel::toEditValue(const QVariant& value) +{ + // TODO - Find existing code which can handle display and editing + // of common value types. + switch (value.userType()) + { + case QVariant::Size: + case QVariant::SizeF: + { + QSize size = value.toSize(); + return QString("%1 x %2").arg(size.width()).arg(size.height()); + } + case QVariant::Rect: + case QVariant::RectF: + { + QRect rect = value.toRect(); + return QString("(%1,%2) (%3,%4)").arg(rect.left()).arg(rect.top()).arg(rect.width()).arg(rect.height()); + } + } + return value; +} + +void ObjectPropertyModel::updatePropertyValue(QStandardItem* item) +{ + QStandardItem* propertyItem = this->item(item->row(),0); + QString propertyName = propertyItem->text(); + + if (m_object) + { + QVariant newValue = item->data(Qt::EditRole); + qDebug() << "setting property" << propertyName << "to" << newValue; + m_object->writeProperty(propertyName,newValue); + } +} + +void ObjectPropertyModel::setObject(ObjectProxy::Pointer object) +{ + clear(); + + setHorizontalHeaderLabels( + QStringList() << tr("Property") << tr("Value") + ); + + m_object = object; + + if (object) + { + object->refresh(); + int rowCount = 0; + QListIterator iter(object->properties()); + while (iter.hasNext()) + { + ObjectProxy::Property property = iter.next(); + QVariant value = property.value; + QVariant editValue = toEditValue(value); + + QStandardItem* nameItem = new QStandardItem(property.name); + nameItem->setEditable(false); + + QStandardItem* valueItem = new QStandardItem; + valueItem->setData(editValue,Qt::EditRole); + valueItem->setEditable(property.isWritable); + + insertRow(rowCount,QList() << nameItem << valueItem); + ++rowCount; + } + } +} + +ObjectProxy::Pointer ObjectPropertyModel::object() const +{ + return m_object; +} + diff --git a/Framework/ObjectPropertyModel.h b/Framework/ObjectPropertyModel.h new file mode 100644 index 0000000..69e176c --- /dev/null +++ b/Framework/ObjectPropertyModel.h @@ -0,0 +1,27 @@ +#pragma once + +#include "lib/ObjectProxy.h" + +#include +#include + +class ObjectPropertyModel : public QStandardItemModel +{ + Q_OBJECT + + public: + ObjectPropertyModel(QObject* parent); + + void setObject(ObjectProxy::Pointer object); + ObjectProxy::Pointer object() const; + + static QVariant toEditValue(const QVariant& value); + + private Q_SLOTS: + void updatePropertyValue(QStandardItem* item); + + private: + ObjectProxy::Pointer m_object; +}; + + diff --git a/Framework/ObjectTreeModel.cpp b/Framework/ObjectTreeModel.cpp new file mode 100644 index 0000000..5d7bbfc --- /dev/null +++ b/Framework/ObjectTreeModel.cpp @@ -0,0 +1,135 @@ +#include "ObjectTreeModel.h" +#include + + +ObjectTreeModel::ObjectTreeModel(QObject* parent) +: QAbstractItemModel(parent) +{ +} + +int ObjectTreeModel::columnCount(const QModelIndex&) const +{ + return 1; +} + +int ObjectTreeModel::rowCount(const QModelIndex& parent) const +{ + QObject* obj = static_cast(parent.internalPointer()); + return (obj) ? obj->children().count() : m_roots.count(); +} + +void ObjectTreeModel::setRootObjects(const QList& roots) +{ + beginResetModel(); + m_roots = roots; + endResetModel(); +} + +QModelIndex ObjectTreeModel::index(int row, int column, const QModelIndex& parent) const +{ + QObject* parentItem = static_cast(parent.internalPointer()); + const QObjectList& children = (parentItem) ? parentItem->children() : m_roots; + bool outOfBounds = (row < 0 || column != 0 || row >= children.count()); + return outOfBounds ? QModelIndex() : createIndex(row,column,children.at(row)); +} + +int ObjectTreeModel::indexInParent(QObject* item) const +{ + QObject* parent = item->parent(); + return (parent && !m_roots.contains(item)) ? parent->children().indexOf(item) : m_roots.indexOf(item); +} + +QModelIndex ObjectTreeModel::parent(const QModelIndex& index) const +{ + QObject* item = static_cast(index.internalPointer()); + QObject* parent = (item && !m_roots.contains(item)) ? item->parent() : 0; + return (parent) ? createIndex(indexInParent(parent),index.column(),parent) : QModelIndex(); +} + +QVariant ObjectTreeModel::data(const QModelIndex& index, int role) const +{ + QObject* item = static_cast(index.internalPointer()); + return (role == Qt::DisplayRole) ? ((item) ? displayText(item) : "[deleted]") : QVariant(); +} + +QString ObjectTreeModel::displayText(QObject* object) const +{ + QString text = object->metaObject()->className(); + if (!object->objectName().isEmpty()) + { + text += QString("[%1]").arg(object->objectName()); + } + return text; +} + +QVariant ObjectTreeModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (section == 0 && orientation == Qt::Horizontal && role == Qt::DisplayRole) + { + return tr("Object"); + } + return QAbstractItemModel::headerData(section,orientation,role); +} + +QObject* ObjectTreeModel::index(ObjPtr object, const QList& items) const +{ + //inline QList findChildren(const QRegularExpression &re, Qt::FindChildOptions options = Qt::FindChildrenRecursively) const + Q_FOREACH(QObject* item, items) + { + if (item == object) + return item; + QObject* match = index(object, item->children()); + if (match) + return match; + } + return 0; +} + +QModelIndex ObjectTreeModel::index(ObjPtr object) const +{ + QObject* item = index(object, m_roots); + return (item) ? createIndex(indexInParent(item),0,item) : QModelIndex(); +} + + + + +/* +bool ObjectTreeModel::matches(ObjPtr object, const QString& query) const +{ + return displayText(object).contains(query,Qt::CaseInsensitive); +} + +void ObjectTreeModel::search(QList* hits, ObjectItem* item, const QString& query) const +{ + ObjPtr object = item;//->object; + if (!object) + { + return; + } + if (matches(object,query)) + { + *hits << object; + } + Q_FOREACH(ObjectItem* child, item->children()) + { + search(hits,child,query); + } +} + +QList ObjectTreeModel::search(const QString& query) const +{ + if (query.isEmpty()) + { + return QList(); + } + + QList matches; + Q_FOREACH(ObjectItem* item, m_roots) + { + search(&matches,item,query); + } + return matches; +} + +*/ diff --git a/Framework/ObjectTreeModel.h b/Framework/ObjectTreeModel.h new file mode 100644 index 0000000..4b50d8a --- /dev/null +++ b/Framework/ObjectTreeModel.h @@ -0,0 +1,47 @@ +#pragma once + +#include +#include +#include + + +class TrackedObject +{ +public: + TrackedObject() {} + TrackedObject(QObject* a_obj) : m_object(a_obj) {} + operator bool() { return !m_object.isNull(); } + operator QObject*() { return m_object.data(); } +private: + QPointer m_object; +}; + + +typedef QObject* ObjPtr; +//typedef TrackedObject ObjPtr; + + +class ObjectTreeModel : public QAbstractItemModel +{ + Q_OBJECT +public: + ObjectTreeModel(QObject* parent); + //void setFilter(ObjectFilter *filter); + //QList search(const QString& query) const; + void setRootObjects(const QList& roots); + QModelIndex index(ObjPtr object) const; + virtual QVariant headerData(int section, Qt::Orientation orientation, int role) const; + virtual int columnCount(const QModelIndex& parent) const; + virtual int rowCount(const QModelIndex& parent) const; + virtual QModelIndex index(int row, int column, const QModelIndex& parent) const; + virtual QModelIndex parent(const QModelIndex& index) const; + virtual QVariant data(const QModelIndex& index, int role) const; +private: + int indexInParent(QObject* item) const; + QObject* index(ObjPtr object, const QList& items) const; + //bool matches(ObjPtr object, const QString& query) const; + //void search(QList* matches, ObjectItem* item, const QString& query) const; + QString displayText(QObject* object) const; + QList m_roots; +}; + diff --git a/Framework/SystemInfomation.cpp b/Framework/SystemInfomation.cpp new file mode 100644 index 0000000..3f09248 --- /dev/null +++ b/Framework/SystemInfomation.cpp @@ -0,0 +1,132 @@ +#include "SystemInfomation.h" +#include "../Util.h" +#include +#include +#include + + +#ifdef __APPLE__ +# include "TargetConditionals.h" +#elif defined(_WIN32) +# include +#else +# include +# include +# include +# include +#endif + + +void getSystemInfo(SystemInfo& a_info) +{ + a_info.m_cpuType = "unknown"; + a_info.m_osType = "unknown"; + a_info.m_osVersion = "unknown"; + +#ifdef _WIN32 + a_info.m_osType = "Windows"; + + // Get RAM size + MEMORYSTATUSEX status; + status.dwLength = sizeof(status); + GlobalMemoryStatusEx(&status); + a_info.m_ramSize = status.ullTotalPhys; + + // Get OS version + const OSVERSIONINFO osver = winOsVersion(); + a_info.m_osVersion.reserve(512); + snprintf(a_info.m_osVersion.data(), "%i.%i.%i", osver.dwMajorVersion, osver.dwMinorVersion, osver.dwBuildNumber); + + // Get OS arch + SYSTEM_INFO info; + GetNativeSystemInfo(&info); +# ifdef _WIN64 + //if (info.wProcessorArchitecture == ???) + a_info.m_cpuType = "x86_64"; +# else + a_info.m_cpuType = "x86"; +# endif +#else + long pages = sysconf(_SC_PHYS_PAGES); + long page_size = sysconf(_SC_PAGE_SIZE); + a_info.m_ramSize = pages * page_size; + struct utsname u; + long ret = uname(&u); + if (ret != -1) + { + a_info.m_cpuType = u.machine; + a_info.m_osVersion = u.sysname + std::string(" ") + u.release; + } +# ifdef __APPLE__ + // Get RAM Size + int mib[2] = { CTL_HW, HW_MEMSIZE }; + size_t len = sizeof(uint64_t); + sysctl(mib, sizeof(mib) / sizeof(mib[0]), &u.ramSize, &len, NULL, 0); +# if TARGET_OS_IPHONE + a_info.m_osType = "iOS"; +# elif TARGET_OS_MAC + const char *macCodeName = ""; + switch (int(MacintoshVersion)) { + case MV_CHEETAH: macCodeName = " Cheetah"; break; + case MV_PUMA: macCodeName = " Puma"; break; + case MV_JAGUAR: macCodeName = " Jaguar"; break; + case MV_PANTHER: macCodeName = " Panther"; break; + case MV_TIGER: macCodeName = " Tiger"; break; + case MV_LEOPARD: macCodeName = " Leopard"; break; + case MV_SNOWLEOPARD: macCodeName = " Snow Leopard"; break; + case MV_LION: macCodeName = " Lion"; break; + case MV_MOUNTAINLION:macCodeName = " Mountain Lion"; break; + case MV_MAVERICKS: macCodeName = " Mavericks"; break; + case MV_YOSEMITE: macCodeName = " Yosemite"; break; + } + a_info.m_osType = "Mac OS X" + std::string(macCodeName); +# endif +# elif __ANDROID__ + a_info.m_osType = "Android"; +# elif __linux + // system("lsb_release -sd"); + FILE* f = fopen("/etc/lsb-release", "rt"); + char dist[64] = { 0 }; + if (f) { + char buf[128]; + while (fgets(buf, 128, f) != 0) + if (sscanf(buf, "DISTRIB_DESCRIPTION=%64c", dist) == 1) + break; + if (dist[0]) { + std::vector strBits = split(dist, '"'); + a_info.m_osType = strBits[(strBits.size()==3)?1:0]; + } else { + a_info.m_osType = "non-specific LSB"; + } + fclose(f); + } else { + a_info.m_osType = "non-LSB"; + } +# else + a_info.m_osType = "Generic UNIX"; +# endif +#endif + + // TODO: improve the CPU detection using libcpuid + + // Make human readable RAM size string + const char* humanUnitStrings[] = { " bytes", " KB", " MB", " GB", " TB" }; + int ramUnit = 0; + uint64_t ramSize = a_info.m_ramSize; + while (ramSize > 1024 && ramUnit < (YQ_ARRAY_SIZE(humanUnitStrings)-1)) + ramUnit++, ramSize /= 1000; + a_info.m_ramSizeStr = std::to_string(ramSize) + humanUnitStrings[ramUnit]; + + // Obtain the screen properties + QScreen *s = QApplication::screens()[0]; + int refreshRate = (s) ? s->refreshRate() : 0; + int depth = (s) ? s->depth() : 0; + a_info.m_resolutionWidth = (s) ? s->geometry().width() : 0; + a_info.m_resolutionHeight = (s) ? s->geometry().height() : 0; + a_info.m_resolutionStr = std::to_string(a_info.m_resolutionWidth) + "x" + + std::to_string(a_info.m_resolutionHeight) + " " + + std::to_string(depth) + "bit " + + "@" + std::to_string(refreshRate) + "Hz"; + ; +} + diff --git a/Framework/SystemInfomation.h b/Framework/SystemInfomation.h new file mode 100644 index 0000000..2833313 --- /dev/null +++ b/Framework/SystemInfomation.h @@ -0,0 +1,26 @@ +#ifndef SYSTEM_INFOMATION_H +#define SYSTEM_INFOMATION_H + + +#include "Utilities.h" +#include +#include + + +struct SystemInfo +{ + std::string m_cpuType; + std::string m_osType; + std::string m_osVersion; + std::string m_ramSizeStr; // Human friendly string, eg: "4 GB" + std::string m_resolutionStr; + uint64_t m_ramSize; + uint32_t m_resolutionWidth; + uint32_t m_resolutionHeight; +}; + + +void getSystemInfo(SystemInfo& a_info); + + +#endif // SYSTEM_INFOMATION_H diff --git a/Framework/Array.h b/Framework/Array.h new file mode 100644 index 0000000..ab83e77 --- /dev/null +++ b/Framework/Array.h @@ -0,0 +1,98 @@ +#ifndef ARRAY_H +#define ARRAY_H + + +#include "Utilities.h" + + +// std::vector replacement that is cut down but familiar. +// It uses the custom allocators, and is restricted to POD elements only +template +struct PODArrayPOD // A dynamic array of POD type elements which is itself a POD type (so can have multi-dimensional arrays of these). +{ + static_assert(std::is_pod::value, "T must be a POD type."); + + void init() { m_magic = 0x01020304; m_capacity = m_size = 0; m_data = nullptr; } + void deinit() { if (m_data != nullptr) YQ_FREE(m_data); } + + const T& at(size_t a_pos) const { checkInited(); checkIndex(a_pos); return m_data[a_pos]; } + T& at(size_t a_pos) { checkInited(); checkIndex(a_pos); return m_data[a_pos]; } + const T& operator[](size_t a_pos) const { checkInited(); checkIndex(a_pos); return m_data[a_pos]; } + void pop_back() { checkInited(); checkIndex(1); m_size--; } + const T& back() const { checkInited(); return m_data[m_size - 1]; } + T& back() { checkInited(); return m_data[m_size - 1]; } + void push_back(const T& a_val) { checkInited(); if (m_size == m_capacity) ensureSpaceFor(1); m_data[m_size++] = a_val; } + void push(const T& a_val) { checkInited(); push_back(a_val); } + T pop() { checkInited(); pop_back(); return m_data[m_size]; } + void reserve(size_t a_count) { checkInited(); m_capacity = a_count; m_data = YQ_REALLOC_ARRAY(m_data, T, a_count); } + size_t capacity() const { checkInited(); return m_capacity; } + size_t size() const { checkInited(); return m_size; } + T* data() const { checkInited(); return m_data; } + void clear() { checkInited(); m_size = 0; } +//private: // Actually making private would make this no longer a POD type + yqResult checkInited() const { if (BT_IsDebug) YQ_API_PRECONDITION(m_magic == 0x01020304, R_Uninitialized); return R_Okay; } + yqResult checkIndex(size_t a_pos) const { YQ_API_PRECONDITION(a_pos < m_size, R_IndexOutOfRange); return R_Okay; } + void ensureSpaceFor(size_t a_count) { reserve(std::max(m_size + a_count, m_capacity + m_capacity / 2)); } // try to grow by 50% + T* m_data; // Because of this pointer it is not safe to copy DynamicArray objects around. Making it a shared_ptr though stops this being a POD type + size_t m_capacity; + size_t m_size; + uint32_t m_magic; +}; + + +static_assert(std::is_pod >::value, "PODArrayPOD must be a POD type."); + + +// A dynamic array of POD type elements. +// It isn't POD itself, but is instead reference counted so safer to deal with. +template +struct ArrayPOD +{ + static_assert(std::is_pod::value, "T must be a POD type."); + + // Actually not reference counted yet, but safe as objects of this type can't be copied + ArrayPOD& operator=(const ArrayPOD&) = delete; + ArrayPOD(const ArrayPOD&) = delete; + ArrayPOD() { m_capacity = m_size = 0; m_data = nullptr; } + ~ArrayPOD() { if (m_data != nullptr) YQ_FREE(m_data); } + + const T& at(size_t a_pos) const { checkIndex(a_pos); return m_data[a_pos]; } + T& at(size_t a_pos) { checkIndex(a_pos); return m_data[a_pos]; } + const T& operator[](size_t a_pos) const { checkIndex(a_pos); return m_data[a_pos]; } + void pop_back() { checkIndex(1); m_size--; } + const T& back() const { return m_data[m_size - 1]; } + T& back() { return m_data[m_size - 1]; } + void push_back(const T& a_val) { if (m_size == m_capacity) ensureSpaceFor(1); m_data[m_size++] = a_val; } + void push(const T& a_val) { push_back(a_val); } + T pop() { pop_back(); return m_data[m_size]; } + void reserve(size_t a_count) { m_capacity = a_count; m_data = YQ_REALLOC_ARRAY(m_data, T, a_count); } + size_t capacity() const { return m_capacity; } + size_t size() const { return m_size; } + T* data() const { return m_data; } + void clear() { m_size = 0; } +private: + yqResult checkIndex(size_t a_pos) const { YQ_API_PRECONDITION(a_pos < m_size, R_IndexOutOfRange); return R_Okay; } + void ensureSpaceFor(size_t a_count) { reserve(std::max(m_size + a_count, m_capacity + m_capacity / 2)); } // try to grow by 50% + T* m_data; // Because of this pointer it is not safe to copy DynamicArray objects around. Making it a shared_ptr though stops this being a POD type + size_t m_capacity; + size_t m_size; +}; + + +struct PODString +{ +public: + void init() { m_data.init(); } + void deinit() { m_data.deinit(); } + void operator=(const char* a_str) { m_data.clear(); operator+=(a_str); } + void operator+=(const char* a_str) { while (*a_str) m_data.push_back(*a_str++); m_data.push(0); m_data.pop(); } + char* c_str() { return m_data.data(); } +//private: // Actually making private would make this no longer a POD type + PODArrayPOD m_data; +}; + + +static_assert(std::is_pod::value, "PODString must be a POD type."); + + +#endif // ARRAY_H diff --git a/Framework/Macros.h b/Framework/Macros.h new file mode 100644 index 0000000..6ca8c86 --- /dev/null +++ b/Framework/Macros.h @@ -0,0 +1,111 @@ +#ifndef MACROS_H +#define MACROS_H + + +// +// Various common macros +// +#define YQ_ARRAY_SIZE(x) (sizeof(x)/sizeof((x)[0])) + +#define YQ_EXPAND(x) x + +#define YQ_STRINGIZE_IMPL(val) #val +#define YQ_STRINGIZE(val) YQ_STRINGIZE_IMPL(val) + +#define YQ_CONCATENATE_IMPL(x, y) x##y +#define YQ_CONCATENATE(x, y) YQ_CONCATENATE_IMPL(x, y) + +// If 0 args, it mis-reports a count of 1. Also it only counts up to 10 args. +#define YQ_ARG_COUNT_IMPL(X,A,B,C,D,E,F,G,H,I,J,N,...) N +#define YQ_ARG_COUNT(...) (YQ_EXPAND(YQ_ARG_COUNT_IMPL(_,__VA_ARGS__, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0))) + +#ifdef __COUNTER__ +# define YQ_ANONYMOUS_VARIABLE(str) YQ_CONCATENATE(str, __COUNTER__) +#else +# define YQ_ANONYMOUS_VARIABLE(str) YQ_CONCATENATE(str, __LINE__) +#endif + +#define YQ_UNUSED(x) std::ignore = (x) + +#ifdef __MINGW32__ +// Avoid callbacks +# define YQ_CALLBACK __cdecl +# define YQ_FUNCTION_NAME __PRETTY_FUNCTION__ // __func__ +#elif defined(_WIN32) +# define YQ_CALLBACK __cdecl +# define YQ_FUNCTION_NAME __FUNCTION__ +#else +# define YQ_CALLBACK +# define YQ_FUNCTION_NAME __PRETTY_FUNCTION__ // __func__ +#endif + +// Helper marcos for doing a variable number of null checks +#define YQ_NULL_CHECK_IMPL_1(arg) if (arg == nullptr) YQ_API_LEAVE(R_InvalidParameter); +#define YQ_NULL_CHECK_IMPL_2(arg,...) YQ_NULL_CHECK_IMPL_1(arg) YQ_NULL_CHECK_IMPL_1(__VA_ARGS__) +#define YQ_NULL_CHECK_IMPL_3(arg,...) YQ_NULL_CHECK_IMPL_1(arg) YQ_NULL_CHECK_IMPL_2(__VA_ARGS__) +#define YQ_NULL_CHECK_IMPL_4(arg,...) YQ_NULL_CHECK_IMPL_1(arg) YQ_NULL_CHECK_IMPL_3(__VA_ARGS__) +#define YQ_NULL_CHECK_IMPL_5(arg,...) YQ_NULL_CHECK_IMPL_1(arg) YQ_NULL_CHECK_IMPL_4(__VA_ARGS__) +#define YQ_NULL_CHECK_IMPL_N3(N, ...) YQ_NULL_CHECK_IMPL_ ## N(__VA_ARGS__) +#define YQ_NULL_CHECK_IMPL_N2(N, ...) YQ_NULL_CHECK_IMPL_N3(N, __VA_ARGS__) +#define YQ_NULL_CHECK_IMPL_N(...) YQ_NULL_CHECK_IMPL_N2(YQ_ARG_COUNT(__VA_ARGS__), __VA_ARGS__) + +// File name, line numbes, and function name are statically concatenated at compile time, with a reduced string sharing trade off +#define YQ_LOCATION __FILE__ "(" YQ_STRINGIZE(__LINE__) "): " + +// Macros to log messages for debug builds. +// For release builds these will not output anything and no code or data will be generate (except for error logging) +#define YQ_LOG_WARNING(a_fmt, ...) ((BT_IsRelease) ? ((void)0) : yqLogMessage(LL_Warning, YQ_LOCATION, YQ_FUNCTION_NAME, "warning: " a_fmt, ##__VA_ARGS__)) +#define YQ_LOG_DEBUG(a_fmt, ...) ((BT_IsRelease) ? ((void)0) : yqLogMessage(LL_Debug, YQ_LOCATION, YQ_FUNCTION_NAME, "debug: " a_fmt, ##__VA_ARGS__)) +#define YQ_LOG_INFO(a_fmt, ...) ((BT_IsRelease) ? ((void)0) : yqLogMessage(LL_Info, YQ_LOCATION, YQ_FUNCTION_NAME, "info: " a_fmt, ##__VA_ARGS__)) +#define YQ_LOG_ERROR(a_fmt, ...) yqLogMessage(LL_Error, YQ_LOCATION, YQ_FUNCTION_NAME, "error: " a_fmt, ##__VA_ARGS__) + +// Macros that only generate log code in verbose debug builds. The logging logs the entry to API calls and the exit from them and what return code is being returned. +// In debug and release, on errors, the code always calls the installed or default error handling routine +enum { YQ_VERBOSE_DEBUGGING = 0 }; +#define YQ_API_VERBOSE(msg) ((YQ_VERBOSE_DEBUGGING) ? YQ_LOG_DEBUG(msg) : ((void)0)) +#define YQ_API_ERROR(result, ...) (yqHandleError(result, YQ_LOCATION, YQ_FUNCTION_NAME, __VA_ARGS__), YQ_LOG_ERROR("Leave: " #result " (%i)", result), result) +#define YQ_API_ENTER() YQ_API_VERBOSE("Enter") +#define YQ_API_LEAVE(result, ...) return ((result == R_Okay) ? (YQ_API_VERBOSE("Leave"), result) : YQ_API_ERROR(result, "" __VA_ARGS__)) + +// Macros to do common parameter and precondition checking for API function calls +// This macro checks the pre-condition is met, if it doesn't then returns the error result and executes the provided cleanup code +// See also 'finally'. Probably during pre-condition checking, there shouldn't be any cleanup yet as no work should be done at this stage. +//#define YQ_API_PRECONDITION_CLEANUP(condition,result,cleanup) do { if (!(condition)) { cleanup; YQ_API_LEAVE(result); } } while (0,0) +#define YQ_API_PRECONDITION(condition,result) do { if (!(condition)) { YQ_API_LEAVE(result, #condition); } } while (0,0)//YQ_API_PRECONDITION_CLEANUP(condition,result, ((void)0)) +#define YQ_API_PRECONDITION_NULL(...) YQ_NULL_CHECK_IMPL_N(__VA_ARGS__) + +// Can redirect to the user heap manager routines if provided +#define YQ_ALLOC(type) ((type*)(yqAllocateMemory(sizeof(type)))) +#define YQ_ALLOC_ARRAY(type,siz) ((type*)(yqAllocateMemory(sizeof(type)*(siz)))) +#define YQ_REALLOC_ARRAY(ptr,type,siz) ((type*)(yqReallocateMemory(ptr,sizeof(type)*(siz)))) +#define YQ_FREE(ptr) (((ptr)) ? (void)(yqFreeMemory((void*)(ptr)), (ptr) = nullptr) : (void)0) + +// C++ wrappers for using the custom allocators to replace use of new / delete (does placement new with custom allocated memory) +// Note: The custom allocators are expected to return null if the allocation fails and not throw. The macros below do not check this. +#define YQ_NEW(type) (new(YQ_ALLOC(type)) type) +#define YQ_NEW_PARAMS(type, params) (new(YQ_ALLOC(type)) type params) +#define YQ_DELETE(obj,type) ((obj) ? (void)((obj)->~type(), YQ_FREE(obj)) : (void)0) + + +/* + +Example usage: + +Result SomeFunction(T1 **Param1, T2 *Param2, T3 *Param3, int Param4) +{ + YQ_API_ENTER(); + + // Check pre-conditions + YQ_API_PRECONDITION_NULL(Param1, Param2, Param3); + YQ_API_PRECONDITION(Param4 == 42, R_InvalidParameter); + + if (errorHappened) + YQ_API_LEAVE(R_Failure); + + YQ_API_LEAVE(R_Okay); +} + +*/ + + +#endif // MACROS_H diff --git a/Framework/NonCopyable.h b/Framework/NonCopyable.h index 3881321..b8cb274 100644 --- a/Framework/NonCopyable.h +++ b/Framework/NonCopyable.h @@ -3,6 +3,62 @@ /// XXX: Not sure but maybe there is a C++11 std::non_copyable that does the same as this +/// Actually C++11 has the additional ability to add '= delete' to a member to say it can be used. + +#ifdef HAVE_CPP11 +# define DELETE = delete +#else +# define DELETE +#endif + + +/// This way has the advantage of not adding to the size of the base class if that is important [1] +/// [1] Ref: http://trac.webkit.org/changeset/68414 +#define MAKE_NONCOPYABLE(ClassName) \ + private: \ + ClassName(const ClassName&) DELETE; \ + ClassName& operator=(const ClassName&) DELETE; \ + + +/* Utility classes */ +struct NoCopy +{ +public: + NoCopy() {} +private: + NoCopy(const NoCopy &) DELETE; +}; + +struct NoAssign +{ +private: + NoAssign &operator=(const NoAssign &) DELETE; +}; + +struct NonInstantiable +{ +private: + NonInstantiable() DELETE; +}; + +struct NoCopyAssign : NoCopy, NoAssign +{ +}; + +typedef NoCopyAssign NoAssignCopy; + +// Not tested +struct NoMove +{ +public: + NoMove() {} +private: + NoMove(const NoMove &&) DELETE; +}; + +struct NoMoveCopyAssign : NoMove, NoCopy, NoAssign +{ +}; /// @@ -10,9 +66,9 @@ /// /// Making a class NonCopyable means preventing objects of the class being copied. This is useful if /// the class manages a resource that can only be held by one object such that it can't be reference -/// counted, or it is the underlying object that becomes reference counted. +/// counted, or it is the underlying object that becomes reference counted. Idea from here [1]. /// -/// Idea comes from here: http://stackoverflow.com/questions/9458741/with-explicitly-deleted-member-functions-in-c11-is-it-still-worthwhile-to-inh +/// [1] Ref: http://stackoverflow.com/questions/9458741/with-explicitly-deleted-member-functions-in-c11-is-it-still-worthwhile-to-inh /// class NonCopyable { @@ -34,12 +90,19 @@ MyNonCopyableClass() {} }; + MyNonCopyableClass create() + { + MyNonCopyableClass var; + return var; + } + Now it is not possible to copy objects of this type without a compiler error: - void function() + void function() { - MyNonCopyableClass obj1; - MyNonCopyableClass obj2; + MyNonCopyableClass obj1; + MyNonCopyableClass obj2; + MyNonCopyableClass obj3 = create(); // Allowed due to move semantics in c++11, use NoMoveCopyAssign to prevent // These attempts to make a copy of obj1 will cause a compiler error MyNonCopyableClass obj3(obj1); diff --git a/Framework/ObjectFilter.cpp b/Framework/ObjectFilter.cpp new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/Framework/ObjectFilter.cpp diff --git a/Framework/ObjectInspector.cpp b/Framework/ObjectInspector.cpp new file mode 100644 index 0000000..91b6726 --- /dev/null +++ b/Framework/ObjectInspector.cpp @@ -0,0 +1,90 @@ +#include "ObjectInspector.h" + +#include "ObjectPropertyModel.h" +#include "lib/ObjectProxy.h" + +#include +#include +#include +#include +#include +#include + +ObjectInspector::ObjectInspector(QWidget* parent) +: QWidget(parent) +{ + QVBoxLayout* layout = new QVBoxLayout(this); + layout->setMargin(0); + + m_nameLabel = new QLabel(this); + layout->addWidget(m_nameLabel); + + m_propertyView = new QTableView(this); + m_propertyView->verticalHeader()->setVisible(false); + m_propertyView->horizontalHeader()->setStretchLastSection(true); + + QHBoxLayout* filterLayout = new QHBoxLayout; + QLabel* filterLabel = new QLabel(tr("Filter:"),this); + m_propertyFilterEdit = new QLineEdit(this); + m_propertyFilterEdit->setToolTip(tr("String or regular expression to filter property list with")); + + connect(m_propertyFilterEdit,SIGNAL(textChanged(QString)), + this,SLOT(changeFilter(QString))); + filterLayout->addWidget(filterLabel); + filterLayout->addWidget(m_propertyFilterEdit); + + m_model = new ObjectPropertyModel(this); + m_propertySortModel = new QSortFilterProxyModel(this); + m_propertySortModel->setSourceModel(m_model); + + m_propertyView->setModel(m_propertySortModel); + + layout->addWidget(m_propertyView); + layout->addLayout(filterLayout); + + setObject(ObjectProxy::Pointer()); +} + +QString ObjectInspector::formatAddress(void* ptr) +{ + quintptr intPtr = reinterpret_cast(ptr); + return QString("0x%1").arg(QString(QByteArray::number(intPtr,16))); +} + +void ObjectInspector::refresh() +{ + setObject(m_currentObject); +} + +void ObjectInspector::setObject(ObjectProxy::Pointer object) +{ + QString labelText; + + if (object) + { + labelText = QString("%1 [%2] (%3)") + .arg(object->className()) + .arg(object->objectName()) + .arg(formatAddress(reinterpret_cast(object->address()))); + } + else + { + labelText = tr("[No Object Selected]"); + } + + m_nameLabel->setText(labelText); + m_currentObject = object; + m_model->setObject(object); + m_propertySortModel->sort(0,Qt::AscendingOrder); +} + +ObjectProxy::Pointer ObjectInspector::object() const +{ + return m_currentObject; +} + +void ObjectInspector::changeFilter(const QString& pattern) +{ + m_propertySortModel->setFilterRegExp(QRegExp(pattern)); +} + diff --git a/Framework/ObjectInspector.h b/Framework/ObjectInspector.h new file mode 100644 index 0000000..e1ce3ac --- /dev/null +++ b/Framework/ObjectInspector.h @@ -0,0 +1,40 @@ +#pragma once + +#include "lib/ObjectProxy.h" + +#include + +class QLabel; +class QLineEdit; +class QTableView; +class QSortFilterProxyModel; + +class ObjectPropertyModel; + +class ObjectInspector : public QWidget +{ + Q_OBJECT + + public: + ObjectInspector(QWidget* parent); + + void setObject(ObjectProxy::Pointer object); + ObjectProxy::Pointer object() const; + + static QString formatAddress(void* ptr); + + public Q_SLOTS: + void refresh(); + + private Q_SLOTS: + void changeFilter(const QString& pattern); + + private: + QLabel* m_nameLabel; + QTableView* m_propertyView; + ObjectProxy::Pointer m_currentObject; + ObjectPropertyModel* m_model; + QSortFilterProxyModel* m_propertySortModel; + QLineEdit* m_propertyFilterEdit; +}; + diff --git a/Framework/ObjectPropertyModel.cpp b/Framework/ObjectPropertyModel.cpp new file mode 100644 index 0000000..6e4d4f7 --- /dev/null +++ b/Framework/ObjectPropertyModel.cpp @@ -0,0 +1,91 @@ +#include "ObjectPropertyModel.h" + +#include "lib/ObjectProxy.h" + +#include +#include + +ObjectPropertyModel::ObjectPropertyModel(QObject* parent) +: QStandardItemModel(parent) +{ + setColumnCount(2); + setObject(ObjectProxy::Pointer()); + + connect(this,SIGNAL(itemChanged(QStandardItem*)), + this,SLOT(updatePropertyValue(QStandardItem*))); +} + +QVariant ObjectPropertyModel::toEditValue(const QVariant& value) +{ + // TODO - Find existing code which can handle display and editing + // of common value types. + switch (value.userType()) + { + case QVariant::Size: + case QVariant::SizeF: + { + QSize size = value.toSize(); + return QString("%1 x %2").arg(size.width()).arg(size.height()); + } + case QVariant::Rect: + case QVariant::RectF: + { + QRect rect = value.toRect(); + return QString("(%1,%2) (%3,%4)").arg(rect.left()).arg(rect.top()).arg(rect.width()).arg(rect.height()); + } + } + return value; +} + +void ObjectPropertyModel::updatePropertyValue(QStandardItem* item) +{ + QStandardItem* propertyItem = this->item(item->row(),0); + QString propertyName = propertyItem->text(); + + if (m_object) + { + QVariant newValue = item->data(Qt::EditRole); + qDebug() << "setting property" << propertyName << "to" << newValue; + m_object->writeProperty(propertyName,newValue); + } +} + +void ObjectPropertyModel::setObject(ObjectProxy::Pointer object) +{ + clear(); + + setHorizontalHeaderLabels( + QStringList() << tr("Property") << tr("Value") + ); + + m_object = object; + + if (object) + { + object->refresh(); + int rowCount = 0; + QListIterator iter(object->properties()); + while (iter.hasNext()) + { + ObjectProxy::Property property = iter.next(); + QVariant value = property.value; + QVariant editValue = toEditValue(value); + + QStandardItem* nameItem = new QStandardItem(property.name); + nameItem->setEditable(false); + + QStandardItem* valueItem = new QStandardItem; + valueItem->setData(editValue,Qt::EditRole); + valueItem->setEditable(property.isWritable); + + insertRow(rowCount,QList() << nameItem << valueItem); + ++rowCount; + } + } +} + +ObjectProxy::Pointer ObjectPropertyModel::object() const +{ + return m_object; +} + diff --git a/Framework/ObjectPropertyModel.h b/Framework/ObjectPropertyModel.h new file mode 100644 index 0000000..69e176c --- /dev/null +++ b/Framework/ObjectPropertyModel.h @@ -0,0 +1,27 @@ +#pragma once + +#include "lib/ObjectProxy.h" + +#include +#include + +class ObjectPropertyModel : public QStandardItemModel +{ + Q_OBJECT + + public: + ObjectPropertyModel(QObject* parent); + + void setObject(ObjectProxy::Pointer object); + ObjectProxy::Pointer object() const; + + static QVariant toEditValue(const QVariant& value); + + private Q_SLOTS: + void updatePropertyValue(QStandardItem* item); + + private: + ObjectProxy::Pointer m_object; +}; + + diff --git a/Framework/ObjectTreeModel.cpp b/Framework/ObjectTreeModel.cpp new file mode 100644 index 0000000..5d7bbfc --- /dev/null +++ b/Framework/ObjectTreeModel.cpp @@ -0,0 +1,135 @@ +#include "ObjectTreeModel.h" +#include + + +ObjectTreeModel::ObjectTreeModel(QObject* parent) +: QAbstractItemModel(parent) +{ +} + +int ObjectTreeModel::columnCount(const QModelIndex&) const +{ + return 1; +} + +int ObjectTreeModel::rowCount(const QModelIndex& parent) const +{ + QObject* obj = static_cast(parent.internalPointer()); + return (obj) ? obj->children().count() : m_roots.count(); +} + +void ObjectTreeModel::setRootObjects(const QList& roots) +{ + beginResetModel(); + m_roots = roots; + endResetModel(); +} + +QModelIndex ObjectTreeModel::index(int row, int column, const QModelIndex& parent) const +{ + QObject* parentItem = static_cast(parent.internalPointer()); + const QObjectList& children = (parentItem) ? parentItem->children() : m_roots; + bool outOfBounds = (row < 0 || column != 0 || row >= children.count()); + return outOfBounds ? QModelIndex() : createIndex(row,column,children.at(row)); +} + +int ObjectTreeModel::indexInParent(QObject* item) const +{ + QObject* parent = item->parent(); + return (parent && !m_roots.contains(item)) ? parent->children().indexOf(item) : m_roots.indexOf(item); +} + +QModelIndex ObjectTreeModel::parent(const QModelIndex& index) const +{ + QObject* item = static_cast(index.internalPointer()); + QObject* parent = (item && !m_roots.contains(item)) ? item->parent() : 0; + return (parent) ? createIndex(indexInParent(parent),index.column(),parent) : QModelIndex(); +} + +QVariant ObjectTreeModel::data(const QModelIndex& index, int role) const +{ + QObject* item = static_cast(index.internalPointer()); + return (role == Qt::DisplayRole) ? ((item) ? displayText(item) : "[deleted]") : QVariant(); +} + +QString ObjectTreeModel::displayText(QObject* object) const +{ + QString text = object->metaObject()->className(); + if (!object->objectName().isEmpty()) + { + text += QString("[%1]").arg(object->objectName()); + } + return text; +} + +QVariant ObjectTreeModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (section == 0 && orientation == Qt::Horizontal && role == Qt::DisplayRole) + { + return tr("Object"); + } + return QAbstractItemModel::headerData(section,orientation,role); +} + +QObject* ObjectTreeModel::index(ObjPtr object, const QList& items) const +{ + //inline QList findChildren(const QRegularExpression &re, Qt::FindChildOptions options = Qt::FindChildrenRecursively) const + Q_FOREACH(QObject* item, items) + { + if (item == object) + return item; + QObject* match = index(object, item->children()); + if (match) + return match; + } + return 0; +} + +QModelIndex ObjectTreeModel::index(ObjPtr object) const +{ + QObject* item = index(object, m_roots); + return (item) ? createIndex(indexInParent(item),0,item) : QModelIndex(); +} + + + + +/* +bool ObjectTreeModel::matches(ObjPtr object, const QString& query) const +{ + return displayText(object).contains(query,Qt::CaseInsensitive); +} + +void ObjectTreeModel::search(QList* hits, ObjectItem* item, const QString& query) const +{ + ObjPtr object = item;//->object; + if (!object) + { + return; + } + if (matches(object,query)) + { + *hits << object; + } + Q_FOREACH(ObjectItem* child, item->children()) + { + search(hits,child,query); + } +} + +QList ObjectTreeModel::search(const QString& query) const +{ + if (query.isEmpty()) + { + return QList(); + } + + QList matches; + Q_FOREACH(ObjectItem* item, m_roots) + { + search(&matches,item,query); + } + return matches; +} + +*/ diff --git a/Framework/ObjectTreeModel.h b/Framework/ObjectTreeModel.h new file mode 100644 index 0000000..4b50d8a --- /dev/null +++ b/Framework/ObjectTreeModel.h @@ -0,0 +1,47 @@ +#pragma once + +#include +#include +#include + + +class TrackedObject +{ +public: + TrackedObject() {} + TrackedObject(QObject* a_obj) : m_object(a_obj) {} + operator bool() { return !m_object.isNull(); } + operator QObject*() { return m_object.data(); } +private: + QPointer m_object; +}; + + +typedef QObject* ObjPtr; +//typedef TrackedObject ObjPtr; + + +class ObjectTreeModel : public QAbstractItemModel +{ + Q_OBJECT +public: + ObjectTreeModel(QObject* parent); + //void setFilter(ObjectFilter *filter); + //QList search(const QString& query) const; + void setRootObjects(const QList& roots); + QModelIndex index(ObjPtr object) const; + virtual QVariant headerData(int section, Qt::Orientation orientation, int role) const; + virtual int columnCount(const QModelIndex& parent) const; + virtual int rowCount(const QModelIndex& parent) const; + virtual QModelIndex index(int row, int column, const QModelIndex& parent) const; + virtual QModelIndex parent(const QModelIndex& index) const; + virtual QVariant data(const QModelIndex& index, int role) const; +private: + int indexInParent(QObject* item) const; + QObject* index(ObjPtr object, const QList& items) const; + //bool matches(ObjPtr object, const QString& query) const; + //void search(QList* matches, ObjectItem* item, const QString& query) const; + QString displayText(QObject* object) const; + QList m_roots; +}; + diff --git a/Framework/SystemInfomation.cpp b/Framework/SystemInfomation.cpp new file mode 100644 index 0000000..3f09248 --- /dev/null +++ b/Framework/SystemInfomation.cpp @@ -0,0 +1,132 @@ +#include "SystemInfomation.h" +#include "../Util.h" +#include +#include +#include + + +#ifdef __APPLE__ +# include "TargetConditionals.h" +#elif defined(_WIN32) +# include +#else +# include +# include +# include +# include +#endif + + +void getSystemInfo(SystemInfo& a_info) +{ + a_info.m_cpuType = "unknown"; + a_info.m_osType = "unknown"; + a_info.m_osVersion = "unknown"; + +#ifdef _WIN32 + a_info.m_osType = "Windows"; + + // Get RAM size + MEMORYSTATUSEX status; + status.dwLength = sizeof(status); + GlobalMemoryStatusEx(&status); + a_info.m_ramSize = status.ullTotalPhys; + + // Get OS version + const OSVERSIONINFO osver = winOsVersion(); + a_info.m_osVersion.reserve(512); + snprintf(a_info.m_osVersion.data(), "%i.%i.%i", osver.dwMajorVersion, osver.dwMinorVersion, osver.dwBuildNumber); + + // Get OS arch + SYSTEM_INFO info; + GetNativeSystemInfo(&info); +# ifdef _WIN64 + //if (info.wProcessorArchitecture == ???) + a_info.m_cpuType = "x86_64"; +# else + a_info.m_cpuType = "x86"; +# endif +#else + long pages = sysconf(_SC_PHYS_PAGES); + long page_size = sysconf(_SC_PAGE_SIZE); + a_info.m_ramSize = pages * page_size; + struct utsname u; + long ret = uname(&u); + if (ret != -1) + { + a_info.m_cpuType = u.machine; + a_info.m_osVersion = u.sysname + std::string(" ") + u.release; + } +# ifdef __APPLE__ + // Get RAM Size + int mib[2] = { CTL_HW, HW_MEMSIZE }; + size_t len = sizeof(uint64_t); + sysctl(mib, sizeof(mib) / sizeof(mib[0]), &u.ramSize, &len, NULL, 0); +# if TARGET_OS_IPHONE + a_info.m_osType = "iOS"; +# elif TARGET_OS_MAC + const char *macCodeName = ""; + switch (int(MacintoshVersion)) { + case MV_CHEETAH: macCodeName = " Cheetah"; break; + case MV_PUMA: macCodeName = " Puma"; break; + case MV_JAGUAR: macCodeName = " Jaguar"; break; + case MV_PANTHER: macCodeName = " Panther"; break; + case MV_TIGER: macCodeName = " Tiger"; break; + case MV_LEOPARD: macCodeName = " Leopard"; break; + case MV_SNOWLEOPARD: macCodeName = " Snow Leopard"; break; + case MV_LION: macCodeName = " Lion"; break; + case MV_MOUNTAINLION:macCodeName = " Mountain Lion"; break; + case MV_MAVERICKS: macCodeName = " Mavericks"; break; + case MV_YOSEMITE: macCodeName = " Yosemite"; break; + } + a_info.m_osType = "Mac OS X" + std::string(macCodeName); +# endif +# elif __ANDROID__ + a_info.m_osType = "Android"; +# elif __linux + // system("lsb_release -sd"); + FILE* f = fopen("/etc/lsb-release", "rt"); + char dist[64] = { 0 }; + if (f) { + char buf[128]; + while (fgets(buf, 128, f) != 0) + if (sscanf(buf, "DISTRIB_DESCRIPTION=%64c", dist) == 1) + break; + if (dist[0]) { + std::vector strBits = split(dist, '"'); + a_info.m_osType = strBits[(strBits.size()==3)?1:0]; + } else { + a_info.m_osType = "non-specific LSB"; + } + fclose(f); + } else { + a_info.m_osType = "non-LSB"; + } +# else + a_info.m_osType = "Generic UNIX"; +# endif +#endif + + // TODO: improve the CPU detection using libcpuid + + // Make human readable RAM size string + const char* humanUnitStrings[] = { " bytes", " KB", " MB", " GB", " TB" }; + int ramUnit = 0; + uint64_t ramSize = a_info.m_ramSize; + while (ramSize > 1024 && ramUnit < (YQ_ARRAY_SIZE(humanUnitStrings)-1)) + ramUnit++, ramSize /= 1000; + a_info.m_ramSizeStr = std::to_string(ramSize) + humanUnitStrings[ramUnit]; + + // Obtain the screen properties + QScreen *s = QApplication::screens()[0]; + int refreshRate = (s) ? s->refreshRate() : 0; + int depth = (s) ? s->depth() : 0; + a_info.m_resolutionWidth = (s) ? s->geometry().width() : 0; + a_info.m_resolutionHeight = (s) ? s->geometry().height() : 0; + a_info.m_resolutionStr = std::to_string(a_info.m_resolutionWidth) + "x" + + std::to_string(a_info.m_resolutionHeight) + " " + + std::to_string(depth) + "bit " + + "@" + std::to_string(refreshRate) + "Hz"; + ; +} + diff --git a/Framework/SystemInfomation.h b/Framework/SystemInfomation.h new file mode 100644 index 0000000..2833313 --- /dev/null +++ b/Framework/SystemInfomation.h @@ -0,0 +1,26 @@ +#ifndef SYSTEM_INFOMATION_H +#define SYSTEM_INFOMATION_H + + +#include "Utilities.h" +#include +#include + + +struct SystemInfo +{ + std::string m_cpuType; + std::string m_osType; + std::string m_osVersion; + std::string m_ramSizeStr; // Human friendly string, eg: "4 GB" + std::string m_resolutionStr; + uint64_t m_ramSize; + uint32_t m_resolutionWidth; + uint32_t m_resolutionHeight; +}; + + +void getSystemInfo(SystemInfo& a_info); + + +#endif // SYSTEM_INFOMATION_H diff --git a/Framework/Tests.cpp b/Framework/Tests.cpp index 046c82c..2a2d78a 100644 --- a/Framework/Tests.cpp +++ b/Framework/Tests.cpp @@ -1,4 +1,5 @@ #include "Utilities.h" +#include "Array.h" #ifdef _MSC_VER diff --git a/Framework/Array.h b/Framework/Array.h new file mode 100644 index 0000000..ab83e77 --- /dev/null +++ b/Framework/Array.h @@ -0,0 +1,98 @@ +#ifndef ARRAY_H +#define ARRAY_H + + +#include "Utilities.h" + + +// std::vector replacement that is cut down but familiar. +// It uses the custom allocators, and is restricted to POD elements only +template +struct PODArrayPOD // A dynamic array of POD type elements which is itself a POD type (so can have multi-dimensional arrays of these). +{ + static_assert(std::is_pod::value, "T must be a POD type."); + + void init() { m_magic = 0x01020304; m_capacity = m_size = 0; m_data = nullptr; } + void deinit() { if (m_data != nullptr) YQ_FREE(m_data); } + + const T& at(size_t a_pos) const { checkInited(); checkIndex(a_pos); return m_data[a_pos]; } + T& at(size_t a_pos) { checkInited(); checkIndex(a_pos); return m_data[a_pos]; } + const T& operator[](size_t a_pos) const { checkInited(); checkIndex(a_pos); return m_data[a_pos]; } + void pop_back() { checkInited(); checkIndex(1); m_size--; } + const T& back() const { checkInited(); return m_data[m_size - 1]; } + T& back() { checkInited(); return m_data[m_size - 1]; } + void push_back(const T& a_val) { checkInited(); if (m_size == m_capacity) ensureSpaceFor(1); m_data[m_size++] = a_val; } + void push(const T& a_val) { checkInited(); push_back(a_val); } + T pop() { checkInited(); pop_back(); return m_data[m_size]; } + void reserve(size_t a_count) { checkInited(); m_capacity = a_count; m_data = YQ_REALLOC_ARRAY(m_data, T, a_count); } + size_t capacity() const { checkInited(); return m_capacity; } + size_t size() const { checkInited(); return m_size; } + T* data() const { checkInited(); return m_data; } + void clear() { checkInited(); m_size = 0; } +//private: // Actually making private would make this no longer a POD type + yqResult checkInited() const { if (BT_IsDebug) YQ_API_PRECONDITION(m_magic == 0x01020304, R_Uninitialized); return R_Okay; } + yqResult checkIndex(size_t a_pos) const { YQ_API_PRECONDITION(a_pos < m_size, R_IndexOutOfRange); return R_Okay; } + void ensureSpaceFor(size_t a_count) { reserve(std::max(m_size + a_count, m_capacity + m_capacity / 2)); } // try to grow by 50% + T* m_data; // Because of this pointer it is not safe to copy DynamicArray objects around. Making it a shared_ptr though stops this being a POD type + size_t m_capacity; + size_t m_size; + uint32_t m_magic; +}; + + +static_assert(std::is_pod >::value, "PODArrayPOD must be a POD type."); + + +// A dynamic array of POD type elements. +// It isn't POD itself, but is instead reference counted so safer to deal with. +template +struct ArrayPOD +{ + static_assert(std::is_pod::value, "T must be a POD type."); + + // Actually not reference counted yet, but safe as objects of this type can't be copied + ArrayPOD& operator=(const ArrayPOD&) = delete; + ArrayPOD(const ArrayPOD&) = delete; + ArrayPOD() { m_capacity = m_size = 0; m_data = nullptr; } + ~ArrayPOD() { if (m_data != nullptr) YQ_FREE(m_data); } + + const T& at(size_t a_pos) const { checkIndex(a_pos); return m_data[a_pos]; } + T& at(size_t a_pos) { checkIndex(a_pos); return m_data[a_pos]; } + const T& operator[](size_t a_pos) const { checkIndex(a_pos); return m_data[a_pos]; } + void pop_back() { checkIndex(1); m_size--; } + const T& back() const { return m_data[m_size - 1]; } + T& back() { return m_data[m_size - 1]; } + void push_back(const T& a_val) { if (m_size == m_capacity) ensureSpaceFor(1); m_data[m_size++] = a_val; } + void push(const T& a_val) { push_back(a_val); } + T pop() { pop_back(); return m_data[m_size]; } + void reserve(size_t a_count) { m_capacity = a_count; m_data = YQ_REALLOC_ARRAY(m_data, T, a_count); } + size_t capacity() const { return m_capacity; } + size_t size() const { return m_size; } + T* data() const { return m_data; } + void clear() { m_size = 0; } +private: + yqResult checkIndex(size_t a_pos) const { YQ_API_PRECONDITION(a_pos < m_size, R_IndexOutOfRange); return R_Okay; } + void ensureSpaceFor(size_t a_count) { reserve(std::max(m_size + a_count, m_capacity + m_capacity / 2)); } // try to grow by 50% + T* m_data; // Because of this pointer it is not safe to copy DynamicArray objects around. Making it a shared_ptr though stops this being a POD type + size_t m_capacity; + size_t m_size; +}; + + +struct PODString +{ +public: + void init() { m_data.init(); } + void deinit() { m_data.deinit(); } + void operator=(const char* a_str) { m_data.clear(); operator+=(a_str); } + void operator+=(const char* a_str) { while (*a_str) m_data.push_back(*a_str++); m_data.push(0); m_data.pop(); } + char* c_str() { return m_data.data(); } +//private: // Actually making private would make this no longer a POD type + PODArrayPOD m_data; +}; + + +static_assert(std::is_pod::value, "PODString must be a POD type."); + + +#endif // ARRAY_H diff --git a/Framework/Macros.h b/Framework/Macros.h new file mode 100644 index 0000000..6ca8c86 --- /dev/null +++ b/Framework/Macros.h @@ -0,0 +1,111 @@ +#ifndef MACROS_H +#define MACROS_H + + +// +// Various common macros +// +#define YQ_ARRAY_SIZE(x) (sizeof(x)/sizeof((x)[0])) + +#define YQ_EXPAND(x) x + +#define YQ_STRINGIZE_IMPL(val) #val +#define YQ_STRINGIZE(val) YQ_STRINGIZE_IMPL(val) + +#define YQ_CONCATENATE_IMPL(x, y) x##y +#define YQ_CONCATENATE(x, y) YQ_CONCATENATE_IMPL(x, y) + +// If 0 args, it mis-reports a count of 1. Also it only counts up to 10 args. +#define YQ_ARG_COUNT_IMPL(X,A,B,C,D,E,F,G,H,I,J,N,...) N +#define YQ_ARG_COUNT(...) (YQ_EXPAND(YQ_ARG_COUNT_IMPL(_,__VA_ARGS__, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0))) + +#ifdef __COUNTER__ +# define YQ_ANONYMOUS_VARIABLE(str) YQ_CONCATENATE(str, __COUNTER__) +#else +# define YQ_ANONYMOUS_VARIABLE(str) YQ_CONCATENATE(str, __LINE__) +#endif + +#define YQ_UNUSED(x) std::ignore = (x) + +#ifdef __MINGW32__ +// Avoid callbacks +# define YQ_CALLBACK __cdecl +# define YQ_FUNCTION_NAME __PRETTY_FUNCTION__ // __func__ +#elif defined(_WIN32) +# define YQ_CALLBACK __cdecl +# define YQ_FUNCTION_NAME __FUNCTION__ +#else +# define YQ_CALLBACK +# define YQ_FUNCTION_NAME __PRETTY_FUNCTION__ // __func__ +#endif + +// Helper marcos for doing a variable number of null checks +#define YQ_NULL_CHECK_IMPL_1(arg) if (arg == nullptr) YQ_API_LEAVE(R_InvalidParameter); +#define YQ_NULL_CHECK_IMPL_2(arg,...) YQ_NULL_CHECK_IMPL_1(arg) YQ_NULL_CHECK_IMPL_1(__VA_ARGS__) +#define YQ_NULL_CHECK_IMPL_3(arg,...) YQ_NULL_CHECK_IMPL_1(arg) YQ_NULL_CHECK_IMPL_2(__VA_ARGS__) +#define YQ_NULL_CHECK_IMPL_4(arg,...) YQ_NULL_CHECK_IMPL_1(arg) YQ_NULL_CHECK_IMPL_3(__VA_ARGS__) +#define YQ_NULL_CHECK_IMPL_5(arg,...) YQ_NULL_CHECK_IMPL_1(arg) YQ_NULL_CHECK_IMPL_4(__VA_ARGS__) +#define YQ_NULL_CHECK_IMPL_N3(N, ...) YQ_NULL_CHECK_IMPL_ ## N(__VA_ARGS__) +#define YQ_NULL_CHECK_IMPL_N2(N, ...) YQ_NULL_CHECK_IMPL_N3(N, __VA_ARGS__) +#define YQ_NULL_CHECK_IMPL_N(...) YQ_NULL_CHECK_IMPL_N2(YQ_ARG_COUNT(__VA_ARGS__), __VA_ARGS__) + +// File name, line numbes, and function name are statically concatenated at compile time, with a reduced string sharing trade off +#define YQ_LOCATION __FILE__ "(" YQ_STRINGIZE(__LINE__) "): " + +// Macros to log messages for debug builds. +// For release builds these will not output anything and no code or data will be generate (except for error logging) +#define YQ_LOG_WARNING(a_fmt, ...) ((BT_IsRelease) ? ((void)0) : yqLogMessage(LL_Warning, YQ_LOCATION, YQ_FUNCTION_NAME, "warning: " a_fmt, ##__VA_ARGS__)) +#define YQ_LOG_DEBUG(a_fmt, ...) ((BT_IsRelease) ? ((void)0) : yqLogMessage(LL_Debug, YQ_LOCATION, YQ_FUNCTION_NAME, "debug: " a_fmt, ##__VA_ARGS__)) +#define YQ_LOG_INFO(a_fmt, ...) ((BT_IsRelease) ? ((void)0) : yqLogMessage(LL_Info, YQ_LOCATION, YQ_FUNCTION_NAME, "info: " a_fmt, ##__VA_ARGS__)) +#define YQ_LOG_ERROR(a_fmt, ...) yqLogMessage(LL_Error, YQ_LOCATION, YQ_FUNCTION_NAME, "error: " a_fmt, ##__VA_ARGS__) + +// Macros that only generate log code in verbose debug builds. The logging logs the entry to API calls and the exit from them and what return code is being returned. +// In debug and release, on errors, the code always calls the installed or default error handling routine +enum { YQ_VERBOSE_DEBUGGING = 0 }; +#define YQ_API_VERBOSE(msg) ((YQ_VERBOSE_DEBUGGING) ? YQ_LOG_DEBUG(msg) : ((void)0)) +#define YQ_API_ERROR(result, ...) (yqHandleError(result, YQ_LOCATION, YQ_FUNCTION_NAME, __VA_ARGS__), YQ_LOG_ERROR("Leave: " #result " (%i)", result), result) +#define YQ_API_ENTER() YQ_API_VERBOSE("Enter") +#define YQ_API_LEAVE(result, ...) return ((result == R_Okay) ? (YQ_API_VERBOSE("Leave"), result) : YQ_API_ERROR(result, "" __VA_ARGS__)) + +// Macros to do common parameter and precondition checking for API function calls +// This macro checks the pre-condition is met, if it doesn't then returns the error result and executes the provided cleanup code +// See also 'finally'. Probably during pre-condition checking, there shouldn't be any cleanup yet as no work should be done at this stage. +//#define YQ_API_PRECONDITION_CLEANUP(condition,result,cleanup) do { if (!(condition)) { cleanup; YQ_API_LEAVE(result); } } while (0,0) +#define YQ_API_PRECONDITION(condition,result) do { if (!(condition)) { YQ_API_LEAVE(result, #condition); } } while (0,0)//YQ_API_PRECONDITION_CLEANUP(condition,result, ((void)0)) +#define YQ_API_PRECONDITION_NULL(...) YQ_NULL_CHECK_IMPL_N(__VA_ARGS__) + +// Can redirect to the user heap manager routines if provided +#define YQ_ALLOC(type) ((type*)(yqAllocateMemory(sizeof(type)))) +#define YQ_ALLOC_ARRAY(type,siz) ((type*)(yqAllocateMemory(sizeof(type)*(siz)))) +#define YQ_REALLOC_ARRAY(ptr,type,siz) ((type*)(yqReallocateMemory(ptr,sizeof(type)*(siz)))) +#define YQ_FREE(ptr) (((ptr)) ? (void)(yqFreeMemory((void*)(ptr)), (ptr) = nullptr) : (void)0) + +// C++ wrappers for using the custom allocators to replace use of new / delete (does placement new with custom allocated memory) +// Note: The custom allocators are expected to return null if the allocation fails and not throw. The macros below do not check this. +#define YQ_NEW(type) (new(YQ_ALLOC(type)) type) +#define YQ_NEW_PARAMS(type, params) (new(YQ_ALLOC(type)) type params) +#define YQ_DELETE(obj,type) ((obj) ? (void)((obj)->~type(), YQ_FREE(obj)) : (void)0) + + +/* + +Example usage: + +Result SomeFunction(T1 **Param1, T2 *Param2, T3 *Param3, int Param4) +{ + YQ_API_ENTER(); + + // Check pre-conditions + YQ_API_PRECONDITION_NULL(Param1, Param2, Param3); + YQ_API_PRECONDITION(Param4 == 42, R_InvalidParameter); + + if (errorHappened) + YQ_API_LEAVE(R_Failure); + + YQ_API_LEAVE(R_Okay); +} + +*/ + + +#endif // MACROS_H diff --git a/Framework/NonCopyable.h b/Framework/NonCopyable.h index 3881321..b8cb274 100644 --- a/Framework/NonCopyable.h +++ b/Framework/NonCopyable.h @@ -3,6 +3,62 @@ /// XXX: Not sure but maybe there is a C++11 std::non_copyable that does the same as this +/// Actually C++11 has the additional ability to add '= delete' to a member to say it can be used. + +#ifdef HAVE_CPP11 +# define DELETE = delete +#else +# define DELETE +#endif + + +/// This way has the advantage of not adding to the size of the base class if that is important [1] +/// [1] Ref: http://trac.webkit.org/changeset/68414 +#define MAKE_NONCOPYABLE(ClassName) \ + private: \ + ClassName(const ClassName&) DELETE; \ + ClassName& operator=(const ClassName&) DELETE; \ + + +/* Utility classes */ +struct NoCopy +{ +public: + NoCopy() {} +private: + NoCopy(const NoCopy &) DELETE; +}; + +struct NoAssign +{ +private: + NoAssign &operator=(const NoAssign &) DELETE; +}; + +struct NonInstantiable +{ +private: + NonInstantiable() DELETE; +}; + +struct NoCopyAssign : NoCopy, NoAssign +{ +}; + +typedef NoCopyAssign NoAssignCopy; + +// Not tested +struct NoMove +{ +public: + NoMove() {} +private: + NoMove(const NoMove &&) DELETE; +}; + +struct NoMoveCopyAssign : NoMove, NoCopy, NoAssign +{ +}; /// @@ -10,9 +66,9 @@ /// /// Making a class NonCopyable means preventing objects of the class being copied. This is useful if /// the class manages a resource that can only be held by one object such that it can't be reference -/// counted, or it is the underlying object that becomes reference counted. +/// counted, or it is the underlying object that becomes reference counted. Idea from here [1]. /// -/// Idea comes from here: http://stackoverflow.com/questions/9458741/with-explicitly-deleted-member-functions-in-c11-is-it-still-worthwhile-to-inh +/// [1] Ref: http://stackoverflow.com/questions/9458741/with-explicitly-deleted-member-functions-in-c11-is-it-still-worthwhile-to-inh /// class NonCopyable { @@ -34,12 +90,19 @@ MyNonCopyableClass() {} }; + MyNonCopyableClass create() + { + MyNonCopyableClass var; + return var; + } + Now it is not possible to copy objects of this type without a compiler error: - void function() + void function() { - MyNonCopyableClass obj1; - MyNonCopyableClass obj2; + MyNonCopyableClass obj1; + MyNonCopyableClass obj2; + MyNonCopyableClass obj3 = create(); // Allowed due to move semantics in c++11, use NoMoveCopyAssign to prevent // These attempts to make a copy of obj1 will cause a compiler error MyNonCopyableClass obj3(obj1); diff --git a/Framework/ObjectFilter.cpp b/Framework/ObjectFilter.cpp new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/Framework/ObjectFilter.cpp diff --git a/Framework/ObjectInspector.cpp b/Framework/ObjectInspector.cpp new file mode 100644 index 0000000..91b6726 --- /dev/null +++ b/Framework/ObjectInspector.cpp @@ -0,0 +1,90 @@ +#include "ObjectInspector.h" + +#include "ObjectPropertyModel.h" +#include "lib/ObjectProxy.h" + +#include +#include +#include +#include +#include +#include + +ObjectInspector::ObjectInspector(QWidget* parent) +: QWidget(parent) +{ + QVBoxLayout* layout = new QVBoxLayout(this); + layout->setMargin(0); + + m_nameLabel = new QLabel(this); + layout->addWidget(m_nameLabel); + + m_propertyView = new QTableView(this); + m_propertyView->verticalHeader()->setVisible(false); + m_propertyView->horizontalHeader()->setStretchLastSection(true); + + QHBoxLayout* filterLayout = new QHBoxLayout; + QLabel* filterLabel = new QLabel(tr("Filter:"),this); + m_propertyFilterEdit = new QLineEdit(this); + m_propertyFilterEdit->setToolTip(tr("String or regular expression to filter property list with")); + + connect(m_propertyFilterEdit,SIGNAL(textChanged(QString)), + this,SLOT(changeFilter(QString))); + filterLayout->addWidget(filterLabel); + filterLayout->addWidget(m_propertyFilterEdit); + + m_model = new ObjectPropertyModel(this); + m_propertySortModel = new QSortFilterProxyModel(this); + m_propertySortModel->setSourceModel(m_model); + + m_propertyView->setModel(m_propertySortModel); + + layout->addWidget(m_propertyView); + layout->addLayout(filterLayout); + + setObject(ObjectProxy::Pointer()); +} + +QString ObjectInspector::formatAddress(void* ptr) +{ + quintptr intPtr = reinterpret_cast(ptr); + return QString("0x%1").arg(QString(QByteArray::number(intPtr,16))); +} + +void ObjectInspector::refresh() +{ + setObject(m_currentObject); +} + +void ObjectInspector::setObject(ObjectProxy::Pointer object) +{ + QString labelText; + + if (object) + { + labelText = QString("%1 [%2] (%3)") + .arg(object->className()) + .arg(object->objectName()) + .arg(formatAddress(reinterpret_cast(object->address()))); + } + else + { + labelText = tr("[No Object Selected]"); + } + + m_nameLabel->setText(labelText); + m_currentObject = object; + m_model->setObject(object); + m_propertySortModel->sort(0,Qt::AscendingOrder); +} + +ObjectProxy::Pointer ObjectInspector::object() const +{ + return m_currentObject; +} + +void ObjectInspector::changeFilter(const QString& pattern) +{ + m_propertySortModel->setFilterRegExp(QRegExp(pattern)); +} + diff --git a/Framework/ObjectInspector.h b/Framework/ObjectInspector.h new file mode 100644 index 0000000..e1ce3ac --- /dev/null +++ b/Framework/ObjectInspector.h @@ -0,0 +1,40 @@ +#pragma once + +#include "lib/ObjectProxy.h" + +#include + +class QLabel; +class QLineEdit; +class QTableView; +class QSortFilterProxyModel; + +class ObjectPropertyModel; + +class ObjectInspector : public QWidget +{ + Q_OBJECT + + public: + ObjectInspector(QWidget* parent); + + void setObject(ObjectProxy::Pointer object); + ObjectProxy::Pointer object() const; + + static QString formatAddress(void* ptr); + + public Q_SLOTS: + void refresh(); + + private Q_SLOTS: + void changeFilter(const QString& pattern); + + private: + QLabel* m_nameLabel; + QTableView* m_propertyView; + ObjectProxy::Pointer m_currentObject; + ObjectPropertyModel* m_model; + QSortFilterProxyModel* m_propertySortModel; + QLineEdit* m_propertyFilterEdit; +}; + diff --git a/Framework/ObjectPropertyModel.cpp b/Framework/ObjectPropertyModel.cpp new file mode 100644 index 0000000..6e4d4f7 --- /dev/null +++ b/Framework/ObjectPropertyModel.cpp @@ -0,0 +1,91 @@ +#include "ObjectPropertyModel.h" + +#include "lib/ObjectProxy.h" + +#include +#include + +ObjectPropertyModel::ObjectPropertyModel(QObject* parent) +: QStandardItemModel(parent) +{ + setColumnCount(2); + setObject(ObjectProxy::Pointer()); + + connect(this,SIGNAL(itemChanged(QStandardItem*)), + this,SLOT(updatePropertyValue(QStandardItem*))); +} + +QVariant ObjectPropertyModel::toEditValue(const QVariant& value) +{ + // TODO - Find existing code which can handle display and editing + // of common value types. + switch (value.userType()) + { + case QVariant::Size: + case QVariant::SizeF: + { + QSize size = value.toSize(); + return QString("%1 x %2").arg(size.width()).arg(size.height()); + } + case QVariant::Rect: + case QVariant::RectF: + { + QRect rect = value.toRect(); + return QString("(%1,%2) (%3,%4)").arg(rect.left()).arg(rect.top()).arg(rect.width()).arg(rect.height()); + } + } + return value; +} + +void ObjectPropertyModel::updatePropertyValue(QStandardItem* item) +{ + QStandardItem* propertyItem = this->item(item->row(),0); + QString propertyName = propertyItem->text(); + + if (m_object) + { + QVariant newValue = item->data(Qt::EditRole); + qDebug() << "setting property" << propertyName << "to" << newValue; + m_object->writeProperty(propertyName,newValue); + } +} + +void ObjectPropertyModel::setObject(ObjectProxy::Pointer object) +{ + clear(); + + setHorizontalHeaderLabels( + QStringList() << tr("Property") << tr("Value") + ); + + m_object = object; + + if (object) + { + object->refresh(); + int rowCount = 0; + QListIterator iter(object->properties()); + while (iter.hasNext()) + { + ObjectProxy::Property property = iter.next(); + QVariant value = property.value; + QVariant editValue = toEditValue(value); + + QStandardItem* nameItem = new QStandardItem(property.name); + nameItem->setEditable(false); + + QStandardItem* valueItem = new QStandardItem; + valueItem->setData(editValue,Qt::EditRole); + valueItem->setEditable(property.isWritable); + + insertRow(rowCount,QList() << nameItem << valueItem); + ++rowCount; + } + } +} + +ObjectProxy::Pointer ObjectPropertyModel::object() const +{ + return m_object; +} + diff --git a/Framework/ObjectPropertyModel.h b/Framework/ObjectPropertyModel.h new file mode 100644 index 0000000..69e176c --- /dev/null +++ b/Framework/ObjectPropertyModel.h @@ -0,0 +1,27 @@ +#pragma once + +#include "lib/ObjectProxy.h" + +#include +#include + +class ObjectPropertyModel : public QStandardItemModel +{ + Q_OBJECT + + public: + ObjectPropertyModel(QObject* parent); + + void setObject(ObjectProxy::Pointer object); + ObjectProxy::Pointer object() const; + + static QVariant toEditValue(const QVariant& value); + + private Q_SLOTS: + void updatePropertyValue(QStandardItem* item); + + private: + ObjectProxy::Pointer m_object; +}; + + diff --git a/Framework/ObjectTreeModel.cpp b/Framework/ObjectTreeModel.cpp new file mode 100644 index 0000000..5d7bbfc --- /dev/null +++ b/Framework/ObjectTreeModel.cpp @@ -0,0 +1,135 @@ +#include "ObjectTreeModel.h" +#include + + +ObjectTreeModel::ObjectTreeModel(QObject* parent) +: QAbstractItemModel(parent) +{ +} + +int ObjectTreeModel::columnCount(const QModelIndex&) const +{ + return 1; +} + +int ObjectTreeModel::rowCount(const QModelIndex& parent) const +{ + QObject* obj = static_cast(parent.internalPointer()); + return (obj) ? obj->children().count() : m_roots.count(); +} + +void ObjectTreeModel::setRootObjects(const QList& roots) +{ + beginResetModel(); + m_roots = roots; + endResetModel(); +} + +QModelIndex ObjectTreeModel::index(int row, int column, const QModelIndex& parent) const +{ + QObject* parentItem = static_cast(parent.internalPointer()); + const QObjectList& children = (parentItem) ? parentItem->children() : m_roots; + bool outOfBounds = (row < 0 || column != 0 || row >= children.count()); + return outOfBounds ? QModelIndex() : createIndex(row,column,children.at(row)); +} + +int ObjectTreeModel::indexInParent(QObject* item) const +{ + QObject* parent = item->parent(); + return (parent && !m_roots.contains(item)) ? parent->children().indexOf(item) : m_roots.indexOf(item); +} + +QModelIndex ObjectTreeModel::parent(const QModelIndex& index) const +{ + QObject* item = static_cast(index.internalPointer()); + QObject* parent = (item && !m_roots.contains(item)) ? item->parent() : 0; + return (parent) ? createIndex(indexInParent(parent),index.column(),parent) : QModelIndex(); +} + +QVariant ObjectTreeModel::data(const QModelIndex& index, int role) const +{ + QObject* item = static_cast(index.internalPointer()); + return (role == Qt::DisplayRole) ? ((item) ? displayText(item) : "[deleted]") : QVariant(); +} + +QString ObjectTreeModel::displayText(QObject* object) const +{ + QString text = object->metaObject()->className(); + if (!object->objectName().isEmpty()) + { + text += QString("[%1]").arg(object->objectName()); + } + return text; +} + +QVariant ObjectTreeModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (section == 0 && orientation == Qt::Horizontal && role == Qt::DisplayRole) + { + return tr("Object"); + } + return QAbstractItemModel::headerData(section,orientation,role); +} + +QObject* ObjectTreeModel::index(ObjPtr object, const QList& items) const +{ + //inline QList findChildren(const QRegularExpression &re, Qt::FindChildOptions options = Qt::FindChildrenRecursively) const + Q_FOREACH(QObject* item, items) + { + if (item == object) + return item; + QObject* match = index(object, item->children()); + if (match) + return match; + } + return 0; +} + +QModelIndex ObjectTreeModel::index(ObjPtr object) const +{ + QObject* item = index(object, m_roots); + return (item) ? createIndex(indexInParent(item),0,item) : QModelIndex(); +} + + + + +/* +bool ObjectTreeModel::matches(ObjPtr object, const QString& query) const +{ + return displayText(object).contains(query,Qt::CaseInsensitive); +} + +void ObjectTreeModel::search(QList* hits, ObjectItem* item, const QString& query) const +{ + ObjPtr object = item;//->object; + if (!object) + { + return; + } + if (matches(object,query)) + { + *hits << object; + } + Q_FOREACH(ObjectItem* child, item->children()) + { + search(hits,child,query); + } +} + +QList ObjectTreeModel::search(const QString& query) const +{ + if (query.isEmpty()) + { + return QList(); + } + + QList matches; + Q_FOREACH(ObjectItem* item, m_roots) + { + search(&matches,item,query); + } + return matches; +} + +*/ diff --git a/Framework/ObjectTreeModel.h b/Framework/ObjectTreeModel.h new file mode 100644 index 0000000..4b50d8a --- /dev/null +++ b/Framework/ObjectTreeModel.h @@ -0,0 +1,47 @@ +#pragma once + +#include +#include +#include + + +class TrackedObject +{ +public: + TrackedObject() {} + TrackedObject(QObject* a_obj) : m_object(a_obj) {} + operator bool() { return !m_object.isNull(); } + operator QObject*() { return m_object.data(); } +private: + QPointer m_object; +}; + + +typedef QObject* ObjPtr; +//typedef TrackedObject ObjPtr; + + +class ObjectTreeModel : public QAbstractItemModel +{ + Q_OBJECT +public: + ObjectTreeModel(QObject* parent); + //void setFilter(ObjectFilter *filter); + //QList search(const QString& query) const; + void setRootObjects(const QList& roots); + QModelIndex index(ObjPtr object) const; + virtual QVariant headerData(int section, Qt::Orientation orientation, int role) const; + virtual int columnCount(const QModelIndex& parent) const; + virtual int rowCount(const QModelIndex& parent) const; + virtual QModelIndex index(int row, int column, const QModelIndex& parent) const; + virtual QModelIndex parent(const QModelIndex& index) const; + virtual QVariant data(const QModelIndex& index, int role) const; +private: + int indexInParent(QObject* item) const; + QObject* index(ObjPtr object, const QList& items) const; + //bool matches(ObjPtr object, const QString& query) const; + //void search(QList* matches, ObjectItem* item, const QString& query) const; + QString displayText(QObject* object) const; + QList m_roots; +}; + diff --git a/Framework/SystemInfomation.cpp b/Framework/SystemInfomation.cpp new file mode 100644 index 0000000..3f09248 --- /dev/null +++ b/Framework/SystemInfomation.cpp @@ -0,0 +1,132 @@ +#include "SystemInfomation.h" +#include "../Util.h" +#include +#include +#include + + +#ifdef __APPLE__ +# include "TargetConditionals.h" +#elif defined(_WIN32) +# include +#else +# include +# include +# include +# include +#endif + + +void getSystemInfo(SystemInfo& a_info) +{ + a_info.m_cpuType = "unknown"; + a_info.m_osType = "unknown"; + a_info.m_osVersion = "unknown"; + +#ifdef _WIN32 + a_info.m_osType = "Windows"; + + // Get RAM size + MEMORYSTATUSEX status; + status.dwLength = sizeof(status); + GlobalMemoryStatusEx(&status); + a_info.m_ramSize = status.ullTotalPhys; + + // Get OS version + const OSVERSIONINFO osver = winOsVersion(); + a_info.m_osVersion.reserve(512); + snprintf(a_info.m_osVersion.data(), "%i.%i.%i", osver.dwMajorVersion, osver.dwMinorVersion, osver.dwBuildNumber); + + // Get OS arch + SYSTEM_INFO info; + GetNativeSystemInfo(&info); +# ifdef _WIN64 + //if (info.wProcessorArchitecture == ???) + a_info.m_cpuType = "x86_64"; +# else + a_info.m_cpuType = "x86"; +# endif +#else + long pages = sysconf(_SC_PHYS_PAGES); + long page_size = sysconf(_SC_PAGE_SIZE); + a_info.m_ramSize = pages * page_size; + struct utsname u; + long ret = uname(&u); + if (ret != -1) + { + a_info.m_cpuType = u.machine; + a_info.m_osVersion = u.sysname + std::string(" ") + u.release; + } +# ifdef __APPLE__ + // Get RAM Size + int mib[2] = { CTL_HW, HW_MEMSIZE }; + size_t len = sizeof(uint64_t); + sysctl(mib, sizeof(mib) / sizeof(mib[0]), &u.ramSize, &len, NULL, 0); +# if TARGET_OS_IPHONE + a_info.m_osType = "iOS"; +# elif TARGET_OS_MAC + const char *macCodeName = ""; + switch (int(MacintoshVersion)) { + case MV_CHEETAH: macCodeName = " Cheetah"; break; + case MV_PUMA: macCodeName = " Puma"; break; + case MV_JAGUAR: macCodeName = " Jaguar"; break; + case MV_PANTHER: macCodeName = " Panther"; break; + case MV_TIGER: macCodeName = " Tiger"; break; + case MV_LEOPARD: macCodeName = " Leopard"; break; + case MV_SNOWLEOPARD: macCodeName = " Snow Leopard"; break; + case MV_LION: macCodeName = " Lion"; break; + case MV_MOUNTAINLION:macCodeName = " Mountain Lion"; break; + case MV_MAVERICKS: macCodeName = " Mavericks"; break; + case MV_YOSEMITE: macCodeName = " Yosemite"; break; + } + a_info.m_osType = "Mac OS X" + std::string(macCodeName); +# endif +# elif __ANDROID__ + a_info.m_osType = "Android"; +# elif __linux + // system("lsb_release -sd"); + FILE* f = fopen("/etc/lsb-release", "rt"); + char dist[64] = { 0 }; + if (f) { + char buf[128]; + while (fgets(buf, 128, f) != 0) + if (sscanf(buf, "DISTRIB_DESCRIPTION=%64c", dist) == 1) + break; + if (dist[0]) { + std::vector strBits = split(dist, '"'); + a_info.m_osType = strBits[(strBits.size()==3)?1:0]; + } else { + a_info.m_osType = "non-specific LSB"; + } + fclose(f); + } else { + a_info.m_osType = "non-LSB"; + } +# else + a_info.m_osType = "Generic UNIX"; +# endif +#endif + + // TODO: improve the CPU detection using libcpuid + + // Make human readable RAM size string + const char* humanUnitStrings[] = { " bytes", " KB", " MB", " GB", " TB" }; + int ramUnit = 0; + uint64_t ramSize = a_info.m_ramSize; + while (ramSize > 1024 && ramUnit < (YQ_ARRAY_SIZE(humanUnitStrings)-1)) + ramUnit++, ramSize /= 1000; + a_info.m_ramSizeStr = std::to_string(ramSize) + humanUnitStrings[ramUnit]; + + // Obtain the screen properties + QScreen *s = QApplication::screens()[0]; + int refreshRate = (s) ? s->refreshRate() : 0; + int depth = (s) ? s->depth() : 0; + a_info.m_resolutionWidth = (s) ? s->geometry().width() : 0; + a_info.m_resolutionHeight = (s) ? s->geometry().height() : 0; + a_info.m_resolutionStr = std::to_string(a_info.m_resolutionWidth) + "x" + + std::to_string(a_info.m_resolutionHeight) + " " + + std::to_string(depth) + "bit " + + "@" + std::to_string(refreshRate) + "Hz"; + ; +} + diff --git a/Framework/SystemInfomation.h b/Framework/SystemInfomation.h new file mode 100644 index 0000000..2833313 --- /dev/null +++ b/Framework/SystemInfomation.h @@ -0,0 +1,26 @@ +#ifndef SYSTEM_INFOMATION_H +#define SYSTEM_INFOMATION_H + + +#include "Utilities.h" +#include +#include + + +struct SystemInfo +{ + std::string m_cpuType; + std::string m_osType; + std::string m_osVersion; + std::string m_ramSizeStr; // Human friendly string, eg: "4 GB" + std::string m_resolutionStr; + uint64_t m_ramSize; + uint32_t m_resolutionWidth; + uint32_t m_resolutionHeight; +}; + + +void getSystemInfo(SystemInfo& a_info); + + +#endif // SYSTEM_INFOMATION_H diff --git a/Framework/Tests.cpp b/Framework/Tests.cpp index 046c82c..2a2d78a 100644 --- a/Framework/Tests.cpp +++ b/Framework/Tests.cpp @@ -1,4 +1,5 @@ #include "Utilities.h" +#include "Array.h" #ifdef _MSC_VER diff --git a/Framework/Utilities.cpp b/Framework/Utilities.cpp index 8dbccf9..1e6acb9 100644 --- a/Framework/Utilities.cpp +++ b/Framework/Utilities.cpp @@ -11,7 +11,7 @@ static ResourceDeviceFactory s_defaultDeviceFactory; -Module g_currentModule{ false, &s_defaultErrorHandler, &s_defaultMessageLogger, &s_defaultHeapManager, &s_defaultDeviceFactory }; +Module g_currentModule{ false, "Main Module", &s_defaultErrorHandler, &s_defaultMessageLogger, &s_defaultHeapManager, &s_defaultDeviceFactory }; void yqDebugBreak() diff --git a/Framework/Array.h b/Framework/Array.h new file mode 100644 index 0000000..ab83e77 --- /dev/null +++ b/Framework/Array.h @@ -0,0 +1,98 @@ +#ifndef ARRAY_H +#define ARRAY_H + + +#include "Utilities.h" + + +// std::vector replacement that is cut down but familiar. +// It uses the custom allocators, and is restricted to POD elements only +template +struct PODArrayPOD // A dynamic array of POD type elements which is itself a POD type (so can have multi-dimensional arrays of these). +{ + static_assert(std::is_pod::value, "T must be a POD type."); + + void init() { m_magic = 0x01020304; m_capacity = m_size = 0; m_data = nullptr; } + void deinit() { if (m_data != nullptr) YQ_FREE(m_data); } + + const T& at(size_t a_pos) const { checkInited(); checkIndex(a_pos); return m_data[a_pos]; } + T& at(size_t a_pos) { checkInited(); checkIndex(a_pos); return m_data[a_pos]; } + const T& operator[](size_t a_pos) const { checkInited(); checkIndex(a_pos); return m_data[a_pos]; } + void pop_back() { checkInited(); checkIndex(1); m_size--; } + const T& back() const { checkInited(); return m_data[m_size - 1]; } + T& back() { checkInited(); return m_data[m_size - 1]; } + void push_back(const T& a_val) { checkInited(); if (m_size == m_capacity) ensureSpaceFor(1); m_data[m_size++] = a_val; } + void push(const T& a_val) { checkInited(); push_back(a_val); } + T pop() { checkInited(); pop_back(); return m_data[m_size]; } + void reserve(size_t a_count) { checkInited(); m_capacity = a_count; m_data = YQ_REALLOC_ARRAY(m_data, T, a_count); } + size_t capacity() const { checkInited(); return m_capacity; } + size_t size() const { checkInited(); return m_size; } + T* data() const { checkInited(); return m_data; } + void clear() { checkInited(); m_size = 0; } +//private: // Actually making private would make this no longer a POD type + yqResult checkInited() const { if (BT_IsDebug) YQ_API_PRECONDITION(m_magic == 0x01020304, R_Uninitialized); return R_Okay; } + yqResult checkIndex(size_t a_pos) const { YQ_API_PRECONDITION(a_pos < m_size, R_IndexOutOfRange); return R_Okay; } + void ensureSpaceFor(size_t a_count) { reserve(std::max(m_size + a_count, m_capacity + m_capacity / 2)); } // try to grow by 50% + T* m_data; // Because of this pointer it is not safe to copy DynamicArray objects around. Making it a shared_ptr though stops this being a POD type + size_t m_capacity; + size_t m_size; + uint32_t m_magic; +}; + + +static_assert(std::is_pod >::value, "PODArrayPOD must be a POD type."); + + +// A dynamic array of POD type elements. +// It isn't POD itself, but is instead reference counted so safer to deal with. +template +struct ArrayPOD +{ + static_assert(std::is_pod::value, "T must be a POD type."); + + // Actually not reference counted yet, but safe as objects of this type can't be copied + ArrayPOD& operator=(const ArrayPOD&) = delete; + ArrayPOD(const ArrayPOD&) = delete; + ArrayPOD() { m_capacity = m_size = 0; m_data = nullptr; } + ~ArrayPOD() { if (m_data != nullptr) YQ_FREE(m_data); } + + const T& at(size_t a_pos) const { checkIndex(a_pos); return m_data[a_pos]; } + T& at(size_t a_pos) { checkIndex(a_pos); return m_data[a_pos]; } + const T& operator[](size_t a_pos) const { checkIndex(a_pos); return m_data[a_pos]; } + void pop_back() { checkIndex(1); m_size--; } + const T& back() const { return m_data[m_size - 1]; } + T& back() { return m_data[m_size - 1]; } + void push_back(const T& a_val) { if (m_size == m_capacity) ensureSpaceFor(1); m_data[m_size++] = a_val; } + void push(const T& a_val) { push_back(a_val); } + T pop() { pop_back(); return m_data[m_size]; } + void reserve(size_t a_count) { m_capacity = a_count; m_data = YQ_REALLOC_ARRAY(m_data, T, a_count); } + size_t capacity() const { return m_capacity; } + size_t size() const { return m_size; } + T* data() const { return m_data; } + void clear() { m_size = 0; } +private: + yqResult checkIndex(size_t a_pos) const { YQ_API_PRECONDITION(a_pos < m_size, R_IndexOutOfRange); return R_Okay; } + void ensureSpaceFor(size_t a_count) { reserve(std::max(m_size + a_count, m_capacity + m_capacity / 2)); } // try to grow by 50% + T* m_data; // Because of this pointer it is not safe to copy DynamicArray objects around. Making it a shared_ptr though stops this being a POD type + size_t m_capacity; + size_t m_size; +}; + + +struct PODString +{ +public: + void init() { m_data.init(); } + void deinit() { m_data.deinit(); } + void operator=(const char* a_str) { m_data.clear(); operator+=(a_str); } + void operator+=(const char* a_str) { while (*a_str) m_data.push_back(*a_str++); m_data.push(0); m_data.pop(); } + char* c_str() { return m_data.data(); } +//private: // Actually making private would make this no longer a POD type + PODArrayPOD m_data; +}; + + +static_assert(std::is_pod::value, "PODString must be a POD type."); + + +#endif // ARRAY_H diff --git a/Framework/Macros.h b/Framework/Macros.h new file mode 100644 index 0000000..6ca8c86 --- /dev/null +++ b/Framework/Macros.h @@ -0,0 +1,111 @@ +#ifndef MACROS_H +#define MACROS_H + + +// +// Various common macros +// +#define YQ_ARRAY_SIZE(x) (sizeof(x)/sizeof((x)[0])) + +#define YQ_EXPAND(x) x + +#define YQ_STRINGIZE_IMPL(val) #val +#define YQ_STRINGIZE(val) YQ_STRINGIZE_IMPL(val) + +#define YQ_CONCATENATE_IMPL(x, y) x##y +#define YQ_CONCATENATE(x, y) YQ_CONCATENATE_IMPL(x, y) + +// If 0 args, it mis-reports a count of 1. Also it only counts up to 10 args. +#define YQ_ARG_COUNT_IMPL(X,A,B,C,D,E,F,G,H,I,J,N,...) N +#define YQ_ARG_COUNT(...) (YQ_EXPAND(YQ_ARG_COUNT_IMPL(_,__VA_ARGS__, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0))) + +#ifdef __COUNTER__ +# define YQ_ANONYMOUS_VARIABLE(str) YQ_CONCATENATE(str, __COUNTER__) +#else +# define YQ_ANONYMOUS_VARIABLE(str) YQ_CONCATENATE(str, __LINE__) +#endif + +#define YQ_UNUSED(x) std::ignore = (x) + +#ifdef __MINGW32__ +// Avoid callbacks +# define YQ_CALLBACK __cdecl +# define YQ_FUNCTION_NAME __PRETTY_FUNCTION__ // __func__ +#elif defined(_WIN32) +# define YQ_CALLBACK __cdecl +# define YQ_FUNCTION_NAME __FUNCTION__ +#else +# define YQ_CALLBACK +# define YQ_FUNCTION_NAME __PRETTY_FUNCTION__ // __func__ +#endif + +// Helper marcos for doing a variable number of null checks +#define YQ_NULL_CHECK_IMPL_1(arg) if (arg == nullptr) YQ_API_LEAVE(R_InvalidParameter); +#define YQ_NULL_CHECK_IMPL_2(arg,...) YQ_NULL_CHECK_IMPL_1(arg) YQ_NULL_CHECK_IMPL_1(__VA_ARGS__) +#define YQ_NULL_CHECK_IMPL_3(arg,...) YQ_NULL_CHECK_IMPL_1(arg) YQ_NULL_CHECK_IMPL_2(__VA_ARGS__) +#define YQ_NULL_CHECK_IMPL_4(arg,...) YQ_NULL_CHECK_IMPL_1(arg) YQ_NULL_CHECK_IMPL_3(__VA_ARGS__) +#define YQ_NULL_CHECK_IMPL_5(arg,...) YQ_NULL_CHECK_IMPL_1(arg) YQ_NULL_CHECK_IMPL_4(__VA_ARGS__) +#define YQ_NULL_CHECK_IMPL_N3(N, ...) YQ_NULL_CHECK_IMPL_ ## N(__VA_ARGS__) +#define YQ_NULL_CHECK_IMPL_N2(N, ...) YQ_NULL_CHECK_IMPL_N3(N, __VA_ARGS__) +#define YQ_NULL_CHECK_IMPL_N(...) YQ_NULL_CHECK_IMPL_N2(YQ_ARG_COUNT(__VA_ARGS__), __VA_ARGS__) + +// File name, line numbes, and function name are statically concatenated at compile time, with a reduced string sharing trade off +#define YQ_LOCATION __FILE__ "(" YQ_STRINGIZE(__LINE__) "): " + +// Macros to log messages for debug builds. +// For release builds these will not output anything and no code or data will be generate (except for error logging) +#define YQ_LOG_WARNING(a_fmt, ...) ((BT_IsRelease) ? ((void)0) : yqLogMessage(LL_Warning, YQ_LOCATION, YQ_FUNCTION_NAME, "warning: " a_fmt, ##__VA_ARGS__)) +#define YQ_LOG_DEBUG(a_fmt, ...) ((BT_IsRelease) ? ((void)0) : yqLogMessage(LL_Debug, YQ_LOCATION, YQ_FUNCTION_NAME, "debug: " a_fmt, ##__VA_ARGS__)) +#define YQ_LOG_INFO(a_fmt, ...) ((BT_IsRelease) ? ((void)0) : yqLogMessage(LL_Info, YQ_LOCATION, YQ_FUNCTION_NAME, "info: " a_fmt, ##__VA_ARGS__)) +#define YQ_LOG_ERROR(a_fmt, ...) yqLogMessage(LL_Error, YQ_LOCATION, YQ_FUNCTION_NAME, "error: " a_fmt, ##__VA_ARGS__) + +// Macros that only generate log code in verbose debug builds. The logging logs the entry to API calls and the exit from them and what return code is being returned. +// In debug and release, on errors, the code always calls the installed or default error handling routine +enum { YQ_VERBOSE_DEBUGGING = 0 }; +#define YQ_API_VERBOSE(msg) ((YQ_VERBOSE_DEBUGGING) ? YQ_LOG_DEBUG(msg) : ((void)0)) +#define YQ_API_ERROR(result, ...) (yqHandleError(result, YQ_LOCATION, YQ_FUNCTION_NAME, __VA_ARGS__), YQ_LOG_ERROR("Leave: " #result " (%i)", result), result) +#define YQ_API_ENTER() YQ_API_VERBOSE("Enter") +#define YQ_API_LEAVE(result, ...) return ((result == R_Okay) ? (YQ_API_VERBOSE("Leave"), result) : YQ_API_ERROR(result, "" __VA_ARGS__)) + +// Macros to do common parameter and precondition checking for API function calls +// This macro checks the pre-condition is met, if it doesn't then returns the error result and executes the provided cleanup code +// See also 'finally'. Probably during pre-condition checking, there shouldn't be any cleanup yet as no work should be done at this stage. +//#define YQ_API_PRECONDITION_CLEANUP(condition,result,cleanup) do { if (!(condition)) { cleanup; YQ_API_LEAVE(result); } } while (0,0) +#define YQ_API_PRECONDITION(condition,result) do { if (!(condition)) { YQ_API_LEAVE(result, #condition); } } while (0,0)//YQ_API_PRECONDITION_CLEANUP(condition,result, ((void)0)) +#define YQ_API_PRECONDITION_NULL(...) YQ_NULL_CHECK_IMPL_N(__VA_ARGS__) + +// Can redirect to the user heap manager routines if provided +#define YQ_ALLOC(type) ((type*)(yqAllocateMemory(sizeof(type)))) +#define YQ_ALLOC_ARRAY(type,siz) ((type*)(yqAllocateMemory(sizeof(type)*(siz)))) +#define YQ_REALLOC_ARRAY(ptr,type,siz) ((type*)(yqReallocateMemory(ptr,sizeof(type)*(siz)))) +#define YQ_FREE(ptr) (((ptr)) ? (void)(yqFreeMemory((void*)(ptr)), (ptr) = nullptr) : (void)0) + +// C++ wrappers for using the custom allocators to replace use of new / delete (does placement new with custom allocated memory) +// Note: The custom allocators are expected to return null if the allocation fails and not throw. The macros below do not check this. +#define YQ_NEW(type) (new(YQ_ALLOC(type)) type) +#define YQ_NEW_PARAMS(type, params) (new(YQ_ALLOC(type)) type params) +#define YQ_DELETE(obj,type) ((obj) ? (void)((obj)->~type(), YQ_FREE(obj)) : (void)0) + + +/* + +Example usage: + +Result SomeFunction(T1 **Param1, T2 *Param2, T3 *Param3, int Param4) +{ + YQ_API_ENTER(); + + // Check pre-conditions + YQ_API_PRECONDITION_NULL(Param1, Param2, Param3); + YQ_API_PRECONDITION(Param4 == 42, R_InvalidParameter); + + if (errorHappened) + YQ_API_LEAVE(R_Failure); + + YQ_API_LEAVE(R_Okay); +} + +*/ + + +#endif // MACROS_H diff --git a/Framework/NonCopyable.h b/Framework/NonCopyable.h index 3881321..b8cb274 100644 --- a/Framework/NonCopyable.h +++ b/Framework/NonCopyable.h @@ -3,6 +3,62 @@ /// XXX: Not sure but maybe there is a C++11 std::non_copyable that does the same as this +/// Actually C++11 has the additional ability to add '= delete' to a member to say it can be used. + +#ifdef HAVE_CPP11 +# define DELETE = delete +#else +# define DELETE +#endif + + +/// This way has the advantage of not adding to the size of the base class if that is important [1] +/// [1] Ref: http://trac.webkit.org/changeset/68414 +#define MAKE_NONCOPYABLE(ClassName) \ + private: \ + ClassName(const ClassName&) DELETE; \ + ClassName& operator=(const ClassName&) DELETE; \ + + +/* Utility classes */ +struct NoCopy +{ +public: + NoCopy() {} +private: + NoCopy(const NoCopy &) DELETE; +}; + +struct NoAssign +{ +private: + NoAssign &operator=(const NoAssign &) DELETE; +}; + +struct NonInstantiable +{ +private: + NonInstantiable() DELETE; +}; + +struct NoCopyAssign : NoCopy, NoAssign +{ +}; + +typedef NoCopyAssign NoAssignCopy; + +// Not tested +struct NoMove +{ +public: + NoMove() {} +private: + NoMove(const NoMove &&) DELETE; +}; + +struct NoMoveCopyAssign : NoMove, NoCopy, NoAssign +{ +}; /// @@ -10,9 +66,9 @@ /// /// Making a class NonCopyable means preventing objects of the class being copied. This is useful if /// the class manages a resource that can only be held by one object such that it can't be reference -/// counted, or it is the underlying object that becomes reference counted. +/// counted, or it is the underlying object that becomes reference counted. Idea from here [1]. /// -/// Idea comes from here: http://stackoverflow.com/questions/9458741/with-explicitly-deleted-member-functions-in-c11-is-it-still-worthwhile-to-inh +/// [1] Ref: http://stackoverflow.com/questions/9458741/with-explicitly-deleted-member-functions-in-c11-is-it-still-worthwhile-to-inh /// class NonCopyable { @@ -34,12 +90,19 @@ MyNonCopyableClass() {} }; + MyNonCopyableClass create() + { + MyNonCopyableClass var; + return var; + } + Now it is not possible to copy objects of this type without a compiler error: - void function() + void function() { - MyNonCopyableClass obj1; - MyNonCopyableClass obj2; + MyNonCopyableClass obj1; + MyNonCopyableClass obj2; + MyNonCopyableClass obj3 = create(); // Allowed due to move semantics in c++11, use NoMoveCopyAssign to prevent // These attempts to make a copy of obj1 will cause a compiler error MyNonCopyableClass obj3(obj1); diff --git a/Framework/ObjectFilter.cpp b/Framework/ObjectFilter.cpp new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/Framework/ObjectFilter.cpp diff --git a/Framework/ObjectInspector.cpp b/Framework/ObjectInspector.cpp new file mode 100644 index 0000000..91b6726 --- /dev/null +++ b/Framework/ObjectInspector.cpp @@ -0,0 +1,90 @@ +#include "ObjectInspector.h" + +#include "ObjectPropertyModel.h" +#include "lib/ObjectProxy.h" + +#include +#include +#include +#include +#include +#include + +ObjectInspector::ObjectInspector(QWidget* parent) +: QWidget(parent) +{ + QVBoxLayout* layout = new QVBoxLayout(this); + layout->setMargin(0); + + m_nameLabel = new QLabel(this); + layout->addWidget(m_nameLabel); + + m_propertyView = new QTableView(this); + m_propertyView->verticalHeader()->setVisible(false); + m_propertyView->horizontalHeader()->setStretchLastSection(true); + + QHBoxLayout* filterLayout = new QHBoxLayout; + QLabel* filterLabel = new QLabel(tr("Filter:"),this); + m_propertyFilterEdit = new QLineEdit(this); + m_propertyFilterEdit->setToolTip(tr("String or regular expression to filter property list with")); + + connect(m_propertyFilterEdit,SIGNAL(textChanged(QString)), + this,SLOT(changeFilter(QString))); + filterLayout->addWidget(filterLabel); + filterLayout->addWidget(m_propertyFilterEdit); + + m_model = new ObjectPropertyModel(this); + m_propertySortModel = new QSortFilterProxyModel(this); + m_propertySortModel->setSourceModel(m_model); + + m_propertyView->setModel(m_propertySortModel); + + layout->addWidget(m_propertyView); + layout->addLayout(filterLayout); + + setObject(ObjectProxy::Pointer()); +} + +QString ObjectInspector::formatAddress(void* ptr) +{ + quintptr intPtr = reinterpret_cast(ptr); + return QString("0x%1").arg(QString(QByteArray::number(intPtr,16))); +} + +void ObjectInspector::refresh() +{ + setObject(m_currentObject); +} + +void ObjectInspector::setObject(ObjectProxy::Pointer object) +{ + QString labelText; + + if (object) + { + labelText = QString("%1 [%2] (%3)") + .arg(object->className()) + .arg(object->objectName()) + .arg(formatAddress(reinterpret_cast(object->address()))); + } + else + { + labelText = tr("[No Object Selected]"); + } + + m_nameLabel->setText(labelText); + m_currentObject = object; + m_model->setObject(object); + m_propertySortModel->sort(0,Qt::AscendingOrder); +} + +ObjectProxy::Pointer ObjectInspector::object() const +{ + return m_currentObject; +} + +void ObjectInspector::changeFilter(const QString& pattern) +{ + m_propertySortModel->setFilterRegExp(QRegExp(pattern)); +} + diff --git a/Framework/ObjectInspector.h b/Framework/ObjectInspector.h new file mode 100644 index 0000000..e1ce3ac --- /dev/null +++ b/Framework/ObjectInspector.h @@ -0,0 +1,40 @@ +#pragma once + +#include "lib/ObjectProxy.h" + +#include + +class QLabel; +class QLineEdit; +class QTableView; +class QSortFilterProxyModel; + +class ObjectPropertyModel; + +class ObjectInspector : public QWidget +{ + Q_OBJECT + + public: + ObjectInspector(QWidget* parent); + + void setObject(ObjectProxy::Pointer object); + ObjectProxy::Pointer object() const; + + static QString formatAddress(void* ptr); + + public Q_SLOTS: + void refresh(); + + private Q_SLOTS: + void changeFilter(const QString& pattern); + + private: + QLabel* m_nameLabel; + QTableView* m_propertyView; + ObjectProxy::Pointer m_currentObject; + ObjectPropertyModel* m_model; + QSortFilterProxyModel* m_propertySortModel; + QLineEdit* m_propertyFilterEdit; +}; + diff --git a/Framework/ObjectPropertyModel.cpp b/Framework/ObjectPropertyModel.cpp new file mode 100644 index 0000000..6e4d4f7 --- /dev/null +++ b/Framework/ObjectPropertyModel.cpp @@ -0,0 +1,91 @@ +#include "ObjectPropertyModel.h" + +#include "lib/ObjectProxy.h" + +#include +#include + +ObjectPropertyModel::ObjectPropertyModel(QObject* parent) +: QStandardItemModel(parent) +{ + setColumnCount(2); + setObject(ObjectProxy::Pointer()); + + connect(this,SIGNAL(itemChanged(QStandardItem*)), + this,SLOT(updatePropertyValue(QStandardItem*))); +} + +QVariant ObjectPropertyModel::toEditValue(const QVariant& value) +{ + // TODO - Find existing code which can handle display and editing + // of common value types. + switch (value.userType()) + { + case QVariant::Size: + case QVariant::SizeF: + { + QSize size = value.toSize(); + return QString("%1 x %2").arg(size.width()).arg(size.height()); + } + case QVariant::Rect: + case QVariant::RectF: + { + QRect rect = value.toRect(); + return QString("(%1,%2) (%3,%4)").arg(rect.left()).arg(rect.top()).arg(rect.width()).arg(rect.height()); + } + } + return value; +} + +void ObjectPropertyModel::updatePropertyValue(QStandardItem* item) +{ + QStandardItem* propertyItem = this->item(item->row(),0); + QString propertyName = propertyItem->text(); + + if (m_object) + { + QVariant newValue = item->data(Qt::EditRole); + qDebug() << "setting property" << propertyName << "to" << newValue; + m_object->writeProperty(propertyName,newValue); + } +} + +void ObjectPropertyModel::setObject(ObjectProxy::Pointer object) +{ + clear(); + + setHorizontalHeaderLabels( + QStringList() << tr("Property") << tr("Value") + ); + + m_object = object; + + if (object) + { + object->refresh(); + int rowCount = 0; + QListIterator iter(object->properties()); + while (iter.hasNext()) + { + ObjectProxy::Property property = iter.next(); + QVariant value = property.value; + QVariant editValue = toEditValue(value); + + QStandardItem* nameItem = new QStandardItem(property.name); + nameItem->setEditable(false); + + QStandardItem* valueItem = new QStandardItem; + valueItem->setData(editValue,Qt::EditRole); + valueItem->setEditable(property.isWritable); + + insertRow(rowCount,QList() << nameItem << valueItem); + ++rowCount; + } + } +} + +ObjectProxy::Pointer ObjectPropertyModel::object() const +{ + return m_object; +} + diff --git a/Framework/ObjectPropertyModel.h b/Framework/ObjectPropertyModel.h new file mode 100644 index 0000000..69e176c --- /dev/null +++ b/Framework/ObjectPropertyModel.h @@ -0,0 +1,27 @@ +#pragma once + +#include "lib/ObjectProxy.h" + +#include +#include + +class ObjectPropertyModel : public QStandardItemModel +{ + Q_OBJECT + + public: + ObjectPropertyModel(QObject* parent); + + void setObject(ObjectProxy::Pointer object); + ObjectProxy::Pointer object() const; + + static QVariant toEditValue(const QVariant& value); + + private Q_SLOTS: + void updatePropertyValue(QStandardItem* item); + + private: + ObjectProxy::Pointer m_object; +}; + + diff --git a/Framework/ObjectTreeModel.cpp b/Framework/ObjectTreeModel.cpp new file mode 100644 index 0000000..5d7bbfc --- /dev/null +++ b/Framework/ObjectTreeModel.cpp @@ -0,0 +1,135 @@ +#include "ObjectTreeModel.h" +#include + + +ObjectTreeModel::ObjectTreeModel(QObject* parent) +: QAbstractItemModel(parent) +{ +} + +int ObjectTreeModel::columnCount(const QModelIndex&) const +{ + return 1; +} + +int ObjectTreeModel::rowCount(const QModelIndex& parent) const +{ + QObject* obj = static_cast(parent.internalPointer()); + return (obj) ? obj->children().count() : m_roots.count(); +} + +void ObjectTreeModel::setRootObjects(const QList& roots) +{ + beginResetModel(); + m_roots = roots; + endResetModel(); +} + +QModelIndex ObjectTreeModel::index(int row, int column, const QModelIndex& parent) const +{ + QObject* parentItem = static_cast(parent.internalPointer()); + const QObjectList& children = (parentItem) ? parentItem->children() : m_roots; + bool outOfBounds = (row < 0 || column != 0 || row >= children.count()); + return outOfBounds ? QModelIndex() : createIndex(row,column,children.at(row)); +} + +int ObjectTreeModel::indexInParent(QObject* item) const +{ + QObject* parent = item->parent(); + return (parent && !m_roots.contains(item)) ? parent->children().indexOf(item) : m_roots.indexOf(item); +} + +QModelIndex ObjectTreeModel::parent(const QModelIndex& index) const +{ + QObject* item = static_cast(index.internalPointer()); + QObject* parent = (item && !m_roots.contains(item)) ? item->parent() : 0; + return (parent) ? createIndex(indexInParent(parent),index.column(),parent) : QModelIndex(); +} + +QVariant ObjectTreeModel::data(const QModelIndex& index, int role) const +{ + QObject* item = static_cast(index.internalPointer()); + return (role == Qt::DisplayRole) ? ((item) ? displayText(item) : "[deleted]") : QVariant(); +} + +QString ObjectTreeModel::displayText(QObject* object) const +{ + QString text = object->metaObject()->className(); + if (!object->objectName().isEmpty()) + { + text += QString("[%1]").arg(object->objectName()); + } + return text; +} + +QVariant ObjectTreeModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (section == 0 && orientation == Qt::Horizontal && role == Qt::DisplayRole) + { + return tr("Object"); + } + return QAbstractItemModel::headerData(section,orientation,role); +} + +QObject* ObjectTreeModel::index(ObjPtr object, const QList& items) const +{ + //inline QList findChildren(const QRegularExpression &re, Qt::FindChildOptions options = Qt::FindChildrenRecursively) const + Q_FOREACH(QObject* item, items) + { + if (item == object) + return item; + QObject* match = index(object, item->children()); + if (match) + return match; + } + return 0; +} + +QModelIndex ObjectTreeModel::index(ObjPtr object) const +{ + QObject* item = index(object, m_roots); + return (item) ? createIndex(indexInParent(item),0,item) : QModelIndex(); +} + + + + +/* +bool ObjectTreeModel::matches(ObjPtr object, const QString& query) const +{ + return displayText(object).contains(query,Qt::CaseInsensitive); +} + +void ObjectTreeModel::search(QList* hits, ObjectItem* item, const QString& query) const +{ + ObjPtr object = item;//->object; + if (!object) + { + return; + } + if (matches(object,query)) + { + *hits << object; + } + Q_FOREACH(ObjectItem* child, item->children()) + { + search(hits,child,query); + } +} + +QList ObjectTreeModel::search(const QString& query) const +{ + if (query.isEmpty()) + { + return QList(); + } + + QList matches; + Q_FOREACH(ObjectItem* item, m_roots) + { + search(&matches,item,query); + } + return matches; +} + +*/ diff --git a/Framework/ObjectTreeModel.h b/Framework/ObjectTreeModel.h new file mode 100644 index 0000000..4b50d8a --- /dev/null +++ b/Framework/ObjectTreeModel.h @@ -0,0 +1,47 @@ +#pragma once + +#include +#include +#include + + +class TrackedObject +{ +public: + TrackedObject() {} + TrackedObject(QObject* a_obj) : m_object(a_obj) {} + operator bool() { return !m_object.isNull(); } + operator QObject*() { return m_object.data(); } +private: + QPointer m_object; +}; + + +typedef QObject* ObjPtr; +//typedef TrackedObject ObjPtr; + + +class ObjectTreeModel : public QAbstractItemModel +{ + Q_OBJECT +public: + ObjectTreeModel(QObject* parent); + //void setFilter(ObjectFilter *filter); + //QList search(const QString& query) const; + void setRootObjects(const QList& roots); + QModelIndex index(ObjPtr object) const; + virtual QVariant headerData(int section, Qt::Orientation orientation, int role) const; + virtual int columnCount(const QModelIndex& parent) const; + virtual int rowCount(const QModelIndex& parent) const; + virtual QModelIndex index(int row, int column, const QModelIndex& parent) const; + virtual QModelIndex parent(const QModelIndex& index) const; + virtual QVariant data(const QModelIndex& index, int role) const; +private: + int indexInParent(QObject* item) const; + QObject* index(ObjPtr object, const QList& items) const; + //bool matches(ObjPtr object, const QString& query) const; + //void search(QList* matches, ObjectItem* item, const QString& query) const; + QString displayText(QObject* object) const; + QList m_roots; +}; + diff --git a/Framework/SystemInfomation.cpp b/Framework/SystemInfomation.cpp new file mode 100644 index 0000000..3f09248 --- /dev/null +++ b/Framework/SystemInfomation.cpp @@ -0,0 +1,132 @@ +#include "SystemInfomation.h" +#include "../Util.h" +#include +#include +#include + + +#ifdef __APPLE__ +# include "TargetConditionals.h" +#elif defined(_WIN32) +# include +#else +# include +# include +# include +# include +#endif + + +void getSystemInfo(SystemInfo& a_info) +{ + a_info.m_cpuType = "unknown"; + a_info.m_osType = "unknown"; + a_info.m_osVersion = "unknown"; + +#ifdef _WIN32 + a_info.m_osType = "Windows"; + + // Get RAM size + MEMORYSTATUSEX status; + status.dwLength = sizeof(status); + GlobalMemoryStatusEx(&status); + a_info.m_ramSize = status.ullTotalPhys; + + // Get OS version + const OSVERSIONINFO osver = winOsVersion(); + a_info.m_osVersion.reserve(512); + snprintf(a_info.m_osVersion.data(), "%i.%i.%i", osver.dwMajorVersion, osver.dwMinorVersion, osver.dwBuildNumber); + + // Get OS arch + SYSTEM_INFO info; + GetNativeSystemInfo(&info); +# ifdef _WIN64 + //if (info.wProcessorArchitecture == ???) + a_info.m_cpuType = "x86_64"; +# else + a_info.m_cpuType = "x86"; +# endif +#else + long pages = sysconf(_SC_PHYS_PAGES); + long page_size = sysconf(_SC_PAGE_SIZE); + a_info.m_ramSize = pages * page_size; + struct utsname u; + long ret = uname(&u); + if (ret != -1) + { + a_info.m_cpuType = u.machine; + a_info.m_osVersion = u.sysname + std::string(" ") + u.release; + } +# ifdef __APPLE__ + // Get RAM Size + int mib[2] = { CTL_HW, HW_MEMSIZE }; + size_t len = sizeof(uint64_t); + sysctl(mib, sizeof(mib) / sizeof(mib[0]), &u.ramSize, &len, NULL, 0); +# if TARGET_OS_IPHONE + a_info.m_osType = "iOS"; +# elif TARGET_OS_MAC + const char *macCodeName = ""; + switch (int(MacintoshVersion)) { + case MV_CHEETAH: macCodeName = " Cheetah"; break; + case MV_PUMA: macCodeName = " Puma"; break; + case MV_JAGUAR: macCodeName = " Jaguar"; break; + case MV_PANTHER: macCodeName = " Panther"; break; + case MV_TIGER: macCodeName = " Tiger"; break; + case MV_LEOPARD: macCodeName = " Leopard"; break; + case MV_SNOWLEOPARD: macCodeName = " Snow Leopard"; break; + case MV_LION: macCodeName = " Lion"; break; + case MV_MOUNTAINLION:macCodeName = " Mountain Lion"; break; + case MV_MAVERICKS: macCodeName = " Mavericks"; break; + case MV_YOSEMITE: macCodeName = " Yosemite"; break; + } + a_info.m_osType = "Mac OS X" + std::string(macCodeName); +# endif +# elif __ANDROID__ + a_info.m_osType = "Android"; +# elif __linux + // system("lsb_release -sd"); + FILE* f = fopen("/etc/lsb-release", "rt"); + char dist[64] = { 0 }; + if (f) { + char buf[128]; + while (fgets(buf, 128, f) != 0) + if (sscanf(buf, "DISTRIB_DESCRIPTION=%64c", dist) == 1) + break; + if (dist[0]) { + std::vector strBits = split(dist, '"'); + a_info.m_osType = strBits[(strBits.size()==3)?1:0]; + } else { + a_info.m_osType = "non-specific LSB"; + } + fclose(f); + } else { + a_info.m_osType = "non-LSB"; + } +# else + a_info.m_osType = "Generic UNIX"; +# endif +#endif + + // TODO: improve the CPU detection using libcpuid + + // Make human readable RAM size string + const char* humanUnitStrings[] = { " bytes", " KB", " MB", " GB", " TB" }; + int ramUnit = 0; + uint64_t ramSize = a_info.m_ramSize; + while (ramSize > 1024 && ramUnit < (YQ_ARRAY_SIZE(humanUnitStrings)-1)) + ramUnit++, ramSize /= 1000; + a_info.m_ramSizeStr = std::to_string(ramSize) + humanUnitStrings[ramUnit]; + + // Obtain the screen properties + QScreen *s = QApplication::screens()[0]; + int refreshRate = (s) ? s->refreshRate() : 0; + int depth = (s) ? s->depth() : 0; + a_info.m_resolutionWidth = (s) ? s->geometry().width() : 0; + a_info.m_resolutionHeight = (s) ? s->geometry().height() : 0; + a_info.m_resolutionStr = std::to_string(a_info.m_resolutionWidth) + "x" + + std::to_string(a_info.m_resolutionHeight) + " " + + std::to_string(depth) + "bit " + + "@" + std::to_string(refreshRate) + "Hz"; + ; +} + diff --git a/Framework/SystemInfomation.h b/Framework/SystemInfomation.h new file mode 100644 index 0000000..2833313 --- /dev/null +++ b/Framework/SystemInfomation.h @@ -0,0 +1,26 @@ +#ifndef SYSTEM_INFOMATION_H +#define SYSTEM_INFOMATION_H + + +#include "Utilities.h" +#include +#include + + +struct SystemInfo +{ + std::string m_cpuType; + std::string m_osType; + std::string m_osVersion; + std::string m_ramSizeStr; // Human friendly string, eg: "4 GB" + std::string m_resolutionStr; + uint64_t m_ramSize; + uint32_t m_resolutionWidth; + uint32_t m_resolutionHeight; +}; + + +void getSystemInfo(SystemInfo& a_info); + + +#endif // SYSTEM_INFOMATION_H diff --git a/Framework/Tests.cpp b/Framework/Tests.cpp index 046c82c..2a2d78a 100644 --- a/Framework/Tests.cpp +++ b/Framework/Tests.cpp @@ -1,4 +1,5 @@ #include "Utilities.h" +#include "Array.h" #ifdef _MSC_VER diff --git a/Framework/Utilities.cpp b/Framework/Utilities.cpp index 8dbccf9..1e6acb9 100644 --- a/Framework/Utilities.cpp +++ b/Framework/Utilities.cpp @@ -11,7 +11,7 @@ static ResourceDeviceFactory s_defaultDeviceFactory; -Module g_currentModule{ false, &s_defaultErrorHandler, &s_defaultMessageLogger, &s_defaultHeapManager, &s_defaultDeviceFactory }; +Module g_currentModule{ false, "Main Module", &s_defaultErrorHandler, &s_defaultMessageLogger, &s_defaultHeapManager, &s_defaultDeviceFactory }; void yqDebugBreak() diff --git a/Framework/Utilities.h b/Framework/Utilities.h index cff9fcc..1f47a04 100644 --- a/Framework/Utilities.h +++ b/Framework/Utilities.h @@ -5,92 +5,7 @@ #include #include #include - - -/// -/// Various common macros -/// -#define YQ_ARRAY_SIZE(x) (sizeof(x)/sizeof((x)[0])) - -#define YQ_EXPAND(x) x - -#define YQ_STRINGIZE_IMPL(val) #val -#define YQ_STRINGIZE(val) YQ_STRINGIZE_IMPL(val) - -#define YQ_CONCATENATE_IMPL(x, y) x##y -#define YQ_CONCATENATE(x, y) YQ_CONCATENATE_IMPL(x, y) - -// If 0 args, it mis-reports a count of 1. Also it only counts up to 10 args. -#define YQ_ARG_COUNT_IMPL(X,A,B,C,D,E,F,G,H,I,J,N,...) N -#define YQ_ARG_COUNT(...) (YQ_EXPAND(YQ_ARG_COUNT_IMPL(_,__VA_ARGS__, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0))) - -#ifdef __COUNTER__ -# define YQ_ANONYMOUS_VARIABLE(str) YQ_CONCATENATE(str, __COUNTER__) -#else -# define YQ_ANONYMOUS_VARIABLE(str) YQ_CONCATENATE(str, __LINE__) -#endif - -#define YQ_UNUSED(x) std::ignore = (x) - -#ifdef __MINGW32__ -// Avoid callbacks -# define YQ_CALLBACK __cdecl -# define YQ_FUNCTION_NAME __PRETTY_FUNCTION__ // __func__ -#elif defined(_WIN32) -# define YQ_CALLBACK __cdecl -# define YQ_FUNCTION_NAME __FUNCTION__ -#else -# define YQ_CALLBACK -# define YQ_FUNCTION_NAME __PRETTY_FUNCTION__ // __func__ -#endif - -// Helper marcos for doing a variable number of null checks -#define YQ_NULL_CHECK_IMPL_1(arg) if (arg == nullptr) YQ_API_LEAVE(R_InvalidParameter); -#define YQ_NULL_CHECK_IMPL_2(arg,...) YQ_NULL_CHECK_IMPL_1(arg) YQ_NULL_CHECK_IMPL_1(__VA_ARGS__) -#define YQ_NULL_CHECK_IMPL_3(arg,...) YQ_NULL_CHECK_IMPL_1(arg) YQ_NULL_CHECK_IMPL_2(__VA_ARGS__) -#define YQ_NULL_CHECK_IMPL_4(arg,...) YQ_NULL_CHECK_IMPL_1(arg) YQ_NULL_CHECK_IMPL_3(__VA_ARGS__) -#define YQ_NULL_CHECK_IMPL_5(arg,...) YQ_NULL_CHECK_IMPL_1(arg) YQ_NULL_CHECK_IMPL_4(__VA_ARGS__) -#define YQ_NULL_CHECK_IMPL_N3(N, ...) YQ_NULL_CHECK_IMPL_ ## N(__VA_ARGS__) -#define YQ_NULL_CHECK_IMPL_N2(N, ...) YQ_NULL_CHECK_IMPL_N3(N, __VA_ARGS__) -#define YQ_NULL_CHECK_IMPL_N(...) YQ_NULL_CHECK_IMPL_N2(YQ_ARG_COUNT(__VA_ARGS__), __VA_ARGS__) - -// File name, line numbes, and function name are statically concatenated at compile time, with a reduced string sharing trade off -#define YQ_LOCATION __FILE__ "(" YQ_STRINGIZE(__LINE__) "): " - -// Macros to log messages for debug builds. -// For release builds these will not output anything and no code or data will be generate (except for error logging) -#define YQ_LOG_WARNING(a_fmt, ...) ((BT_IsRelease) ? ((void)0) : yqLogMessage(LL_Warning, YQ_LOCATION, YQ_FUNCTION_NAME, "warning: " a_fmt, ##__VA_ARGS__)) -#define YQ_LOG_DEBUG(a_fmt, ...) ((BT_IsRelease) ? ((void)0) : yqLogMessage(LL_Debug, YQ_LOCATION, YQ_FUNCTION_NAME, "debug: " a_fmt, ##__VA_ARGS__)) -#define YQ_LOG_INFO(a_fmt, ...) ((BT_IsRelease) ? ((void)0) : yqLogMessage(LL_Info, YQ_LOCATION, YQ_FUNCTION_NAME, "info: " a_fmt, ##__VA_ARGS__)) -#define YQ_LOG_ERROR(a_fmt, ...) yqLogMessage(LL_Error, YQ_LOCATION, YQ_FUNCTION_NAME, "error: " a_fmt, ##__VA_ARGS__) - -// Macros that only generate log code in verbose debug builds. The logging logs the entry to API calls and the exit from them and what return code is being returned. -// In debug and release, on errors, the code always calls the installed or default error handling routine -enum { YQ_VERBOSE_DEBUGGING = 0 }; -#define YQ_API_VERBOSE(msg) ((YQ_VERBOSE_DEBUGGING) ? YQ_LOG_DEBUG(msg) : ((void)0)) -#define YQ_API_ERROR(result, ...) (yqHandleError(result, YQ_LOCATION, YQ_FUNCTION_NAME, __VA_ARGS__), YQ_LOG_ERROR("Leave: " #result " (%i)", result), result) -#define YQ_API_ENTER() YQ_API_VERBOSE("Enter") -#define YQ_API_LEAVE(result, ...) return ((result == R_Okay) ? (YQ_API_VERBOSE("Leave"), result) : YQ_API_ERROR(result, "" __VA_ARGS__)) - -// Macros to do common parameter and precondition checking for API function calls -// This macro checks the pre-condition is met, if it doesn't then returns the error result and executes the provided cleanup code -// See also 'finally'. Probably during pre-condition checking, there shouldn't be any cleanup yet as no work should be done at this stage. -//#define YQ_API_PRECONDITION_CLEANUP(condition,result,cleanup) do { if (!(condition)) { cleanup; YQ_API_LEAVE(result); } } while (0,0) -#define YQ_API_PRECONDITION(condition,result) do { if (!(condition)) { YQ_API_LEAVE(result, #condition); } } while (0,0)//YQ_API_PRECONDITION_CLEANUP(condition,result, ((void)0)) -#define YQ_API_PRECONDITION_NULL(...) YQ_NULL_CHECK_IMPL_N(__VA_ARGS__) - -// Can redirect to the user heap manager routines if provided -#define YQ_ALLOC(type) ((type*)(yqAllocateMemory(sizeof(type)))) -#define YQ_ALLOC_ARRAY(type,siz) ((type*)(yqAllocateMemory(sizeof(type)*(siz)))) -#define YQ_REALLOC_ARRAY(ptr,type,siz) ((type*)(yqReallocateMemory(ptr,sizeof(type)*(siz)))) -#define YQ_FREE(ptr) (((ptr)) ? (void)(yqFreeMemory((void*)(ptr)), (ptr) = nullptr) : (void)0) - -// C++ wrappers for using the custom allocators to replace use of new / delete (does placement new with custom allocated memory) -// Note: The custom allocators are expected to return null if the allocation fails and not throw. The macros below do not check this. -#define YQ_NEW(type) (new(YQ_ALLOC(type)) type) -#define YQ_NEW_PARAMS(type, params) (new(YQ_ALLOC(type)) type params) -#define YQ_DELETE(obj,type) ((obj) ? (void)((obj)->~type(), YQ_FREE(obj)) : (void)0) - +#include "Macros.h" enum yqResult @@ -206,6 +121,7 @@ { public: bool m_initialized; + const char* m_moduleName; ErrorHandler* m_errorHandler; Logger* m_messageLogger; HeapManager* m_heapManager; @@ -216,111 +132,4 @@ extern Module g_currentModule; -// std::vector replacement that is cut down but familiar, uses the custom allocators, and restricted to POD elements only -template -struct PODArrayPOD // A dynamic array of POD type elements which is itself a POD type (so can have multi-dimensional arrays of these). -{ - static_assert(std::is_pod::value, "T must be a POD type."); - - void init() { m_magic = 0x01020304; m_capacity = m_size = 0; m_data = nullptr; } - void deinit() { if (m_data != nullptr) YQ_FREE(m_data); } - - const T& at(size_t a_pos) const { checkInited(); checkIndex(a_pos); return m_data[a_pos]; } - T& at(size_t a_pos) { checkInited(); checkIndex(a_pos); return m_data[a_pos]; } - const T& operator[](size_t a_pos) const { checkInited(); checkIndex(a_pos); return m_data[a_pos]; } - void pop_back() { checkInited(); checkIndex(1); m_size--; } - const T& back() const { checkInited(); return m_data[m_size - 1]; } - T& back() { checkInited(); return m_data[m_size - 1]; } - void push_back(const T& a_val) { checkInited(); if (m_size == m_capacity) ensureSpaceFor(1); m_data[m_size++] = a_val; } - void push(const T& a_val) { checkInited(); push_back(a_val); } - T pop() { checkInited(); pop_back(); return m_data[m_size]; } - void reserve(size_t a_count) { checkInited(); m_capacity = a_count; m_data = YQ_REALLOC_ARRAY(m_data, T, a_count); } - size_t capacity() const { checkInited(); return m_capacity; } - size_t size() const { checkInited(); return m_size; } - T* data() const { checkInited(); return m_data; } - void clear() { checkInited(); m_size = 0; } -//private: // Actually making private would make this no longer a POD type - yqResult checkInited() const { if (BT_IsDebug) YQ_API_PRECONDITION(m_magic == 0x01020304, R_Uninitialized); return R_Okay; } - yqResult checkIndex(size_t a_pos) const { YQ_API_PRECONDITION(a_pos < m_size, R_IndexOutOfRange); return R_Okay; } - void ensureSpaceFor(size_t a_count) { reserve(std::max(m_size + a_count, m_capacity + m_capacity / 2)); } // try to grow by 50% - T* m_data; // Because of this pointer it is not safe to copy DynamicArray objects around. Making it a shared_ptr though stops this being a POD type - size_t m_capacity; - size_t m_size; - uint32_t m_magic; -}; - - -static_assert(std::is_pod >::value, "PODArrayPOD must be a POD type."); - - -template -struct ArrayPOD // A dynamic array of POD type elements. It isn't POD itself, but is instead reference counted so safer to deal with. -{ - static_assert(std::is_pod::value, "T must be a POD type."); - - ArrayPOD& operator=(const ArrayPOD&) = delete; - ArrayPOD(const ArrayPOD&) = delete; - ArrayPOD() { m_capacity = m_size = 0; m_data = nullptr; } - ~ArrayPOD() { if (m_data != nullptr) YQ_FREE(m_data); } - - const T& at(size_t a_pos) const { checkIndex(a_pos); return m_data[a_pos]; } - T& at(size_t a_pos) { checkIndex(a_pos); return m_data[a_pos]; } - const T& operator[](size_t a_pos) const { checkIndex(a_pos); return m_data[a_pos]; } - void pop_back() { checkIndex(1); m_size--; } - const T& back() const { return m_data[m_size - 1]; } - T& back() { return m_data[m_size - 1]; } - void push_back(const T& a_val) { if (m_size == m_capacity) ensureSpaceFor(1); m_data[m_size++] = a_val; } - void push(const T& a_val) { push_back(a_val); } - T pop() { pop_back(); return m_data[m_size]; } - void reserve(size_t a_count) { m_capacity = a_count; m_data = YQ_REALLOC_ARRAY(m_data, T, a_count); } - size_t capacity() const { return m_capacity; } - size_t size() const { return m_size; } - T* data() const { return m_data; } - void clear() { m_size = 0; } -private: - yqResult checkIndex(size_t a_pos) const { YQ_API_PRECONDITION(a_pos < m_size, R_IndexOutOfRange); return R_Okay; } - void ensureSpaceFor(size_t a_count) { reserve(std::max(m_size + a_count, m_capacity + m_capacity / 2)); } // try to grow by 50% - T* m_data; // Because of this pointer it is not safe to copy DynamicArray objects around. Making it a shared_ptr though stops this being a POD type - size_t m_capacity; - size_t m_size; -}; - - -struct PODString -{ -public: - void init() { m_data.init(); } - void deinit() { m_data.deinit(); } - void operator=(const char* a_str) { m_data.clear(); operator+=(a_str); } - void operator+=(const char* a_str) { while (*a_str) m_data.push_back(*a_str++); m_data.push(0); m_data.pop(); } - char* c_str() { return m_data.data(); } -//private: // Actually making private would make this no longer a POD type - PODArrayPOD m_data; -}; - - -static_assert(std::is_pod::value, "PODString must be a POD type."); - - -/* - -Example usage: - -Result SomeFunction(T1 **Param1, T2 *Param2, T3 *Param3, int Param4) -{ - YQ_API_ENTER(); - - // Check pre-conditions - YQ_API_PRECONDITION_NULL(Param1, Param2, Param3); - YQ_API_PRECONDITION(Param4 == 42, R_InvalidParameter); - - if (errorHappened) - YQ_API_LEAVE(R_Failure); - - YQ_API_LEAVE(R_Okay); -} - -*/ - - #endif // UTILITIES_H diff --git a/Framework/Array.h b/Framework/Array.h new file mode 100644 index 0000000..ab83e77 --- /dev/null +++ b/Framework/Array.h @@ -0,0 +1,98 @@ +#ifndef ARRAY_H +#define ARRAY_H + + +#include "Utilities.h" + + +// std::vector replacement that is cut down but familiar. +// It uses the custom allocators, and is restricted to POD elements only +template +struct PODArrayPOD // A dynamic array of POD type elements which is itself a POD type (so can have multi-dimensional arrays of these). +{ + static_assert(std::is_pod::value, "T must be a POD type."); + + void init() { m_magic = 0x01020304; m_capacity = m_size = 0; m_data = nullptr; } + void deinit() { if (m_data != nullptr) YQ_FREE(m_data); } + + const T& at(size_t a_pos) const { checkInited(); checkIndex(a_pos); return m_data[a_pos]; } + T& at(size_t a_pos) { checkInited(); checkIndex(a_pos); return m_data[a_pos]; } + const T& operator[](size_t a_pos) const { checkInited(); checkIndex(a_pos); return m_data[a_pos]; } + void pop_back() { checkInited(); checkIndex(1); m_size--; } + const T& back() const { checkInited(); return m_data[m_size - 1]; } + T& back() { checkInited(); return m_data[m_size - 1]; } + void push_back(const T& a_val) { checkInited(); if (m_size == m_capacity) ensureSpaceFor(1); m_data[m_size++] = a_val; } + void push(const T& a_val) { checkInited(); push_back(a_val); } + T pop() { checkInited(); pop_back(); return m_data[m_size]; } + void reserve(size_t a_count) { checkInited(); m_capacity = a_count; m_data = YQ_REALLOC_ARRAY(m_data, T, a_count); } + size_t capacity() const { checkInited(); return m_capacity; } + size_t size() const { checkInited(); return m_size; } + T* data() const { checkInited(); return m_data; } + void clear() { checkInited(); m_size = 0; } +//private: // Actually making private would make this no longer a POD type + yqResult checkInited() const { if (BT_IsDebug) YQ_API_PRECONDITION(m_magic == 0x01020304, R_Uninitialized); return R_Okay; } + yqResult checkIndex(size_t a_pos) const { YQ_API_PRECONDITION(a_pos < m_size, R_IndexOutOfRange); return R_Okay; } + void ensureSpaceFor(size_t a_count) { reserve(std::max(m_size + a_count, m_capacity + m_capacity / 2)); } // try to grow by 50% + T* m_data; // Because of this pointer it is not safe to copy DynamicArray objects around. Making it a shared_ptr though stops this being a POD type + size_t m_capacity; + size_t m_size; + uint32_t m_magic; +}; + + +static_assert(std::is_pod >::value, "PODArrayPOD must be a POD type."); + + +// A dynamic array of POD type elements. +// It isn't POD itself, but is instead reference counted so safer to deal with. +template +struct ArrayPOD +{ + static_assert(std::is_pod::value, "T must be a POD type."); + + // Actually not reference counted yet, but safe as objects of this type can't be copied + ArrayPOD& operator=(const ArrayPOD&) = delete; + ArrayPOD(const ArrayPOD&) = delete; + ArrayPOD() { m_capacity = m_size = 0; m_data = nullptr; } + ~ArrayPOD() { if (m_data != nullptr) YQ_FREE(m_data); } + + const T& at(size_t a_pos) const { checkIndex(a_pos); return m_data[a_pos]; } + T& at(size_t a_pos) { checkIndex(a_pos); return m_data[a_pos]; } + const T& operator[](size_t a_pos) const { checkIndex(a_pos); return m_data[a_pos]; } + void pop_back() { checkIndex(1); m_size--; } + const T& back() const { return m_data[m_size - 1]; } + T& back() { return m_data[m_size - 1]; } + void push_back(const T& a_val) { if (m_size == m_capacity) ensureSpaceFor(1); m_data[m_size++] = a_val; } + void push(const T& a_val) { push_back(a_val); } + T pop() { pop_back(); return m_data[m_size]; } + void reserve(size_t a_count) { m_capacity = a_count; m_data = YQ_REALLOC_ARRAY(m_data, T, a_count); } + size_t capacity() const { return m_capacity; } + size_t size() const { return m_size; } + T* data() const { return m_data; } + void clear() { m_size = 0; } +private: + yqResult checkIndex(size_t a_pos) const { YQ_API_PRECONDITION(a_pos < m_size, R_IndexOutOfRange); return R_Okay; } + void ensureSpaceFor(size_t a_count) { reserve(std::max(m_size + a_count, m_capacity + m_capacity / 2)); } // try to grow by 50% + T* m_data; // Because of this pointer it is not safe to copy DynamicArray objects around. Making it a shared_ptr though stops this being a POD type + size_t m_capacity; + size_t m_size; +}; + + +struct PODString +{ +public: + void init() { m_data.init(); } + void deinit() { m_data.deinit(); } + void operator=(const char* a_str) { m_data.clear(); operator+=(a_str); } + void operator+=(const char* a_str) { while (*a_str) m_data.push_back(*a_str++); m_data.push(0); m_data.pop(); } + char* c_str() { return m_data.data(); } +//private: // Actually making private would make this no longer a POD type + PODArrayPOD m_data; +}; + + +static_assert(std::is_pod::value, "PODString must be a POD type."); + + +#endif // ARRAY_H diff --git a/Framework/Macros.h b/Framework/Macros.h new file mode 100644 index 0000000..6ca8c86 --- /dev/null +++ b/Framework/Macros.h @@ -0,0 +1,111 @@ +#ifndef MACROS_H +#define MACROS_H + + +// +// Various common macros +// +#define YQ_ARRAY_SIZE(x) (sizeof(x)/sizeof((x)[0])) + +#define YQ_EXPAND(x) x + +#define YQ_STRINGIZE_IMPL(val) #val +#define YQ_STRINGIZE(val) YQ_STRINGIZE_IMPL(val) + +#define YQ_CONCATENATE_IMPL(x, y) x##y +#define YQ_CONCATENATE(x, y) YQ_CONCATENATE_IMPL(x, y) + +// If 0 args, it mis-reports a count of 1. Also it only counts up to 10 args. +#define YQ_ARG_COUNT_IMPL(X,A,B,C,D,E,F,G,H,I,J,N,...) N +#define YQ_ARG_COUNT(...) (YQ_EXPAND(YQ_ARG_COUNT_IMPL(_,__VA_ARGS__, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0))) + +#ifdef __COUNTER__ +# define YQ_ANONYMOUS_VARIABLE(str) YQ_CONCATENATE(str, __COUNTER__) +#else +# define YQ_ANONYMOUS_VARIABLE(str) YQ_CONCATENATE(str, __LINE__) +#endif + +#define YQ_UNUSED(x) std::ignore = (x) + +#ifdef __MINGW32__ +// Avoid callbacks +# define YQ_CALLBACK __cdecl +# define YQ_FUNCTION_NAME __PRETTY_FUNCTION__ // __func__ +#elif defined(_WIN32) +# define YQ_CALLBACK __cdecl +# define YQ_FUNCTION_NAME __FUNCTION__ +#else +# define YQ_CALLBACK +# define YQ_FUNCTION_NAME __PRETTY_FUNCTION__ // __func__ +#endif + +// Helper marcos for doing a variable number of null checks +#define YQ_NULL_CHECK_IMPL_1(arg) if (arg == nullptr) YQ_API_LEAVE(R_InvalidParameter); +#define YQ_NULL_CHECK_IMPL_2(arg,...) YQ_NULL_CHECK_IMPL_1(arg) YQ_NULL_CHECK_IMPL_1(__VA_ARGS__) +#define YQ_NULL_CHECK_IMPL_3(arg,...) YQ_NULL_CHECK_IMPL_1(arg) YQ_NULL_CHECK_IMPL_2(__VA_ARGS__) +#define YQ_NULL_CHECK_IMPL_4(arg,...) YQ_NULL_CHECK_IMPL_1(arg) YQ_NULL_CHECK_IMPL_3(__VA_ARGS__) +#define YQ_NULL_CHECK_IMPL_5(arg,...) YQ_NULL_CHECK_IMPL_1(arg) YQ_NULL_CHECK_IMPL_4(__VA_ARGS__) +#define YQ_NULL_CHECK_IMPL_N3(N, ...) YQ_NULL_CHECK_IMPL_ ## N(__VA_ARGS__) +#define YQ_NULL_CHECK_IMPL_N2(N, ...) YQ_NULL_CHECK_IMPL_N3(N, __VA_ARGS__) +#define YQ_NULL_CHECK_IMPL_N(...) YQ_NULL_CHECK_IMPL_N2(YQ_ARG_COUNT(__VA_ARGS__), __VA_ARGS__) + +// File name, line numbes, and function name are statically concatenated at compile time, with a reduced string sharing trade off +#define YQ_LOCATION __FILE__ "(" YQ_STRINGIZE(__LINE__) "): " + +// Macros to log messages for debug builds. +// For release builds these will not output anything and no code or data will be generate (except for error logging) +#define YQ_LOG_WARNING(a_fmt, ...) ((BT_IsRelease) ? ((void)0) : yqLogMessage(LL_Warning, YQ_LOCATION, YQ_FUNCTION_NAME, "warning: " a_fmt, ##__VA_ARGS__)) +#define YQ_LOG_DEBUG(a_fmt, ...) ((BT_IsRelease) ? ((void)0) : yqLogMessage(LL_Debug, YQ_LOCATION, YQ_FUNCTION_NAME, "debug: " a_fmt, ##__VA_ARGS__)) +#define YQ_LOG_INFO(a_fmt, ...) ((BT_IsRelease) ? ((void)0) : yqLogMessage(LL_Info, YQ_LOCATION, YQ_FUNCTION_NAME, "info: " a_fmt, ##__VA_ARGS__)) +#define YQ_LOG_ERROR(a_fmt, ...) yqLogMessage(LL_Error, YQ_LOCATION, YQ_FUNCTION_NAME, "error: " a_fmt, ##__VA_ARGS__) + +// Macros that only generate log code in verbose debug builds. The logging logs the entry to API calls and the exit from them and what return code is being returned. +// In debug and release, on errors, the code always calls the installed or default error handling routine +enum { YQ_VERBOSE_DEBUGGING = 0 }; +#define YQ_API_VERBOSE(msg) ((YQ_VERBOSE_DEBUGGING) ? YQ_LOG_DEBUG(msg) : ((void)0)) +#define YQ_API_ERROR(result, ...) (yqHandleError(result, YQ_LOCATION, YQ_FUNCTION_NAME, __VA_ARGS__), YQ_LOG_ERROR("Leave: " #result " (%i)", result), result) +#define YQ_API_ENTER() YQ_API_VERBOSE("Enter") +#define YQ_API_LEAVE(result, ...) return ((result == R_Okay) ? (YQ_API_VERBOSE("Leave"), result) : YQ_API_ERROR(result, "" __VA_ARGS__)) + +// Macros to do common parameter and precondition checking for API function calls +// This macro checks the pre-condition is met, if it doesn't then returns the error result and executes the provided cleanup code +// See also 'finally'. Probably during pre-condition checking, there shouldn't be any cleanup yet as no work should be done at this stage. +//#define YQ_API_PRECONDITION_CLEANUP(condition,result,cleanup) do { if (!(condition)) { cleanup; YQ_API_LEAVE(result); } } while (0,0) +#define YQ_API_PRECONDITION(condition,result) do { if (!(condition)) { YQ_API_LEAVE(result, #condition); } } while (0,0)//YQ_API_PRECONDITION_CLEANUP(condition,result, ((void)0)) +#define YQ_API_PRECONDITION_NULL(...) YQ_NULL_CHECK_IMPL_N(__VA_ARGS__) + +// Can redirect to the user heap manager routines if provided +#define YQ_ALLOC(type) ((type*)(yqAllocateMemory(sizeof(type)))) +#define YQ_ALLOC_ARRAY(type,siz) ((type*)(yqAllocateMemory(sizeof(type)*(siz)))) +#define YQ_REALLOC_ARRAY(ptr,type,siz) ((type*)(yqReallocateMemory(ptr,sizeof(type)*(siz)))) +#define YQ_FREE(ptr) (((ptr)) ? (void)(yqFreeMemory((void*)(ptr)), (ptr) = nullptr) : (void)0) + +// C++ wrappers for using the custom allocators to replace use of new / delete (does placement new with custom allocated memory) +// Note: The custom allocators are expected to return null if the allocation fails and not throw. The macros below do not check this. +#define YQ_NEW(type) (new(YQ_ALLOC(type)) type) +#define YQ_NEW_PARAMS(type, params) (new(YQ_ALLOC(type)) type params) +#define YQ_DELETE(obj,type) ((obj) ? (void)((obj)->~type(), YQ_FREE(obj)) : (void)0) + + +/* + +Example usage: + +Result SomeFunction(T1 **Param1, T2 *Param2, T3 *Param3, int Param4) +{ + YQ_API_ENTER(); + + // Check pre-conditions + YQ_API_PRECONDITION_NULL(Param1, Param2, Param3); + YQ_API_PRECONDITION(Param4 == 42, R_InvalidParameter); + + if (errorHappened) + YQ_API_LEAVE(R_Failure); + + YQ_API_LEAVE(R_Okay); +} + +*/ + + +#endif // MACROS_H diff --git a/Framework/NonCopyable.h b/Framework/NonCopyable.h index 3881321..b8cb274 100644 --- a/Framework/NonCopyable.h +++ b/Framework/NonCopyable.h @@ -3,6 +3,62 @@ /// XXX: Not sure but maybe there is a C++11 std::non_copyable that does the same as this +/// Actually C++11 has the additional ability to add '= delete' to a member to say it can be used. + +#ifdef HAVE_CPP11 +# define DELETE = delete +#else +# define DELETE +#endif + + +/// This way has the advantage of not adding to the size of the base class if that is important [1] +/// [1] Ref: http://trac.webkit.org/changeset/68414 +#define MAKE_NONCOPYABLE(ClassName) \ + private: \ + ClassName(const ClassName&) DELETE; \ + ClassName& operator=(const ClassName&) DELETE; \ + + +/* Utility classes */ +struct NoCopy +{ +public: + NoCopy() {} +private: + NoCopy(const NoCopy &) DELETE; +}; + +struct NoAssign +{ +private: + NoAssign &operator=(const NoAssign &) DELETE; +}; + +struct NonInstantiable +{ +private: + NonInstantiable() DELETE; +}; + +struct NoCopyAssign : NoCopy, NoAssign +{ +}; + +typedef NoCopyAssign NoAssignCopy; + +// Not tested +struct NoMove +{ +public: + NoMove() {} +private: + NoMove(const NoMove &&) DELETE; +}; + +struct NoMoveCopyAssign : NoMove, NoCopy, NoAssign +{ +}; /// @@ -10,9 +66,9 @@ /// /// Making a class NonCopyable means preventing objects of the class being copied. This is useful if /// the class manages a resource that can only be held by one object such that it can't be reference -/// counted, or it is the underlying object that becomes reference counted. +/// counted, or it is the underlying object that becomes reference counted. Idea from here [1]. /// -/// Idea comes from here: http://stackoverflow.com/questions/9458741/with-explicitly-deleted-member-functions-in-c11-is-it-still-worthwhile-to-inh +/// [1] Ref: http://stackoverflow.com/questions/9458741/with-explicitly-deleted-member-functions-in-c11-is-it-still-worthwhile-to-inh /// class NonCopyable { @@ -34,12 +90,19 @@ MyNonCopyableClass() {} }; + MyNonCopyableClass create() + { + MyNonCopyableClass var; + return var; + } + Now it is not possible to copy objects of this type without a compiler error: - void function() + void function() { - MyNonCopyableClass obj1; - MyNonCopyableClass obj2; + MyNonCopyableClass obj1; + MyNonCopyableClass obj2; + MyNonCopyableClass obj3 = create(); // Allowed due to move semantics in c++11, use NoMoveCopyAssign to prevent // These attempts to make a copy of obj1 will cause a compiler error MyNonCopyableClass obj3(obj1); diff --git a/Framework/ObjectFilter.cpp b/Framework/ObjectFilter.cpp new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/Framework/ObjectFilter.cpp diff --git a/Framework/ObjectInspector.cpp b/Framework/ObjectInspector.cpp new file mode 100644 index 0000000..91b6726 --- /dev/null +++ b/Framework/ObjectInspector.cpp @@ -0,0 +1,90 @@ +#include "ObjectInspector.h" + +#include "ObjectPropertyModel.h" +#include "lib/ObjectProxy.h" + +#include +#include +#include +#include +#include +#include + +ObjectInspector::ObjectInspector(QWidget* parent) +: QWidget(parent) +{ + QVBoxLayout* layout = new QVBoxLayout(this); + layout->setMargin(0); + + m_nameLabel = new QLabel(this); + layout->addWidget(m_nameLabel); + + m_propertyView = new QTableView(this); + m_propertyView->verticalHeader()->setVisible(false); + m_propertyView->horizontalHeader()->setStretchLastSection(true); + + QHBoxLayout* filterLayout = new QHBoxLayout; + QLabel* filterLabel = new QLabel(tr("Filter:"),this); + m_propertyFilterEdit = new QLineEdit(this); + m_propertyFilterEdit->setToolTip(tr("String or regular expression to filter property list with")); + + connect(m_propertyFilterEdit,SIGNAL(textChanged(QString)), + this,SLOT(changeFilter(QString))); + filterLayout->addWidget(filterLabel); + filterLayout->addWidget(m_propertyFilterEdit); + + m_model = new ObjectPropertyModel(this); + m_propertySortModel = new QSortFilterProxyModel(this); + m_propertySortModel->setSourceModel(m_model); + + m_propertyView->setModel(m_propertySortModel); + + layout->addWidget(m_propertyView); + layout->addLayout(filterLayout); + + setObject(ObjectProxy::Pointer()); +} + +QString ObjectInspector::formatAddress(void* ptr) +{ + quintptr intPtr = reinterpret_cast(ptr); + return QString("0x%1").arg(QString(QByteArray::number(intPtr,16))); +} + +void ObjectInspector::refresh() +{ + setObject(m_currentObject); +} + +void ObjectInspector::setObject(ObjectProxy::Pointer object) +{ + QString labelText; + + if (object) + { + labelText = QString("%1 [%2] (%3)") + .arg(object->className()) + .arg(object->objectName()) + .arg(formatAddress(reinterpret_cast(object->address()))); + } + else + { + labelText = tr("[No Object Selected]"); + } + + m_nameLabel->setText(labelText); + m_currentObject = object; + m_model->setObject(object); + m_propertySortModel->sort(0,Qt::AscendingOrder); +} + +ObjectProxy::Pointer ObjectInspector::object() const +{ + return m_currentObject; +} + +void ObjectInspector::changeFilter(const QString& pattern) +{ + m_propertySortModel->setFilterRegExp(QRegExp(pattern)); +} + diff --git a/Framework/ObjectInspector.h b/Framework/ObjectInspector.h new file mode 100644 index 0000000..e1ce3ac --- /dev/null +++ b/Framework/ObjectInspector.h @@ -0,0 +1,40 @@ +#pragma once + +#include "lib/ObjectProxy.h" + +#include + +class QLabel; +class QLineEdit; +class QTableView; +class QSortFilterProxyModel; + +class ObjectPropertyModel; + +class ObjectInspector : public QWidget +{ + Q_OBJECT + + public: + ObjectInspector(QWidget* parent); + + void setObject(ObjectProxy::Pointer object); + ObjectProxy::Pointer object() const; + + static QString formatAddress(void* ptr); + + public Q_SLOTS: + void refresh(); + + private Q_SLOTS: + void changeFilter(const QString& pattern); + + private: + QLabel* m_nameLabel; + QTableView* m_propertyView; + ObjectProxy::Pointer m_currentObject; + ObjectPropertyModel* m_model; + QSortFilterProxyModel* m_propertySortModel; + QLineEdit* m_propertyFilterEdit; +}; + diff --git a/Framework/ObjectPropertyModel.cpp b/Framework/ObjectPropertyModel.cpp new file mode 100644 index 0000000..6e4d4f7 --- /dev/null +++ b/Framework/ObjectPropertyModel.cpp @@ -0,0 +1,91 @@ +#include "ObjectPropertyModel.h" + +#include "lib/ObjectProxy.h" + +#include +#include + +ObjectPropertyModel::ObjectPropertyModel(QObject* parent) +: QStandardItemModel(parent) +{ + setColumnCount(2); + setObject(ObjectProxy::Pointer()); + + connect(this,SIGNAL(itemChanged(QStandardItem*)), + this,SLOT(updatePropertyValue(QStandardItem*))); +} + +QVariant ObjectPropertyModel::toEditValue(const QVariant& value) +{ + // TODO - Find existing code which can handle display and editing + // of common value types. + switch (value.userType()) + { + case QVariant::Size: + case QVariant::SizeF: + { + QSize size = value.toSize(); + return QString("%1 x %2").arg(size.width()).arg(size.height()); + } + case QVariant::Rect: + case QVariant::RectF: + { + QRect rect = value.toRect(); + return QString("(%1,%2) (%3,%4)").arg(rect.left()).arg(rect.top()).arg(rect.width()).arg(rect.height()); + } + } + return value; +} + +void ObjectPropertyModel::updatePropertyValue(QStandardItem* item) +{ + QStandardItem* propertyItem = this->item(item->row(),0); + QString propertyName = propertyItem->text(); + + if (m_object) + { + QVariant newValue = item->data(Qt::EditRole); + qDebug() << "setting property" << propertyName << "to" << newValue; + m_object->writeProperty(propertyName,newValue); + } +} + +void ObjectPropertyModel::setObject(ObjectProxy::Pointer object) +{ + clear(); + + setHorizontalHeaderLabels( + QStringList() << tr("Property") << tr("Value") + ); + + m_object = object; + + if (object) + { + object->refresh(); + int rowCount = 0; + QListIterator iter(object->properties()); + while (iter.hasNext()) + { + ObjectProxy::Property property = iter.next(); + QVariant value = property.value; + QVariant editValue = toEditValue(value); + + QStandardItem* nameItem = new QStandardItem(property.name); + nameItem->setEditable(false); + + QStandardItem* valueItem = new QStandardItem; + valueItem->setData(editValue,Qt::EditRole); + valueItem->setEditable(property.isWritable); + + insertRow(rowCount,QList() << nameItem << valueItem); + ++rowCount; + } + } +} + +ObjectProxy::Pointer ObjectPropertyModel::object() const +{ + return m_object; +} + diff --git a/Framework/ObjectPropertyModel.h b/Framework/ObjectPropertyModel.h new file mode 100644 index 0000000..69e176c --- /dev/null +++ b/Framework/ObjectPropertyModel.h @@ -0,0 +1,27 @@ +#pragma once + +#include "lib/ObjectProxy.h" + +#include +#include + +class ObjectPropertyModel : public QStandardItemModel +{ + Q_OBJECT + + public: + ObjectPropertyModel(QObject* parent); + + void setObject(ObjectProxy::Pointer object); + ObjectProxy::Pointer object() const; + + static QVariant toEditValue(const QVariant& value); + + private Q_SLOTS: + void updatePropertyValue(QStandardItem* item); + + private: + ObjectProxy::Pointer m_object; +}; + + diff --git a/Framework/ObjectTreeModel.cpp b/Framework/ObjectTreeModel.cpp new file mode 100644 index 0000000..5d7bbfc --- /dev/null +++ b/Framework/ObjectTreeModel.cpp @@ -0,0 +1,135 @@ +#include "ObjectTreeModel.h" +#include + + +ObjectTreeModel::ObjectTreeModel(QObject* parent) +: QAbstractItemModel(parent) +{ +} + +int ObjectTreeModel::columnCount(const QModelIndex&) const +{ + return 1; +} + +int ObjectTreeModel::rowCount(const QModelIndex& parent) const +{ + QObject* obj = static_cast(parent.internalPointer()); + return (obj) ? obj->children().count() : m_roots.count(); +} + +void ObjectTreeModel::setRootObjects(const QList& roots) +{ + beginResetModel(); + m_roots = roots; + endResetModel(); +} + +QModelIndex ObjectTreeModel::index(int row, int column, const QModelIndex& parent) const +{ + QObject* parentItem = static_cast(parent.internalPointer()); + const QObjectList& children = (parentItem) ? parentItem->children() : m_roots; + bool outOfBounds = (row < 0 || column != 0 || row >= children.count()); + return outOfBounds ? QModelIndex() : createIndex(row,column,children.at(row)); +} + +int ObjectTreeModel::indexInParent(QObject* item) const +{ + QObject* parent = item->parent(); + return (parent && !m_roots.contains(item)) ? parent->children().indexOf(item) : m_roots.indexOf(item); +} + +QModelIndex ObjectTreeModel::parent(const QModelIndex& index) const +{ + QObject* item = static_cast(index.internalPointer()); + QObject* parent = (item && !m_roots.contains(item)) ? item->parent() : 0; + return (parent) ? createIndex(indexInParent(parent),index.column(),parent) : QModelIndex(); +} + +QVariant ObjectTreeModel::data(const QModelIndex& index, int role) const +{ + QObject* item = static_cast(index.internalPointer()); + return (role == Qt::DisplayRole) ? ((item) ? displayText(item) : "[deleted]") : QVariant(); +} + +QString ObjectTreeModel::displayText(QObject* object) const +{ + QString text = object->metaObject()->className(); + if (!object->objectName().isEmpty()) + { + text += QString("[%1]").arg(object->objectName()); + } + return text; +} + +QVariant ObjectTreeModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (section == 0 && orientation == Qt::Horizontal && role == Qt::DisplayRole) + { + return tr("Object"); + } + return QAbstractItemModel::headerData(section,orientation,role); +} + +QObject* ObjectTreeModel::index(ObjPtr object, const QList& items) const +{ + //inline QList findChildren(const QRegularExpression &re, Qt::FindChildOptions options = Qt::FindChildrenRecursively) const + Q_FOREACH(QObject* item, items) + { + if (item == object) + return item; + QObject* match = index(object, item->children()); + if (match) + return match; + } + return 0; +} + +QModelIndex ObjectTreeModel::index(ObjPtr object) const +{ + QObject* item = index(object, m_roots); + return (item) ? createIndex(indexInParent(item),0,item) : QModelIndex(); +} + + + + +/* +bool ObjectTreeModel::matches(ObjPtr object, const QString& query) const +{ + return displayText(object).contains(query,Qt::CaseInsensitive); +} + +void ObjectTreeModel::search(QList* hits, ObjectItem* item, const QString& query) const +{ + ObjPtr object = item;//->object; + if (!object) + { + return; + } + if (matches(object,query)) + { + *hits << object; + } + Q_FOREACH(ObjectItem* child, item->children()) + { + search(hits,child,query); + } +} + +QList ObjectTreeModel::search(const QString& query) const +{ + if (query.isEmpty()) + { + return QList(); + } + + QList matches; + Q_FOREACH(ObjectItem* item, m_roots) + { + search(&matches,item,query); + } + return matches; +} + +*/ diff --git a/Framework/ObjectTreeModel.h b/Framework/ObjectTreeModel.h new file mode 100644 index 0000000..4b50d8a --- /dev/null +++ b/Framework/ObjectTreeModel.h @@ -0,0 +1,47 @@ +#pragma once + +#include +#include +#include + + +class TrackedObject +{ +public: + TrackedObject() {} + TrackedObject(QObject* a_obj) : m_object(a_obj) {} + operator bool() { return !m_object.isNull(); } + operator QObject*() { return m_object.data(); } +private: + QPointer m_object; +}; + + +typedef QObject* ObjPtr; +//typedef TrackedObject ObjPtr; + + +class ObjectTreeModel : public QAbstractItemModel +{ + Q_OBJECT +public: + ObjectTreeModel(QObject* parent); + //void setFilter(ObjectFilter *filter); + //QList search(const QString& query) const; + void setRootObjects(const QList& roots); + QModelIndex index(ObjPtr object) const; + virtual QVariant headerData(int section, Qt::Orientation orientation, int role) const; + virtual int columnCount(const QModelIndex& parent) const; + virtual int rowCount(const QModelIndex& parent) const; + virtual QModelIndex index(int row, int column, const QModelIndex& parent) const; + virtual QModelIndex parent(const QModelIndex& index) const; + virtual QVariant data(const QModelIndex& index, int role) const; +private: + int indexInParent(QObject* item) const; + QObject* index(ObjPtr object, const QList& items) const; + //bool matches(ObjPtr object, const QString& query) const; + //void search(QList* matches, ObjectItem* item, const QString& query) const; + QString displayText(QObject* object) const; + QList m_roots; +}; + diff --git a/Framework/SystemInfomation.cpp b/Framework/SystemInfomation.cpp new file mode 100644 index 0000000..3f09248 --- /dev/null +++ b/Framework/SystemInfomation.cpp @@ -0,0 +1,132 @@ +#include "SystemInfomation.h" +#include "../Util.h" +#include +#include +#include + + +#ifdef __APPLE__ +# include "TargetConditionals.h" +#elif defined(_WIN32) +# include +#else +# include +# include +# include +# include +#endif + + +void getSystemInfo(SystemInfo& a_info) +{ + a_info.m_cpuType = "unknown"; + a_info.m_osType = "unknown"; + a_info.m_osVersion = "unknown"; + +#ifdef _WIN32 + a_info.m_osType = "Windows"; + + // Get RAM size + MEMORYSTATUSEX status; + status.dwLength = sizeof(status); + GlobalMemoryStatusEx(&status); + a_info.m_ramSize = status.ullTotalPhys; + + // Get OS version + const OSVERSIONINFO osver = winOsVersion(); + a_info.m_osVersion.reserve(512); + snprintf(a_info.m_osVersion.data(), "%i.%i.%i", osver.dwMajorVersion, osver.dwMinorVersion, osver.dwBuildNumber); + + // Get OS arch + SYSTEM_INFO info; + GetNativeSystemInfo(&info); +# ifdef _WIN64 + //if (info.wProcessorArchitecture == ???) + a_info.m_cpuType = "x86_64"; +# else + a_info.m_cpuType = "x86"; +# endif +#else + long pages = sysconf(_SC_PHYS_PAGES); + long page_size = sysconf(_SC_PAGE_SIZE); + a_info.m_ramSize = pages * page_size; + struct utsname u; + long ret = uname(&u); + if (ret != -1) + { + a_info.m_cpuType = u.machine; + a_info.m_osVersion = u.sysname + std::string(" ") + u.release; + } +# ifdef __APPLE__ + // Get RAM Size + int mib[2] = { CTL_HW, HW_MEMSIZE }; + size_t len = sizeof(uint64_t); + sysctl(mib, sizeof(mib) / sizeof(mib[0]), &u.ramSize, &len, NULL, 0); +# if TARGET_OS_IPHONE + a_info.m_osType = "iOS"; +# elif TARGET_OS_MAC + const char *macCodeName = ""; + switch (int(MacintoshVersion)) { + case MV_CHEETAH: macCodeName = " Cheetah"; break; + case MV_PUMA: macCodeName = " Puma"; break; + case MV_JAGUAR: macCodeName = " Jaguar"; break; + case MV_PANTHER: macCodeName = " Panther"; break; + case MV_TIGER: macCodeName = " Tiger"; break; + case MV_LEOPARD: macCodeName = " Leopard"; break; + case MV_SNOWLEOPARD: macCodeName = " Snow Leopard"; break; + case MV_LION: macCodeName = " Lion"; break; + case MV_MOUNTAINLION:macCodeName = " Mountain Lion"; break; + case MV_MAVERICKS: macCodeName = " Mavericks"; break; + case MV_YOSEMITE: macCodeName = " Yosemite"; break; + } + a_info.m_osType = "Mac OS X" + std::string(macCodeName); +# endif +# elif __ANDROID__ + a_info.m_osType = "Android"; +# elif __linux + // system("lsb_release -sd"); + FILE* f = fopen("/etc/lsb-release", "rt"); + char dist[64] = { 0 }; + if (f) { + char buf[128]; + while (fgets(buf, 128, f) != 0) + if (sscanf(buf, "DISTRIB_DESCRIPTION=%64c", dist) == 1) + break; + if (dist[0]) { + std::vector strBits = split(dist, '"'); + a_info.m_osType = strBits[(strBits.size()==3)?1:0]; + } else { + a_info.m_osType = "non-specific LSB"; + } + fclose(f); + } else { + a_info.m_osType = "non-LSB"; + } +# else + a_info.m_osType = "Generic UNIX"; +# endif +#endif + + // TODO: improve the CPU detection using libcpuid + + // Make human readable RAM size string + const char* humanUnitStrings[] = { " bytes", " KB", " MB", " GB", " TB" }; + int ramUnit = 0; + uint64_t ramSize = a_info.m_ramSize; + while (ramSize > 1024 && ramUnit < (YQ_ARRAY_SIZE(humanUnitStrings)-1)) + ramUnit++, ramSize /= 1000; + a_info.m_ramSizeStr = std::to_string(ramSize) + humanUnitStrings[ramUnit]; + + // Obtain the screen properties + QScreen *s = QApplication::screens()[0]; + int refreshRate = (s) ? s->refreshRate() : 0; + int depth = (s) ? s->depth() : 0; + a_info.m_resolutionWidth = (s) ? s->geometry().width() : 0; + a_info.m_resolutionHeight = (s) ? s->geometry().height() : 0; + a_info.m_resolutionStr = std::to_string(a_info.m_resolutionWidth) + "x" + + std::to_string(a_info.m_resolutionHeight) + " " + + std::to_string(depth) + "bit " + + "@" + std::to_string(refreshRate) + "Hz"; + ; +} + diff --git a/Framework/SystemInfomation.h b/Framework/SystemInfomation.h new file mode 100644 index 0000000..2833313 --- /dev/null +++ b/Framework/SystemInfomation.h @@ -0,0 +1,26 @@ +#ifndef SYSTEM_INFOMATION_H +#define SYSTEM_INFOMATION_H + + +#include "Utilities.h" +#include +#include + + +struct SystemInfo +{ + std::string m_cpuType; + std::string m_osType; + std::string m_osVersion; + std::string m_ramSizeStr; // Human friendly string, eg: "4 GB" + std::string m_resolutionStr; + uint64_t m_ramSize; + uint32_t m_resolutionWidth; + uint32_t m_resolutionHeight; +}; + + +void getSystemInfo(SystemInfo& a_info); + + +#endif // SYSTEM_INFOMATION_H diff --git a/Framework/Tests.cpp b/Framework/Tests.cpp index 046c82c..2a2d78a 100644 --- a/Framework/Tests.cpp +++ b/Framework/Tests.cpp @@ -1,4 +1,5 @@ #include "Utilities.h" +#include "Array.h" #ifdef _MSC_VER diff --git a/Framework/Utilities.cpp b/Framework/Utilities.cpp index 8dbccf9..1e6acb9 100644 --- a/Framework/Utilities.cpp +++ b/Framework/Utilities.cpp @@ -11,7 +11,7 @@ static ResourceDeviceFactory s_defaultDeviceFactory; -Module g_currentModule{ false, &s_defaultErrorHandler, &s_defaultMessageLogger, &s_defaultHeapManager, &s_defaultDeviceFactory }; +Module g_currentModule{ false, "Main Module", &s_defaultErrorHandler, &s_defaultMessageLogger, &s_defaultHeapManager, &s_defaultDeviceFactory }; void yqDebugBreak() diff --git a/Framework/Utilities.h b/Framework/Utilities.h index cff9fcc..1f47a04 100644 --- a/Framework/Utilities.h +++ b/Framework/Utilities.h @@ -5,92 +5,7 @@ #include #include #include - - -/// -/// Various common macros -/// -#define YQ_ARRAY_SIZE(x) (sizeof(x)/sizeof((x)[0])) - -#define YQ_EXPAND(x) x - -#define YQ_STRINGIZE_IMPL(val) #val -#define YQ_STRINGIZE(val) YQ_STRINGIZE_IMPL(val) - -#define YQ_CONCATENATE_IMPL(x, y) x##y -#define YQ_CONCATENATE(x, y) YQ_CONCATENATE_IMPL(x, y) - -// If 0 args, it mis-reports a count of 1. Also it only counts up to 10 args. -#define YQ_ARG_COUNT_IMPL(X,A,B,C,D,E,F,G,H,I,J,N,...) N -#define YQ_ARG_COUNT(...) (YQ_EXPAND(YQ_ARG_COUNT_IMPL(_,__VA_ARGS__, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0))) - -#ifdef __COUNTER__ -# define YQ_ANONYMOUS_VARIABLE(str) YQ_CONCATENATE(str, __COUNTER__) -#else -# define YQ_ANONYMOUS_VARIABLE(str) YQ_CONCATENATE(str, __LINE__) -#endif - -#define YQ_UNUSED(x) std::ignore = (x) - -#ifdef __MINGW32__ -// Avoid callbacks -# define YQ_CALLBACK __cdecl -# define YQ_FUNCTION_NAME __PRETTY_FUNCTION__ // __func__ -#elif defined(_WIN32) -# define YQ_CALLBACK __cdecl -# define YQ_FUNCTION_NAME __FUNCTION__ -#else -# define YQ_CALLBACK -# define YQ_FUNCTION_NAME __PRETTY_FUNCTION__ // __func__ -#endif - -// Helper marcos for doing a variable number of null checks -#define YQ_NULL_CHECK_IMPL_1(arg) if (arg == nullptr) YQ_API_LEAVE(R_InvalidParameter); -#define YQ_NULL_CHECK_IMPL_2(arg,...) YQ_NULL_CHECK_IMPL_1(arg) YQ_NULL_CHECK_IMPL_1(__VA_ARGS__) -#define YQ_NULL_CHECK_IMPL_3(arg,...) YQ_NULL_CHECK_IMPL_1(arg) YQ_NULL_CHECK_IMPL_2(__VA_ARGS__) -#define YQ_NULL_CHECK_IMPL_4(arg,...) YQ_NULL_CHECK_IMPL_1(arg) YQ_NULL_CHECK_IMPL_3(__VA_ARGS__) -#define YQ_NULL_CHECK_IMPL_5(arg,...) YQ_NULL_CHECK_IMPL_1(arg) YQ_NULL_CHECK_IMPL_4(__VA_ARGS__) -#define YQ_NULL_CHECK_IMPL_N3(N, ...) YQ_NULL_CHECK_IMPL_ ## N(__VA_ARGS__) -#define YQ_NULL_CHECK_IMPL_N2(N, ...) YQ_NULL_CHECK_IMPL_N3(N, __VA_ARGS__) -#define YQ_NULL_CHECK_IMPL_N(...) YQ_NULL_CHECK_IMPL_N2(YQ_ARG_COUNT(__VA_ARGS__), __VA_ARGS__) - -// File name, line numbes, and function name are statically concatenated at compile time, with a reduced string sharing trade off -#define YQ_LOCATION __FILE__ "(" YQ_STRINGIZE(__LINE__) "): " - -// Macros to log messages for debug builds. -// For release builds these will not output anything and no code or data will be generate (except for error logging) -#define YQ_LOG_WARNING(a_fmt, ...) ((BT_IsRelease) ? ((void)0) : yqLogMessage(LL_Warning, YQ_LOCATION, YQ_FUNCTION_NAME, "warning: " a_fmt, ##__VA_ARGS__)) -#define YQ_LOG_DEBUG(a_fmt, ...) ((BT_IsRelease) ? ((void)0) : yqLogMessage(LL_Debug, YQ_LOCATION, YQ_FUNCTION_NAME, "debug: " a_fmt, ##__VA_ARGS__)) -#define YQ_LOG_INFO(a_fmt, ...) ((BT_IsRelease) ? ((void)0) : yqLogMessage(LL_Info, YQ_LOCATION, YQ_FUNCTION_NAME, "info: " a_fmt, ##__VA_ARGS__)) -#define YQ_LOG_ERROR(a_fmt, ...) yqLogMessage(LL_Error, YQ_LOCATION, YQ_FUNCTION_NAME, "error: " a_fmt, ##__VA_ARGS__) - -// Macros that only generate log code in verbose debug builds. The logging logs the entry to API calls and the exit from them and what return code is being returned. -// In debug and release, on errors, the code always calls the installed or default error handling routine -enum { YQ_VERBOSE_DEBUGGING = 0 }; -#define YQ_API_VERBOSE(msg) ((YQ_VERBOSE_DEBUGGING) ? YQ_LOG_DEBUG(msg) : ((void)0)) -#define YQ_API_ERROR(result, ...) (yqHandleError(result, YQ_LOCATION, YQ_FUNCTION_NAME, __VA_ARGS__), YQ_LOG_ERROR("Leave: " #result " (%i)", result), result) -#define YQ_API_ENTER() YQ_API_VERBOSE("Enter") -#define YQ_API_LEAVE(result, ...) return ((result == R_Okay) ? (YQ_API_VERBOSE("Leave"), result) : YQ_API_ERROR(result, "" __VA_ARGS__)) - -// Macros to do common parameter and precondition checking for API function calls -// This macro checks the pre-condition is met, if it doesn't then returns the error result and executes the provided cleanup code -// See also 'finally'. Probably during pre-condition checking, there shouldn't be any cleanup yet as no work should be done at this stage. -//#define YQ_API_PRECONDITION_CLEANUP(condition,result,cleanup) do { if (!(condition)) { cleanup; YQ_API_LEAVE(result); } } while (0,0) -#define YQ_API_PRECONDITION(condition,result) do { if (!(condition)) { YQ_API_LEAVE(result, #condition); } } while (0,0)//YQ_API_PRECONDITION_CLEANUP(condition,result, ((void)0)) -#define YQ_API_PRECONDITION_NULL(...) YQ_NULL_CHECK_IMPL_N(__VA_ARGS__) - -// Can redirect to the user heap manager routines if provided -#define YQ_ALLOC(type) ((type*)(yqAllocateMemory(sizeof(type)))) -#define YQ_ALLOC_ARRAY(type,siz) ((type*)(yqAllocateMemory(sizeof(type)*(siz)))) -#define YQ_REALLOC_ARRAY(ptr,type,siz) ((type*)(yqReallocateMemory(ptr,sizeof(type)*(siz)))) -#define YQ_FREE(ptr) (((ptr)) ? (void)(yqFreeMemory((void*)(ptr)), (ptr) = nullptr) : (void)0) - -// C++ wrappers for using the custom allocators to replace use of new / delete (does placement new with custom allocated memory) -// Note: The custom allocators are expected to return null if the allocation fails and not throw. The macros below do not check this. -#define YQ_NEW(type) (new(YQ_ALLOC(type)) type) -#define YQ_NEW_PARAMS(type, params) (new(YQ_ALLOC(type)) type params) -#define YQ_DELETE(obj,type) ((obj) ? (void)((obj)->~type(), YQ_FREE(obj)) : (void)0) - +#include "Macros.h" enum yqResult @@ -206,6 +121,7 @@ { public: bool m_initialized; + const char* m_moduleName; ErrorHandler* m_errorHandler; Logger* m_messageLogger; HeapManager* m_heapManager; @@ -216,111 +132,4 @@ extern Module g_currentModule; -// std::vector replacement that is cut down but familiar, uses the custom allocators, and restricted to POD elements only -template -struct PODArrayPOD // A dynamic array of POD type elements which is itself a POD type (so can have multi-dimensional arrays of these). -{ - static_assert(std::is_pod::value, "T must be a POD type."); - - void init() { m_magic = 0x01020304; m_capacity = m_size = 0; m_data = nullptr; } - void deinit() { if (m_data != nullptr) YQ_FREE(m_data); } - - const T& at(size_t a_pos) const { checkInited(); checkIndex(a_pos); return m_data[a_pos]; } - T& at(size_t a_pos) { checkInited(); checkIndex(a_pos); return m_data[a_pos]; } - const T& operator[](size_t a_pos) const { checkInited(); checkIndex(a_pos); return m_data[a_pos]; } - void pop_back() { checkInited(); checkIndex(1); m_size--; } - const T& back() const { checkInited(); return m_data[m_size - 1]; } - T& back() { checkInited(); return m_data[m_size - 1]; } - void push_back(const T& a_val) { checkInited(); if (m_size == m_capacity) ensureSpaceFor(1); m_data[m_size++] = a_val; } - void push(const T& a_val) { checkInited(); push_back(a_val); } - T pop() { checkInited(); pop_back(); return m_data[m_size]; } - void reserve(size_t a_count) { checkInited(); m_capacity = a_count; m_data = YQ_REALLOC_ARRAY(m_data, T, a_count); } - size_t capacity() const { checkInited(); return m_capacity; } - size_t size() const { checkInited(); return m_size; } - T* data() const { checkInited(); return m_data; } - void clear() { checkInited(); m_size = 0; } -//private: // Actually making private would make this no longer a POD type - yqResult checkInited() const { if (BT_IsDebug) YQ_API_PRECONDITION(m_magic == 0x01020304, R_Uninitialized); return R_Okay; } - yqResult checkIndex(size_t a_pos) const { YQ_API_PRECONDITION(a_pos < m_size, R_IndexOutOfRange); return R_Okay; } - void ensureSpaceFor(size_t a_count) { reserve(std::max(m_size + a_count, m_capacity + m_capacity / 2)); } // try to grow by 50% - T* m_data; // Because of this pointer it is not safe to copy DynamicArray objects around. Making it a shared_ptr though stops this being a POD type - size_t m_capacity; - size_t m_size; - uint32_t m_magic; -}; - - -static_assert(std::is_pod >::value, "PODArrayPOD must be a POD type."); - - -template -struct ArrayPOD // A dynamic array of POD type elements. It isn't POD itself, but is instead reference counted so safer to deal with. -{ - static_assert(std::is_pod::value, "T must be a POD type."); - - ArrayPOD& operator=(const ArrayPOD&) = delete; - ArrayPOD(const ArrayPOD&) = delete; - ArrayPOD() { m_capacity = m_size = 0; m_data = nullptr; } - ~ArrayPOD() { if (m_data != nullptr) YQ_FREE(m_data); } - - const T& at(size_t a_pos) const { checkIndex(a_pos); return m_data[a_pos]; } - T& at(size_t a_pos) { checkIndex(a_pos); return m_data[a_pos]; } - const T& operator[](size_t a_pos) const { checkIndex(a_pos); return m_data[a_pos]; } - void pop_back() { checkIndex(1); m_size--; } - const T& back() const { return m_data[m_size - 1]; } - T& back() { return m_data[m_size - 1]; } - void push_back(const T& a_val) { if (m_size == m_capacity) ensureSpaceFor(1); m_data[m_size++] = a_val; } - void push(const T& a_val) { push_back(a_val); } - T pop() { pop_back(); return m_data[m_size]; } - void reserve(size_t a_count) { m_capacity = a_count; m_data = YQ_REALLOC_ARRAY(m_data, T, a_count); } - size_t capacity() const { return m_capacity; } - size_t size() const { return m_size; } - T* data() const { return m_data; } - void clear() { m_size = 0; } -private: - yqResult checkIndex(size_t a_pos) const { YQ_API_PRECONDITION(a_pos < m_size, R_IndexOutOfRange); return R_Okay; } - void ensureSpaceFor(size_t a_count) { reserve(std::max(m_size + a_count, m_capacity + m_capacity / 2)); } // try to grow by 50% - T* m_data; // Because of this pointer it is not safe to copy DynamicArray objects around. Making it a shared_ptr though stops this being a POD type - size_t m_capacity; - size_t m_size; -}; - - -struct PODString -{ -public: - void init() { m_data.init(); } - void deinit() { m_data.deinit(); } - void operator=(const char* a_str) { m_data.clear(); operator+=(a_str); } - void operator+=(const char* a_str) { while (*a_str) m_data.push_back(*a_str++); m_data.push(0); m_data.pop(); } - char* c_str() { return m_data.data(); } -//private: // Actually making private would make this no longer a POD type - PODArrayPOD m_data; -}; - - -static_assert(std::is_pod::value, "PODString must be a POD type."); - - -/* - -Example usage: - -Result SomeFunction(T1 **Param1, T2 *Param2, T3 *Param3, int Param4) -{ - YQ_API_ENTER(); - - // Check pre-conditions - YQ_API_PRECONDITION_NULL(Param1, Param2, Param3); - YQ_API_PRECONDITION(Param4 == 42, R_InvalidParameter); - - if (errorHappened) - YQ_API_LEAVE(R_Failure); - - YQ_API_LEAVE(R_Okay); -} - -*/ - - #endif // UTILITIES_H diff --git a/Framework/WidgetInspector.cpp b/Framework/WidgetInspector.cpp new file mode 100644 index 0000000..19c38e6 --- /dev/null +++ b/Framework/WidgetInspector.cpp @@ -0,0 +1,174 @@ + +#include "WidgetInspector.h" + +#include "ObjectInspector.h" +#include "ObjectTreeModel.h" +#include "OutOfProcessClipboard.h" +#include "RootObjectList.h" +#include "WidgetInspectorShortcut.h" +#include "lib/DirectWidgetPicker.h" +#include "lib/WidgetPicker.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +WidgetInspector::WidgetInspector(RootObjectList* rootList, QWidget* parent) +: QWidget(parent) +, m_rootList(rootList) +, m_picker(0) +, m_externalClipboard(new OutOfProcessClipboard(this)) +{ + setWindowTitle(tr("Widget Inspector")); + + m_objectModel = new ObjectTreeModel(this); + m_objectTree = new QTreeView(this); + m_objectTree->header()->setVisible(false); + m_objectTree->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel); + m_objectTree->setModel(m_objectModel); + +#if QT_VERSION >= QT_VERSION_CHECK(5,0,0) + m_objectTree->header()->setSectionResizeMode(0,QHeaderView::ResizeToContents); +#else + m_objectTree->header()->setResizeMode(0,QHeaderView::ResizeToContents); +#endif + + m_objectTree->header()->setStretchLastSection(false); + + connect(m_objectTree->selectionModel(),SIGNAL(currentChanged(QModelIndex,QModelIndex)), + this,SLOT(selectionChanged(QModelIndex,QModelIndex))); + + m_objectInspector = new ObjectInspector(this); + + QVBoxLayout* layout = new QVBoxLayout(this); + + QSplitter* topSplitter = new QSplitter(this); + topSplitter->addWidget(m_objectTree); + topSplitter->addWidget(m_objectInspector); + layout->addWidget(topSplitter); + + QHBoxLayout* searchLayout = new QHBoxLayout; + searchLayout->addWidget(new QLabel(tr("Search:"),this)); + + QLineEdit* searchEdit = new QLineEdit(this); + connect(searchEdit,SIGNAL(textChanged(QString)),this,SLOT(search(QString))); + searchLayout->addWidget(searchEdit); + layout->addLayout(searchLayout); + + QPushButton* copyToDebuggerButton = new QPushButton(tr("Copy Reference"),this); + connect(copyToDebuggerButton,SIGNAL(clicked()), + this,SLOT(copyDebuggerReference())); + + m_pickButton = new QPushButton(tr("Pick Widget"),this); + setWidgetPicker(new DirectWidgetPicker(this)); + + QPushButton* refreshButton = new QPushButton(tr("Refresh Tree"),this); + connect(refreshButton,SIGNAL(clicked()),this,SLOT(refreshTree())); + + QPushButton* refreshObjectButton = new QPushButton(tr("Refresh Object"),this); + connect(refreshObjectButton,SIGNAL(clicked()),m_objectInspector,SLOT(refresh())); + + QHBoxLayout* actionLayout = new QHBoxLayout; + actionLayout->addStretch(); + actionLayout->addWidget(m_pickButton); + actionLayout->addWidget(copyToDebuggerButton); + actionLayout->addWidget(refreshButton); + actionLayout->addWidget(refreshObjectButton); + layout->addLayout(actionLayout); + + resize(700,400); + + refreshTree(); +} + +void WidgetInspector::pickerFinished(ObjectProxy::Pointer widget) +{ + // TODO - Re-implement widget picker + select(widget); +} + +void WidgetInspector::copyDebuggerReference() +{ + ObjectProxy::Pointer object = m_objectInspector->object(); + if (!object) + { + return; + } + + void* address = reinterpret_cast(object->address()); + QString reference = QString("(%1*)(%2)") + .arg(object->className()) + .arg(ObjectInspector::formatAddress(address)); + +#ifdef Q_OS_LINUX + m_externalClipboard->setText(reference); +#else + QApplication::clipboard()->setText(reference); +#endif +} + +void WidgetInspector::selectionChanged(const QModelIndex& current, const QModelIndex& previous) +{ + Q_UNUSED(previous); + m_objectInspector->setObject(ObjectTreeModel::objectFromIndex(current)); +} + +void WidgetInspector::refreshTree() +{ + QModelIndex current = m_objectTree->selectionModel()->currentIndex(); + ObjectProxy::Pointer currentObject = ObjectTreeModel::objectFromIndex(current); + m_objectModel->setRootObjects(m_rootList->rootObjects()); + if (currentObject) + { + select(currentObject); + } +} + +void WidgetInspector::search(const QString& query) +{ + QList results = m_objectModel->search(query); + if (!results.isEmpty()) + { + select(results.first()); + } +} + +void WidgetInspector::select(ObjectProxy::Pointer object) +{ + QModelIndex index = m_objectModel->index(object); + if (!index.isValid()) + { + // if no matching object is found in the model, then try refreshing + // the model and searching again + refreshTree(); + index = m_objectModel->index(object); + } + + m_objectTree->scrollTo(index); + m_objectTree->selectionModel()->setCurrentIndex(index,QItemSelectionModel::SelectCurrent); +} + +void WidgetInspector::registerGlobalShortcut(const QKeySequence& key, QWidget* parentWidget) +{ + WidgetInspectorShortcut* shortcut = new WidgetInspectorShortcut(parentWidget); + shortcut->setKey(key); + shortcut->setContext(Qt::ApplicationShortcut); +} + +void WidgetInspector::setWidgetPicker(WidgetPicker* picker) +{ + delete m_picker; + picker->setParent(this); + m_picker = picker; + connect(m_picker,SIGNAL(widgetPicked(ObjectProxy::Pointer)),this,SLOT(pickerFinished(ObjectProxy::Pointer))); + connect(m_pickButton,SIGNAL(clicked()),m_picker,SLOT(start())); +} + diff --git a/Framework/Array.h b/Framework/Array.h new file mode 100644 index 0000000..ab83e77 --- /dev/null +++ b/Framework/Array.h @@ -0,0 +1,98 @@ +#ifndef ARRAY_H +#define ARRAY_H + + +#include "Utilities.h" + + +// std::vector replacement that is cut down but familiar. +// It uses the custom allocators, and is restricted to POD elements only +template +struct PODArrayPOD // A dynamic array of POD type elements which is itself a POD type (so can have multi-dimensional arrays of these). +{ + static_assert(std::is_pod::value, "T must be a POD type."); + + void init() { m_magic = 0x01020304; m_capacity = m_size = 0; m_data = nullptr; } + void deinit() { if (m_data != nullptr) YQ_FREE(m_data); } + + const T& at(size_t a_pos) const { checkInited(); checkIndex(a_pos); return m_data[a_pos]; } + T& at(size_t a_pos) { checkInited(); checkIndex(a_pos); return m_data[a_pos]; } + const T& operator[](size_t a_pos) const { checkInited(); checkIndex(a_pos); return m_data[a_pos]; } + void pop_back() { checkInited(); checkIndex(1); m_size--; } + const T& back() const { checkInited(); return m_data[m_size - 1]; } + T& back() { checkInited(); return m_data[m_size - 1]; } + void push_back(const T& a_val) { checkInited(); if (m_size == m_capacity) ensureSpaceFor(1); m_data[m_size++] = a_val; } + void push(const T& a_val) { checkInited(); push_back(a_val); } + T pop() { checkInited(); pop_back(); return m_data[m_size]; } + void reserve(size_t a_count) { checkInited(); m_capacity = a_count; m_data = YQ_REALLOC_ARRAY(m_data, T, a_count); } + size_t capacity() const { checkInited(); return m_capacity; } + size_t size() const { checkInited(); return m_size; } + T* data() const { checkInited(); return m_data; } + void clear() { checkInited(); m_size = 0; } +//private: // Actually making private would make this no longer a POD type + yqResult checkInited() const { if (BT_IsDebug) YQ_API_PRECONDITION(m_magic == 0x01020304, R_Uninitialized); return R_Okay; } + yqResult checkIndex(size_t a_pos) const { YQ_API_PRECONDITION(a_pos < m_size, R_IndexOutOfRange); return R_Okay; } + void ensureSpaceFor(size_t a_count) { reserve(std::max(m_size + a_count, m_capacity + m_capacity / 2)); } // try to grow by 50% + T* m_data; // Because of this pointer it is not safe to copy DynamicArray objects around. Making it a shared_ptr though stops this being a POD type + size_t m_capacity; + size_t m_size; + uint32_t m_magic; +}; + + +static_assert(std::is_pod >::value, "PODArrayPOD must be a POD type."); + + +// A dynamic array of POD type elements. +// It isn't POD itself, but is instead reference counted so safer to deal with. +template +struct ArrayPOD +{ + static_assert(std::is_pod::value, "T must be a POD type."); + + // Actually not reference counted yet, but safe as objects of this type can't be copied + ArrayPOD& operator=(const ArrayPOD&) = delete; + ArrayPOD(const ArrayPOD&) = delete; + ArrayPOD() { m_capacity = m_size = 0; m_data = nullptr; } + ~ArrayPOD() { if (m_data != nullptr) YQ_FREE(m_data); } + + const T& at(size_t a_pos) const { checkIndex(a_pos); return m_data[a_pos]; } + T& at(size_t a_pos) { checkIndex(a_pos); return m_data[a_pos]; } + const T& operator[](size_t a_pos) const { checkIndex(a_pos); return m_data[a_pos]; } + void pop_back() { checkIndex(1); m_size--; } + const T& back() const { return m_data[m_size - 1]; } + T& back() { return m_data[m_size - 1]; } + void push_back(const T& a_val) { if (m_size == m_capacity) ensureSpaceFor(1); m_data[m_size++] = a_val; } + void push(const T& a_val) { push_back(a_val); } + T pop() { pop_back(); return m_data[m_size]; } + void reserve(size_t a_count) { m_capacity = a_count; m_data = YQ_REALLOC_ARRAY(m_data, T, a_count); } + size_t capacity() const { return m_capacity; } + size_t size() const { return m_size; } + T* data() const { return m_data; } + void clear() { m_size = 0; } +private: + yqResult checkIndex(size_t a_pos) const { YQ_API_PRECONDITION(a_pos < m_size, R_IndexOutOfRange); return R_Okay; } + void ensureSpaceFor(size_t a_count) { reserve(std::max(m_size + a_count, m_capacity + m_capacity / 2)); } // try to grow by 50% + T* m_data; // Because of this pointer it is not safe to copy DynamicArray objects around. Making it a shared_ptr though stops this being a POD type + size_t m_capacity; + size_t m_size; +}; + + +struct PODString +{ +public: + void init() { m_data.init(); } + void deinit() { m_data.deinit(); } + void operator=(const char* a_str) { m_data.clear(); operator+=(a_str); } + void operator+=(const char* a_str) { while (*a_str) m_data.push_back(*a_str++); m_data.push(0); m_data.pop(); } + char* c_str() { return m_data.data(); } +//private: // Actually making private would make this no longer a POD type + PODArrayPOD m_data; +}; + + +static_assert(std::is_pod::value, "PODString must be a POD type."); + + +#endif // ARRAY_H diff --git a/Framework/Macros.h b/Framework/Macros.h new file mode 100644 index 0000000..6ca8c86 --- /dev/null +++ b/Framework/Macros.h @@ -0,0 +1,111 @@ +#ifndef MACROS_H +#define MACROS_H + + +// +// Various common macros +// +#define YQ_ARRAY_SIZE(x) (sizeof(x)/sizeof((x)[0])) + +#define YQ_EXPAND(x) x + +#define YQ_STRINGIZE_IMPL(val) #val +#define YQ_STRINGIZE(val) YQ_STRINGIZE_IMPL(val) + +#define YQ_CONCATENATE_IMPL(x, y) x##y +#define YQ_CONCATENATE(x, y) YQ_CONCATENATE_IMPL(x, y) + +// If 0 args, it mis-reports a count of 1. Also it only counts up to 10 args. +#define YQ_ARG_COUNT_IMPL(X,A,B,C,D,E,F,G,H,I,J,N,...) N +#define YQ_ARG_COUNT(...) (YQ_EXPAND(YQ_ARG_COUNT_IMPL(_,__VA_ARGS__, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0))) + +#ifdef __COUNTER__ +# define YQ_ANONYMOUS_VARIABLE(str) YQ_CONCATENATE(str, __COUNTER__) +#else +# define YQ_ANONYMOUS_VARIABLE(str) YQ_CONCATENATE(str, __LINE__) +#endif + +#define YQ_UNUSED(x) std::ignore = (x) + +#ifdef __MINGW32__ +// Avoid callbacks +# define YQ_CALLBACK __cdecl +# define YQ_FUNCTION_NAME __PRETTY_FUNCTION__ // __func__ +#elif defined(_WIN32) +# define YQ_CALLBACK __cdecl +# define YQ_FUNCTION_NAME __FUNCTION__ +#else +# define YQ_CALLBACK +# define YQ_FUNCTION_NAME __PRETTY_FUNCTION__ // __func__ +#endif + +// Helper marcos for doing a variable number of null checks +#define YQ_NULL_CHECK_IMPL_1(arg) if (arg == nullptr) YQ_API_LEAVE(R_InvalidParameter); +#define YQ_NULL_CHECK_IMPL_2(arg,...) YQ_NULL_CHECK_IMPL_1(arg) YQ_NULL_CHECK_IMPL_1(__VA_ARGS__) +#define YQ_NULL_CHECK_IMPL_3(arg,...) YQ_NULL_CHECK_IMPL_1(arg) YQ_NULL_CHECK_IMPL_2(__VA_ARGS__) +#define YQ_NULL_CHECK_IMPL_4(arg,...) YQ_NULL_CHECK_IMPL_1(arg) YQ_NULL_CHECK_IMPL_3(__VA_ARGS__) +#define YQ_NULL_CHECK_IMPL_5(arg,...) YQ_NULL_CHECK_IMPL_1(arg) YQ_NULL_CHECK_IMPL_4(__VA_ARGS__) +#define YQ_NULL_CHECK_IMPL_N3(N, ...) YQ_NULL_CHECK_IMPL_ ## N(__VA_ARGS__) +#define YQ_NULL_CHECK_IMPL_N2(N, ...) YQ_NULL_CHECK_IMPL_N3(N, __VA_ARGS__) +#define YQ_NULL_CHECK_IMPL_N(...) YQ_NULL_CHECK_IMPL_N2(YQ_ARG_COUNT(__VA_ARGS__), __VA_ARGS__) + +// File name, line numbes, and function name are statically concatenated at compile time, with a reduced string sharing trade off +#define YQ_LOCATION __FILE__ "(" YQ_STRINGIZE(__LINE__) "): " + +// Macros to log messages for debug builds. +// For release builds these will not output anything and no code or data will be generate (except for error logging) +#define YQ_LOG_WARNING(a_fmt, ...) ((BT_IsRelease) ? ((void)0) : yqLogMessage(LL_Warning, YQ_LOCATION, YQ_FUNCTION_NAME, "warning: " a_fmt, ##__VA_ARGS__)) +#define YQ_LOG_DEBUG(a_fmt, ...) ((BT_IsRelease) ? ((void)0) : yqLogMessage(LL_Debug, YQ_LOCATION, YQ_FUNCTION_NAME, "debug: " a_fmt, ##__VA_ARGS__)) +#define YQ_LOG_INFO(a_fmt, ...) ((BT_IsRelease) ? ((void)0) : yqLogMessage(LL_Info, YQ_LOCATION, YQ_FUNCTION_NAME, "info: " a_fmt, ##__VA_ARGS__)) +#define YQ_LOG_ERROR(a_fmt, ...) yqLogMessage(LL_Error, YQ_LOCATION, YQ_FUNCTION_NAME, "error: " a_fmt, ##__VA_ARGS__) + +// Macros that only generate log code in verbose debug builds. The logging logs the entry to API calls and the exit from them and what return code is being returned. +// In debug and release, on errors, the code always calls the installed or default error handling routine +enum { YQ_VERBOSE_DEBUGGING = 0 }; +#define YQ_API_VERBOSE(msg) ((YQ_VERBOSE_DEBUGGING) ? YQ_LOG_DEBUG(msg) : ((void)0)) +#define YQ_API_ERROR(result, ...) (yqHandleError(result, YQ_LOCATION, YQ_FUNCTION_NAME, __VA_ARGS__), YQ_LOG_ERROR("Leave: " #result " (%i)", result), result) +#define YQ_API_ENTER() YQ_API_VERBOSE("Enter") +#define YQ_API_LEAVE(result, ...) return ((result == R_Okay) ? (YQ_API_VERBOSE("Leave"), result) : YQ_API_ERROR(result, "" __VA_ARGS__)) + +// Macros to do common parameter and precondition checking for API function calls +// This macro checks the pre-condition is met, if it doesn't then returns the error result and executes the provided cleanup code +// See also 'finally'. Probably during pre-condition checking, there shouldn't be any cleanup yet as no work should be done at this stage. +//#define YQ_API_PRECONDITION_CLEANUP(condition,result,cleanup) do { if (!(condition)) { cleanup; YQ_API_LEAVE(result); } } while (0,0) +#define YQ_API_PRECONDITION(condition,result) do { if (!(condition)) { YQ_API_LEAVE(result, #condition); } } while (0,0)//YQ_API_PRECONDITION_CLEANUP(condition,result, ((void)0)) +#define YQ_API_PRECONDITION_NULL(...) YQ_NULL_CHECK_IMPL_N(__VA_ARGS__) + +// Can redirect to the user heap manager routines if provided +#define YQ_ALLOC(type) ((type*)(yqAllocateMemory(sizeof(type)))) +#define YQ_ALLOC_ARRAY(type,siz) ((type*)(yqAllocateMemory(sizeof(type)*(siz)))) +#define YQ_REALLOC_ARRAY(ptr,type,siz) ((type*)(yqReallocateMemory(ptr,sizeof(type)*(siz)))) +#define YQ_FREE(ptr) (((ptr)) ? (void)(yqFreeMemory((void*)(ptr)), (ptr) = nullptr) : (void)0) + +// C++ wrappers for using the custom allocators to replace use of new / delete (does placement new with custom allocated memory) +// Note: The custom allocators are expected to return null if the allocation fails and not throw. The macros below do not check this. +#define YQ_NEW(type) (new(YQ_ALLOC(type)) type) +#define YQ_NEW_PARAMS(type, params) (new(YQ_ALLOC(type)) type params) +#define YQ_DELETE(obj,type) ((obj) ? (void)((obj)->~type(), YQ_FREE(obj)) : (void)0) + + +/* + +Example usage: + +Result SomeFunction(T1 **Param1, T2 *Param2, T3 *Param3, int Param4) +{ + YQ_API_ENTER(); + + // Check pre-conditions + YQ_API_PRECONDITION_NULL(Param1, Param2, Param3); + YQ_API_PRECONDITION(Param4 == 42, R_InvalidParameter); + + if (errorHappened) + YQ_API_LEAVE(R_Failure); + + YQ_API_LEAVE(R_Okay); +} + +*/ + + +#endif // MACROS_H diff --git a/Framework/NonCopyable.h b/Framework/NonCopyable.h index 3881321..b8cb274 100644 --- a/Framework/NonCopyable.h +++ b/Framework/NonCopyable.h @@ -3,6 +3,62 @@ /// XXX: Not sure but maybe there is a C++11 std::non_copyable that does the same as this +/// Actually C++11 has the additional ability to add '= delete' to a member to say it can be used. + +#ifdef HAVE_CPP11 +# define DELETE = delete +#else +# define DELETE +#endif + + +/// This way has the advantage of not adding to the size of the base class if that is important [1] +/// [1] Ref: http://trac.webkit.org/changeset/68414 +#define MAKE_NONCOPYABLE(ClassName) \ + private: \ + ClassName(const ClassName&) DELETE; \ + ClassName& operator=(const ClassName&) DELETE; \ + + +/* Utility classes */ +struct NoCopy +{ +public: + NoCopy() {} +private: + NoCopy(const NoCopy &) DELETE; +}; + +struct NoAssign +{ +private: + NoAssign &operator=(const NoAssign &) DELETE; +}; + +struct NonInstantiable +{ +private: + NonInstantiable() DELETE; +}; + +struct NoCopyAssign : NoCopy, NoAssign +{ +}; + +typedef NoCopyAssign NoAssignCopy; + +// Not tested +struct NoMove +{ +public: + NoMove() {} +private: + NoMove(const NoMove &&) DELETE; +}; + +struct NoMoveCopyAssign : NoMove, NoCopy, NoAssign +{ +}; /// @@ -10,9 +66,9 @@ /// /// Making a class NonCopyable means preventing objects of the class being copied. This is useful if /// the class manages a resource that can only be held by one object such that it can't be reference -/// counted, or it is the underlying object that becomes reference counted. +/// counted, or it is the underlying object that becomes reference counted. Idea from here [1]. /// -/// Idea comes from here: http://stackoverflow.com/questions/9458741/with-explicitly-deleted-member-functions-in-c11-is-it-still-worthwhile-to-inh +/// [1] Ref: http://stackoverflow.com/questions/9458741/with-explicitly-deleted-member-functions-in-c11-is-it-still-worthwhile-to-inh /// class NonCopyable { @@ -34,12 +90,19 @@ MyNonCopyableClass() {} }; + MyNonCopyableClass create() + { + MyNonCopyableClass var; + return var; + } + Now it is not possible to copy objects of this type without a compiler error: - void function() + void function() { - MyNonCopyableClass obj1; - MyNonCopyableClass obj2; + MyNonCopyableClass obj1; + MyNonCopyableClass obj2; + MyNonCopyableClass obj3 = create(); // Allowed due to move semantics in c++11, use NoMoveCopyAssign to prevent // These attempts to make a copy of obj1 will cause a compiler error MyNonCopyableClass obj3(obj1); diff --git a/Framework/ObjectFilter.cpp b/Framework/ObjectFilter.cpp new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/Framework/ObjectFilter.cpp diff --git a/Framework/ObjectInspector.cpp b/Framework/ObjectInspector.cpp new file mode 100644 index 0000000..91b6726 --- /dev/null +++ b/Framework/ObjectInspector.cpp @@ -0,0 +1,90 @@ +#include "ObjectInspector.h" + +#include "ObjectPropertyModel.h" +#include "lib/ObjectProxy.h" + +#include +#include +#include +#include +#include +#include + +ObjectInspector::ObjectInspector(QWidget* parent) +: QWidget(parent) +{ + QVBoxLayout* layout = new QVBoxLayout(this); + layout->setMargin(0); + + m_nameLabel = new QLabel(this); + layout->addWidget(m_nameLabel); + + m_propertyView = new QTableView(this); + m_propertyView->verticalHeader()->setVisible(false); + m_propertyView->horizontalHeader()->setStretchLastSection(true); + + QHBoxLayout* filterLayout = new QHBoxLayout; + QLabel* filterLabel = new QLabel(tr("Filter:"),this); + m_propertyFilterEdit = new QLineEdit(this); + m_propertyFilterEdit->setToolTip(tr("String or regular expression to filter property list with")); + + connect(m_propertyFilterEdit,SIGNAL(textChanged(QString)), + this,SLOT(changeFilter(QString))); + filterLayout->addWidget(filterLabel); + filterLayout->addWidget(m_propertyFilterEdit); + + m_model = new ObjectPropertyModel(this); + m_propertySortModel = new QSortFilterProxyModel(this); + m_propertySortModel->setSourceModel(m_model); + + m_propertyView->setModel(m_propertySortModel); + + layout->addWidget(m_propertyView); + layout->addLayout(filterLayout); + + setObject(ObjectProxy::Pointer()); +} + +QString ObjectInspector::formatAddress(void* ptr) +{ + quintptr intPtr = reinterpret_cast(ptr); + return QString("0x%1").arg(QString(QByteArray::number(intPtr,16))); +} + +void ObjectInspector::refresh() +{ + setObject(m_currentObject); +} + +void ObjectInspector::setObject(ObjectProxy::Pointer object) +{ + QString labelText; + + if (object) + { + labelText = QString("%1 [%2] (%3)") + .arg(object->className()) + .arg(object->objectName()) + .arg(formatAddress(reinterpret_cast(object->address()))); + } + else + { + labelText = tr("[No Object Selected]"); + } + + m_nameLabel->setText(labelText); + m_currentObject = object; + m_model->setObject(object); + m_propertySortModel->sort(0,Qt::AscendingOrder); +} + +ObjectProxy::Pointer ObjectInspector::object() const +{ + return m_currentObject; +} + +void ObjectInspector::changeFilter(const QString& pattern) +{ + m_propertySortModel->setFilterRegExp(QRegExp(pattern)); +} + diff --git a/Framework/ObjectInspector.h b/Framework/ObjectInspector.h new file mode 100644 index 0000000..e1ce3ac --- /dev/null +++ b/Framework/ObjectInspector.h @@ -0,0 +1,40 @@ +#pragma once + +#include "lib/ObjectProxy.h" + +#include + +class QLabel; +class QLineEdit; +class QTableView; +class QSortFilterProxyModel; + +class ObjectPropertyModel; + +class ObjectInspector : public QWidget +{ + Q_OBJECT + + public: + ObjectInspector(QWidget* parent); + + void setObject(ObjectProxy::Pointer object); + ObjectProxy::Pointer object() const; + + static QString formatAddress(void* ptr); + + public Q_SLOTS: + void refresh(); + + private Q_SLOTS: + void changeFilter(const QString& pattern); + + private: + QLabel* m_nameLabel; + QTableView* m_propertyView; + ObjectProxy::Pointer m_currentObject; + ObjectPropertyModel* m_model; + QSortFilterProxyModel* m_propertySortModel; + QLineEdit* m_propertyFilterEdit; +}; + diff --git a/Framework/ObjectPropertyModel.cpp b/Framework/ObjectPropertyModel.cpp new file mode 100644 index 0000000..6e4d4f7 --- /dev/null +++ b/Framework/ObjectPropertyModel.cpp @@ -0,0 +1,91 @@ +#include "ObjectPropertyModel.h" + +#include "lib/ObjectProxy.h" + +#include +#include + +ObjectPropertyModel::ObjectPropertyModel(QObject* parent) +: QStandardItemModel(parent) +{ + setColumnCount(2); + setObject(ObjectProxy::Pointer()); + + connect(this,SIGNAL(itemChanged(QStandardItem*)), + this,SLOT(updatePropertyValue(QStandardItem*))); +} + +QVariant ObjectPropertyModel::toEditValue(const QVariant& value) +{ + // TODO - Find existing code which can handle display and editing + // of common value types. + switch (value.userType()) + { + case QVariant::Size: + case QVariant::SizeF: + { + QSize size = value.toSize(); + return QString("%1 x %2").arg(size.width()).arg(size.height()); + } + case QVariant::Rect: + case QVariant::RectF: + { + QRect rect = value.toRect(); + return QString("(%1,%2) (%3,%4)").arg(rect.left()).arg(rect.top()).arg(rect.width()).arg(rect.height()); + } + } + return value; +} + +void ObjectPropertyModel::updatePropertyValue(QStandardItem* item) +{ + QStandardItem* propertyItem = this->item(item->row(),0); + QString propertyName = propertyItem->text(); + + if (m_object) + { + QVariant newValue = item->data(Qt::EditRole); + qDebug() << "setting property" << propertyName << "to" << newValue; + m_object->writeProperty(propertyName,newValue); + } +} + +void ObjectPropertyModel::setObject(ObjectProxy::Pointer object) +{ + clear(); + + setHorizontalHeaderLabels( + QStringList() << tr("Property") << tr("Value") + ); + + m_object = object; + + if (object) + { + object->refresh(); + int rowCount = 0; + QListIterator iter(object->properties()); + while (iter.hasNext()) + { + ObjectProxy::Property property = iter.next(); + QVariant value = property.value; + QVariant editValue = toEditValue(value); + + QStandardItem* nameItem = new QStandardItem(property.name); + nameItem->setEditable(false); + + QStandardItem* valueItem = new QStandardItem; + valueItem->setData(editValue,Qt::EditRole); + valueItem->setEditable(property.isWritable); + + insertRow(rowCount,QList() << nameItem << valueItem); + ++rowCount; + } + } +} + +ObjectProxy::Pointer ObjectPropertyModel::object() const +{ + return m_object; +} + diff --git a/Framework/ObjectPropertyModel.h b/Framework/ObjectPropertyModel.h new file mode 100644 index 0000000..69e176c --- /dev/null +++ b/Framework/ObjectPropertyModel.h @@ -0,0 +1,27 @@ +#pragma once + +#include "lib/ObjectProxy.h" + +#include +#include + +class ObjectPropertyModel : public QStandardItemModel +{ + Q_OBJECT + + public: + ObjectPropertyModel(QObject* parent); + + void setObject(ObjectProxy::Pointer object); + ObjectProxy::Pointer object() const; + + static QVariant toEditValue(const QVariant& value); + + private Q_SLOTS: + void updatePropertyValue(QStandardItem* item); + + private: + ObjectProxy::Pointer m_object; +}; + + diff --git a/Framework/ObjectTreeModel.cpp b/Framework/ObjectTreeModel.cpp new file mode 100644 index 0000000..5d7bbfc --- /dev/null +++ b/Framework/ObjectTreeModel.cpp @@ -0,0 +1,135 @@ +#include "ObjectTreeModel.h" +#include + + +ObjectTreeModel::ObjectTreeModel(QObject* parent) +: QAbstractItemModel(parent) +{ +} + +int ObjectTreeModel::columnCount(const QModelIndex&) const +{ + return 1; +} + +int ObjectTreeModel::rowCount(const QModelIndex& parent) const +{ + QObject* obj = static_cast(parent.internalPointer()); + return (obj) ? obj->children().count() : m_roots.count(); +} + +void ObjectTreeModel::setRootObjects(const QList& roots) +{ + beginResetModel(); + m_roots = roots; + endResetModel(); +} + +QModelIndex ObjectTreeModel::index(int row, int column, const QModelIndex& parent) const +{ + QObject* parentItem = static_cast(parent.internalPointer()); + const QObjectList& children = (parentItem) ? parentItem->children() : m_roots; + bool outOfBounds = (row < 0 || column != 0 || row >= children.count()); + return outOfBounds ? QModelIndex() : createIndex(row,column,children.at(row)); +} + +int ObjectTreeModel::indexInParent(QObject* item) const +{ + QObject* parent = item->parent(); + return (parent && !m_roots.contains(item)) ? parent->children().indexOf(item) : m_roots.indexOf(item); +} + +QModelIndex ObjectTreeModel::parent(const QModelIndex& index) const +{ + QObject* item = static_cast(index.internalPointer()); + QObject* parent = (item && !m_roots.contains(item)) ? item->parent() : 0; + return (parent) ? createIndex(indexInParent(parent),index.column(),parent) : QModelIndex(); +} + +QVariant ObjectTreeModel::data(const QModelIndex& index, int role) const +{ + QObject* item = static_cast(index.internalPointer()); + return (role == Qt::DisplayRole) ? ((item) ? displayText(item) : "[deleted]") : QVariant(); +} + +QString ObjectTreeModel::displayText(QObject* object) const +{ + QString text = object->metaObject()->className(); + if (!object->objectName().isEmpty()) + { + text += QString("[%1]").arg(object->objectName()); + } + return text; +} + +QVariant ObjectTreeModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (section == 0 && orientation == Qt::Horizontal && role == Qt::DisplayRole) + { + return tr("Object"); + } + return QAbstractItemModel::headerData(section,orientation,role); +} + +QObject* ObjectTreeModel::index(ObjPtr object, const QList& items) const +{ + //inline QList findChildren(const QRegularExpression &re, Qt::FindChildOptions options = Qt::FindChildrenRecursively) const + Q_FOREACH(QObject* item, items) + { + if (item == object) + return item; + QObject* match = index(object, item->children()); + if (match) + return match; + } + return 0; +} + +QModelIndex ObjectTreeModel::index(ObjPtr object) const +{ + QObject* item = index(object, m_roots); + return (item) ? createIndex(indexInParent(item),0,item) : QModelIndex(); +} + + + + +/* +bool ObjectTreeModel::matches(ObjPtr object, const QString& query) const +{ + return displayText(object).contains(query,Qt::CaseInsensitive); +} + +void ObjectTreeModel::search(QList* hits, ObjectItem* item, const QString& query) const +{ + ObjPtr object = item;//->object; + if (!object) + { + return; + } + if (matches(object,query)) + { + *hits << object; + } + Q_FOREACH(ObjectItem* child, item->children()) + { + search(hits,child,query); + } +} + +QList ObjectTreeModel::search(const QString& query) const +{ + if (query.isEmpty()) + { + return QList(); + } + + QList matches; + Q_FOREACH(ObjectItem* item, m_roots) + { + search(&matches,item,query); + } + return matches; +} + +*/ diff --git a/Framework/ObjectTreeModel.h b/Framework/ObjectTreeModel.h new file mode 100644 index 0000000..4b50d8a --- /dev/null +++ b/Framework/ObjectTreeModel.h @@ -0,0 +1,47 @@ +#pragma once + +#include +#include +#include + + +class TrackedObject +{ +public: + TrackedObject() {} + TrackedObject(QObject* a_obj) : m_object(a_obj) {} + operator bool() { return !m_object.isNull(); } + operator QObject*() { return m_object.data(); } +private: + QPointer m_object; +}; + + +typedef QObject* ObjPtr; +//typedef TrackedObject ObjPtr; + + +class ObjectTreeModel : public QAbstractItemModel +{ + Q_OBJECT +public: + ObjectTreeModel(QObject* parent); + //void setFilter(ObjectFilter *filter); + //QList search(const QString& query) const; + void setRootObjects(const QList& roots); + QModelIndex index(ObjPtr object) const; + virtual QVariant headerData(int section, Qt::Orientation orientation, int role) const; + virtual int columnCount(const QModelIndex& parent) const; + virtual int rowCount(const QModelIndex& parent) const; + virtual QModelIndex index(int row, int column, const QModelIndex& parent) const; + virtual QModelIndex parent(const QModelIndex& index) const; + virtual QVariant data(const QModelIndex& index, int role) const; +private: + int indexInParent(QObject* item) const; + QObject* index(ObjPtr object, const QList& items) const; + //bool matches(ObjPtr object, const QString& query) const; + //void search(QList* matches, ObjectItem* item, const QString& query) const; + QString displayText(QObject* object) const; + QList m_roots; +}; + diff --git a/Framework/SystemInfomation.cpp b/Framework/SystemInfomation.cpp new file mode 100644 index 0000000..3f09248 --- /dev/null +++ b/Framework/SystemInfomation.cpp @@ -0,0 +1,132 @@ +#include "SystemInfomation.h" +#include "../Util.h" +#include +#include +#include + + +#ifdef __APPLE__ +# include "TargetConditionals.h" +#elif defined(_WIN32) +# include +#else +# include +# include +# include +# include +#endif + + +void getSystemInfo(SystemInfo& a_info) +{ + a_info.m_cpuType = "unknown"; + a_info.m_osType = "unknown"; + a_info.m_osVersion = "unknown"; + +#ifdef _WIN32 + a_info.m_osType = "Windows"; + + // Get RAM size + MEMORYSTATUSEX status; + status.dwLength = sizeof(status); + GlobalMemoryStatusEx(&status); + a_info.m_ramSize = status.ullTotalPhys; + + // Get OS version + const OSVERSIONINFO osver = winOsVersion(); + a_info.m_osVersion.reserve(512); + snprintf(a_info.m_osVersion.data(), "%i.%i.%i", osver.dwMajorVersion, osver.dwMinorVersion, osver.dwBuildNumber); + + // Get OS arch + SYSTEM_INFO info; + GetNativeSystemInfo(&info); +# ifdef _WIN64 + //if (info.wProcessorArchitecture == ???) + a_info.m_cpuType = "x86_64"; +# else + a_info.m_cpuType = "x86"; +# endif +#else + long pages = sysconf(_SC_PHYS_PAGES); + long page_size = sysconf(_SC_PAGE_SIZE); + a_info.m_ramSize = pages * page_size; + struct utsname u; + long ret = uname(&u); + if (ret != -1) + { + a_info.m_cpuType = u.machine; + a_info.m_osVersion = u.sysname + std::string(" ") + u.release; + } +# ifdef __APPLE__ + // Get RAM Size + int mib[2] = { CTL_HW, HW_MEMSIZE }; + size_t len = sizeof(uint64_t); + sysctl(mib, sizeof(mib) / sizeof(mib[0]), &u.ramSize, &len, NULL, 0); +# if TARGET_OS_IPHONE + a_info.m_osType = "iOS"; +# elif TARGET_OS_MAC + const char *macCodeName = ""; + switch (int(MacintoshVersion)) { + case MV_CHEETAH: macCodeName = " Cheetah"; break; + case MV_PUMA: macCodeName = " Puma"; break; + case MV_JAGUAR: macCodeName = " Jaguar"; break; + case MV_PANTHER: macCodeName = " Panther"; break; + case MV_TIGER: macCodeName = " Tiger"; break; + case MV_LEOPARD: macCodeName = " Leopard"; break; + case MV_SNOWLEOPARD: macCodeName = " Snow Leopard"; break; + case MV_LION: macCodeName = " Lion"; break; + case MV_MOUNTAINLION:macCodeName = " Mountain Lion"; break; + case MV_MAVERICKS: macCodeName = " Mavericks"; break; + case MV_YOSEMITE: macCodeName = " Yosemite"; break; + } + a_info.m_osType = "Mac OS X" + std::string(macCodeName); +# endif +# elif __ANDROID__ + a_info.m_osType = "Android"; +# elif __linux + // system("lsb_release -sd"); + FILE* f = fopen("/etc/lsb-release", "rt"); + char dist[64] = { 0 }; + if (f) { + char buf[128]; + while (fgets(buf, 128, f) != 0) + if (sscanf(buf, "DISTRIB_DESCRIPTION=%64c", dist) == 1) + break; + if (dist[0]) { + std::vector strBits = split(dist, '"'); + a_info.m_osType = strBits[(strBits.size()==3)?1:0]; + } else { + a_info.m_osType = "non-specific LSB"; + } + fclose(f); + } else { + a_info.m_osType = "non-LSB"; + } +# else + a_info.m_osType = "Generic UNIX"; +# endif +#endif + + // TODO: improve the CPU detection using libcpuid + + // Make human readable RAM size string + const char* humanUnitStrings[] = { " bytes", " KB", " MB", " GB", " TB" }; + int ramUnit = 0; + uint64_t ramSize = a_info.m_ramSize; + while (ramSize > 1024 && ramUnit < (YQ_ARRAY_SIZE(humanUnitStrings)-1)) + ramUnit++, ramSize /= 1000; + a_info.m_ramSizeStr = std::to_string(ramSize) + humanUnitStrings[ramUnit]; + + // Obtain the screen properties + QScreen *s = QApplication::screens()[0]; + int refreshRate = (s) ? s->refreshRate() : 0; + int depth = (s) ? s->depth() : 0; + a_info.m_resolutionWidth = (s) ? s->geometry().width() : 0; + a_info.m_resolutionHeight = (s) ? s->geometry().height() : 0; + a_info.m_resolutionStr = std::to_string(a_info.m_resolutionWidth) + "x" + + std::to_string(a_info.m_resolutionHeight) + " " + + std::to_string(depth) + "bit " + + "@" + std::to_string(refreshRate) + "Hz"; + ; +} + diff --git a/Framework/SystemInfomation.h b/Framework/SystemInfomation.h new file mode 100644 index 0000000..2833313 --- /dev/null +++ b/Framework/SystemInfomation.h @@ -0,0 +1,26 @@ +#ifndef SYSTEM_INFOMATION_H +#define SYSTEM_INFOMATION_H + + +#include "Utilities.h" +#include +#include + + +struct SystemInfo +{ + std::string m_cpuType; + std::string m_osType; + std::string m_osVersion; + std::string m_ramSizeStr; // Human friendly string, eg: "4 GB" + std::string m_resolutionStr; + uint64_t m_ramSize; + uint32_t m_resolutionWidth; + uint32_t m_resolutionHeight; +}; + + +void getSystemInfo(SystemInfo& a_info); + + +#endif // SYSTEM_INFOMATION_H diff --git a/Framework/Tests.cpp b/Framework/Tests.cpp index 046c82c..2a2d78a 100644 --- a/Framework/Tests.cpp +++ b/Framework/Tests.cpp @@ -1,4 +1,5 @@ #include "Utilities.h" +#include "Array.h" #ifdef _MSC_VER diff --git a/Framework/Utilities.cpp b/Framework/Utilities.cpp index 8dbccf9..1e6acb9 100644 --- a/Framework/Utilities.cpp +++ b/Framework/Utilities.cpp @@ -11,7 +11,7 @@ static ResourceDeviceFactory s_defaultDeviceFactory; -Module g_currentModule{ false, &s_defaultErrorHandler, &s_defaultMessageLogger, &s_defaultHeapManager, &s_defaultDeviceFactory }; +Module g_currentModule{ false, "Main Module", &s_defaultErrorHandler, &s_defaultMessageLogger, &s_defaultHeapManager, &s_defaultDeviceFactory }; void yqDebugBreak() diff --git a/Framework/Utilities.h b/Framework/Utilities.h index cff9fcc..1f47a04 100644 --- a/Framework/Utilities.h +++ b/Framework/Utilities.h @@ -5,92 +5,7 @@ #include #include #include - - -/// -/// Various common macros -/// -#define YQ_ARRAY_SIZE(x) (sizeof(x)/sizeof((x)[0])) - -#define YQ_EXPAND(x) x - -#define YQ_STRINGIZE_IMPL(val) #val -#define YQ_STRINGIZE(val) YQ_STRINGIZE_IMPL(val) - -#define YQ_CONCATENATE_IMPL(x, y) x##y -#define YQ_CONCATENATE(x, y) YQ_CONCATENATE_IMPL(x, y) - -// If 0 args, it mis-reports a count of 1. Also it only counts up to 10 args. -#define YQ_ARG_COUNT_IMPL(X,A,B,C,D,E,F,G,H,I,J,N,...) N -#define YQ_ARG_COUNT(...) (YQ_EXPAND(YQ_ARG_COUNT_IMPL(_,__VA_ARGS__, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0))) - -#ifdef __COUNTER__ -# define YQ_ANONYMOUS_VARIABLE(str) YQ_CONCATENATE(str, __COUNTER__) -#else -# define YQ_ANONYMOUS_VARIABLE(str) YQ_CONCATENATE(str, __LINE__) -#endif - -#define YQ_UNUSED(x) std::ignore = (x) - -#ifdef __MINGW32__ -// Avoid callbacks -# define YQ_CALLBACK __cdecl -# define YQ_FUNCTION_NAME __PRETTY_FUNCTION__ // __func__ -#elif defined(_WIN32) -# define YQ_CALLBACK __cdecl -# define YQ_FUNCTION_NAME __FUNCTION__ -#else -# define YQ_CALLBACK -# define YQ_FUNCTION_NAME __PRETTY_FUNCTION__ // __func__ -#endif - -// Helper marcos for doing a variable number of null checks -#define YQ_NULL_CHECK_IMPL_1(arg) if (arg == nullptr) YQ_API_LEAVE(R_InvalidParameter); -#define YQ_NULL_CHECK_IMPL_2(arg,...) YQ_NULL_CHECK_IMPL_1(arg) YQ_NULL_CHECK_IMPL_1(__VA_ARGS__) -#define YQ_NULL_CHECK_IMPL_3(arg,...) YQ_NULL_CHECK_IMPL_1(arg) YQ_NULL_CHECK_IMPL_2(__VA_ARGS__) -#define YQ_NULL_CHECK_IMPL_4(arg,...) YQ_NULL_CHECK_IMPL_1(arg) YQ_NULL_CHECK_IMPL_3(__VA_ARGS__) -#define YQ_NULL_CHECK_IMPL_5(arg,...) YQ_NULL_CHECK_IMPL_1(arg) YQ_NULL_CHECK_IMPL_4(__VA_ARGS__) -#define YQ_NULL_CHECK_IMPL_N3(N, ...) YQ_NULL_CHECK_IMPL_ ## N(__VA_ARGS__) -#define YQ_NULL_CHECK_IMPL_N2(N, ...) YQ_NULL_CHECK_IMPL_N3(N, __VA_ARGS__) -#define YQ_NULL_CHECK_IMPL_N(...) YQ_NULL_CHECK_IMPL_N2(YQ_ARG_COUNT(__VA_ARGS__), __VA_ARGS__) - -// File name, line numbes, and function name are statically concatenated at compile time, with a reduced string sharing trade off -#define YQ_LOCATION __FILE__ "(" YQ_STRINGIZE(__LINE__) "): " - -// Macros to log messages for debug builds. -// For release builds these will not output anything and no code or data will be generate (except for error logging) -#define YQ_LOG_WARNING(a_fmt, ...) ((BT_IsRelease) ? ((void)0) : yqLogMessage(LL_Warning, YQ_LOCATION, YQ_FUNCTION_NAME, "warning: " a_fmt, ##__VA_ARGS__)) -#define YQ_LOG_DEBUG(a_fmt, ...) ((BT_IsRelease) ? ((void)0) : yqLogMessage(LL_Debug, YQ_LOCATION, YQ_FUNCTION_NAME, "debug: " a_fmt, ##__VA_ARGS__)) -#define YQ_LOG_INFO(a_fmt, ...) ((BT_IsRelease) ? ((void)0) : yqLogMessage(LL_Info, YQ_LOCATION, YQ_FUNCTION_NAME, "info: " a_fmt, ##__VA_ARGS__)) -#define YQ_LOG_ERROR(a_fmt, ...) yqLogMessage(LL_Error, YQ_LOCATION, YQ_FUNCTION_NAME, "error: " a_fmt, ##__VA_ARGS__) - -// Macros that only generate log code in verbose debug builds. The logging logs the entry to API calls and the exit from them and what return code is being returned. -// In debug and release, on errors, the code always calls the installed or default error handling routine -enum { YQ_VERBOSE_DEBUGGING = 0 }; -#define YQ_API_VERBOSE(msg) ((YQ_VERBOSE_DEBUGGING) ? YQ_LOG_DEBUG(msg) : ((void)0)) -#define YQ_API_ERROR(result, ...) (yqHandleError(result, YQ_LOCATION, YQ_FUNCTION_NAME, __VA_ARGS__), YQ_LOG_ERROR("Leave: " #result " (%i)", result), result) -#define YQ_API_ENTER() YQ_API_VERBOSE("Enter") -#define YQ_API_LEAVE(result, ...) return ((result == R_Okay) ? (YQ_API_VERBOSE("Leave"), result) : YQ_API_ERROR(result, "" __VA_ARGS__)) - -// Macros to do common parameter and precondition checking for API function calls -// This macro checks the pre-condition is met, if it doesn't then returns the error result and executes the provided cleanup code -// See also 'finally'. Probably during pre-condition checking, there shouldn't be any cleanup yet as no work should be done at this stage. -//#define YQ_API_PRECONDITION_CLEANUP(condition,result,cleanup) do { if (!(condition)) { cleanup; YQ_API_LEAVE(result); } } while (0,0) -#define YQ_API_PRECONDITION(condition,result) do { if (!(condition)) { YQ_API_LEAVE(result, #condition); } } while (0,0)//YQ_API_PRECONDITION_CLEANUP(condition,result, ((void)0)) -#define YQ_API_PRECONDITION_NULL(...) YQ_NULL_CHECK_IMPL_N(__VA_ARGS__) - -// Can redirect to the user heap manager routines if provided -#define YQ_ALLOC(type) ((type*)(yqAllocateMemory(sizeof(type)))) -#define YQ_ALLOC_ARRAY(type,siz) ((type*)(yqAllocateMemory(sizeof(type)*(siz)))) -#define YQ_REALLOC_ARRAY(ptr,type,siz) ((type*)(yqReallocateMemory(ptr,sizeof(type)*(siz)))) -#define YQ_FREE(ptr) (((ptr)) ? (void)(yqFreeMemory((void*)(ptr)), (ptr) = nullptr) : (void)0) - -// C++ wrappers for using the custom allocators to replace use of new / delete (does placement new with custom allocated memory) -// Note: The custom allocators are expected to return null if the allocation fails and not throw. The macros below do not check this. -#define YQ_NEW(type) (new(YQ_ALLOC(type)) type) -#define YQ_NEW_PARAMS(type, params) (new(YQ_ALLOC(type)) type params) -#define YQ_DELETE(obj,type) ((obj) ? (void)((obj)->~type(), YQ_FREE(obj)) : (void)0) - +#include "Macros.h" enum yqResult @@ -206,6 +121,7 @@ { public: bool m_initialized; + const char* m_moduleName; ErrorHandler* m_errorHandler; Logger* m_messageLogger; HeapManager* m_heapManager; @@ -216,111 +132,4 @@ extern Module g_currentModule; -// std::vector replacement that is cut down but familiar, uses the custom allocators, and restricted to POD elements only -template -struct PODArrayPOD // A dynamic array of POD type elements which is itself a POD type (so can have multi-dimensional arrays of these). -{ - static_assert(std::is_pod::value, "T must be a POD type."); - - void init() { m_magic = 0x01020304; m_capacity = m_size = 0; m_data = nullptr; } - void deinit() { if (m_data != nullptr) YQ_FREE(m_data); } - - const T& at(size_t a_pos) const { checkInited(); checkIndex(a_pos); return m_data[a_pos]; } - T& at(size_t a_pos) { checkInited(); checkIndex(a_pos); return m_data[a_pos]; } - const T& operator[](size_t a_pos) const { checkInited(); checkIndex(a_pos); return m_data[a_pos]; } - void pop_back() { checkInited(); checkIndex(1); m_size--; } - const T& back() const { checkInited(); return m_data[m_size - 1]; } - T& back() { checkInited(); return m_data[m_size - 1]; } - void push_back(const T& a_val) { checkInited(); if (m_size == m_capacity) ensureSpaceFor(1); m_data[m_size++] = a_val; } - void push(const T& a_val) { checkInited(); push_back(a_val); } - T pop() { checkInited(); pop_back(); return m_data[m_size]; } - void reserve(size_t a_count) { checkInited(); m_capacity = a_count; m_data = YQ_REALLOC_ARRAY(m_data, T, a_count); } - size_t capacity() const { checkInited(); return m_capacity; } - size_t size() const { checkInited(); return m_size; } - T* data() const { checkInited(); return m_data; } - void clear() { checkInited(); m_size = 0; } -//private: // Actually making private would make this no longer a POD type - yqResult checkInited() const { if (BT_IsDebug) YQ_API_PRECONDITION(m_magic == 0x01020304, R_Uninitialized); return R_Okay; } - yqResult checkIndex(size_t a_pos) const { YQ_API_PRECONDITION(a_pos < m_size, R_IndexOutOfRange); return R_Okay; } - void ensureSpaceFor(size_t a_count) { reserve(std::max(m_size + a_count, m_capacity + m_capacity / 2)); } // try to grow by 50% - T* m_data; // Because of this pointer it is not safe to copy DynamicArray objects around. Making it a shared_ptr though stops this being a POD type - size_t m_capacity; - size_t m_size; - uint32_t m_magic; -}; - - -static_assert(std::is_pod >::value, "PODArrayPOD must be a POD type."); - - -template -struct ArrayPOD // A dynamic array of POD type elements. It isn't POD itself, but is instead reference counted so safer to deal with. -{ - static_assert(std::is_pod::value, "T must be a POD type."); - - ArrayPOD& operator=(const ArrayPOD&) = delete; - ArrayPOD(const ArrayPOD&) = delete; - ArrayPOD() { m_capacity = m_size = 0; m_data = nullptr; } - ~ArrayPOD() { if (m_data != nullptr) YQ_FREE(m_data); } - - const T& at(size_t a_pos) const { checkIndex(a_pos); return m_data[a_pos]; } - T& at(size_t a_pos) { checkIndex(a_pos); return m_data[a_pos]; } - const T& operator[](size_t a_pos) const { checkIndex(a_pos); return m_data[a_pos]; } - void pop_back() { checkIndex(1); m_size--; } - const T& back() const { return m_data[m_size - 1]; } - T& back() { return m_data[m_size - 1]; } - void push_back(const T& a_val) { if (m_size == m_capacity) ensureSpaceFor(1); m_data[m_size++] = a_val; } - void push(const T& a_val) { push_back(a_val); } - T pop() { pop_back(); return m_data[m_size]; } - void reserve(size_t a_count) { m_capacity = a_count; m_data = YQ_REALLOC_ARRAY(m_data, T, a_count); } - size_t capacity() const { return m_capacity; } - size_t size() const { return m_size; } - T* data() const { return m_data; } - void clear() { m_size = 0; } -private: - yqResult checkIndex(size_t a_pos) const { YQ_API_PRECONDITION(a_pos < m_size, R_IndexOutOfRange); return R_Okay; } - void ensureSpaceFor(size_t a_count) { reserve(std::max(m_size + a_count, m_capacity + m_capacity / 2)); } // try to grow by 50% - T* m_data; // Because of this pointer it is not safe to copy DynamicArray objects around. Making it a shared_ptr though stops this being a POD type - size_t m_capacity; - size_t m_size; -}; - - -struct PODString -{ -public: - void init() { m_data.init(); } - void deinit() { m_data.deinit(); } - void operator=(const char* a_str) { m_data.clear(); operator+=(a_str); } - void operator+=(const char* a_str) { while (*a_str) m_data.push_back(*a_str++); m_data.push(0); m_data.pop(); } - char* c_str() { return m_data.data(); } -//private: // Actually making private would make this no longer a POD type - PODArrayPOD m_data; -}; - - -static_assert(std::is_pod::value, "PODString must be a POD type."); - - -/* - -Example usage: - -Result SomeFunction(T1 **Param1, T2 *Param2, T3 *Param3, int Param4) -{ - YQ_API_ENTER(); - - // Check pre-conditions - YQ_API_PRECONDITION_NULL(Param1, Param2, Param3); - YQ_API_PRECONDITION(Param4 == 42, R_InvalidParameter); - - if (errorHappened) - YQ_API_LEAVE(R_Failure); - - YQ_API_LEAVE(R_Okay); -} - -*/ - - #endif // UTILITIES_H diff --git a/Framework/WidgetInspector.cpp b/Framework/WidgetInspector.cpp new file mode 100644 index 0000000..19c38e6 --- /dev/null +++ b/Framework/WidgetInspector.cpp @@ -0,0 +1,174 @@ + +#include "WidgetInspector.h" + +#include "ObjectInspector.h" +#include "ObjectTreeModel.h" +#include "OutOfProcessClipboard.h" +#include "RootObjectList.h" +#include "WidgetInspectorShortcut.h" +#include "lib/DirectWidgetPicker.h" +#include "lib/WidgetPicker.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +WidgetInspector::WidgetInspector(RootObjectList* rootList, QWidget* parent) +: QWidget(parent) +, m_rootList(rootList) +, m_picker(0) +, m_externalClipboard(new OutOfProcessClipboard(this)) +{ + setWindowTitle(tr("Widget Inspector")); + + m_objectModel = new ObjectTreeModel(this); + m_objectTree = new QTreeView(this); + m_objectTree->header()->setVisible(false); + m_objectTree->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel); + m_objectTree->setModel(m_objectModel); + +#if QT_VERSION >= QT_VERSION_CHECK(5,0,0) + m_objectTree->header()->setSectionResizeMode(0,QHeaderView::ResizeToContents); +#else + m_objectTree->header()->setResizeMode(0,QHeaderView::ResizeToContents); +#endif + + m_objectTree->header()->setStretchLastSection(false); + + connect(m_objectTree->selectionModel(),SIGNAL(currentChanged(QModelIndex,QModelIndex)), + this,SLOT(selectionChanged(QModelIndex,QModelIndex))); + + m_objectInspector = new ObjectInspector(this); + + QVBoxLayout* layout = new QVBoxLayout(this); + + QSplitter* topSplitter = new QSplitter(this); + topSplitter->addWidget(m_objectTree); + topSplitter->addWidget(m_objectInspector); + layout->addWidget(topSplitter); + + QHBoxLayout* searchLayout = new QHBoxLayout; + searchLayout->addWidget(new QLabel(tr("Search:"),this)); + + QLineEdit* searchEdit = new QLineEdit(this); + connect(searchEdit,SIGNAL(textChanged(QString)),this,SLOT(search(QString))); + searchLayout->addWidget(searchEdit); + layout->addLayout(searchLayout); + + QPushButton* copyToDebuggerButton = new QPushButton(tr("Copy Reference"),this); + connect(copyToDebuggerButton,SIGNAL(clicked()), + this,SLOT(copyDebuggerReference())); + + m_pickButton = new QPushButton(tr("Pick Widget"),this); + setWidgetPicker(new DirectWidgetPicker(this)); + + QPushButton* refreshButton = new QPushButton(tr("Refresh Tree"),this); + connect(refreshButton,SIGNAL(clicked()),this,SLOT(refreshTree())); + + QPushButton* refreshObjectButton = new QPushButton(tr("Refresh Object"),this); + connect(refreshObjectButton,SIGNAL(clicked()),m_objectInspector,SLOT(refresh())); + + QHBoxLayout* actionLayout = new QHBoxLayout; + actionLayout->addStretch(); + actionLayout->addWidget(m_pickButton); + actionLayout->addWidget(copyToDebuggerButton); + actionLayout->addWidget(refreshButton); + actionLayout->addWidget(refreshObjectButton); + layout->addLayout(actionLayout); + + resize(700,400); + + refreshTree(); +} + +void WidgetInspector::pickerFinished(ObjectProxy::Pointer widget) +{ + // TODO - Re-implement widget picker + select(widget); +} + +void WidgetInspector::copyDebuggerReference() +{ + ObjectProxy::Pointer object = m_objectInspector->object(); + if (!object) + { + return; + } + + void* address = reinterpret_cast(object->address()); + QString reference = QString("(%1*)(%2)") + .arg(object->className()) + .arg(ObjectInspector::formatAddress(address)); + +#ifdef Q_OS_LINUX + m_externalClipboard->setText(reference); +#else + QApplication::clipboard()->setText(reference); +#endif +} + +void WidgetInspector::selectionChanged(const QModelIndex& current, const QModelIndex& previous) +{ + Q_UNUSED(previous); + m_objectInspector->setObject(ObjectTreeModel::objectFromIndex(current)); +} + +void WidgetInspector::refreshTree() +{ + QModelIndex current = m_objectTree->selectionModel()->currentIndex(); + ObjectProxy::Pointer currentObject = ObjectTreeModel::objectFromIndex(current); + m_objectModel->setRootObjects(m_rootList->rootObjects()); + if (currentObject) + { + select(currentObject); + } +} + +void WidgetInspector::search(const QString& query) +{ + QList results = m_objectModel->search(query); + if (!results.isEmpty()) + { + select(results.first()); + } +} + +void WidgetInspector::select(ObjectProxy::Pointer object) +{ + QModelIndex index = m_objectModel->index(object); + if (!index.isValid()) + { + // if no matching object is found in the model, then try refreshing + // the model and searching again + refreshTree(); + index = m_objectModel->index(object); + } + + m_objectTree->scrollTo(index); + m_objectTree->selectionModel()->setCurrentIndex(index,QItemSelectionModel::SelectCurrent); +} + +void WidgetInspector::registerGlobalShortcut(const QKeySequence& key, QWidget* parentWidget) +{ + WidgetInspectorShortcut* shortcut = new WidgetInspectorShortcut(parentWidget); + shortcut->setKey(key); + shortcut->setContext(Qt::ApplicationShortcut); +} + +void WidgetInspector::setWidgetPicker(WidgetPicker* picker) +{ + delete m_picker; + picker->setParent(this); + m_picker = picker; + connect(m_picker,SIGNAL(widgetPicked(ObjectProxy::Pointer)),this,SLOT(pickerFinished(ObjectProxy::Pointer))); + connect(m_pickButton,SIGNAL(clicked()),m_picker,SLOT(start())); +} + diff --git a/Framework/WidgetInspector.h b/Framework/WidgetInspector.h new file mode 100644 index 0000000..e3910bb --- /dev/null +++ b/Framework/WidgetInspector.h @@ -0,0 +1,52 @@ +#pragma once + +#include "lib/ObjectProxy.h" + +#include + +class ObjectInspector; +class ObjectTreeModel; +class ObjectPropertyModel; +class OutOfProcessClipboard; +class RootObjectList; +class WidgetPicker; + +class QPushButton; +class QModelIndex; +class QTreeView; + +class WidgetInspector : public QWidget +{ + Q_OBJECT + + public: + WidgetInspector(RootObjectList* rootList, QWidget* parent = 0); + + /** Sets the picker used by the 'Pick Widget' button + * to select a widget in the application by clicking on it. + * + * The WidgetInspector takes ownership of the picker. + */ + void setWidgetPicker(WidgetPicker* picker); + + static void registerGlobalShortcut(const QKeySequence& key, QWidget* parentWidget); + + private Q_SLOTS: + void refreshTree(); + void search(const QString& query); + void pickerFinished(ObjectProxy::Pointer widget); + void selectionChanged(const QModelIndex& current, const QModelIndex& previous); + void copyDebuggerReference(); + + private: + void select(ObjectProxy::Pointer object); + + RootObjectList* m_rootList; + ObjectTreeModel* m_objectModel; + QTreeView* m_objectTree; + WidgetPicker* m_picker; + ObjectInspector* m_objectInspector; + OutOfProcessClipboard* m_externalClipboard; + QPushButton* m_pickButton; +}; +