Files
allhaileris afb81b8278
Some checks failed
Docker. / Ubuntu (push) Has been cancelled
User-agent updater. / User-agent (push) Failing after 15s
Lock Threads / lock (push) Failing after 10s
Waiting for answer. / waiting-for-answer (push) Failing after 22s
Close stale issues and PRs / stale (push) Successful in 13s
Needs user action. / needs-user-action (push) Failing after 8s
Can't reproduce. / cant-reproduce (push) Failing after 8s
init
2026-02-16 15:50:16 +03:00

2767 lines
75 KiB
C++

#include <array>
#include <iostream>
#include <memory>
#include <unordered_map>
#include <vector>
// always enable assert
#undef NDEBUG
#include "assert.h"
#define GI_CLASS_IMPL_PRAGMA
#define GI_TEST 1
#include "gi.hpp"
#include "test/test_boxed.h"
#include "test/test_object.h"
using namespace gi;
namespace GLib = gi::repository::GLib;
namespace GObject_ = gi::repository::GObject;
#define BOXED_COPY 0
#if BOXED_COPY
namespace gi
{
namespace repository
{
GI_ENABLE_BOXED_COPY(GBExample)
}
} // namespace gi
#endif
// test examples;
// GBoxed
class GBoxedExampleBase
: public detail::GBoxedWrapperBase<GBoxedExampleBase, GBExample>
{
typedef detail::GBoxedWrapperBase<GBoxedExampleBase, GBExample> super_type;
public:
static GType get_type_() { return GI_CPP_TYPE_BOXED_EXAMPLE; }
GBoxedExampleBase(std::nullptr_t = nullptr) : super_type() {}
};
class GBoxedExampleRef;
class GBoxedExample : public detail::GBoxedWrapper<GBoxedExample, GBExample,
GBoxedExampleBase, GBoxedExampleRef>
{
typedef detail::GBoxedWrapper<GBoxedExample, GBExample, GBoxedExampleBase,
GBoxedExampleRef>
super_type;
using super_type::super_type;
};
class GBoxedExampleRef : public detail::GBoxedRefWrapper<GBoxedExample,
GBExample, GBoxedExampleBase>
{
typedef detail::GBoxedRefWrapper<GBoxedExample, GBExample, GBoxedExampleBase>
super_type;
using super_type::super_type;
};
// plain C
class CBoxedExampleBase
: public detail::CBoxedWrapperBase<CBoxedExampleBase, CBExample>
{
typedef detail::CBoxedWrapperBase<CBoxedExampleBase, CBExample> super_type;
public:
CBoxedExampleBase(std::nullptr_t = nullptr) : super_type() {}
};
class CBoxedExampleRef;
class CBoxedExample : public detail::CBoxedWrapper<CBoxedExample, CBExample,
CBoxedExampleBase, CBoxedExampleRef>
{
typedef detail::CBoxedWrapper<CBoxedExample, CBExample, CBoxedExampleBase,
CBoxedExampleRef>
super_type;
using super_type::super_type;
};
class CBoxedExampleRef : public detail::CBoxedRefWrapper<CBoxedExample,
CBExample, CBoxedExampleBase>
{
typedef detail::CBoxedRefWrapper<CBoxedExample, CBExample, CBoxedExampleBase>
super_type;
using super_type::super_type;
};
enum class CppEnum { VALUE_0 = ENUM_VALUE_0, VALUE_1 = ENUM_VALUE_1 };
enum class CppFlags { VALUE_0 = FLAG_VALUE_0, VALUE_1 = FLAG_VALUE_1 };
GI_FLAG_OPERATORS(CppFlags)
namespace gi
{
namespace repository
{
template<>
struct declare_cpptype_of<GBExample>
{
typedef GBoxedExample type;
};
template<>
struct declare_cpptype_of<CBExample>
{
typedef CBoxedExample type;
};
template<>
struct declare_ctype_of<CppEnum>
{
typedef CEnum type;
};
template<>
struct declare_cpptype_of<CEnum>
{
typedef CppEnum type;
};
template<>
struct declare_gtype_of<CppEnum>
{
static GType get_type() { return GI_CPP_TYPE_ENUM; }
};
template<>
struct is_enumeration<CppEnum> : public std::true_type
{};
template<>
struct declare_ctype_of<CppFlags>
{
typedef CFlags type;
};
template<>
struct declare_cpptype_of<CFlags>
{
typedef CppFlags type;
};
template<>
struct declare_gtype_of<CppFlags>
{
static GType get_type() { return GI_CPP_TYPE_FLAGS; }
};
template<>
struct is_bitfield<CppFlags> : public std::true_type
{};
} // namespace repository
} // namespace gi
#define DECLARE_PROPERTY(pname, ptype) \
property_proxy<ptype> property_##pname() \
{ \
return property_proxy<ptype>(*this, #pname); \
} \
const property_proxy<ptype> property_##pname() const \
{ \
return property_proxy<ptype>(*this, #pname); \
}
#define DECLARE_SIGNAL(name, sig) \
signal_proxy<sig> signal_##name() { return signal_proxy<sig>(*this, #name); }
// simulate override construction
class Derived;
typedef GICppExample CDerived;
namespace base
{
class DerivedBase : public GObject_::Object
{
typedef Object super_type;
public:
typedef Derived self;
typedef CDerived BaseObjectType;
DerivedBase(std::nullptr_t = nullptr) : super_type() {}
BaseObjectType *gobj_() { return (BaseObjectType *)super_type::gobj_(); }
const BaseObjectType *gobj_() const
{
return (const BaseObjectType *)super_type::gobj_();
}
BaseObjectType *gobj_copy_() const
{
return (BaseObjectType *)super_type::gobj_copy_();
}
static GType get_type_() { return GI_CPP_TYPE_EXAMPLE; }
DECLARE_PROPERTY(number, int)
DECLARE_PROPERTY(fnumber, double)
DECLARE_PROPERTY(data, std::string)
DECLARE_PROPERTY(present, gboolean)
DECLARE_PROPERTY(object, Object)
DECLARE_PROPERTY(choice, CppEnum)
DECLARE_PROPERTY(flags, CppFlags)
DECLARE_PROPERTY(error, GLib::Error)
DECLARE_SIGNAL(
to_int, int(Derived, Object, bool, gboolean, const std::string &))
DECLARE_SIGNAL(to_string, std::string(Derived, int, gint64))
DECLARE_SIGNAL(to_void, void(Derived, double, CppEnum, CppFlags))
DECLARE_SIGNAL(to_output_int,
void(Derived, unsigned &,
gi::Collection<::GPtrArray, char *, gi::transfer_none_t>,
gi::Collection<::GSList, char *, gi::transfer_none_t>))
};
} // namespace base
class Derived : public base::DerivedBase
{
typedef base::DerivedBase super;
using super::super;
};
namespace gi
{
namespace repository
{
template<>
struct declare_cpptype_of<CDerived>
{
typedef Derived type;
};
} // namespace repository
} // namespace gi
void
test_trait()
{
static_assert(traits::is_basic<int>::value, "");
static_assert(traits::is_basic<float>::value, "");
static_assert(traits::is_basic<void *>::value, "");
static_assert(!traits::is_basic<char *>::value, "");
static_assert(traits::is_boxed<GBoxedExample>::value, "");
static_assert(traits::is_object<Derived>::value, "");
static_assert(!traits::is_object<int>::value, "");
static_assert(traits::is_reftype<GBoxedExampleRef>::value, "");
static_assert(!traits::is_reftype<GBoxedExample>::value, "");
static_assert(!traits::is_reftype<int>::value, "");
static_assert(traits::has_ctype_member<Derived>::value, "");
static_assert(!traits::has_ctype_member<int>::value, "");
static_assert(
std::is_same<traits::ctype<Derived>::type, CDerived *>::value, "");
static_assert(
std::is_same<traits::ctype<const Derived>::type, const CDerived *>::value,
"");
static_assert(
std::is_same<traits::cpptype<CDerived *>::type, Derived>::value, "");
static_assert(std::is_same<traits::cpptype<const CDerived *>::type,
const Derived>::value,
"");
static_assert(std::is_same<traits::ctype<CppEnum>::type, CEnum>::value, "");
static_assert(std::is_same<traits::cpptype<CEnum>::type, CppEnum>::value, "");
static_assert(
std::is_same<traits::ctype<CBoxedExample>::type, CBExample *>::value, "");
static_assert(
std::is_same<traits::cpptype<CBExample>::type, CBoxedExample>::value, "");
static_assert(std::is_same<traits::reftype<CBoxedExample>::type,
CBoxedExampleRef>::value,
"");
static_assert(
std::is_same<traits::ctype<GBoxedExample>::type, GBExample *>::value, "");
static_assert(
std::is_same<traits::cpptype<GBExample>::type, GBoxedExample>::value, "");
static_assert(std::is_same<traits::reftype<GBoxedExample>::type,
GBoxedExampleRef>::value,
"");
static_assert(
std::is_same<traits::cpptype<GBExample *, transfer_none_t>::type,
GBoxedExampleRef>::value,
"");
static_assert(
std::is_same<traits::ctype<GObject_::Object>::type, _GObject *>::value,
"");
static_assert(
std::is_same<traits::cpptype<_GObject>::type, GObject_::Object>::value,
"");
static_assert(std::is_same<traits::cpptype<bool>::type, bool>::value, "");
static_assert(traits::gtype<int>::value, "");
static_assert(traits::gtype<std::string>::value, "");
static_assert(traits::gvalue<int>::value, "");
static_assert(traits::gvalue<CppEnum>::value, "");
static_assert(traits::is_enumeration<CppEnum>::value, "");
static_assert(!traits::is_bitfield<CppEnum>::value, "");
static_assert(traits::is_bitfield<CppFlags>::value, "");
static_assert(
std::is_pointer<GLib::DestroyNotify::cfunction_type>::value, "");
static_assert(!detail::allocator<int>::value, "");
static_assert(detail::allocator<GObject_::Value>::value, "");
static_assert(detail::allocator<GBoxedExample>::value, "");
static_assert(traits::is_type_complete<GBExample>::value, "");
struct BlackBox;
static_assert(!traits::is_type_complete<BlackBox>::value, "");
static_assert(
std::is_copy_constructible<GBoxedExample>::value == BOXED_COPY, "");
static_assert(
std::is_copy_assignable<GBoxedExample>::value == BOXED_COPY, "");
static_assert(std::is_move_constructible<GBoxedExample>::value, "");
static_assert(std::is_move_assignable<GBoxedExample>::value, "");
static_assert(std::is_copy_constructible<GBoxedExampleRef>::value, "");
static_assert(std::is_copy_assignable<GBoxedExampleRef>::value, "");
static_assert(
std::is_copy_constructible<CBoxedExample>::value == BOXED_COPY, "");
static_assert(
std::is_copy_assignable<CBoxedExample>::value == BOXED_COPY, "");
static_assert(std::is_move_constructible<CBoxedExample>::value, "");
static_assert(std::is_move_assignable<CBoxedExample>::value, "");
static_assert(std::is_copy_constructible<CBoxedExampleRef>::value, "");
static_assert(std::is_copy_assignable<CBoxedExampleRef>::value, "");
// internal
static_assert(detail::_traits::is_basic_argument<int>::value, "");
static_assert(!detail::_traits::is_basic_argument<int &>::value, "");
static_assert(!detail::_traits::is_basic_argument<std::string>::value, "");
static_assert(
detail::_traits::is_basic_argument<char *const *const>::value, "");
static_assert(
detail::_traits::is_const_lvalue<const std::string &>::value, "");
static_assert(!detail::_traits::is_const_lvalue<GBoxedExample &>::value, "");
}
// test helper
template<typename T>
int
refcount(T *obj)
{
return (((GObject *)(obj))->ref_count);
}
void
test_wrap()
{
{ // plain boxed
CBExample *ex = gi_cpp_cbexample_new();
auto cb = wrap(ex, transfer_full);
assert(cb.gobj_() == ex);
auto cb2 = wrap(ex, transfer_none);
assert(cb2 == cb);
assert(cb.gobj_() == cb2.gobj_());
auto cb3 = cb2;
assert(cb3 == cb2);
assert(cb2.gobj_() == cb3.gobj_());
auto cbn = CBoxedExample::new_();
assert(cbn.gobj_() != nullptr);
assert(cbn.gobj_()->data == 0);
// a peek
auto ex2 = unwrap(cbn, transfer_none);
assert(ex2 == cbn.gobj_());
// a fresh copy
auto ex3 = unwrap(std::move(cbn), transfer_full);
assert(ex3 != cbn.gobj_());
gi_cpp_cbexample_free(ex3);
cbn = CBoxedExample();
assert(cbn == nullptr);
assert(cbn.gobj_() == nullptr);
assert(!cbn);
auto ex4 = unwrap(std::move(cbn), transfer_full);
assert(ex4 == nullptr);
// various basic operations
CBoxedExample cbm(std::move(cbn));
cbm = std::move(cbn);
assert(!cbn);
std::swap(cbm, cbn);
assert(!cbm);
// compile check
CBoxedExample f = CBoxedExample::new_();
f = nullptr;
// ref
CBoxedExampleRef r(f);
assert(r.gobj_() == f.gobj_());
r = nullptr;
assert(r.gobj_() == nullptr);
r = f;
assert(r.gobj_() == f.gobj_());
// ref wrap
auto r2 = wrap(ex, transfer_none);
static_assert(std::is_same<decltype(r2), CBoxedExampleRef>::value, "");
r = r2;
assert(r.gobj_() == ex);
auto ro = unwrap(r, transfer_none);
auto ri = unwrap(r, transfer_none);
assert(ri == ro);
}
{ // similarly so gboxed
GBExample *exc = gi_cpp_gbexample_new();
GBoxedExample w1 = wrap(exc, transfer_full);
GBoxedExample w2 = std::move(w1);
assert(!w1);
assert(w2.gobj_() == exc);
GBoxedExample w3 = w2.copy_();
assert(w3);
assert(w3 != w2);
assert(w3.gobj_() != w2.gobj_());
w3 = nullptr;
// ref
GBoxedExampleRef r(w2);
assert(r.gobj_() == w2.gobj_());
r = nullptr;
assert(r.gobj_() == nullptr);
r = w2;
assert(r.gobj_() == w2.gobj_());
// ref wrap
r = wrap(exc, transfer_none);
assert(r.gobj_() == exc);
auto ro = unwrap(r, transfer_none);
auto ri = unwrap(r, transfer_none);
assert(ri == ro);
auto rc = r.copy_();
assert(rc != r);
#if BOXED_COPY
{
GBoxedExample ww{w3};
ww = w3;
r = w3;
ww = r;
ww = std::move(r);
}
#endif
}
{ // string
const gchar *str{};
std::string s1 = wrap(str, transfer_none);
assert(s1.empty());
str = "TEST";
std::string s2 = wrap(str, transfer_none);
assert(s2 == str);
auto *cs = unwrap(s2, transfer_none);
assert(g_strcmp0(cs, str) == 0);
// now something we should be able to free
cs = unwrap(s2, transfer_full);
g_free((gpointer)cs);
// unwrap empty optional
const std::string &emptytest = "";
auto cempty = unwrap(emptytest, transfer_none);
assert(*cempty == 0);
auto ocempty = unwrap(
static_cast<const detail::optional_string &>(emptytest), transfer_none);
assert(ocempty == nullptr);
}
{ // some more plain cases
wrap(5);
unwrap(5, transfer_none);
const double ex = 5.0;
auto w = wrap(ex);
auto ex2 = unwrap(w);
assert(ex == ex2);
CEnum ec{};
CppEnum ecpp{};
ecpp = wrap(ec);
unwrap(ecpp, transfer_none);
}
{ // object
CDerived *ob = gi_cpp_example_new();
assert(refcount(ob) == 1);
auto wo = wrap(ob, transfer_none);
assert(refcount(ob) == 2);
assert(wo.gobj_() == ob);
{
auto wo2 = wo;
assert(wo2 == wo);
assert(wo2.gobj_() == ob);
assert(refcount(ob) == 3);
// cast to own type should work fine
auto wo3 = object_cast<Derived>(wo2);
assert(wo3);
assert(refcount(ob) == 4);
}
assert(refcount(ob) == 2);
auto ob2 = unwrap(wo, transfer_none);
assert(ob2 == ob);
assert(refcount(ob) == 2);
ob2 = unwrap(wo, transfer_full);
assert(ob2 == ob);
assert(refcount(ob) == 3);
g_object_unref(ob2);
ob2 = nullptr;
assert(refcount(ob) == 2);
// no change for full transfer
auto wo2 = wrap(ob, transfer_full);
assert(wo2.gobj_() == ob);
assert(refcount(ob) == 2);
// compensate
g_object_ref(ob);
wo2 = Derived();
assert(wo2.gobj_() == nullptr);
assert(refcount(ob) == 2);
wo2 = std::move(wo);
assert(wo.gobj_() == nullptr);
assert(wo2.gobj_() == ob);
wo2 = wo;
assert(refcount(ob) == 1);
g_object_unref(ob);
Derived wo3(wo2);
if (wo3)
wo3 = nullptr;
}
{
// object creation
Derived ob = GObject_::Object::new_<Derived>();
ob = GObject_::Object::new_<Derived>(
NAME_NUMBER, (int)5, NAME_PRESENT, true);
assert(ob.property_number().get() == 5);
assert(ob.property_present().get() == true);
Derived ob2 = GObject_::Object::new_<Derived>();
std::swap(ob2, ob);
}
{
// paramspec
auto pspec = GObject_::ParamSpec::new_<int>("p", "p", "p", 0, 10);
// is otherwise similar to property and is tested there further
}
{ // wrap_to
int x = 5;
auto y = wrap_to<int>(x, transfer_none);
assert(x == y);
wrap_to<std::string>("x", transfer_none);
}
{ // unwrap_maybe_float
CDerived *ob = gi_cpp_example_new();
assert(refcount(ob) == 1);
auto wo = wrap(ob, transfer_full);
assert(refcount(ob) == 1);
CDerived *uwo = detail::unwrap_maybe_float(std::move(wo), transfer_none);
assert(uwo == ob);
assert(!wo);
assert(refcount(ob) == 1);
assert(g_object_is_floating(ob));
wo = wrap(ob, transfer_none);
assert(refcount(ob) == 1);
assert(!g_object_is_floating(ob));
uwo = detail::unwrap_maybe_float(std::move(wo), transfer_full);
assert(!wo);
assert(uwo == ob);
assert(refcount(ob) == 1);
wo = wrap(ob, transfer_full);
assert(refcount(ob) == 1);
// wrapper will clean up
GBExample *exc = gi_cpp_gbexample_new();
GBoxedExample w1 = wrap(exc, transfer_full);
GBExample *uw1 = detail::unwrap_maybe_float(std::move(w1), transfer_full);
assert(!w1);
assert(uw1 == exc);
w1 = wrap(exc, transfer_full);
// wrapper will clean up
}
{ // compile checks on cs_ptr helper
using Object = gi::repository::GObject::Object;
auto g = [](Object) {};
auto f = [&g](const gi::cs_ptr<Object> &ob) {
ob->handler_block(0);
Object obx = ob;
auto y = ob;
auto x = [y]() { y->handler_block(1); };
g(ob);
};
Object ob{};
if (false)
f(ob);
}
}
void
test_helpers()
{
constexpr int VALUE = 5;
int value = VALUE * 3;
using nmtype = std::unique_ptr<int>;
struct Args
{
gi::required<nmtype> ptr;
gi::required<int> v;
int ov{};
int &rv;
};
auto rf = [&value](nmtype ptr, int v, int ov, int &rv) {
assert(ptr && *ptr == VALUE);
assert(v == VALUE * 2);
assert(ov == 0);
assert(rv == value);
};
auto f = [&rf](Args args) {
rf(std::move(args.ptr), std::move(args.v), std::move(args.ov), args.rv);
};
f({.ptr = std::make_unique<int>(VALUE), .v = VALUE * 2, .rv = value});
}
using gi::detail::Collection;
using gi::detail::CollectionHolder;
namespace collection_helpers
{
struct LVHandler
{
template<typename T>
T &f(T &t)
{
return t;
}
};
struct RVHandler
{
template<typename T>
T &&f(T &t)
{
return std::move(t);
}
};
template<typename ListType, typename T, typename Transfer>
using CollectionProxy =
Collection<ListType, typename traits::ctype<T>::type, Transfer>;
template<typename ListType, typename T, typename Transfer>
using CollectionHolderProxy =
CollectionHolder<ListType, typename traits::ctype<T>::type, Transfer>;
std::string
get_value(const std::string &x)
{
return x;
}
std::string
get_value(const gi::cstring &x)
{
return x;
}
template<typename T>
auto
get_value(const T &x) -> decltype(unwrap(x, transfer_none))
{
return unwrap(const_cast<T &>(x), transfer_none);
}
template<template<typename, typename, typename> class CT = CollectionProxy,
typename CppType, typename Transfer, typename LVRHandler>
void
check_collection(std::vector<CppType> &&v, const Transfer &t, LVRHandler h)
{
// collect size
const auto VS = v.size();
// and (internal) value for later check
using VT = decltype(get_value(v.front()));
std::vector<VT> baseline;
auto get_values = [&v] {
decltype(baseline) out;
for (auto &val : v)
out.push_back(get_value(val));
return out;
};
baseline = get_values();
auto do_check = [&get_values, &baseline]() {
auto current = get_values();
assert(current == baseline);
};
auto check_list_it = [&baseline](auto &list) {
decltype(baseline) current;
int cnt = 0;
for (auto &e : list) {
// e should be const & iff Transfer none
using el_type = typename std::remove_reference<decltype(e)>::type;
static_assert(std::is_const<el_type>::value ==
std::is_same<Transfer, transfer_none_t>::value,
"");
assert(e);
++cnt;
current.push_back(get_value(e));
}
assert(cnt == int(baseline.size()));
assert(current == baseline);
cnt = 0;
current.clear();
const auto &clist = list;
for (auto &e : clist) {
// e should always be const & here
using el_type = typename std::remove_reference<decltype(e)>::type;
static_assert(std::is_const<el_type>::value, "");
assert(e);
++cnt;
current.push_back(get_value(e));
}
assert(cnt == int(baseline.size()));
assert(current == baseline);
// assign from non-const to const
auto cb = clist.begin();
cb = list.begin();
assert(!cnt || *cb);
};
// various trips through variations
CT<::GList, CppType, Transfer> l{h.f(v)};
static constexpr bool is_sub = std::is_same<decltype(l),
CollectionHolderProxy<::GList, CppType, Transfer>>::value;
static_assert(is_sub || (sizeof(l) == sizeof(::GList *)), "");
check_list_it(l);
v = std::move(l);
assert(!l);
assert(v.size() == VS);
do_check();
l = h.f(v);
v = std::move(l);
assert(!l);
assert(v.size() == VS);
do_check();
CT<::GSList, CppType, Transfer> sl{h.f(v)};
static_assert(is_sub || (sizeof(l) == sizeof(::GSList *)), "");
check_list_it(sl);
v = std::move(sl);
assert(!sl);
assert(v.size() == VS);
do_check();
sl = h.f(v);
v = std::move(sl);
assert(!sl);
assert(v.size() == VS);
do_check();
CT<::GPtrArray, CppType, Transfer> pa{h.f(v)};
static_assert(is_sub || (sizeof(l) == sizeof(::GPtrArray *)), "");
check_list_it(pa);
v = std::move(pa);
assert(!pa);
assert(v.size() == VS);
do_check();
pa = h.f(v);
v = std::move(pa);
assert(!pa);
assert(v.size() == VS);
do_check();
CT<DSpan, CppType, Transfer> da{h.f(v)};
check_list_it(da);
v = std::move(da);
assert(!da);
assert(v.size() == VS);
do_check();
da = h.f(v);
v = std::move(da);
assert(!da);
assert(v.size() == VS);
do_check();
CT<ZTSpan, CppType, Transfer> za{h.f(v)};
check_list_it(za);
v = std::move(za);
assert(!za);
za = h.f(v);
v = std::move(za);
assert(!za);
assert(v.size() == VS);
do_check();
l = h.f(v);
// temporarily use a separate sv
// (so v can keep its elements alive, e.g. std::string case)
std::vector<CppType> sv;
sv.resize(VS);
std::move(l).move_to(sv.data());
assert(!l);
v = std::move(sv);
sv.clear();
do_check();
// in case of holder; l has ownership here (so none wrap/unwrap below ok)
l = h.f(v);
auto lu = unwrap(std::move(l), t);
assert(!l || !t.value);
// last part; always need to wrap to a basic wrapper
using WrapTypeL = CollectionProxy<::GList, CppType, Transfer>;
auto ln = wrap_to<WrapTypeL>(lu, t);
assert(lu == ln.gobj_());
v = std::move(ln);
do_check();
pa = h.f(v);
auto pau = unwrap(std::move(pa), t);
assert(!pa || !t.value);
// ref'ed case, so it can always take ownership
using WrapTypeA = CollectionProxy<::GPtrArray, CppType, Transfer>;
auto pan = wrap_to<WrapTypeA>(pau, t);
assert(pau == pan.gobj_());
}
template<template<typename, typename, typename> class CT = CollectionProxy,
typename CppKey, typename CppType, typename Transfer, typename LVRHandler>
void
check_collection(std::map<CppKey, CppType> &&v, const Transfer &t, LVRHandler h)
{
const auto VS = v.size();
using VT = decltype(get_value(v.begin()->second));
std::map<CppKey, VT> baseline;
auto get_values = [](auto &c) {
decltype(baseline) out;
for (auto &val : c)
out[val.first] = get_value(val.second);
return out;
};
baseline = get_values(v);
auto do_check = [&get_values, &baseline](auto &c) {
auto current = get_values(c);
assert(current == baseline);
};
auto check_map_it = [&baseline](auto &list) {
decltype(baseline) current;
int cnt = 0;
for (auto &e : list) {
assert(e.first);
assert(e.second);
++cnt;
current[get_value(e.first)] = get_value(e.second);
}
assert(cnt == int(baseline.size()));
assert(current == baseline);
cnt = 0;
current.clear();
const auto &clist = list;
for (auto &e : clist) {
assert(e.first);
assert(e.second);
++cnt;
current[get_value(e.first)] = get_value(e.second);
}
assert(cnt == int(baseline.size()));
assert(current == baseline);
};
CT<::GHashTable, std::pair<CppKey, CppType>, Transfer> l{h.f(v)};
static constexpr bool is_sub = std::is_same<decltype(l),
CollectionHolderProxy<::GHashTable, std::pair<CppKey, CppType>,
Transfer>>::value;
static_assert(is_sub || (sizeof(l) == sizeof(::GHashTable *)), "");
check_map_it(l);
v = std::move(l);
assert(!l);
assert(v.size() == VS);
do_check(v);
l = h.f(v);
v = std::move(l);
assert(!l);
assert(v.size() == VS);
do_check(v);
l = h.f(v);
std::unordered_map<std::string, CppType> w;
w = std::move(l);
assert(!l);
assert(w.size() == VS);
do_check(w);
// in case of holder; l has ownership here (so none wrap/unwrap below ok)
l = h.f(w);
auto lu = unwrap(std::move(l), t);
assert(!l || !t.value);
// last part; always need to wrap to a basic wrapper
using WrapType =
CollectionProxy<::GHashTable, std::pair<CppKey, CppType>, Transfer>;
auto ln = wrap_to<WrapType>(lu, t);
assert(lu == ln.gobj_());
}
template<typename ListType, typename Transfer>
static void
check_collection_null()
{ // check nullptr cases
// type not relevant in these cases
using BT = CDerived *;
std::vector<Derived> bv;
Transfer t;
{
auto ln =
wrap_to<Collection<ListType, BT, Transfer>>((ListType *)(nullptr), t);
assert(!ln);
ln = nullptr;
assert(!ln);
bv = std::move(ln);
assert(bv.empty());
}
}
} // namespace collection_helpers
void
test_collection()
{
using namespace collection_helpers;
auto make_object = [] { return wrap(gi_cpp_example_new(), transfer_full); };
auto make_box = []() -> GBoxedExample {
return wrap(gi_cpp_gbexample_new(), transfer_full);
};
{ // init list construction
CollectionProxy<::GPtrArray, Derived, transfer_full_t> coll_pa{
make_object(), make_object()};
assert(coll_pa.size() == 2);
CollectionProxy<gi::DSpan, int, transfer_full_t> coll_da{2, 3, 4};
assert(coll_da.size() == 3);
#if 0
// will not work, since not copyable
CollectionProxy<gi::ZTSpan, GBoxedExample, transfer_full_t> coll_za{
make_box()};
assert(coll_za.size() == 1);
// but neither will the following
std::vector<std::unique_ptr<int>> x{std::make_unique<int>(5)};
#endif
}
// full
{ // object
std::vector<Derived> v{make_object(), make_object()};
check_collection(std::move(v), transfer_full, LVHandler());
// map
std::map<std::string, Derived> m{
{"x", make_object()}, {"y", make_object()}};
check_collection(std::move(m), transfer_full, LVHandler());
}
{ // non-copyable boxed
std::vector<GBoxedExample> v;
v.push_back(make_box());
v.push_back(make_box());
check_collection(std::move(v), transfer_full, RVHandler());
// map
std::map<std::string, GBoxedExample> m;
m["x"] = make_box();
m["y"] = make_box();
check_collection(std::move(m), transfer_full, RVHandler());
}
{ // string
std::vector<std::string> v{"foo", "bar"};
check_collection(std::move(v), transfer_full, LVHandler());
// map
std::map<std::string, std::string> m{{"x", "foo"}, {"y", "bar"}};
check_collection(std::move(m), transfer_full, LVHandler());
}
{ // cstring
std::vector<gi::cstring> v{"foo", "bar"};
check_collection(std::move(v), transfer_full, LVHandler());
// map
std::map<gi::cstring, gi::cstring> m{{"x", "foo"}, {"y", "bar"}};
check_collection(std::move(m), transfer_full, LVHandler());
}
{ // plain
std::vector<int> v{2, 3};
auto w = v;
Collection<gi::DSpan, int, transfer_full_t> da{v.data(), v.size()};
v = std::move(da);
assert(!da);
assert(v == w);
da = v;
std::move(da).move_to(v.data());
assert(v == w);
}
// container
{ // object
std::vector<Derived> v{make_object(), make_object()};
// hold item ownership as they are moved about
auto t = v;
check_collection(std::move(v), transfer_container, LVHandler());
// map
std::map<std::string, Derived> m{
{"x", make_object()}, {"y", make_object()}};
auto tm = m;
check_collection(std::move(m), transfer_container, LVHandler());
}
{ // copyable ref boxed
std::vector<GBoxedExample> v;
v.push_back(make_box());
v.push_back(make_box());
std::vector<GBoxedExampleRef> w{v.begin(), v.end()};
check_collection(std::move(w), transfer_container, LVHandler());
// map
std::map<std::string, GBoxedExampleRef> m{
{"x", v.front()}, {"y", v.back()}};
check_collection(std::move(m), transfer_container, LVHandler());
}
{ // string
std::vector<std::string> v{"foo", "bar"};
check_collection(std::move(v), transfer_container, LVHandler());
// map
std::map<std::string, std::string> m{{"x", "foo"}, {"y", "bar"}};
check_collection(std::move(m), transfer_container, LVHandler());
}
{ // cstring
std::vector<gi::cstring> v{"foo", "bar"};
std::vector<gi::cstring_v> w{v.begin(), v.end()};
check_collection(std::move(w), transfer_container, LVHandler());
// map
std::map<gi::cstring, gi::cstring_v> m{{"x", v.front()}, {"y", v.back()}};
check_collection(std::move(m), transfer_container, LVHandler());
}
{ // plain
std::vector<CppEnum> v{CppEnum::VALUE_0, CppEnum::VALUE_1};
auto w = v;
CollectionProxy<gi::DSpan, CppEnum, transfer_container_t> da{
v.data(), v.size()};
v = std::move(da);
assert(!da);
assert(v == w);
da = v;
std::move(da).move_to(v.data());
assert(v == w);
}
// none
{
static_assert(!std::is_copy_constructible<
CollectionHolder<::GList, CDerived *>>::value,
"");
static_assert(std::is_move_constructible<
CollectionHolder<::GList, CDerived *>>::value,
"");
}
// parameter helper should behave much as a dynamic container transfer
{ // object
std::vector<Derived> v{make_object(), make_object()};
// hold item ownership as they are moved about
auto t = v;
check_collection<CollectionHolderProxy>(
std::move(v), transfer_none, LVHandler());
// map
std::map<std::string, Derived> m{
{"x", make_object()}, {"y", make_object()}};
auto tm = m;
check_collection<CollectionHolderProxy>(
std::move(m), transfer_none, LVHandler());
}
{ // copyable ref boxed
std::vector<GBoxedExample> v;
v.push_back(make_box());
v.push_back(make_box());
std::vector<GBoxedExampleRef> w{v.begin(), v.end()};
check_collection<CollectionHolderProxy>(
std::move(w), transfer_none, LVHandler());
// map
std::map<std::string, GBoxedExampleRef> m{
{"x", v.front()}, {"y", v.back()}};
check_collection<CollectionHolderProxy>(
std::move(m), transfer_none, LVHandler());
}
{ // string
std::vector<std::string> v{"foo", "bar"};
check_collection<CollectionHolderProxy>(
std::move(v), transfer_none, LVHandler());
// map
std::map<std::string, std::string> m{{"x", "foo"}, {"y", "bar"}};
check_collection<CollectionHolderProxy>(
std::move(m), transfer_none, LVHandler());
}
{ // cstring
std::vector<gi::cstring> v{"foo", "bar"};
std::vector<gi::cstring_v> w{v.begin(), v.end()};
check_collection<CollectionHolderProxy>(
std::move(w), transfer_none, LVHandler());
// map
std::map<gi::cstring, gi::cstring_v> m{{"x", v.front()}, {"y", v.back()}};
check_collection<CollectionHolderProxy>(
std::move(m), transfer_none, LVHandler());
}
// some other array cases
{
std::array<const char *, 2> sa{{"x", "y"}};
Collection<gi::DSpan, char *, transfer_none_t> sc;
sc = wrap_to<decltype(sc)>(sa.data(), sa.size(), transfer_none);
assert(sc.gobj_() == sa.data());
auto usa = unwrap(sc, transfer_none);
assert(usa == sa.data());
}
{ // case that might arise trying to pass main's(argv, argc) to a parameter
std::array<const char *, 2> sa{{"x", "y"}};
CollectionParameter<gi::DSpan, char *, transfer_none_t> sc{
(char **)sa.data(), sa.size()};
assert(sc.gobj_() == sa.data());
auto usa = unwrap(sc, transfer_none);
assert(usa == sa.data());
}
{
std::array<int, 2> a{3, 4};
CollectionHolderProxy<gi::DSpan, int, transfer_none_t> ac{
a.data(), a.size()};
// optimization applies
assert(ac.gobj_() == a.data());
assert(ac._size() == a.size());
auto rc = ac.gobj_();
auto uac = unwrap(std::move(ac), transfer_none);
assert(rc == uac);
for (int i : {0, 1})
assert(uac[i] == a[i]);
ac = wrap_to<decltype(ac)>(uac, a.size(), transfer_none);
assert(ac.gobj_() == uac);
}
{
std::array<int, 2> a{5, 6};
// allocate new array
Collection<gi::DSpan, int, transfer_full_t> ac{a.data(), a.size()};
assert(ac.gobj_() != a.data());
assert(ac._size() == a.size());
auto rc = ac.gobj_();
auto uac = unwrap(std::move(ac), transfer_full);
// now owned by uac above
assert(!ac);
assert(rc == uac);
for (int i : {0, 1})
assert(uac[i] == a[i]);
// transfer full to ac again
ac = wrap_to<decltype(ac)>(uac, a.size(), transfer_full);
assert(ac.gobj_() == uac);
}
{ // check GHashTable transfer_none hack
using VT = std::pair<char *, char *>;
using TT = gi::CollectionParameter<::GHashTable, VT, transfer_none_t>;
static_assert(
std::is_same<TT,
gi::detail::Collection<::GHashTable, VT, transfer_full_t>>::value,
"");
// should work for refcnt case
TT t;
unwrap(t, transfer_none);
// also check null handling
assert(!t);
t = nullptr;
assert(!t);
std::map<std::string, std::string> m = std::move(t);
assert(m.empty());
}
{ // nullptr handling
check_collection_null<::GList, transfer_full_t>();
check_collection_null<::GList, transfer_none_t>();
check_collection_null<::GSList, transfer_full_t>();
check_collection_null<::GSList, transfer_none_t>();
check_collection_null<::GPtrArray, transfer_full_t>();
check_collection_null<::GPtrArray, transfer_none_t>();
// array cases
using BT = int;
std::vector<int> bv;
{
auto ln = wrap_to<Collection<ZTSpan, BT, transfer_full_t>>(
(BT *)(nullptr), transfer_full);
assert(!ln);
ln = nullptr;
assert(!ln);
bv = std::move(ln);
assert(bv.empty());
}
{
auto ln = wrap_to<Collection<DSpan, BT, transfer_none_t>>(
(BT *)(nullptr), transfer_none);
assert(!ln);
ln = nullptr;
assert(!ln);
bv = std::move(ln);
assert(bv.empty());
}
}
{ // transition to other transfer types
auto check_equal = [](auto &l1, auto &l2) {
assert(!l1.empty());
assert(!l2.empty());
assert(l1.front());
assert(l2.front());
auto it1 = l1.begin();
auto it2 = l2.begin();
for (; it1 != l1.end() && it2 != l2.end(); ++it1, ++it2) {
assert(*it1 == *it2);
}
};
CollectionProxy<::GList, GBoxedExample, transfer_full_t> lf;
lf.push_back(make_box());
lf.push_back(make_box());
assert(!lf.empty());
assert(++lf.begin() != lf.end());
assert(++(++lf.begin()) == lf.end());
// generic container creation
CollectionProxy<::GList, GBoxedExample, transfer_container_t> lc{lf};
check_equal(lf, lc);
// special case to none
CollectionProxy<::GList, GBoxedExample, transfer_none_t> ln{lf};
check_equal(lf, ln);
// also this way
lc = lf;
check_equal(lf, lc);
ln = lc;
check_equal(ln, lc);
}
auto uw = [](const auto &obj) { return unwrap(obj, transfer_none); };
auto check_list = [&uw](auto &lf, auto factory) {
lf.push_back(factory());
assert(!lf.empty());
auto p = uw(lf.front());
lf.push_back(factory());
assert(uw(lf.front()) == p);
assert(std::next(lf.begin()) != lf.end());
assert(std::next(lf.begin(), 2) == lf.end());
lf.push_front(factory());
assert(uw(lf.front()) != p);
lf.pop_front();
assert(uw(lf.front()) == p);
lf.pop_back();
assert(uw(lf.front()) == p);
lf.pop_back();
assert(lf.empty());
lf.push_front(factory());
assert(!lf.empty());
lf.pop_front();
assert(lf.empty());
lf.push_front(factory());
assert(!lf.empty());
lf.clear();
assert(lf.empty());
auto it = lf.insert(lf.begin(), factory());
p = uw(lf.front());
assert(it == lf.begin());
it = lf.insert(lf.begin(), factory());
assert(it == lf.begin());
assert(uw(lf.front()) != p);
lf.insert(lf.end(), factory());
assert(uw(*std::next(lf.begin())) == p);
it = std::next(lf.begin());
while (it != lf.end())
it = lf.erase(it);
assert(std::next(lf.begin()) == lf.end());
it = lf.erase(lf.begin());
assert(it == lf.end());
assert(lf.empty());
lf.push_front(factory());
typename std::remove_reference<decltype(lf)>::type lfo;
lf.swap(lfo);
assert(lf.empty());
assert(!lfo.empty());
lfo.pop_front();
assert(lfo.empty());
};
auto check_array = [&uw](auto &l, auto factory) {
l.clear();
l.push_back(factory());
auto p = uw(l.front());
assert(p);
assert(l.data()[0] == l[0]);
assert(l.data()[0] == l.at(0));
assert(l.data()[0] == l.front());
assert(l.at(0));
l.resize(10);
assert(l.size() == 10);
assert(!l.back());
l.resize(1);
assert(l.size() == 1);
assert(l.back() == l.front());
assert(uw(l.back()) == p);
};
{ // check common operations on lists
CollectionProxy<::GList, GBoxedExample, transfer_full_t> lf;
check_list(lf, make_box);
// special list part
lf.clear();
lf.push_back(make_box());
lf.push_back(make_box());
auto p = lf.front().gobj_();
lf.reverse();
assert(lf.front().gobj_() != p);
lf.reverse();
assert(lf.front().gobj_() == p);
// ptrarray
CollectionProxy<::GPtrArray, GBoxedExample, transfer_full_t> pa;
check_list(pa, make_box);
check_array(pa, make_box);
// dyn array
CollectionProxy<gi::DSpan, GBoxedExample, transfer_full_t> da;
check_list(da, make_box);
check_array(da, make_box);
// zt array
CollectionProxy<gi::ZTSpan, GBoxedExample, transfer_full_t> za;
check_list(za, make_box);
check_array(za, make_box);
// check ZT
za.clear();
za.push_back(make_box());
za.push_back(make_box());
assert(*za.end() == nullptr);
p = za.front().gobj_();
// auto d = za.data();
auto cap = za.capacity();
za.reserve(5 * cap);
assert(za.capacity() >= 5 * cap);
// NOTE new alloc might match old alloc
// assert(d != za.data());
assert(za.front().gobj_() == p);
// plain array
CollectionProxy<gi::DSpan, int, transfer_full_t> pda;
int gen = 1;
auto make_int = [&gen]() { return ++gen; };
check_list(pda, make_int);
check_array(pda, make_int);
}
{ // map operations
Collection<::GHashTable, std::pair<char *, GBExample *>, transfer_full_t>
mf;
assert(mf.empty());
const char *KEY1 = "key1";
const char *KEY2 = "key2";
auto v1 = make_box();
auto v2 = make_box();
auto p1 = uw(v1);
auto p2 = uw(v2);
auto ret = mf.replace(KEY1, std::move(v1));
assert(ret);
assert(mf.size() == 1);
ret = mf.replace(KEY2, std::move(v2));
assert(ret);
assert(mf.size() == 2);
assert(std::next(std::next(mf.begin())) == mf.end());
assert(mf.find("blah") == mf.end());
assert(!mf.lookup("blah"));
auto it = mf.find(KEY1);
assert(it != mf.end());
assert(uw(it->second) == p1);
it = mf.find(KEY2);
assert(it != mf.end());
assert(uw(it->second) == p2);
// transfer to variant container
// generic to container
Collection<::GHashTable, std::pair<char *, GBExample *>,
transfer_container_t>
mc{mf};
assert(mc.size() == mf.size());
mc.clear();
assert(mc.empty());
mc = mf;
assert(mc.size() == mf.size());
assert(uw(mc.lookup(KEY2)) == p2);
// always down to none
Collection<::GHashTable, std::pair<char *, GBExample *>, transfer_none_t>
mn{mf};
assert(mn.size() == mf.size());
assert(uw(mn.lookup(KEY2)) == p2);
mn = mc;
assert(mn.size() == mf.size());
assert(uw(mn.lookup(KEY1)) == p1);
}
}
namespace string_helpers
{
struct MyView
{
const char *c_str() const { return nullptr; }
size_t size() const { return 0; }
};
struct MyViewCustom
{};
using cstr = detail::cstring;
using cstrv = detail::cstring_v;
template<typename StringType>
void
check_string()
{
constexpr bool is_view = std::is_same<cstrv, StringType>::value;
// construct
const char *STR = "ab";
StringType cs(STR);
assert((cs.data() == STR) == is_view);
std::string s("cd");
StringType tcs(s);
cs = s;
assert((cs.data() == s.data()) == is_view);
cstrv csv(cs);
assert(csv.data() == cs.data());
tcs = std::move(cs);
assert(tcs.data() == csv.data());
cs = std::move(tcs);
assert(cs.data() == csv.data());
// ops
for (auto &c : cs)
assert(c);
assert(cs.size() == s.size());
assert(cs.length() == cs.size());
assert(!cs.empty());
assert(cs.at(0) == s.at(0));
assert(cs[0] == s[0]);
assert(cs.front() == s.front());
assert(cs.compare(cs) == 0);
assert(cs.find(s) == 0);
assert(cs.find(s, 1) == cs.npos);
assert(cs.rfind(s) == 0);
assert(cs.rfind(s, 1) == cs.npos);
// assign/construct to string
std::string ns{cs};
assert(ns == s);
ns = cs;
assert(ns == s);
assert(ns == cs);
assert(cs == cs);
ns = "xy";
assert(cs <= ns);
assert(cs < ns);
assert(ns > cs);
assert(ns >= cs);
assert(ns != cs);
// construct using convert
{
MyViewCustom cv;
StringType cs(cv);
assert(!cs);
}
cs.swap(tcs);
assert(tcs);
cs = nullptr;
assert(!cs);
assert(!s.empty());
{
std::map<StringType, int> m;
cs = s;
m[cs] = 5;
assert(m[cs] == 5);
}
{
std::unordered_map<StringType, int> m;
cs = s;
m[cs] = 5;
assert(m[cs] == 5);
}
#if __cplusplus >= 201703L
// C++17 specifics
cs = std::nullopt;
std::optional<std::string> os;
cs = os;
assert(!cs);
os = s;
cs = os;
assert(cs == s);
os = std::nullopt;
os = cs;
assert(os && os.value() == s);
cs = nullptr;
assert(!cs);
os = cs.opt_();
assert(!os);
cs = STR;
assert(cs.find_first_of("b") == 1);
assert(cs.find_first_of('b') == 1);
#endif
}
} // namespace string_helpers
namespace gi
{
namespace convert
{
template<typename Transfer>
struct converter<string_helpers::MyViewCustom, detail::cstr<Transfer>>
: public converter_base<string_helpers::MyViewCustom,
detail::cstr<Transfer>>
{
static detail::cstr<Transfer> convert(const string_helpers::MyViewCustom &v)
{
(void)v;
return nullptr;
}
};
} // namespace convert
} // namespace gi
void
test_string()
{
using namespace string_helpers;
{ // generic
check_string<cstr>();
check_string<cstrv>();
}
std::string s("xy");
{ // string only
cstr cs(s, 1, 1);
{
MyView cv;
cstr cs(cv);
assert(!cs);
}
{
auto *s = (char *)g_malloc0(5);
cstr cs(s, transfer_full);
assert(cs.data() == s);
}
// ops
cs = s;
cs = cs + s;
assert(cs == s + s);
assert(cs.size() == 4);
cs = s;
cs = s + cs;
assert(cs == s + s);
cs.pop_back();
assert(cs.size() == 3);
cs.push_back(s.back());
assert(cs.size() == 4);
assert(cs == s + s);
cs = cs.substr(0, 2);
assert(cs == s);
cs += s;
assert(cs == s + s);
cs.assign(2, 'x');
assert(cs == "xx");
cs = 'y' + cs;
assert(cs == "yxx");
cs = cs + 'y';
assert(cs == "yxxy");
cs.clear();
assert(!cs);
cs = s;
auto sp = unwrap(std::move(cs), transfer_full);
assert(s == sp);
assert(sp);
assert(!cs);
cs = cstr{sp, transfer_full};
assert(cs.data() == sp);
sp = unwrap(std::move(cs), transfer_full);
assert(!cs);
cs = wrap(sp, transfer_full);
assert(cs.data() == sp);
// C++17 string view
#if __cplusplus >= 201703L
std::string_view sv(s);
cs = sv;
assert(cs.data() != sv.data());
assert(sv == cs);
assert(cs == sv);
sv = "zz";
assert(cs <= sv);
assert(cs < sv);
assert(sv > cs);
assert(sv >= cs);
assert(sv != cs);
#endif
}
{ // view only
cstrv cv(s);
assert(cv.data() == s.data());
cv.remove_prefix(1);
assert(cv.size() == s.size() - 1);
assert(cv == s.substr(1));
const cstrv ccv(s);
std::string ns(ccv);
}
{ // combination
cstr cs("blah");
cstrv cv(cs);
assert(cv.data() == cs.data());
cv = cs;
assert(cv.data() == cs.data());
cs = cv;
assert(cs.data() != cv.data());
}
}
void
test_exception()
{
static_assert(traits::is_gboxed<GLib::Error>::value, "");
GQuark domain = g_quark_from_string("test-domain");
const char *msg = "exception_test";
const int code = 42;
GError *err = g_error_new_literal(domain, code, msg);
auto w = wrap(err, transfer_full);
assert(w.matches(domain, code));
auto what = w.what();
assert(strstr(what, msg) != NULL);
auto wr = wrap(err, transfer_none);
assert(wr.matches(domain, code));
GLib::Error e{std::move(w)};
assert(!w);
assert(e.matches(domain, code));
check_error(nullptr);
err = g_error_new_literal(domain, code, msg);
detail::make_unexpected(err);
bool value = true;
auto r = make_result<bool>(value, nullptr);
static_assert(detail::is_result<decltype(r)>::value, "");
static_assert(!detail::is_result<bool>::value, "");
// make sure we have bool result
auto s = expect(std::move(r));
static_assert(std::is_same<bool, decltype(s)>::value, "");
auto &e2 = expect(e);
assert(&e2 == &e);
}
void
test_enumflag()
{
const char *name = "EnumValue1";
const char *nick = "v1";
auto v = CppEnum::VALUE_1;
auto w = value_info(v);
auto w1 = EnumValue<CppEnum>::get_by_name(name);
auto w2 = EnumValue<CppEnum>::get_by_nick(nick);
assert(w == w1);
assert(w1 == w2);
assert(w1.value_name() == name);
assert(w1.value_nick() == nick);
// convenient operations
auto f = CppFlags::VALUE_0 | CppFlags::VALUE_1;
f = f & CppFlags::VALUE_1;
f = ~f;
f ^= CppFlags::VALUE_0;
}
typedef int(CCallback)(int, float);
typedef detail::callback<int(int, float), transfer_none_t,
std::tuple<transfer_none_t, transfer_none_t>>
CppCallback;
void
test_callback()
{
{ // argument selection helper
auto f = [](int a, int b) { return a - b; };
using Y = detail::args_index<1, 0>;
auto rr = detail::apply_with_args<Y>(f, 7, 3, 5);
assert(rr == -4);
}
{
const char *t = "";
auto f = [](const char *s) -> decltype(auto) { return *s; };
static_assert(std::is_reference<decltype(f(t))>::value, "");
using Y = detail::args_index<0>;
auto &rr = detail::apply_with_args<Y>(f, t);
assert(&rr == t);
}
{
using T = typename detail::arg_traits<transfer_none_t>::transfer_type;
static_assert(std::is_same<transfer_none_t, T>::value, "");
static_assert(detail::is_simple_cb<std::tuple<transfer_none_t>>::value, "");
using TC = std::tuple<
detail::arg_info<transfer_full_t, false, void, detail::args_index<0>>,
detail::arg_info<transfer_full_t, false, void, detail::args_index<1>>>;
static_assert(!detail::is_simple_cb<TC>::value, "");
}
{ // exception helpers
detail::report_exception<true>(std::runtime_error("fail"), 5, nullptr);
detail::report_exception(
std::runtime_error("fail"), 5, (::GError **)(nullptr));
GError *error = nullptr;
detail::report_exception(std::runtime_error("fail"), 5, &error);
assert(error);
g_error_free(error);
error = nullptr;
}
int calls = 0;
auto l = [&](int a, float b) {
++calls;
return a * b;
};
detail::transform_callback_wrapper<int(int, float)>::with_transfer<false,
transfer_full_t, transfer_none_t, transfer_none_t>
x{l};
x.wrapper(1, 2, &x);
assert(calls == 1);
x.take_data(std::make_shared<int>(0));
auto m = [&](int a, CBoxedExample /*b*/, int &x, int *y) {
x = 1;
*y = 2;
++calls;
return a;
};
detail::transform_callback_wrapper<void(
int, CBoxedExample, int &, int *)>::with_transfer<false, transfer_full_t,
transfer_full_t, transfer_full_t, transfer_none_t, transfer_none_t>
y{m};
int p = 0, q = 0;
y.wrapper(1, gi_cpp_cbexample_new(), &p, &q, &y);
assert(calls == 2);
assert(p == 1);
assert(q == 2);
auto w = wrap(gi_cpp_example_new(), transfer_full);
auto w2 = wrap(gi_cpp_example_new(), transfer_full);
char str[] = "blah";
auto n = [&](GBoxedExample /*b*/, Derived &ob, CppEnum *e, gi::cstring_v &s,
gpointer &vb, GLib::Error * /*error*/) {
++calls;
if (e)
*e = CppEnum::VALUE_1;
ob = w2;
s = str;
vb = str;
return w;
};
detail::transform_callback_wrapper<Derived(GBoxedExample, Derived &,
CppEnum *, gi::cstring_v &, gpointer &, GLib::Error *)>::
with_transfer<false, transfer_full_t, transfer_full_t, transfer_full_t,
transfer_none_t, transfer_none_t, transfer_none_t, transfer_full_t>
z{n};
CDerived *ob{};
CEnum e{};
char *s{};
gpointer vb{};
auto r = z.wrapper(gi_cpp_gbexample_new(), &ob, &e, &s, &vb, nullptr, &z);
assert(calls == 3);
assert(r == w.gobj_());
assert(refcount(r) == 2);
g_object_unref(r);
assert(e == CEnum::ENUM_VALUE_1);
assert(s == str);
assert(vb == s);
assert(ob == w2.gobj_());
assert(refcount(ob) == 2);
g_object_unref(ob);
// extended case
{
using CLocalCallback_CF_CType = int (*)(int, gpointer);
struct CLocalCallback_CF_Trait
{
using handler_cb_type = CLocalCallback_CF_CType;
static auto handler(int x, handler_cb_type cb, gpointer ud)
{
return cb(x, ud);
};
};
using CppLocalCallback = detail::callback<int(int), transfer_none_t,
std::tuple<transfer_none_t>>;
int xx = 0;
auto nx = [&](GBoxedExample /*b*/, Derived &ob, CppEnum *e, int x,
CppLocalCallback cb, GLib::Error * /*error*/) {
++calls;
xx = x;
ob = w2;
if (e)
*e = CppEnum::VALUE_1;
auto r = cb(x);
// supplied cb below is identity function
assert(r == x);
return w;
};
detail::transform_callback_wrapper<Derived(GBoxedExample, Derived &,
CppEnum *, int, CppLocalCallback,
GLib::Error *),
CDerived *(GBExample *, CDerived **, CEnum *, int,
CLocalCallback_CF_CType, gpointer,
::GError **)>::with_transfer<false, transfer_none_t,
detail::arg_info<transfer_full_t, false, void, detail::args_index<0>>,
detail::arg_info<transfer_none_t, false, void, detail::args_index<1>>,
detail::arg_info<transfer_full_t, false, void, detail::args_index<2>>,
detail::arg_info<transfer_full_t, false, void, detail::args_index<3>>,
detail::arg_info<transfer_full_t, false, CLocalCallback_CF_Trait,
detail::args_index<4, 5>>,
detail::arg_info<transfer_full_t, false, void, detail::args_index<6>>>
zx{nx};
CDerived *obx{};
CEnum ex{};
auto cb = [](int v, gpointer ud) {
if (ud)
*(int *)(ud) = v;
return v;
};
int oi = 0;
auto r = zx.wrapper(
gi_cpp_gbexample_new(), &obx, &ex, 5, cb, (gpointer)&oi, nullptr, &zx);
// cpp callback should have been called
assert(calls == 4);
assert(xx == 5);
assert(r == w.gobj_());
assert(obx != nullptr);
assert(obx == w2.gobj_());
assert(e == CEnum::ENUM_VALUE_1);
// which in turn should have called the supplied callback
assert(oi == 5);
//
// destroy_notify parameter
bool called = false;
int INTVAL = 7;
auto ndx = [&](CppLocalCallback cb) {
called = true;
int x = INTVAL;
auto r = cb(x);
// supplied cb below is identity function
assert(r == x);
return w;
};
detail::transform_callback_wrapper<void(CppLocalCallback),
void(CLocalCallback_CF_CType, gpointer,
GDestroyNotify)>::with_transfer<false, transfer_none_t,
detail::arg_info<transfer_full_t, false, CLocalCallback_CF_Trait,
detail::args_index<0, 1, 2>>>
zdx{ndx};
auto dn = [](gpointer ud) { *(int *)ud = 0; };
zdx.wrapper(cb, (gpointer)&oi, dn, &zdx);
assert(called);
// destroy notify called last
assert(oi == 0);
//
// void return case, and sized array out
char *sx{};
gpointer vbx{};
const int V = 56;
int *a_data{};
int a_size = 0;
using TestColType = gi::Collection<gi::DSpan, int, transfer_full_t>;
auto nnx = [&](gi::cstring_v &s, gpointer &vb, CppLocalCallback cb,
TestColType &d) {
++calls;
s = str;
vb = str;
auto r = cb(V);
// supplied cb below is identity function
assert(r == V);
d.push_back(4);
};
detail::transform_callback_wrapper<void(gi::cstring_v & s, gpointer & vb,
CppLocalCallback, TestColType &),
void(char **, gpointer *, CLocalCallback_CF_CType, gpointer, int **,
int *)>::with_transfer<false, transfer_full_t,
detail::arg_info<transfer_none_t, false, void, detail::args_index<0>>,
detail::arg_info<transfer_none_t, false, void, detail::args_index<1>>,
detail::arg_info<transfer_full_t, false, CLocalCallback_CF_Trait,
detail::args_index<2, 3>>,
detail::arg_info<transfer_full_t, false, void,
detail::args_index<4, 5>>>
zzx{nnx};
zzx.wrapper(&sx, &vbx, cb, (gpointer)&oi, &a_data, &a_size, &zzx);
assert(calls == 5);
assert(sx == str);
assert(vbx == str);
// cpp callback should have been called
assert(oi == V);
// handle returned array
assert(a_size == 1);
assert(*a_data == 4);
g_free(a_data);
}
{ // compilation checks
const CppCallback cppcb(l);
auto uw{unwrap(cppcb, gi::scope_async)};
delete uw;
auto uw2{unwrap(cppcb, gi::scope_notified)};
delete uw2;
}
}
void
test_value()
{
using GObject_::Value;
static_assert(traits::gtype<Value>::value, "");
assert(g_type_is_a(GI_CPP_TYPE_ENUM, G_TYPE_ENUM));
assert(g_type_is_a(GI_CPP_TYPE_FLAGS, G_TYPE_FLAGS));
// detail helper
{
detail::Value v(5);
auto vs = detail::transform_value<std::string>(&v);
assert(vs == "5");
detail::Value v2("ab");
auto w = detail::get_value<std::string>(&v2);
assert(w == "ab");
}
// main Value
{
Value v;
v.init(G_TYPE_STRING);
v.set_value("ab");
auto w = v.get_value<std::string>();
assert(w == "ab");
}
{
Value v(0);
auto w = v.get_value<int>();
assert(w == 0);
v.set_value(5);
w = v.get_value<int>();
assert(w == 5);
}
{
Value v{std::string()};
auto w = v.get_value<std::string>();
assert(w.empty());
}
{
Value v((double)1.0);
auto w = v.get_value<double>();
assert(w == 1.0);
}
{
Value v('a');
auto w = v.get_value<char>();
assert(w == 'a');
}
{
CDerived *ob = gi_cpp_example_new();
auto wob = wrap(ob, transfer_none);
Value v(wob);
assert(refcount(ob) == 3);
auto w2 = v.get_value<Derived>();
assert(w2 == wob);
assert(refcount(ob) == 4);
auto wr = wrap(v.gobj_(), transfer_none);
assert(wr == v);
assert(refcount(ob) == 4);
g_object_unref(ob);
// others clean up by magic
}
{
Value v(GBoxedExample{});
auto w = v.get_value<GBoxedExample>();
assert(w.gobj_() == nullptr);
auto wr = v.get_value<GBoxedExampleRef>();
assert(wr.gobj_() == nullptr);
}
{
Value v(CppEnum::VALUE_0);
auto w = v.get_value<CppEnum>();
assert(w == CppEnum::VALUE_0);
Value v1(v.copy_());
assert(v != v1);
}
{
Value v(CppFlags::VALUE_1);
auto w = v.get_value<CppFlags>();
assert(w == CppFlags::VALUE_1);
Value v1(v.copy_());
assert(v != v1);
}
{ // test function; auto conversion
auto tf = [](Value v, GType t) { assert(G_VALUE_TYPE(v.gobj_()) == t); };
tf(5, G_TYPE_INT);
tf("ab", G_TYPE_STRING);
tf(GBoxedExample(), GI_CPP_TYPE_BOXED_EXAMPLE);
}
{ // wrapping
GValue *v = (GValue *)1;
auto w = wrap(v, transfer_none);
auto v1 = unwrap(w, transfer_none);
assert(v1 = v);
}
}
void
test_property()
{
CDerived *ob = gi_cpp_example_new();
// take ownership
Derived w = wrap(ob, transfer_full);
// manual
w.set_property(NAME_NUMBER, 5);
assert(w.get_property<int>(NAME_NUMBER) == 5);
w.set_property(NAME_PRESENT, true);
assert(w.get_property<bool>(NAME_PRESENT) == true);
const char *str = "value";
w.set_property(NAME_DATA, str);
assert(w.get_property<std::string>(NAME_DATA) == str);
w.set_property(NAME_OBJECT, w);
auto w2 = w.get_property<Derived>(NAME_OBJECT);
assert(w2 == w);
assert(refcount(ob) == 3);
// remove cycle ref held within ob
w.set_property(NAME_OBJECT, Derived());
assert(refcount(ob) == 2);
w.set_property(NAME_ENUM, CppEnum::VALUE_1);
assert(w.get_property<CppEnum>(NAME_ENUM) == CppEnum::VALUE_1);
// multiple props
w.set_properties(NAME_NUMBER, 10, NAME_FNUMBER, 5.2, NAME_PRESENT, FALSE);
assert(w.get_property<int>(NAME_NUMBER) == 10);
assert(w.get_property<double>(NAME_FNUMBER) == 5.2);
assert(w.get_property<bool>(NAME_PRESENT) == false);
// generic value
#ifdef GI_GOBJECT_PROPERTY_VALUE
w.get_property(NAME_NUMBER);
#endif
// via proxy
Derived w3 = wrap(gi_cpp_example_new(), transfer_full);
w.property_number().set(7);
w.property_fnumber().set(6.2);
w.property_data().set(str);
w.property_object().set(w3);
w.property_present().set(true);
w.property_choice().set(CppEnum::VALUE_0);
w.property_flags().set(CppFlags::VALUE_0);
// boxed property
GQuark domain = g_quark_from_string("test-domain");
auto error = GLib::Error::new_literal(domain, 1, "msg");
w.property_error().set(error.copy());
w.property_error().set(std::move(error));
const Derived cw = w;
assert(cw.property_number().get() == 7);
assert(cw.property_fnumber().get() == 6.2);
assert(cw.property_data().get() == str);
assert(cw.property_object().get() == w3);
assert(cw.property_data().get() == str);
assert(cw.property_choice().get() == CppEnum::VALUE_0);
assert(cw.property_flags().get() == CppFlags::VALUE_0);
// property queries
auto pspec = cw.find_property(NAME_NUMBER);
assert(pspec);
assert(pspec.get_name() == NAME_NUMBER);
auto pspecs = cw.list_properties();
assert(pspecs.size() == PROP_LAST);
}
void
test_signal()
{
// example values
double v_d = 2.7;
int v_i = 4;
std::string v_s = "values";
bool v_b = true;
CppEnum v_e = CppEnum::VALUE_1;
CppFlags v_f = CppFlags::VALUE_1;
Derived v_o = wrap(gi_cpp_example_new(), transfer_full);
// object to signal on
CDerived *ob = gi_cpp_example_new();
// take ownership
Derived w = wrap(ob, transfer_full);
// lambda callbacks
int recv = 0;
int ret = 7;
auto l1 = [&](Derived src, GObject_::Object o, bool b, bool c,
const std::string &s) -> int {
assert(src == w);
assert(o == v_o);
assert(s == v_s);
assert(b == v_b);
assert(c == !b);
++recv;
return ret;
};
w.signal_to_int().connect(l1);
auto r = w.signal_to_int().emit(v_o, v_b, !v_b, v_s);
assert(recv == 1);
assert(r == ret);
// another signal
auto l2 = [&](Derived src, int i, gint64 ll) -> std::string {
assert(src == w);
++recv;
return std::to_string(i + ll);
};
w.signal_to_string().connect(l2);
gint64 ll = 4;
auto sr = w.signal_to_string().emit(v_i, ll);
assert(recv == 2);
assert(std::stoi(sr) == v_i + ll);
// and another
auto l3 = w.signal_to_void().slot(
[&](Derived src, double d, CppEnum e, CppFlags f) {
assert(src == w);
assert(d == v_d);
assert(e == v_e);
assert(f == v_f);
++recv;
});
auto id = w.signal_to_void().connect(l3);
w.signal_to_void().emit(v_d, v_e, v_f);
assert(recv == 3);
auto conn = make_connection(id, l3, w);
assert(conn.connected());
{
// safe to disconnect twice (or attempt so)
GObject_::SignalScopedConnection sconn(conn), sconn2(conn);
assert(sconn.connected());
assert(sconn2.connected());
}
assert(!conn.connected());
w.signal_to_void().emit(v_d, v_e, v_f);
assert(recv == 3);
// signal collection and output argument
auto l4 = [&](Derived src, unsigned &o, auto pa, auto la) {
assert(src == w);
o = pa.size() + std::distance(la.begin(), la.end());
};
gi::Collection<::GPtrArray, char *, gi::transfer_full_t> pa;
gi::Collection<::GSList, char *, gi::transfer_full_t> la;
pa.push_back("blah");
la = pa;
assert(!la.empty());
la.push_back("foo");
w.signal_to_output_int().connect(l4);
unsigned result = 0;
w.signal_to_output_int().emit(result, pa, nullptr);
assert(result == pa.size());
w.signal_to_output_int().emit(result, nullptr, la);
assert(result == 2);
// assert exception helper
auto assert_exc = [](const std::function<void()> &func) {
bool exc = false;
try {
func();
} catch (std::exception &) {
exc = true;
}
assert(exc);
};
// check connect check
assert_exc(
[&]() { w.connect<std::string(Derived, int, gint64)>("to_void", l2); });
// check property value conversion
assert_exc([&]() { w.set_property<std::string>(NAME_OBJECT, "blah"); });
}
// ExampleInterface
class ExampleInterface : public gi::InterfaceBase
{
public:
typedef GICppExampleItf BaseObjectType;
static GType get_type_() G_GNUC_CONST
{
return gi_cpp_example_interface_get_type();
}
};
class ExampleInterfaceDef
{
typedef ExampleInterfaceDef self;
public:
typedef ExampleInterface instance_type;
typedef GICppExampleInterface interface_type;
using GI_MEMBER_CHECK_CONFLICT(vmethod) = self;
using GI_MEMBER_CHECK_CONFLICT(imethod) = self;
struct TypeInitData;
protected:
~ExampleInterfaceDef() = default;
static void interface_init(gpointer iface, gpointer /*data*/);
virtual int vmethod_(int a) = 0;
virtual int imethod_(int a) = 0;
};
using ExampleInterfaceImpl = gi::detail::InterfaceImpl<ExampleInterfaceDef>;
class ExampleInterfaceClassImpl
: public gi::detail::InterfaceClassImpl<ExampleInterfaceImpl>
{
friend class ExampleInterfaceDef;
typedef ExampleInterfaceImpl self;
typedef gi::detail::InterfaceClassImpl<ExampleInterfaceImpl> super;
protected:
using super::super;
int vmethod_(int a) override
{
auto _struct = get_struct_();
return _struct->vmethod(this->gobj_(), a);
}
int imethod_(int a) override
{
auto _struct = get_struct_();
return _struct->imethod(this->gobj_(), a);
}
};
struct ExampleInterfaceDef::TypeInitData
{
GI_MEMBER_DEFINE(ExampleInterfaceClassImpl, vmethod)
GI_MEMBER_DEFINE(ExampleInterfaceClassImpl, imethod)
template<typename SubClass>
constexpr static TypeInitData factory()
{
using DefData = detail::DefinitionData<SubClass, TypeInitData>;
return {GI_MEMBER_HAS_DEFINITION(SubClass, DefData, vmethod),
GI_MEMBER_HAS_DEFINITION(SubClass, DefData, imethod)};
}
};
void
ExampleInterfaceDef::interface_init(gpointer iface, gpointer data)
{
auto init_data = GI_MEMBER_INIT_DATA(TypeInitData, data);
auto itf = (interface_type *)(iface);
if (init_data.vmethod)
itf->vmethod = gi::detail::method_wrapper<self, int (*)(int),
transfer_full_t, std::tuple<transfer_none_t>>::wrapper<&self::vmethod_>;
if (init_data.imethod)
itf->imethod = gi::detail::method_wrapper<self, int (*)(int),
transfer_full_t, std::tuple<transfer_none_t>>::wrapper<&self::imethod_>;
}
// PropertyInterface
class PropertyInterface : public gi::InterfaceBase
{
public:
typedef GICppPropertyItf BaseObjectType;
static GType get_type_() G_GNUC_CONST
{
return gi_cpp_property_interface_get_type();
}
};
class PropertyInterfaceDef
{
typedef PropertyInterfaceDef self;
public:
typedef PropertyInterface instance_type;
typedef GICppPropertyInterface interface_type;
protected:
static void interface_init(gpointer iface, gpointer /*data*/)
{
auto itf = (interface_type *)(iface);
(void)itf;
}
};
using PropertyInterfaceImpl = gi::detail::InterfaceImpl<PropertyInterfaceDef>;
class PropertyInterfaceClassImpl
: public gi::detail::InterfaceClassImpl<PropertyInterfaceImpl>
{
typedef PropertyInterfaceImpl self;
typedef gi::detail::InterfaceClassImpl<PropertyInterfaceImpl> super;
protected:
using super::super;
};
class DerivedClassDef
{
typedef DerivedClassDef self;
public:
typedef Derived instance_type;
typedef GICppExampleClass class_type;
struct TypeInitData;
using GI_MEMBER_CHECK_CONFLICT(vmethod) = self;
using GI_MEMBER_CHECK_CONFLICT(cmethod) = self;
protected:
~DerivedClassDef() = default;
static void class_init(gpointer g_class, gpointer class_data_factory);
virtual int vmethod_(int a, int b) = 0;
virtual int cmethod_(int a, int b) = 0;
};
GI_CLASS_IMPL_BEGIN
class DerivedClass
: public gi::detail::ClassTemplate<DerivedClassDef,
GObject_::impl::internal::ObjectClass, ExampleInterfaceClassImpl>
{
friend class DerivedClassDef;
typedef DerivedClass self;
typedef gi::detail::ClassTemplate<DerivedClassDef,
GObject_::impl::internal::ObjectClass, ExampleInterfaceClassImpl>
super;
public:
typedef ExampleInterfaceClassImpl ExampleInterface_type;
private:
// make local helpers private
using super::get_struct_;
using super::gobj_;
protected:
GI_DISABLE_DEPRECATED_WARN_BEGIN
using super::super;
GI_DISABLE_DEPRECATED_WARN_END
virtual int vmethod_(int a, int b) override
{
auto _struct = get_struct_();
return _struct->vmethod(gobj_(), a, b);
}
virtual int cmethod_(int a, int b) override
{
auto _struct = get_struct_();
return _struct->cmethod(gobj_(), a, b);
}
};
struct DerivedClassDef::TypeInitData
{
GI_MEMBER_DEFINE(DerivedClass, vmethod)
GI_MEMBER_DEFINE(DerivedClass, cmethod)
template<typename SubClass>
constexpr static TypeInitData factory()
{
using DefData = detail::DefinitionData<SubClass, TypeInitData>;
return {GI_MEMBER_HAS_DEFINITION(SubClass, DefData, vmethod),
GI_MEMBER_HAS_DEFINITION(SubClass, DefData, cmethod)};
}
};
void
DerivedClassDef::class_init(gpointer g_class, gpointer class_data_factory)
{
auto class_data = GI_MEMBER_INIT_DATA(TypeInitData, class_data_factory);
GICppExampleClass *klass = (GICppExampleClass *)g_class;
if (class_data.vmethod)
klass->vmethod = gi::detail::method_wrapper<self, int (*)(int, int),
transfer_full_t,
std::tuple<transfer_none_t, transfer_none_t>>::wrapper<&self::vmethod_>;
if (class_data.cmethod)
klass->cmethod = gi::detail::method_wrapper<self, int (*)(int, int),
transfer_full_t,
std::tuple<transfer_none_t, transfer_none_t>>::wrapper<&self::cmethod_>;
// local compile check
(void)gi::detail::method_wrapper<self, int (*)(int, int),
std::nullptr_t>::wrapper<&self::cmethod_>;
}
GI_CLASS_IMPL_END
using DerivedImpl = gi::detail::ObjectImpl<Derived, DerivedClass>;
template<typename T>
class custom_property : public gi::property<T>
{
public:
using super = gi::property<T>;
using super::super;
using handler = std::function<void(const T &)>;
handler handler_;
void set_property(const GValue *value) override
{
super::set_property(value);
if (handler_)
handler_(this->get_value());
}
};
class UserDerived : public DerivedImpl
{
public:
// possible conflict for vmethod
// so we specify the override situation explicity
struct DefinitionData
{
GI_DEFINES_MEMBER(DerivedClassDef, vmethod, true)
GI_DEFINES_MEMBER(ExampleInterfaceDef, vmethod, true)
};
UserDerived()
: DerivedImpl(this), prop_int_set(this, "prop_int_set", "prop_int_set",
"prop_int_set", 0, 10, 0),
prop_bool_override(this, NAME_PRESENT)
{
// check detection of method definitions
constexpr auto class_def =
DerivedClassDef::TypeInitData::factory<UserDerived>();
static_assert(class_def.vmethod.value, "");
static_assert(!class_def.cmethod.value, "");
constexpr auto itf_def =
ExampleInterfaceDef::TypeInitData::factory<UserDerived>();
static_assert(itf_def.vmethod.value, "");
static_assert(!itf_def.imethod.value, "");
}
int vmethod_(int a, int b) override { return a * b; }
int pvmethod(int a, int b) { return DerivedImpl::vmethod_(a, b); }
int vmethod_(int a) override { return 2 * a; }
int pivmethod(int a) { return ExampleInterface_type::vmethod_(a); }
custom_property<int> prop_int_set;
custom_property<bool> prop_bool_override;
};
class UserDerived2 : public DerivedImpl
{
public:
// possible conflict for vmethod
// so we specify the override situation explicity
struct DefinitionData
{
GI_DEFINES_MEMBER(DerivedClassDef, vmethod, true)
GI_DEFINES_MEMBER(ExampleInterfaceDef, vmethod, false)
};
UserDerived2() : DerivedImpl(this)
{
// check detection of method definitions
constexpr auto x = DerivedClassDef::TypeInitData::factory<UserDerived2>();
static_assert(x.vmethod.value, "");
static_assert(x.cmethod.value, "");
constexpr auto itf_def =
ExampleInterfaceDef::TypeInitData::factory<UserDerived2>();
static_assert(!itf_def.vmethod.value, "");
static_assert(itf_def.imethod.value, "");
}
int vmethod_(int a, int b) override { return a * b; }
int cmethod_(int a, int b) override { return a * b; }
int imethod_(int a) override { return 5 * a; }
};
GI_DISABLE_DEPRECATED_WARN_BEGIN
class OldUserDerived : public DerivedImpl
{
public:
OldUserDerived() : DerivedImpl(typeid(*this)) {}
};
GI_DISABLE_DEPRECATED_WARN_END
static const int DEFAULT_PROP_INT = 7;
class UserObject : public ExampleInterfaceImpl,
public PropertyInterfaceImpl,
public GObject_::impl::ObjectImpl
{
public:
UserObject()
: ObjectImpl(this, {}, {{NAME_INUMBER, {&prop_itf_int, nullptr}}}),
signal_demo_(this, "demo"), prop_itf_int(this, NAME_INUMBER),
prop_int(
this, "prop_int", "prop_int", "prop_int", 0, 10, DEFAULT_PROP_INT),
prop_bool(this, "prop_bool", "prop_bool", "prop_bool", false),
prop_str(this, "prop_str", "prop_str", "prop_str", ""),
prop_object(this, "prop_object", "prop_object", "prop_object"),
prop_enum(this, "prop_enum", "prop_enum", "prop_enum")
{}
int vmethod_(int a) override { return 5 * a; }
int imethod_(int a) override { return 7 * a; }
gi::signal<void(Object, int)> signal_demo_;
gi::property<int> prop_itf_int;
gi::property<int> prop_int;
gi::property<bool> prop_bool;
gi::property<std::string> prop_str;
gi::property<Object> prop_object;
gi::property<CppEnum> prop_enum;
};
class UserCObject : public ExampleInterfaceImpl,
public PropertyInterfaceImpl,
public GObject_::impl::ObjectImpl
{
using self_type = UserCObject;
public:
UserCObject(const InitData &id) : ObjectImpl(this, id) { assert(id); }
static GType get_type_()
{
return register_type_<UserCObject>("UserCObject", 0,
{ExampleInterfaceImpl::interface_init_data<UserCObject>(),
{PropertyInterfaceImpl::register_interface, nullptr}},
{{&self_type::prop_itf_int, NAME_INUMBER},
{&self_type::prop_int, "prop_int", "prop_int", "prop_int", 0, 10,
DEFAULT_PROP_INT}},
{{&self_type::signal_demo_, "demo"}});
}
int vmethod_(int a) override { return 5 * a; }
int imethod_(int a) override { return 7 * a; }
// details provided in get_type_
gi::signal<void(Object, int)> signal_demo_{this, "demo"};
gi::property<int> prop_itf_int{this, NAME_INUMBER};
gi::property<int> prop_int{this, "prop_int"};
};
void
test_impl()
{
{
// base object implements interface
UserDerived u, v;
assert(u.gobj_type_() == v.gobj_type_());
assert(u.pvmethod(2, 3) == 5);
auto klass =
G_TYPE_INSTANCE_GET_CLASS(u.gobj_(), u.gobj_type(), GICppExampleClass);
assert(klass->vmethod(u.gobj_(), 2, 3) == 6);
// no cmethod
assert(!klass->cmethod);
// interface
assert(u.pivmethod(4) == 6);
auto iface = G_TYPE_INSTANCE_GET_INTERFACE(
u.gobj_(), ExampleInterface::get_type_(), GICppExampleInterface);
assert(iface->vmethod((GICppExampleItf *)u.gobj_(), 4) == 8);
assert(!iface->imethod);
// implemented here
UserDerived2 w;
assert(w.imethod_(2) == 10);
// compilation check
assert(u.gobj_klass()->vmethod);
{ // check custom property (on a non-Object derived class)
int value = 0;
auto &tp = u.prop_int_set;
tp.handler_ = [&value](int setv) { value = setv; };
auto proxy = tp.get_proxy();
//
const int NEW_VALUE = 7;
proxy.set(NEW_VALUE);
assert(value == NEW_VALUE);
assert(tp.get_value() == NEW_VALUE);
tp.handler_ = nullptr;
}
{ // likewise on overriden property
auto &tp = u.prop_bool_override;
auto proxy = tp.get_proxy();
proxy.set(false);
assert(!proxy.get());
bool value = false;
tp.handler_ = [&value](bool setv) { value = setv; };
const bool NEW_VALUE = true;
//
proxy.set(NEW_VALUE);
assert(value == NEW_VALUE);
assert(tp.get_value() == NEW_VALUE);
tp.handler_ = nullptr;
}
}
{
// vanilla object
UserObject u, v;
assert(u.gobj_type_() == v.gobj_type_());
auto iface = G_TYPE_INSTANCE_GET_INTERFACE(
u.gobj_(), ExampleInterface::get_type_(), GICppExampleInterface);
assert(iface);
assert(iface->vmethod((GICppExampleItf *)u.gobj_(), 4) == 20);
// signal
int i = 0, j = 5;
u.signal_demo_.connect([&i](GObject_::Object, int in) -> void { i = in; });
u.signal_demo_.emit(j);
assert(i == j);
// properties
{
// also check notification
bool notified = false;
auto proxy = u.prop_int.get_proxy();
auto l = proxy.signal_notify().slot(
[&notified](
GObject_::Object, GObject_::ParamSpec) { notified = true; });
GObject_::SignalScopedConnection conn =
make_connection(proxy.signal_notify().connect(l), l, u);
assert(u.prop_int == DEFAULT_PROP_INT);
u.prop_int = j;
assert(notified);
notified = false;
assert(proxy.get() == j);
proxy.set(2 * j);
assert(u.prop_int == 2 * j);
assert(notified);
}
{
auto proxy = u.prop_bool.get_proxy();
u.prop_bool = true;
assert(proxy.get() == true);
proxy.set(false);
assert(u.prop_bool == false);
}
{
auto proxy = u.prop_str.get_proxy();
const std::string strv = "value";
u.prop_str = strv;
assert(proxy.get() == strv);
proxy.set(strv + strv);
assert(u.prop_str.get_value() == (strv + strv));
}
{
auto proxy = u.prop_object.get_proxy();
u.prop_object = v;
assert(proxy.get() == v);
proxy.set(nullptr);
assert(u.prop_object.get_value() == nullptr);
}
{
CppEnum v1 = CppEnum::VALUE_1, v0 = CppEnum::VALUE_0;
auto proxy = u.prop_enum.get_proxy();
u.prop_enum = v1;
assert(proxy.get() == v1);
proxy.set(v0);
assert(u.prop_enum == v0);
}
{
const int val = 8;
u.prop_itf_int = val;
auto proxy = u.prop_itf_int.get_proxy();
assert(proxy.get() == val);
proxy.set(val);
assert(u.prop_itf_int == val);
}
{
// local properties
property_read<bool> p{&u, "p", "p", "p", false};
p.get_proxy();
property_write<bool> q{&u, "p", "p", "p", false};
q.get_proxy();
}
}
{
// create non-stack
auto u = gi::make_ref<UserObject>();
assert(u->list_properties().size() > 0);
// cast works ok
GObject_::Object v = u;
assert(refcount(v.gobj_()) == 2);
auto l = [](GObject_::Object) {};
l(u);
auto u2 = ref_ptr_cast<UserObject>(v);
assert(u2 == u);
assert(refcount(v.gobj_()) == 3);
auto u3 = u2;
assert(refcount(v.gobj_()) == 4);
auto u4 = std::move(u3);
assert(refcount(v.gobj_()) == 4);
assert(!u3);
auto um = gi::make_ref<UserObject, gi::construct_cpp_t>();
assert(um->gobj_type_() == u->gobj_type_());
}
{
// similar for C-style
auto u = gi::make_ref<UserCObject>("prop_int", DEFAULT_PROP_INT / 2);
assert(u->list_properties().size() > 0);
assert(u->prop_int.get_value() == DEFAULT_PROP_INT / 2);
auto um = gi::make_ref<UserCObject>();
assert(um->prop_int.get_value() == DEFAULT_PROP_INT);
assert(um->gobj_type_() == u->gobj_type_());
assert(um->gobj_type_() == gi::register_type<UserCObject>());
}
}
int
main(int argc, char *argv[])
{
(void)argc;
(void)argv;
test_trait();
test_wrap();
test_helpers();
test_collection();
test_string();
test_exception();
test_enumflag();
test_value();
test_property();
test_signal();
test_callback();
test_impl();
return 0;
}