Closed svenevs closed 6 years ago
I remember thinking about this usage problem when I was making the tab widget but I never got to doing anything about it. Digging through my old notes and reacquainting myself with the code, here is what I thought could be alternatives to only documenting it:
I personally prefer them in the order given above but some mix of the three might actually be the best.
Hey @Thorius thanks for the TabWidget
! I think docs + (1) are reasonable, the VScrollPanel
does something similar:
Doing any kind of default action would be very confusing, and might go unnoticed. My understanding is there should be exactly two children for a TabWidget
:
So if mChildren
is not exactly {mHeader, mContent}
issue an exception in performLayout
?
That might negatively affect subclasses, I was thinking of calling say protected virtual void validateChildren()
in performLayout
. It's default would be something like
if (mChildren.size() > 2)
throw std::runtime_error("TabWidget: do not add children directly to the TabWidget, create tabs and add children to those");
// Make sure mHeader and mContent are both present in mChildren
bool header_found = false;
bool content_found = false;
for (auto *child : mChildren) {
if (child == mHeader) header_found;
else if (child == mContent) content_found;
else
throw std::runtime_error("TabWidget: do not modify the list of children for a TabWidget. The expected children are exactly two: `mHeader` and `mContent`.");
}
if (!header_found)
throw std::runtime_error("TabWidget: do not modify the list of children for a TabWidget. `mHeader` was not present in `mChildren`!");
if (!content_found)
throw std::runtime_error("TabWidget: do not modify the list of children for a TabWidget.`mContent` was not present in `mChildren`!");
That may be a little excessive, but the idea would be that if somebody needs to subclass TabWidget
, then they would want to override validateChildren()
. This enables them to still call TabWidget::performLayout(ctx)
.
I can write it up if it makes sense, but it feels kind of clunky to me.
Well, this seems reasonable to me. It is somewhat long but it is quite straightforward. So, definitely write it up if you can. The only sort-of problem is that we catch this misuse during performLayout and not during addChildren (which is ass soon as we can catch it). Maybe just have validateChildren() called then? But then again, the VScrollPanel class does it at that time so it could be consistent with that. (but it might make sense to update that if it would make for better usage).
I will look into it today/tomorrow to see if I can think up of anything better and I will write back here.
Hmm. So you can actually modify the children list directly (though I think it's safe to say this is frowned upon). @wjakob is VScrollPanel
checking that mChildren.size() == 1
in performLayout
to "guarantee" that the check actually takes place (e.g., rather than overriding Widget::addChild
methods)?
@svenevs , modifying the list of children is definitely not a good idea (honestly, I think that it might be better to eventually make mChildren
private in Widget and only access then trough some protected observer but that is a topic for another time).
What I was referring to, is something along these lines.
void TabWidget::addChild(Widget* child) override {
// Only the header and content should ever get added. They should also be added before invoking addChild.
if (child != mHeader && child != mContent) {
throw std::runtime_error("TabWidget: do not add children directly to the TabWidget, create tabs and add children to those");
}
Widget::addChild(child);
}
Honestly, this seems quite reasonable, but I am not quite sure if this will work because of the circular way children are added to the child vector. Also is it safe to throw an exception. Will the memory pointed to by child
be freed if it is not added to the child vector? We might need to invoke Widget::addChild
before throwing.
If there is no other fundamental reason to making this, I feel it is a bit cleaner than throwing in performLayout
, as we catch the error earlier.
Yes, I think that is much cleaner. The Widget::addChild(int, Widget *)
is the only one that should be overridden (it's the only virtual one, and is the real implementation). There's an "easier" way to bypass the circle problem, which is to construct them as orphans (parent is nullptr
). Let's continue the discussion on #306 and change things there if it's not quite what you imagined.
Thanks for weighing in on how to fix this!
I was creating a tab widget and wasn't being very careful, it looked something like this
The
wrapper
needed to havelayer
notmTabWidget
as the parent, but the side effect is that when the tab widget performs its layout a floating point exception occurs.Can I just add docs to the tab widget saying "never add a child directly to the tab widget, always create a new tab and add children to that tab"? I don't really feel like trying to find / catch / prevent this is worth it...
Related: would it be worth saying in the docs explicitly that a floating point exception will occur if you do this? It might help future users who accidentally do this understand what is wrong, but it may be overkill