Albrecht Schlosser schrieb am
Donnerstag, 27. Oktober 2022 um 14:58:20 UTC+2:
The Group thing is quite easy to
resolve: we just need to make Fl_Group::insert(),
remove(), and clear() virtual.
Unfortunately not. Been there, "tried" that, but it
can't work because we have too many overloaded add(),
remove() and clear() and maybe also insert() methods with
entirely unrelated semantics.
I just checked. The union of child1_ and array_ is only
written in Fl_Group::insert(Fl_Widget*, int index) and
Fl_Group::remove(int index) and nowhere else.
Fl_Group::erase() accesses it via array() in the most heinous
of ways.
OK, we're drifting even more OT, but yes: that's something I would
really like to remove. It's IMHO useless anyway since a group is
something that should hold more than one child in most cases. The
special case 1 (child) is an intermediate status, not something we
need to save memory. So why not allocate the array with a certain
number of children already for the first child and manage it
dynamically as with other arrays? The advantage is that we wouldn't
need to alloc/realloc/free (off the top of my head, not checked) for
every single child that is added or removed. The drawback would be
that we need another variable for the allocated array size. But this
would (a) simplify the code and (b) remove many allocations and
deallocations when children are added or removed.
Fl_Scroll and Fl_Tree reshuffle children, but at least do
not add or remove any.
I don't understand. Do you mean that their code does not explicitly
add or remove children? This should always be the case because this
is implemented in Fl_Group. So what are you saying here?
Fl_Table does its own thing anyway (sigh). Everything else
just reads array(). So I think insert, remove, and clear are
good enough. Fl_Group::array() should be deprecated, disable,
curried, and removed.
Fl_Group::array() is again just another topic and off-topic here. It
has nothing to do with the point I was making. What I wanted to say
is that we have several methods in derived classes that have the
same
*names* as one of the methods in question but different
semantics, arguments, and/or return types. You can't make a method
of the base class like remove() virtual and expect it to work in a
derived class which already has a method of the same name but with
totally different semantics. Let's look for an example ...
OK, found one: try to make all (three) variants of
Fl_Group::remove(...) virtual in Fl_Group.H:
virtual void remove(int index);
virtual void remove(Fl_Widget&);
virtual void remove(Fl_Widget* o) {remove(*o);}
Trying to compile:
[44/483] Building CXX object src/CMakeFiles/fltk.dir/Fl_Check_Browser.cxx.o
FAILED: src/CMakeFiles/fltk.dir/Fl_Check_Browser.cxx.o
/usr/bin/c++ -DFL_LIBRARY -D_FILE_OFFSET_BITS=64 -D_LARGEFILE64_SOURCE -D_LARGEFILE_SOURCE -I. -I../../ -I/usr/include/freetype2 -I/usr/include/pango-1.0 -I/usr/include/fribidi -I/usr/include/cairo -I/usr/include/pixman-1 -I/usr/include/harfbuzz -I/usr/include/glib-2.0 -I/usr/lib/x86_64-linux-gnu/glib-2.0/include -I/usr/include/uuid -I/usr/include/libpng16 -g -D_THREAD_SAFE -D_REENTRANT -MD -MT src/CMakeFiles/fltk.dir/Fl_Check_Browser.cxx.o -MF src/CMakeFiles/fltk.dir/Fl_Check_Browser.cxx.o.d -o src/CMakeFiles/fltk.dir/Fl_Check_Browser.cxx.o -c ../../src/Fl_Check_Browser.cxx
In file included from ../../src/Fl_Check_Browser.cxx:22:
../../FL/Fl_Check_Browser.H:81:7: error: conflicting return type specified for ‘virtual int Fl_Check_Browser::remove(int)’
81 | int remove(int item); // delete an item. Returns nitems()
| ^~~~~~
In file included from ../../FL/Fl_Browser_.H:26,
from ../../FL/Fl_Check_Browser.H:24,
from ../../src/Fl_Check_Browser.cxx:22:
../../FL/Fl_Group.H:111:15: note: overridden function is ‘virtual void Fl_Group::remove(int)’
111 | virtual void remove(int index);
| ^~~~~~
[240/483] Building CXX object fluid/CMakeFiles/fluid.dir/factory.cxx.o
FAILED: fluid/CMakeFiles/fluid.dir/factory.cxx.o
/usr/bin/c++ -D_FILE_OFFSET_BITS=64 -D_LARGEFILE64_SOURCE -D_LARGEFILE_SOURCE -I. -I../../ -I/usr/include/freetype2 -I/usr/include/pango-1.0 -I/usr/include/fribidi -I/usr/include/cairo -I/usr/include/pixman-1 -I/usr/include/harfbuzz -I/usr/include/glib-2.0 -I/usr/lib/x86_64-linux-gnu/glib-2.0/include -I/usr/include/uuid -I/usr/include/libpng16 -g -D_THREAD_SAFE -D_REENTRANT -MD -MT fluid/CMakeFiles/fluid.dir/factory.cxx.o -MF fluid/CMakeFiles/fluid.dir/factory.cxx.o.d -o fluid/CMakeFiles/fluid.dir/factory.cxx.o -c ../../fluid/factory.cxx
In file included from ../../fluid/factory.cxx:174:
../../FL/Fl_Check_Browser.H:81:7: error: conflicting return type specified for ‘virtual int Fl_Check_Browser::remove(int)’
81 | int remove(int item); // delete an item. Returns nitems()
| ^~~~~~
In file included from ../../FL/Fl_Window.H:24,
from ../../fluid/factory.cxx:33:
../../FL/Fl_Group.H:111:15: note: overridden function is ‘virtual void Fl_Group::remove(int)’
111 | virtual void remove(int index);
| ^~~~~~
[353/483] Building CXX object test/CMakeFiles/sudoku.dir/sudoku.cxx.o
ninja: build stopped: cannot make progress due to previous errors.
This is only one example where different argument and return types
prevent to make Fl_Group::remove() virtual. I believe that I found
more but it's not necessary to find all these conflicts, one
conflict is enough.
I'm not (yet) talking about all those Fl_Group:: methods like
clear() which you can make virtual and that would pass the compiler
but still change semantics unnoticed and/or break existing programs.
I remember when Fl_Window::hide() and show() were made virtual and
even code in FLTK was suddenly broken in an unpredicted way. Yes,
that's true.
I know that I tried making all these children handling methods
virtual but I gave up. That's the point I was making: we just can't
make some methods virtual because we have some conflicting methods
of the same names in derived classes.
Can we work around these conflicts w/o breaking existing programs? I
doubt that we can but I'd like to learn that we can - because that
was what I initially wanted to do but couldn't.
This needs just a few lines of code to be implemented:
in Fl_Widget at the top:
if (when() &
FL_WHEN_DELETING) {
int old_event =
Fl::event();
Fl::event(FL_DELETING);
do_callback();
Fl::event(old_event);
}
plus the declaration of those constants and some
Fluid changes.
Where "in Fl_Widget" would this be implemented? I assume
you mean in ~Fl_Widget(), i.e. in the destructor. Or did you
mean the handle() method? Anyway, assuming correct
implementation:
In ~Fl_Widget as one of the first lines to keep much of
Fl_Widget in tact.
Yes, OK, that's what I thought too. But it keeps only those parts of
the base class Fl_Widget intact and not those of the derived class
that is being destroyed - as far as I understand the C++ mechanics.
And that is something the user programmer *needs* to know: at the
time the callback is called the widget is already partially
destroyed (dangerous!). I think you can still call methods of
Fl_Widget but none of any subclass or access member variables of the
subclass (although in practice, you can, but that violates C++
rules).
But it doesn't;t really matter as it is really only there
to avoid deriving a new class. If a new class is derived, this
code can go into the destructor anyway. Also, doe we really
need thin in ::handle()? Because, if a new widget is created
anyway to have a new handle() method, why not again just put
that code in the destructor. It doesn't really do any harm
either, so, eh?
I would opt to not send an event in the destructor - for the same
reasons as said before. Calling the callback should be enough. The
usual way to call a callback is in the handle() method when an event
is processed and a condition is fulfilled to call the callback. If
we'd send an event to a (partially destroyed) widget *and* call the
callback then the callback could be called twice under certain
circumstances (we can't avoid programmer errors). It's also usual to
call the event handling procedure of each parent class, but what
would all these parent classes add to this callback processing? I
can't imagine what this could be good for.
If we'd do this, then I believe that calling the callback from
~Fl_Widget() would be enough and sending an event would make things
only unnecessarily complicated and error prone. I may be wrong, but
that's why we are discussing this. To learn what to do and how...
+1
There's one remaining problem though, if I'm not missing
anything. Your example seems perfect, but I believe at the
time the destructor of Fl_Widget is called, the derived
class would already be partially destroyed. Hence we need to
document clearly that the callback would only be allowed to
deallocate the related (user) data but not access the
widget, i.e. any parts of the derived class. Or whatever.
Yes, that's correct, and it's one reason why it should only
be available in the callback, to make clear that that is
really the main (and only?) purpose. It could bused to clear
external links *to* that widget, but that's a bit of a
stretch.
Well put. I agree.
Fl_Widget::copy_label() does indeed make a copy of a
string and the previous owner of the string can free it
immediately. Hence the name `copy...`.
Yes, my example was not a good match. I just wanted to
illustrate that we can have a label that is automatically
free'd when the widget goes, and we could also have user_data
that can be marked to be automatically free'd or deleted.
OK, then I believe we can drop this idea which might be problematic
API-wise anyway and keep the callback idea as proposed.