Added methods to uiControl to catch and handle parent changing from the perspective of the individual controls. This will be used to implement the transition from not being backed by an OS handle to being backed by one and back on Windows, and should be all we need to start re-adding controls after uiWindow.

This commit is contained in:
Pietro Gagliardi 2020-05-13 12:38:44 -04:00
parent e01c732760
commit 7aeb937b93
4 changed files with 159 additions and 2 deletions

View File

@ -68,6 +68,8 @@ uint32_t uiRegisterControlType(const char *name, const uiControlVtable *vtable,
}
checkMethod(Init)
checkMethod(Free)
checkMethod(ParentChanging)
checkMethod(ParentChanged)
#undef checkMethod
if (osVtable == NULL) {
@ -241,7 +243,10 @@ void uiControlSetParent(uiControl *c, uiControl *parent)
uiprivProgrammerErrorControlParentCycle(uiprivFunc);
return;
}
callVtable(c->type->vtable.ParentChanging, c, c->implData, c->parent);
c->parent = parent;
callVtable(c->type->vtable.ParentChanged, c, c->implData, c->parent);
}
void *uiControlImplData(uiControl *c)

View File

@ -28,6 +28,8 @@ struct uiControlVtable {
size_t Size;
bool (*Init)(uiControl *c, void *implData, void *initData);
void (*Free)(uiControl *c, void *implData);
void (*ParentChanging)(uiControl *c, void *implData, uiControl *oldParent);
void (*ParentChanged)(uiControl *c, void *implData, uiControl *newParent);
};
```
@ -117,7 +119,9 @@ It is a programmer error to introduce a cycle when changing the parent of a cont
TODO top-levels and parenting
**For control implementations**: You would call this when adding a control to your container, preferably before actually doing the OS-level work involved. Likewise, call this when removing a child, preferably after doing the OS-level work involved.
**For control implementations**: You would call this when adding a control to your container, preferably before actually doing any internal bookkeeping. Likewise, call this when removing a child, preferably after doing any internal bookkeeping.
The child will receive a call to its `ParentChanging()` method before the actual change happens, and a call to `ParentChanged()` afterward, receiving the old and new parent, respectively, as an argument. This is where OS-specific parenting code would go; specific instructions can be found in each OS's uiControl implementation page.
TODO do things this way to avoid needing to check if reparenting from a container implementation, or do that manually each time? we used to have uiControlVerifySetParent()...
TODO I forgot what this meant

View File

@ -12,12 +12,24 @@ static void vtableNopFree(uiControl *c, void *implData)
// do nothing
}
static void vtableNopParentChanging(uiControl *c, void *implData, uiControl *oldParent)
{
// do nothing
}
static void vtableNopParentChanged(uiControl *c, void *implData, uiControl *newParent)
{
// do nothing
}
void testControlLoadNopVtable(uiControlVtable *vtable)
{
memset(vtable, 0, sizeof (uiControlVtable));
vtable->Size = sizeof (uiControlVtable);
vtable->Init = vtableNopInit;
vtable->Free = vtableNopFree;
vtable->ParentChanging = vtableNopParentChanging;
vtable->ParentChanged = vtableNopParentChanged;
}
// TODO we'll have to eventually find out for real if memset(0) is sufficient to set pointers to NULL or not; C99 doesn't seem to say
@ -56,6 +68,10 @@ Test(ZeroSizeImplDataIsNULL)
struct counts {
unsigned int countInit;
unsigned int countFree;
unsigned int countParentChanging;
unsigned int countParentChanged;
uiControl *oldParent;
uiControl *newParent;
};
struct testImplData {
@ -93,10 +109,37 @@ static void testVtableFree(uiControl *c, void *implData)
}
}
static void testVtableParentChanging(uiControl *c, void *implData, uiControl *oldParent)
{
struct testImplData *d = (struct testImplData *) implData;
if (d->counts != NULL) {
d->counts->oldParent = oldParent;
d->counts->countParentChanging++;
if (d->counts->countParentChanging > 3)
d->counts->countParentChanging = 3;
}
}
static void testVtableParentChanged(uiControl *c, void *implData, uiControl *newParent)
{
struct testImplData *d = (struct testImplData *) implData;
if (d->counts != NULL) {
d->counts->newParent = newParent;
d->counts->countParentChanged++;
if (d->counts->countParentChanged > 3)
d->counts->countParentChanged = 3;
}
}
static const uiControlVtable vtable = {
.Size = sizeof (uiControlVtable),
.Init = testVtableInit,
.Free = testVtableFree,
.ParentChanging = testVtableParentChanging,
.ParentChanged = testVtableParentChanged,
};
static uint32_t testControlType(void)
@ -119,7 +162,7 @@ static uint32_t testControlType2(void)
Test(ControlMethodsCalled)
{
uiControl *c;
uiControl *c, *d;
struct counts counts;
memset(&counts, 0, sizeof (struct counts));
@ -137,6 +180,81 @@ Test(ControlMethodsCalled)
}
if (counts.countFree != 0)
TestErrorf("Free() called unexpectedly by uiNewControl()");
// yes, the casts to void * are necessary, because the "equivalence" of data pointers to void * is really just the compiler doing conversions for you and this does not (and cannot) extend to the parameter lists of varargs functions (https://stackoverflow.com/questions/34723062, https://stackoverflow.com/questions/9053658)
if (counts.countParentChanging != 0)
TestErrorf("ParentChanging() called unexpectedly by uiNewControl(); most recent oldParent = %p", (void *) (counts.oldParent));
if (counts.countParentChanged != 0)
TestErrorf("ParentChanged() called unexpectedly by uiNewControl(); most recent newParent = %p", (void *) (counts.newParent));
d = uiNewControl(testControlType(), NULL);
uiControlSetParent(c, d);
switch (counts.countParentChanging) {
case 0:
TestErrorf("ParentChanging() was not called by SetParent(non-NULL)");
break;
case 1:
// do nothing; this is the expected case
break;
default:
TestErrorf("ParentChanging() called more than once by SetParent(non-NULL)");
}
if (counts.oldParent != NULL)
TestErrorf("ParentChanging() called with wrong oldParent by SetParent(non-NULL) (if called more than once, this is the most recent call):" diff("%p"),
(void *) (counts.oldParent), (void *) NULL);
switch (counts.countParentChanged) {
case 0:
TestErrorf("ParentChanged() was not called by SetParent(non-NULL)");
break;
case 1:
// do nothing; this is the expected case
break;
default:
TestErrorf("ParentChanged() called more than once by SetParent(non-NULL)");
}
if (counts.newParent != d)
TestErrorf("ParentChanged() called with wrong newParent by SetParent(non-NULL) (if called more than once, this is the most recent call):" diff("%p"),
(void *) (counts.newParent), (void *) d);
if (counts.countInit != 1)
TestErrorf("Init() called unexpectedly by uiControlSetParent(non-NULL)");
if (counts.countFree != 0)
TestErrorf("Free() called unexpectedly by uiControlSetParent(non-NULL)");
uiControlSetParent(c, NULL);
switch (counts.countParentChanging) {
case 0:
case 1:
TestErrorf("ParentChanging() was not called by SetParent(NULL)");
break;
case 2:
// do nothing; this is the expected case
break;
default:
TestErrorf("ParentChanging() called more than once by SetParent(NULL)");
}
if (counts.oldParent != d)
TestErrorf("ParentChanging() called with wrong oldParent by SetParent(NULL) (if called more than once, this is the most recent call):" diff("%p"),
(void *) (counts.oldParent), (void *) d);
switch (counts.countParentChanged) {
case 0:
case 1:
TestErrorf("ParentChanged() was not called by SetParent(NULL)");
break;
case 2:
// do nothing; this is the expected case
break;
default:
TestErrorf("ParentChanged() called more than once by SetParent(NULL)");
}
if (counts.newParent != NULL)
TestErrorf("ParentChanged() called with wrong newParent by SetParent(NULL) (if called more than once, this is the most recent call):" diff("%p"),
(void *) (counts.newParent), (void *) NULL);
if (counts.countInit != 1)
TestErrorf("Init() called unexpectedly by uiControlSetParent(NULL)");
if (counts.countFree != 0)
TestErrorf("Free() called unexpectedly by uiControlSetParent(NULL)");
uiControlFree(d);
uiControlFree(c);
switch (counts.countFree) {
@ -151,6 +269,10 @@ Test(ControlMethodsCalled)
}
if (counts.countInit != 1)
TestErrorf("Init() called unexpectedly by uiControlFree()");
if (counts.countParentChanging != 2)
TestErrorf("ParentChanging() called unexpectedly by uiNewControl(); most recent oldParent = %p", (void *) (counts.oldParent));
if (counts.countParentChanged != 2)
TestErrorf("ParentChanged() called unexpectedly by uiNewControl(); most recent newParent = %p", (void *) (counts.newParent));
}
Test(NullControlTypeNameIsProgrammerError)
@ -207,6 +329,30 @@ Test(ControlVtableWithMissingFreeMethodIsProgrammerError)
endCheckProgrammerError(ctx);
}
Test(ControlVtableWithMissingParentChangingMethodIsProgrammerError)
{
uiControlVtable vt;
void *ctx;
ctx = beginCheckProgrammerError("uiRegisterControlType(): required uiControlVtable method ParentChanging() missing for uiControl type name");
vt = vtable;
vt.ParentChanging = NULL;
uiRegisterControlType("name", &vt, NULL, 0);
endCheckProgrammerError(ctx);
}
Test(ControlVtableWithMissingParentChangedMethodIsProgrammerError)
{
uiControlVtable vt;
void *ctx;
ctx = beginCheckProgrammerError("uiRegisterControlType(): required uiControlVtable method ParentChanged() missing for uiControl type name");
vt = vtable;
vt.ParentChanged = NULL;
uiRegisterControlType("name", &vt, NULL, 0);
endCheckProgrammerError(ctx);
}
Test(NullControlOSVtableIsProgrammerError)
{
void *ctx;

2
ui.h
View File

@ -54,6 +54,8 @@ struct uiControlVtable {
size_t Size;
bool (*Init)(uiControl *c, void *implData, void *initData);
void (*Free)(uiControl *c, void *implData);
void (*ParentChanging)(uiControl *c, void *implData, uiControl *oldParent);
void (*ParentChanged)(uiControl *c, void *implData, uiControl *newParent);
};
uiprivExtern uint32_t uiRegisterControlType(const char *nane, const uiControlVtable *vtable, const uiControlOSVtable *osVtable, size_t implDataSize);