diff --git a/CHANGES.txt b/CHANGES.txt index 282cb233f..2e1c34a3a 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -121,6 +121,8 @@ Changes in FLTK 1.4.0 Released: ??? ?? 2022 - Added Fl_Surface_Device::push_current(new_surface) and Fl_Surface_Device::pop_current() to set/unset the current surface receiving graphics commands. + - New macros for easy function and method callbacks with multiple + type safe arguments (see FL_METHOD_CALLBACK_1 etc.) . New Configuration Options (ABI Version) diff --git a/FL/Fl_Widget.H b/FL/Fl_Widget.H index cf3a16b4f..fc7091225 100644 --- a/FL/Fl_Widget.H +++ b/FL/Fl_Widget.H @@ -70,6 +70,24 @@ struct FL_EXPORT Fl_Label { }; +/** A class prototype that allows for additional data in callbacks. + + Users can extend this class and pass it to widget callbacks. Widgets can + take ownership of the callback data, deleting the data when the widget + itself is deleted. + + The destructor of this class is virtual, allowing for additional code to + deallocate resources when the user data is deleted. + + \see FL_FUNCTION_CALLBACK, FL_METHOD_CALLBACK, FL_INLINE_CALLBACK + \see Fl_Widget::callback(Fl_Callback*, Fl_Callback_User_Data*, bool) + \see Fl_Widget::user_data(Fl_Callback_User_Data*, bool) + */ +class Fl_Callback_User_Data { +public: + virtual ~Fl_Callback_User_Data() { } +}; + /** Fl_Widget is the base class for all widgets in FLTK. @@ -152,7 +170,7 @@ protected: CLIP_CHILDREN = 1<<11, ///< all drawing within this widget will be clipped (Fl_Group) MENU_WINDOW = 1<<12, ///< a temporary popup window, dismissed by clicking outside (Fl_Window) TOOLTIP_WINDOW = 1<<13, ///< a temporary popup, transparent to events, and dismissed easily (Fl_Window) - MODAL = 1<<14, ///< a window blocking input to all other winows (Fl_Window) + MODAL = 1<<14, ///< a window blocking input to all other windows (Fl_Window) NO_OVERLAY = 1<<15, ///< window not using a hardware overlay plane (Fl_Menu_Window) GROUP_RELATIVE = 1<<16, ///< Reserved, not implemented. DO NOT USE. COPIED_TOOLTIP = 1<<17, ///< the widget tooltip is internally copied, its destruction is handled by the widget @@ -161,6 +179,7 @@ protected: NEEDS_KEYBOARD = 1<<20, ///< set on touch screen devices if a widget needs a keyboard when it gets the focus. Reserved, not yet used in 1.4.0. \see Fl_Widget::needs_keyboard() IMAGE_BOUND = 1<<21, ///< binding the image to the widget will transfer ownership, so that the widget will delete the image when it is no longer needed DEIMAGE_BOUND = 1<<22, ///< bind the inactive image to the widget, so the widget deletes the image when it is no longer needed + AUTO_DELETE_USER_DATA = 1<<23, ///< automatically call `delete` on the user_data pointer when destroying this widget; if set, user_data must point to a class derived from the class Fl_Callback_User_Data // Note to devs: add new FLTK core flags above this line (up to 1<<28). @@ -667,12 +686,27 @@ public: */ Fl_Callback_p callback() const {return callback_;} - /** Sets the current callback function for the widget. + /** Sets the current callback function and data for the widget. Each widget has a single callback. \param[in] cb new callback \param[in] p user data */ - void callback(Fl_Callback* cb, void* p) {callback_ = cb; user_data_ = p;} + void callback(Fl_Callback* cb, void* p) { + callback_ = cb; + user_data(p); + } + + /** Sets the current callback function and managed user data for the widget. + Setting auto_free will transfer ownership of the callback user data to the + widget. Deleting the widget will then also delete the user data. + \param[in] cb new callback + \param[in] p user data + \param[in] auto_free if set, the widget will free user data when destroyed + */ + void callback(Fl_Callback* cb, Fl_Callback_User_Data* p, bool auto_free) { + callback_ = cb; + user_data(p, auto_free); + } /** Sets the current callback function for the widget. Each widget has a single callback. @@ -695,7 +729,7 @@ public: */ void callback(Fl_Callback1* cb, long p = 0) { callback_ = (Fl_Callback*)(fl_intptr_t)(cb); - user_data_ = (void*)(fl_intptr_t)p; + user_data((void*)(fl_intptr_t)p); } /** Gets the user data for this widget. @@ -704,11 +738,11 @@ public: */ void* user_data() const {return user_data_;} - /** Sets the user data for this widget. - Sets the new user data (void *) argument that is passed to the callback function. - \param[in] v new user data - */ - void user_data(void* v) {user_data_ = v;} + /** \brief Sets the user data for this widget. */ + void user_data(void* v); + + /** \brief Sets the user data for this widget. */ + void user_data(Fl_Callback_User_Data* v, bool auto_free); /** Gets the current user data (long) argument that is passed to the callback function. @@ -727,7 +761,7 @@ public: \see argument() */ - void argument(long v) {user_data_ = (void*)(fl_intptr_t)v;} + void argument(long v) {user_data((void*)(fl_intptr_t)v);} /** Returns the conditions under which the callback is called. diff --git a/FL/fl_callback_macros.H b/FL/fl_callback_macros.H new file mode 100644 index 000000000..589606377 --- /dev/null +++ b/FL/fl_callback_macros.H @@ -0,0 +1,607 @@ +/* + * Macros for easy callbacks for the Fast Light Tool Kit (FLTK). + * + * Copyright 2023 by Bill Spitzak and others. + * + * This library is free software. Distribution and use rights are outlined in + * the file "COPYING" which should have been included with this file. If this + * file is missing or damaged, see the license at: + * + * https://www.fltk.org/COPYING.php + * + * Please see the following page on how to report bugs and issues: + * + * https://www.fltk.org/bugs.php + */ + +#ifndef _FL_FL_CALLBACK_MACROS_H_ +#define _FL_FL_CALLBACK_MACROS_H_ + +#include + +/** + \file fl_callback_macros.H + This file provides macros for easy function and method callbacks + with multiple type safe arguments. +*/ + +#ifdef FL_DOXYGEN + +/** + \brief Declare a C function callback with custom parameters. + + You can declare a plain C function callback or a static method callback with + custom parameters using this macro. It simplifies the process of calling + arbitrary functions with up to five custom parameters. The macro generates + code that ensures type safety and expands FLTK's standard callbacks, which + are limited to a single `void*` or `long` argument. + + To use the macro, you provide the widget that will handle the callback as the + first argument. The second argument can be either a regular function or a + static method in any class. + + Following these arguments, you can include up to five pairs, where each + pair consists of a type and a value. For example, + `int, 3` specifies an integer parameter with a value of 3. If you need to + pass two arguments, you can use two pairs, like this: + `int, 3, int, 4`. The last digit of the macro name must be the same as + the number of pairs (0..5) + + Whenever the code generated by the macro is called, the custom parameters are + duplicated and marked for automatic deallocation using `delete` when the + callback widget is destroyed. + + \code{.cpp} + #include + ... + Fl_Button *btn1 = new Fl_Button(10, 10, 100, 20, "Beep"); + FL_FUNCTION_CALLBACK(btn1, fl_beep); + ... + Fl_Button *btn2 = new Fl_Button(10, 40, 100, 20, "Hello"); + FL_FUNCTION_CALLBACK(btn2, + fl_message, + const char *, text, "Hello\n%d %d %d %d", + int, a, 1, int, b, 2, int, c, 3, int, d, 4 + ); + \endcode + + You can find a small demonstration program showcasing the usage of + `FL_*_CALLBACK_*` in the `examples/callbacks.cxx` file. + + \param WIDGET the widget that will call the callback + \param FUNC a C/C++ function or a static class method + \param TYPE0 VALUE0 TYPE1 VALUE1 TYPE2 VALUE2 a list of zero to five type/value pairs, all separated by commas + + \see FL_METHOD_CALLBACK_1, FL_INLINE_CALLBACK_2 + */ +#define FL_FUNCTION_CALLBACK_3(WIDGET, FUNC, TYPE0, VALUE0, TYPE1, VALUE1, TYPE2, VALUE2) + +/** + \brief Declare a non-static class method callback with custom parameters. + + You can declare a callback for a non-static class method with custom parameters + using this macro. It provides a convenient way to call arbitrary methods in + any class, overcoming FLTK's limitation of passing only a single `void*` or + `long` argument. Furthermore, it ensures type safety. + + The first argument of the macro specifies the widget that will handle the + callback. The second argument indicates the class type to be called. The + third argument must be a pointer to an instance of that class. The + fourth argument is the name of the method within the class. That method must be + public and should not be static. + + Following these arguments, you can include up to five pairs, where each + pair consists of a type and a value. For example, + `int, 3` specifies an integer parameter with a value of 3. If you need to + pass two arguments, you can use two pairs, like this: + `int, 3, int, 4`. The last digit of the macro name must be the same as + the number of pairs (0..5) + + Whenever the code generated by the macro is called, the custom parameters are + duplicated and marked for automatic deallocation using `delete` when the + callback widget is destroyed. + + \code{.cpp} + #include + ... + Fl_Button *btn = new Fl_Button(10, 10, 100, 20, "Test"); + FL_METHOD_CALLBACK_1(btn, Fl_Button, btn, color, Fl_Color, FL_GREEN); + \endcode + + You can find a small demonstration program showcasing the usage of + `FL_*_CALLBACK_*` in the `examples/callbacks.cxx` file. + + \param WIDGET the widget that will call the callback + \param CLASS the class type + \param SELF a pointer to an instance of the class + \param METH a C++ class method that must be public and not static + \param TYPE0 VALUE0 a list of zero to five type/value pairs, all separated by commas + + \see FL_FUNCTION_CALLBACK_3, FL_INLINE_CALLBACK_2 + */ +#define FL_METHOD_CALLBACK_1(WIDGET, CLASS, SELF, METH, TYPE0, VALUE0) + +/** + \brief Creates code to declare a callback function in line with instantiating a widget. + + You can use this macro to create a function as a callback, allowing you to + define the callback function right where the widget and callback are declared, + similar to a Lambda function. + + The first argument of the macro specifies the widget that will handle the + callback. Next, you can include up to five triplets, where each triplet + consists of a type, a parameter name, and a value. For example, `int, x, 3` + specifies an integer parameter with a value of 3. If you need to pass two + arguments, you can use two triplets, such as `int, x, 3, int, y, 4`. The last + digit of the macro name must be the same as the number of triplets (0..5). + + The last argument is the actual function body itself. + + The function body is limited to a syntax that the macro preprocessor can + handle. It should include the leading '{' and trailing '}' and may contain + local variable declarations, use global variables and functions, and use also + the variables listed and initialized in the argument triples of the macro. + Very large function bodies should be avoided because they may exceed the + admissible size of a macro argument. + + Whenever the code generated by the macro is called, the custom parameters are + duplicated and marked for automatic deallocation using `delete` when the + callback widget is destroyed. + + \code{.cpp} + #include + ... + Fl_Button *btn = new Fl_Button(10, 10, 100, 20, "Test"); + FL_INLINE_CALLBACK_1(btn, + Fl_String, name, btn->label(), + { + fl_message("Greetings from the %s button", name); + } + ); + \endcode + + You can find a small demonstration program showcasing the usage of + `FL_*_CALLBACK_*` in the `examples/callbacks.cxx` file. + + \param WIDGET the widget that will call the callback + \param TYPE0 the type of the first parameter in the function call + \param NAME0 an arbitrary variable name that can be used as a parameter in the function body + \param VALUE0 a constant value or a variable; the value of the variable is copied when the callback is created + \param TYPE1 NAME1 VALUE1 as above; there are six macros that support 0 to 5 parameters + \param LAMBDA_FUNCTION_BODY the function body within the limits of the C macro preprocessor + + \see FL_METHOD_CALLBACK_1, FL_FUNCTION_CALLBACK_3 + */ +#define FL_INLINE_CALLBACK_2(WIDGET, TYPE0, NAME0, VALUE0, TYPE1, VALUE1, NAME1, LAMBDA_FUNCTION_BODY) + +#else // FL_DOXYGEN + +/* + These two macros make it possible to call macros with names that are created + by concatenating the name in x and (in this context) the number in y. + */ +#define _FL_CBD_CONCAT_IMPL(x, y) x##y +#define _FL_CBD_CONCAT(x, y) _FL_CBD_CONCAT_IMPL(x, y) + +/* + Create a unique name for the derived class based on the current source code + line number. + */ +#define _FL_CBD_CLASS_NAME _FL_CBD_CONCAT(Fl_Callback_User_Data_,__LINE__) + + +/* + These macros create boilerplate code for callbacks to functions and + static class methods with up to five arguments. + + This macro invocation for example + ``` + FL_FUNCTION_CALLBACK_2( func_cb_btn_2, hello_2_args_cb, + const char *, text, "FLTK", + int, number, 2 ); + ``` + will generate the following code: + + ``` + do { + class Fl_Callback_User_Data_92 : public Fl_Callback_User_Data { + public: + const char * p0_; + int p1_; + static void cb(Fl_Widget *w, void *user_data) { + Fl_Callback_User_Data_92 *d = (Fl_Callback_User_Data_92*)user_data; + hello_2_args_cb(d->p0_, d->p1_); + }; + Fl_Callback_User_Data_92(const char * p0, int p1) + : p0_(p0), + p1_(p1) + { } + }; + func_cb_btn_2->callback(Fl_Callback_User_Data_92::cb, + new Fl_Callback_User_Data_92("FLTK", 2), + true); + } while(0) + ``` + + Clicking the Fl_Button `func_cb_btn_2` will call `hello_2_args_cb("FLTK", 2)`. + Deleting the button will also delete the data that was created in our + boilerplate code. + */ +#define FL_FUNCTION_CALLBACK_5(WIDGET, FUNC, TYPE0, VALUE0, TYPE1, VALUE1, TYPE2, VALUE2, TYPE3, VALUE3, TYPE4, VALUE4) \ + do { \ + class _FL_CBD_CLASS_NAME : public Fl_Callback_User_Data { \ + public: \ + TYPE0 p0_; TYPE1 p1_; TYPE2 p2_; TYPE3 p3_; TYPE4 p4_; \ + static void cb(Fl_Widget *w, void *user_data) { \ + _FL_CBD_CLASS_NAME *d = (_FL_CBD_CLASS_NAME*)user_data; \ + FUNC(d->p0_, d->p1_, d->p2_, d->p3_, d->p4_); \ + }; \ + _FL_CBD_CLASS_NAME(TYPE0 p0, TYPE1 p1, TYPE2 p2, TYPE3 p3, TYPE4 p4) \ + : p0_(p0), p1_(p1), p2_(p2), p3_(p3), p4_(p4) { }; \ + }; \ + WIDGET->callback(_FL_CBD_CLASS_NAME::cb, new _FL_CBD_CLASS_NAME(VALUE0, VALUE1, VALUE2, VALUE3, VALUE4), true); \ + } while(0) + +#define FL_FUNCTION_CALLBACK_4(WIDGET, FUNC, TYPE0, VALUE0, TYPE1, VALUE1, TYPE2, VALUE2, TYPE3, VALUE3) \ + do { \ + class _FL_CBD_CLASS_NAME : public Fl_Callback_User_Data { \ + public: \ + TYPE0 p0_; TYPE1 p1_; TYPE2 p2_; TYPE3 p3_; \ + static void cb(Fl_Widget *w, void *user_data) { \ + _FL_CBD_CLASS_NAME *d = (_FL_CBD_CLASS_NAME*)user_data; \ + FUNC(d->p0_, d->p1_, d->p2_, d->p3_); \ + }; \ + _FL_CBD_CLASS_NAME(TYPE0 p0, TYPE1 p1, TYPE2 p2, TYPE3 p3) \ + : p0_(p0), p1_(p1), p2_(p2), p3_(p3) { }; \ + }; \ + WIDGET->callback(_FL_CBD_CLASS_NAME::cb, new _FL_CBD_CLASS_NAME(VALUE0, VALUE1, VALUE2, VALUE3), true); \ + } while(0) + +#define FL_FUNCTION_CALLBACK_3(WIDGET, FUNC, TYPE0, VALUE0, TYPE1, VALUE1, TYPE2, VALUE2) \ + do { \ + class _FL_CBD_CLASS_NAME : public Fl_Callback_User_Data { \ + public: \ + TYPE0 p0_; TYPE1 p1_; TYPE2 p2_; \ + static void cb(Fl_Widget *w, void *user_data) { \ + _FL_CBD_CLASS_NAME *d = (_FL_CBD_CLASS_NAME*)user_data; \ + FUNC(d->p0_, d->p1_, d->p2_); \ + }; \ + _FL_CBD_CLASS_NAME(TYPE0 p0, TYPE1 p1, TYPE2 p2) \ + : p0_(p0), p1_(p1), p2_(p2) { }; \ + }; \ + WIDGET->callback(_FL_CBD_CLASS_NAME::cb, new _FL_CBD_CLASS_NAME(VALUE0, VALUE1, VALUE2), true); \ + } while(0) + +#define FL_FUNCTION_CALLBACK_2(WIDGET, FUNC, TYPE0, VALUE0, TYPE1, VALUE1) \ + do { \ + class _FL_CBD_CLASS_NAME : public Fl_Callback_User_Data { \ + public: \ + TYPE0 p0_; TYPE1 p1_; \ + static void cb(Fl_Widget *w, void *user_data) { \ + _FL_CBD_CLASS_NAME *d = (_FL_CBD_CLASS_NAME*)user_data; \ + FUNC(d->p0_, d->p1_); \ + }; \ + _FL_CBD_CLASS_NAME(TYPE0 p0, TYPE1 p1) \ + : p0_(p0), p1_(p1) { }; \ + }; \ + WIDGET->callback(_FL_CBD_CLASS_NAME::cb, new _FL_CBD_CLASS_NAME(VALUE0, VALUE1), true); \ + } while(0) + +#define FL_FUNCTION_CALLBACK_1(WIDGET, FUNC, TYPE0, VALUE0) \ + do { \ + class _FL_CBD_CLASS_NAME : public Fl_Callback_User_Data { \ + public: \ + TYPE0 p0_; \ + static void cb(Fl_Widget *w, void *user_data) { \ + _FL_CBD_CLASS_NAME *d = (_FL_CBD_CLASS_NAME*)user_data; \ + FUNC(d->p0_); \ + }; \ + _FL_CBD_CLASS_NAME(TYPE0 p0) \ + : p0_(p0) { }; \ + }; \ + WIDGET->callback(_FL_CBD_CLASS_NAME::cb, new _FL_CBD_CLASS_NAME(VALUE0), true); \ + } while(0) + +#define FL_FUNCTION_CALLBACK_0(WIDGET, FUNC) \ + do { \ + class _FL_CBD_CLASS_NAME : public Fl_Callback_User_Data { \ + public: \ + static void cb(Fl_Widget *w, void *user_data) { \ + FUNC(); \ + }; \ + _FL_CBD_CLASS_NAME() { }; \ + }; \ + WIDGET->callback(_FL_CBD_CLASS_NAME::cb, new _FL_CBD_CLASS_NAME(), true); \ + } while(0) + +/* + These macros create boilerplate code for callbacks to class methods + with up to five arguments. + + This macro invocation for example + ``` + FL_METHOD_CALLBACK_4(btn, + MyWindow, win, resize, + int, test_x+10, + int, test_y+10, + int, 320, + int, 400); + ``` + will generate the following code: + + ``` + do { + class Fl_Callback_User_Data_73 : public Fl_Callback_User_Data { + public: + int p0_; + int p1_; + int p2_; + int p3_; + MyWindow *self_; + static void cb(Fl_Widget *w, void *user_data) { + Fl_Callback_User_Data_73 *d = (Fl_Callback_User_Data_73*)user_data; + d->self_->resize(d->p0_, d->p1_, d->p2_, d->p3_); + }; + Fl_Callback_User_Data_73(MyWindow *self, int p0, int p1, int p2, int p3) + : self_(self), p0_(p0), p1_(p1), p2_(p2), p3_(p3) { } + }; + btn->callback(Fl_Callback_User_Data_73::cb, + new Fl_Callback_User_Data_73(win, test_x+10, test_y+10, 320, 400), + true); + } while(0); + ``` + + Clicking the Fl_Button `btn` will call + `win->resize(test_x+10, test_y+10, 320, 400);`. + Deleting the button will also delete the data that was created in our + boilerplate code. + */ + +#define FL_METHOD_CALLBACK_5(WIDGET, CLASS, SELF, METHOD, TYPE0, VALUE0, TYPE1, VALUE1, TYPE2, VALUE2, TYPE3, VALUE3, TYPE4, VALUE4) \ + do { \ + class _FL_CBD_CLASS_NAME : public Fl_Callback_User_Data { \ + public: \ + CLASS *self_; \ + TYPE0 p0_; TYPE1 p1_; TYPE2 p2_; TYPE3 p3_; TYPE4 p4_; \ + static void cb(Fl_Widget *w, void *user_data) { \ + _FL_CBD_CLASS_NAME *d = (_FL_CBD_CLASS_NAME*)user_data; \ + d->self_->METHOD(d->p0_, d->p1_, d->p2_, d->p3_, d->p4_); \ + }; \ + _FL_CBD_CLASS_NAME(CLASS *self, TYPE0 p0, TYPE1 p1, TYPE2 p2, TYPE3 p3, TYPE4 p4) \ + : self_(self), p0_(p0), p1_(p1), p2_(p2), p3_(p3), p4_(p4) { }; \ + }; \ + WIDGET->callback(_FL_CBD_CLASS_NAME::cb, new _FL_CBD_CLASS_NAME(SELF, VALUE0, VALUE1, VALUE2, VALUE3, VALUE4), true); \ + } while(0) + +#define FL_METHOD_CALLBACK_4(WIDGET, CLASS, SELF, METHOD, TYPE0, VALUE0, TYPE1, VALUE1, TYPE2, VALUE2, TYPE3, VALUE3) \ + do { \ + class _FL_CBD_CLASS_NAME : public Fl_Callback_User_Data { \ + public: \ + CLASS *self_; \ + TYPE0 p0_; TYPE1 p1_; TYPE2 p2_; TYPE3 p3_; \ + static void cb(Fl_Widget *w, void *user_data) { \ + _FL_CBD_CLASS_NAME *d = (_FL_CBD_CLASS_NAME*)user_data; \ + d->self_->METHOD(d->p0_, d->p1_, d->p2_, d->p3_); \ + }; \ + _FL_CBD_CLASS_NAME(CLASS *self, TYPE0 p0, TYPE1 p1, TYPE2 p2, TYPE3 p3) \ + : self_(self), p0_(p0), p1_(p1), p2_(p2), p3_(p3) { }; \ + }; \ + WIDGET->callback(_FL_CBD_CLASS_NAME::cb, new _FL_CBD_CLASS_NAME(SELF, VALUE0, VALUE1, VALUE2, VALUE3), true); \ + } while(0) + +#define FL_METHOD_CALLBACK_3(WIDGET, CLASS, SELF, METHOD, TYPE0, VALUE0, TYPE1, VALUE1, TYPE2, VALUE2) \ + do { \ + class _FL_CBD_CLASS_NAME : public Fl_Callback_User_Data { \ + public: \ + CLASS *self_; \ + TYPE0 p0_; TYPE1 p1_; TYPE2 p2_; \ + static void cb(Fl_Widget *w, void *user_data) { \ + _FL_CBD_CLASS_NAME *d = (_FL_CBD_CLASS_NAME*)user_data; \ + d->self_->METHOD(d->p0_, d->p1_, d->p2_); \ + }; \ + _FL_CBD_CLASS_NAME(CLASS *self, TYPE0 p0, TYPE1 p1, TYPE2 p2) \ + : self_(self), p0_(p0), p1_(p1), p2_(p2) { }; \ + }; \ + WIDGET->callback(_FL_CBD_CLASS_NAME::cb, new _FL_CBD_CLASS_NAME(SELF, VALUE0, VALUE1, VALUE2), true); \ + } while(0) + +#define FL_METHOD_CALLBACK_2(WIDGET, CLASS, SELF, METHOD, TYPE0, VALUE0, TYPE1, VALUE1) \ + do { \ + class _FL_CBD_CLASS_NAME : public Fl_Callback_User_Data { \ + public: \ + CLASS *self_; \ + TYPE0 p0_; TYPE1 p1_; \ + static void cb(Fl_Widget *w, void *user_data) { \ + _FL_CBD_CLASS_NAME *d = (_FL_CBD_CLASS_NAME*)user_data; \ + d->self_->METHOD(d->p0_, d->p1_); \ + }; \ + _FL_CBD_CLASS_NAME(CLASS *self, TYPE0 p0, TYPE1 p1) \ + : self_(self), p0_(p0), p1_(p1) { }; \ + }; \ + WIDGET->callback(_FL_CBD_CLASS_NAME::cb, new _FL_CBD_CLASS_NAME(SELF, VALUE0, VALUE1), true); \ + } while(0) + +#define FL_METHOD_CALLBACK_1(WIDGET, CLASS, SELF, METHOD, TYPE0, VALUE0) \ + do { \ + class _FL_CBD_CLASS_NAME : public Fl_Callback_User_Data { \ + public: \ + CLASS *self_; \ + TYPE0 p0_; \ + static void cb(Fl_Widget *w, void *user_data) { \ + _FL_CBD_CLASS_NAME *d = (_FL_CBD_CLASS_NAME*)user_data; \ + d->self_->METHOD(d->p0_); \ + }; \ + _FL_CBD_CLASS_NAME(CLASS *self, TYPE0 p0) \ + : self_(self), p0_(p0) { }; \ + }; \ + WIDGET->callback(_FL_CBD_CLASS_NAME::cb, new _FL_CBD_CLASS_NAME(SELF, VALUE0), true); \ + } while(0) + +#define FL_METHOD_CALLBACK_0(WIDGET, CLASS, SELF, METHOD) \ + do { \ + class _FL_CBD_CLASS_NAME : public Fl_Callback_User_Data { \ + public: \ + CLASS *self_; \ + static void cb(Fl_Widget *w, void *user_data) { \ + _FL_CBD_CLASS_NAME *d = (_FL_CBD_CLASS_NAME*)user_data; \ + d->self_->METHOD(); \ + }; \ + _FL_CBD_CLASS_NAME(CLASS *self) \ + : self_(self) { }; \ + }; \ + WIDGET->callback(_FL_CBD_CLASS_NAME::cb, new _FL_CBD_CLASS_NAME(SELF), true); \ + } while(0) + +/* + These macros create boilerplate code for callback functions inlined into + the widget creation code (similar to lambda functions in C++11 and up) + with up to five arguments. + + This macro invocation for example + ``` + FL_INLINE_CALLBACK_2( // callback has two parameters + btn, // attach callback to this button + const char *, text, "FLTK", // first parameter (type, name, value) + int, number, 2, // second parameter + { // function body + fl_message("We received the message %s with %d!", text, number); + } + ); + ``` + will generate the following code: + ``` + do { + class Fl_Callback_User_Data_133 : public Fl_Callback_User_Data { + public: + const char * p0_; // store first parameter here + int p1_; // store second parameter here + // lambda style function + static void fn(const char * text, int number ) { + fl_message("We received the message %s with %d!", text, number); + }; + // FLTK style callback + static void cb(Fl_Widget *w, void *user_data) { + Fl_Callback_User_Data_133 *d = (Fl_Callback_User_Data_133*)user_data; + fn(d->p0_, d->p1_); + }; + // class constructor + Fl_Callback_User_Data_133(const char * p0, int p1) + : p0_(p0), // copy parameter 0 + p1_(p1) // copy parameter 1 + { } // constructor body + }; + // connect our class to the widget callback + btn->callback(Fl_Callback_User_Data_133::cb, + new Fl_Callback_User_Data_133("FLTK", 2), + true); + } while(0); // user code adds semicolon + ``` + + Clicking the Fl_Button `btn` will call + `fl_message("We received the message %s with %d!", "FLTK", 2);`. + Deleting the button will also delete the data that was created in our + boilerplate code. + */ + +#define FL_INLINE_CALLBACK_5(WIDGET, TYPE0, NAME0, VALUE0, TYPE1, NAME1, VALUE1, TYPE2, NAME2, VALUE2, TYPE3, NAME3, VALUE3, TYPE4, NAME4, VALUE4, LAMBDA) \ + do { \ + class _FL_CBD_CLASS_NAME : public Fl_Callback_User_Data { \ + public: \ + TYPE0 p0_; TYPE1 p1_; TYPE2 p2_; TYPE3 p3_; TYPE4 p4_; \ + static void fn(TYPE0 NAME0, TYPE1 NAME1, TYPE2 NAME2, TYPE3 NAME3, TYPE4 NAME4) \ + LAMBDA; \ + static void cb(Fl_Widget *w, void *user_data) { \ + _FL_CBD_CLASS_NAME *d = (_FL_CBD_CLASS_NAME*)user_data; \ + _FL_CBD_CLASS_NAME::fn(d->p0_, d->p1_, d->p2_, d->p3_, d->p4_); \ + }; \ + _FL_CBD_CLASS_NAME(TYPE0 p0, TYPE1 p1, TYPE2 p2, TYPE3 p3, TYPE4 p4) \ + : p0_(p0), p1_(p1), p2_(p2), p3_(p3), p4_(p4) { }; \ + }; \ + WIDGET->callback(_FL_CBD_CLASS_NAME::cb, new _FL_CBD_CLASS_NAME(VALUE0, VALUE1, VALUE2, VALUE3, VALUE4), true); \ + } while(0) + +#define FL_INLINE_CALLBACK_4(WIDGET, TYPE0, NAME0, VALUE0, TYPE1, NAME1, VALUE1, TYPE2, NAME2, VALUE2, TYPE3, NAME3, VALUE3, LAMBDA) \ + do { \ + class _FL_CBD_CLASS_NAME : public Fl_Callback_User_Data { \ + public: \ + TYPE0 p0_; TYPE1 p1_; TYPE2 p2_; TYPE3 p3_; \ + static void fn(TYPE0 NAME0, TYPE1 NAME1, TYPE2 NAME2, TYPE3 NAME3) \ + LAMBDA; \ + static void cb(Fl_Widget *w, void *user_data) { \ + _FL_CBD_CLASS_NAME *d = (_FL_CBD_CLASS_NAME*)user_data; \ + _FL_CBD_CLASS_NAME::fn(d->p0_, d->p1_, d->p2_, d->p3_); \ + }; \ + _FL_CBD_CLASS_NAME(TYPE0 p0, TYPE1 p1, TYPE2 p2, TYPE3 p3) \ + : p0_(p0), p1_(p1), p2_(p2), p3_(p3) { }; \ + }; \ + WIDGET->callback(_FL_CBD_CLASS_NAME::cb, new _FL_CBD_CLASS_NAME(VALUE0, VALUE1, VALUE2, VALUE3), true); \ + } while(0) + +#define FL_INLINE_CALLBACK_3(WIDGET, TYPE0, NAME0, VALUE0, TYPE1, NAME1, VALUE1, TYPE2, NAME2, VALUE2, LAMBDA) \ + do { \ + class _FL_CBD_CLASS_NAME : public Fl_Callback_User_Data { \ + public: \ + TYPE0 p0_; TYPE1 p1_; TYPE2 p2_; \ + static void fn(TYPE0 NAME0, TYPE1 NAME1, TYPE2 NAME2) \ + LAMBDA; \ + static void cb(Fl_Widget *w, void *user_data) { \ + _FL_CBD_CLASS_NAME *d = (_FL_CBD_CLASS_NAME*)user_data; \ + _FL_CBD_CLASS_NAME::fn(d->p0_, d->p1_, d->p2_); \ + }; \ + _FL_CBD_CLASS_NAME(TYPE0 p0, TYPE1 p1, TYPE2 p2) \ + : p0_(p0), p1_(p1), p2_(p2) { }; \ + }; \ + WIDGET->callback(_FL_CBD_CLASS_NAME::cb, new _FL_CBD_CLASS_NAME(VALUE0, VALUE1, VALUE2), true); \ + } while(0) + +#define FL_INLINE_CALLBACK_2(WIDGET, TYPE0, NAME0, VALUE0, TYPE1, NAME1, VALUE1, LAMBDA) \ + do { \ + class _FL_CBD_CLASS_NAME : public Fl_Callback_User_Data { \ + public: \ + TYPE0 p0_; TYPE1 p1_; \ + static void fn(TYPE0 NAME0, TYPE1 NAME1) \ + LAMBDA; \ + static void cb(Fl_Widget *w, void *user_data) { \ + _FL_CBD_CLASS_NAME *d = (_FL_CBD_CLASS_NAME*)user_data; \ + _FL_CBD_CLASS_NAME::fn(d->p0_, d->p1_); \ + }; \ + _FL_CBD_CLASS_NAME(TYPE0 p0, TYPE1 p1) \ + : p0_(p0), p1_(p1) { }; \ + }; \ + WIDGET->callback(_FL_CBD_CLASS_NAME::cb, new _FL_CBD_CLASS_NAME(VALUE0, VALUE1), true); \ + } while(0) + +#define FL_INLINE_CALLBACK_1(WIDGET, TYPE0, NAME0, VALUE0, LAMBDA) \ + do { \ + class _FL_CBD_CLASS_NAME : public Fl_Callback_User_Data { \ + public: \ + TYPE0 p0_; \ + static void fn(TYPE0 NAME0) \ + LAMBDA; \ + static void cb(Fl_Widget *w, void *user_data) { \ + _FL_CBD_CLASS_NAME *d = (_FL_CBD_CLASS_NAME*)user_data; \ + _FL_CBD_CLASS_NAME::fn(d->p0_); \ + }; \ + _FL_CBD_CLASS_NAME(TYPE0 p0) \ + : p0_(p0) { }; \ + }; \ + WIDGET->callback(_FL_CBD_CLASS_NAME::cb, new _FL_CBD_CLASS_NAME(VALUE0), true); \ + } while(0) + +#define FL_INLINE_CALLBACK_0(WIDGET, LAMBDA) \ + do { \ + class _FL_CBD_CLASS_NAME : public Fl_Callback_User_Data { \ + public: \ + static void fn() \ + LAMBDA; \ + static void cb(Fl_Widget *w, void *user_data) { \ + _FL_CBD_CLASS_NAME::fn(); \ + }; \ + _FL_CBD_CLASS_NAME() { }; \ + }; \ + WIDGET->callback(_FL_CBD_CLASS_NAME::cb, new _FL_CBD_CLASS_NAME(), true); \ + } while(0) + +#endif // FL_DOXYGEN + +#endif /* !_FL_FL_CALLBACK_MACROS_H_ */ diff --git a/documentation/src/common.dox b/documentation/src/common.dox index d8a5c0940..dcad4b1fe 100644 --- a/documentation/src/common.dox +++ b/documentation/src/common.dox @@ -550,6 +550,72 @@ int xyz_data; button->callback(xyz_callback, &xyz_data); \endcode +\note You cannot delete a widget inside a callback, as the +widget may still be accessed by FLTK after your callback +is completed. Instead, use the Fl::delete_widget() +method to mark your widget for deletion when it is safe +to do so. + +Many programmers new to FLTK or C++ try to use a +non-static class method instead of a static class method +or function for their callback. Since callbacks are done +outside a C++ class, the `this` pointer is not +initialized for class methods. + +To work around this problem, define a static method +in your class that accepts a pointer to the class, and +then have the static method call the class method(s) as +needed. The data pointer you provide to the +\p callback() method of the widget can be a +pointer to the instance of your class. + +\code +class Foo { + void my_callback(Fl_Widget *w); + static void my_static_callback(Fl_Widget *w, void *f) { ((Foo *)f)->my_callback(w); } + ... +} +... +w->callback(my_static_callback, (void *)this); +\endcode + +In an effort to make callbacks easier, more flexible, and type safe, FLTK +provides three groups of macros that generate the code needed to call class +methods directly with up to five custom parameters. + + - `FL_FUNCTION_CALLBACK_#(WIDGET, FUNCTION, ...)` creates code for callbacks to + functions and static class methods with up to five arguments. The `#` must + be replaced by the number of callback arguments. + - `FL_METHOD_CALLBACK_#(WIDGET, CLASS, SELF, METH, ...)` creates code for + callbacks to arbitrary public class methods + - `FL_INLINE_CALLBACK_#(WIDGET, ..., FUNCTION_BODY)` creates code for callback + functions that are very close to (almost in the same line) the widget + creation code, similar to lambda function in C++11. The last argument of + this macro is the callback code. + +The syntax is a bit unconventional, but the resulting code is flexible and +needs no additional maintenance. It is also C++98 compatible. For example: + +\code +#include +... +Fl_String *str = new Fl_String("FLTK"); +Fl_Button *btn = new Fl_Button(10, 10, 100, 100); +FL_METHOD_CALLBACK_2(btn, Fl_String, str, insert, int, 2, const char*, "..."); +... +Fl_Button *inline_cb_btn_2 = new Fl_Button(390, 60, 180, 25, "2 args"); +FL_INLINE_CALLBACK_2( inline_cb_btn_2, + const char *, text, "FLTK", int, number, 2, + { + fl_message("We received the message %s with %d!", text, number); + } + ); +\endcode + +\see Fl_Widget::callback(Fl_Callback*, void*), FL_FUNCTION_CALLBACK_3, FL_METHOD_CALLBACK_1, FL_INLINE_CALLBACK_2 + +\section common_when When and Reason + Normally callbacks are performed only when the value of the widget changes. You can change this using the Fl_Widget::when() method: @@ -564,45 +630,13 @@ button->when(FL_WHEN_ENTER_KEY_ALWAYS); button->when(FL_WHEN_CHANGED | FL_WHEN_NOT_CHANGED); \endcode -
- - - -
Note: +Within the callback, you can query why the callback was called using +Fl::callback_reason(). For example, setting - You cannot delete a widget inside a callback, as the - widget may still be accessed by FLTK after your callback - is completed. Instead, use the Fl::delete_widget() - method to mark your widget for deletion when it is safe - to do so. +\code myInput->when(FL_WHEN_RELEASE|FL_WHEN_CHANGED) \endcode - Hint: - - Many programmers new to FLTK or C++ try to use a - non-static class method instead of a static class method - or function for their callback. Since callbacks are done - outside a C++ class, the this pointer is not - initialized for class methods. - - To work around this problem, define a static method - in your class that accepts a pointer to the class, and - then have the static method call the class method(s) as - needed. The data pointer you provide to the - \p callback() method of the widget can be a - pointer to the instance of your class. - -\code -class Foo { - void my_callback(Fl_Widget *w); - static void my_static_callback(Fl_Widget *w, void *f) { ((Foo *)f)->my_callback(w); } - ... -} - -... - -w->callback(my_static_callback, (void *)this); -\endcode -
+for a text input field may return \ref FL_REASON_LOST_FOCUS or +\ref FL_REASON_CHANGED as a callback reason. \section common_shortcuts Shortcuts diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index faa6d7daa..b1409d12e 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -32,6 +32,7 @@ file (MAKE_DIRECTORY ${EXECUTABLE_OUTPUT_PATH}) set (SIMPLE_SOURCES chart-simple + callbacks browser-simple draggable-group howto-add_fd-and-popen diff --git a/examples/Makefile b/examples/Makefile index 946eda808..57d87de39 100644 --- a/examples/Makefile +++ b/examples/Makefile @@ -28,6 +28,7 @@ ALL = animgifimage$(EXEEXT) \ animgifimage-resize$(EXEEXT) \ browser-simple$(EXEEXT) \ cairo-draw-x$(EXEEXT) \ + callbacks$(EXEEXT) \ chart-simple$(EXEEXT) \ draggable-group$(EXEEXT) \ howto-add_fd-and-popen$(EXEEXT) \ diff --git a/examples/callbacks.cxx b/examples/callbacks.cxx new file mode 100644 index 000000000..3fb240d30 --- /dev/null +++ b/examples/callbacks.cxx @@ -0,0 +1,152 @@ +// +// Callback macros example program for the Fast Light Tool Kit (FLTK). +// +// Copyright 2023 by Bill Spitzak and others. +// +// This library is free software. Distribution and use rights are outlined in +// the file "COPYING" which should have been included with this file. If this +// file is missing or damaged, see the license at: +// +// https://www.fltk.org/COPYING.php +// +// Please see the following page on how to report bugs and issues: +// +// https://www.fltk.org/bugs.php +// + +#include +#include +#include +#include +#include +#include +#include +#include + +Fl_Window *window = NULL; + + +/* + Here is a list of callback functions that can take custom parameters and are + not limited to FLTK's built-in `void*` or `long` user_data. + */ +void hello_0_args_cb() { + fl_message("Hello with 0 arguments"); +} + +void hello_2_args_cb(Fl_String &text, int number) { + fl_message("Hello with 2 arguments,\n\"%s\" and '%d'", text.c_str(), number); +} + +void hello_4_args_cb(int a1, int a2, int a3, int a4) { + fl_message("Hello with 4 arguments:\n%d %d %d %d", a1, a2, a3, a4); +} + +/* + We create our own little class here that uses method callbacks. + */ +class MyButton : public Fl_Button { + // id will be set in the constructor + int id_; +public: + // create a simple push button + MyButton(int x, int y, int w, int h, const char *label, int id) + : Fl_Button(x, y, w, h, label), id_(id) + { } + // public non-static callback method + void hello(int a, int b, int c) { + // it's not a static method, so we have full access to all members, i.e. 'id_' + fl_message("MyButton has the id %d\nand was called with the custom parameters\n%d, %d, and %d.", id_, a, b, c); + } +}; + +/* + Whenever the code created by the macro is called, custom parameters are + duplicated. This ensures that each widget created dynamically with the same + function has its own separate set of user data at runtime. Consequently, + multiple widgets can be created dynamically, and each widget will have its + own unique set of parameters. + */ +void make_button(Fl_Window *win, int set) { + int y_lut[] = { 60, 90 }; + const char *label_lut[] = { "id 2 (5, 6, 7)", "id 3 (6, 7, 8)" }; + MyButton *btn = new MyButton(200, y_lut[set], 180, 25, label_lut[set], set+2); + FL_METHOD_CALLBACK_3(btn, MyButton, btn, hello, int, set+5, int, set+6, int, set+7); +} + + +int main(int argc, char ** argv) { + window = new Fl_Window(580, 120); + + /* -- testing function callbacks with multiple arguments + These buttons demo the use of the CALLBACK macro to call standard C style + functions with up to five custom parameters. + */ + + new Fl_Box(10, 5, 180, 25, "Function Callbacks:"); + + Fl_Button *func_cb_btn_0 = new Fl_Button(10, 30, 180, 25, "0 args"); + FL_FUNCTION_CALLBACK_0(func_cb_btn_0, hello_0_args_cb); + + Fl_Button *func_cb_btn_2 = new Fl_Button(10, 60, 180, 25, "2 args"); + FL_FUNCTION_CALLBACK_2(func_cb_btn_2, hello_2_args_cb, Fl_String, "FLTK", int, 2); + + Fl_Button *func_cb_btn_4 = new Fl_Button(10, 90, 180, 25, "4 args"); + FL_FUNCTION_CALLBACK_4(func_cb_btn_4, hello_4_args_cb, int, 1, int, 2, int, 3, int, 4); + + /* -- testing non-static method callbacks with multiple arguments + The following buttons call non-static class methods with custom parameters. + Check the class above to see how this is implemented. + */ + + new Fl_Box(200, 5, 180, 25, "Method Callbacks:"); + + MyButton *meth_cb_btn_0 = new MyButton(200, 30, 180, 25, "id 1 (1, 2, 3)", 1); + // 1: the macro needs a pointer to the button first + // 2: we can call a method in any class, but here we call ourselves + // 3: call a method in our own class, so we need to set 'meth_cb_btn_0' again + // Note: we could just as well call a method in a different class. + // 4: this is the method that we want to call; it must be "public" + // 5: add zero to five parameter triplets, note the comma placement + FL_METHOD_CALLBACK_3(meth_cb_btn_0, MyButton, meth_cb_btn_0, hello, int, 1, int, 2, int, 3); + + // call the same FL_METHOD_CALLBACK macro multiple times to ensure we get + // individual parameter sets + make_button(window, 0); + make_button(window, 1); + + /* -- testing inline callback functions + Adding a simple Lambda style functionality to FLTK without actually using + lambdas and staying C++99 compatible. + */ + + new Fl_Box(390, 5, 180, 25, "Inline Callbacks:"); + + Fl_Button *inline_cb_btn_0 = new Fl_Button(390, 30, 180, 25, "0 args"); + FL_INLINE_CALLBACK_0(inline_cb_btn_0, + { fl_message("Inline callback with 0 args."); } + ); + + Fl_Button *inline_cb_btn_2 = new Fl_Button(390, 60, 180, 25, "2 args"); + FL_INLINE_CALLBACK_2(inline_cb_btn_2, + const char *, text, "FLTK", int, number, 2, + { fl_message("We received the message %s with %d!", text, number); } + ); + + Fl_Button *inline_cb_btn_4 = new Fl_Button(390, 90, 180, 25, "4 args"); + FL_INLINE_CALLBACK_4(inline_cb_btn_4, + int, x, window->x(), + int, y, window->y(), + int, w, window->w(), + int, h, window->h(), + { fl_message("The main window was at\nx:%d, y:%d, w:%d, h:%d\n" + "when the callback was created\n" + "and is now at x:%d, y:%d", x, y, w, h, + window->x(), window->y()); + } + ); + + window->end(); + window->show(argc,argv); + return Fl::run(); +} diff --git a/src/Fl_Widget.cxx b/src/Fl_Widget.cxx index 6518f002f..c5b59f5db 100644 --- a/src/Fl_Widget.cxx +++ b/src/Fl_Widget.cxx @@ -178,6 +178,8 @@ Fl_Widget::~Fl_Widget() { fl_throw_focus(this); // remove stale entries from default callback queue (Fl::readqueue()) if (callback_ == default_callback) cleanup_readqueue(this); + if ( (flags_ & AUTO_DELETE_USER_DATA) && user_data_) + delete (Fl_Callback_User_Data*)user_data_; } /** @@ -399,3 +401,28 @@ void Fl_Widget::do_callback(Fl_Widget *widget, void *arg, Fl_Callback_Reason rea if (callback_ != default_callback) clear_changed(); } + +/* + \brief Sets the user data for this widget. + Sets the new user data (void *) argument that is passed to the callback function. + \param[in] v new user data + */ +void Fl_Widget::user_data(void* v) { + if ((flags_ & AUTO_DELETE_USER_DATA) && user_data_) + delete (Fl_Callback_User_Data*)user_data_; + clear_flag(AUTO_DELETE_USER_DATA); + user_data_ = v; +} + +/* + \brief Sets the user data for this widget. + Sets the new user data (void *) argument that is passed to the callback function. + \param[in] v new user data + \param[in] auto_free if set, the widget will free user data when destroyed; defaults to false + */ +void Fl_Widget::user_data(Fl_Callback_User_Data* v, bool auto_free) { + user_data((void*)v); + if (auto_free) + set_flag(AUTO_DELETE_USER_DATA); +} + diff --git a/test/unittest_core.cxx b/test/unittest_core.cxx index 30def3782..65fd71630 100644 --- a/test/unittest_core.cxx +++ b/test/unittest_core.cxx @@ -17,8 +17,10 @@ #include "unittests.h" #include +#include #include #include +#include /* Test Fl_String constructor and assignment. */ TEST(Fl_String, Assignment) { @@ -216,6 +218,68 @@ TEST(Fl_Preferences, Strings) { return true; } +bool cb1a_ok = false, cb1b_ok = false, cb1c_ok = false; +int cb1_alloc = 0; +class MyString : public Fl_String { +public: + MyString() : Fl_String() { cb1_alloc++; } + MyString(const MyString &str) : Fl_String(str) { cb1_alloc++; } + MyString(const char *t) : Fl_String(t) { cb1_alloc++; } + ~MyString() { cb1_alloc--; } +}; +void cb1(MyString a, int b) { + cb1a_ok = true; + if (strcmp(a.c_str(),"FLTK")==0) cb1b_ok = true; + if (b==4) cb1c_ok = true; +} + +/* Test callback macros. */ +TEST(Fl_Callback_Macros, FL_FUNCTION_CALLBACK) { + Fl_Group::current(NULL); + Fl_Button *btn = new Fl_Button(10, 10, 100, 100); + FL_FUNCTION_CALLBACK_2(btn, cb1, MyString, "FLTK", int, 4); + + do { class Fl_Callback_User_Data_240 : public Fl_Callback_User_Data { + public: MyString a_; int b_; + static void cb(Fl_Widget *w, void *user_data) { + Fl_Callback_User_Data_240 *cbdata = (Fl_Callback_User_Data_240*)user_data; (void)cbdata; cb1(cbdata->a_, cbdata->b_); }; Fl_Callback_User_Data_240(MyString a, int b) : a_(a), b_(b) { } }; btn->callback(Fl_Callback_User_Data_240::cb, new Fl_Callback_User_Data_240("FLTK", 4), true); } while(0); + + btn->do_callback(); + delete btn; + EXPECT_TRUE(cb1a_ok); // callback called + EXPECT_TRUE(cb1b_ok); // string stored correctly + EXPECT_TRUE(cb1c_ok); // integer stored correctly + EXPECT_TRUE(cb1_alloc==0); // string destroyed correctly (allocated as often as deallocated) + return true; +} + +TEST(Fl_Callback_Macros, FL_METHOD_CALLBACK) { + Fl_Group::current(NULL); + Fl_String *str = new Fl_String("FLTK"); + Fl_Button *btn = new Fl_Button(10, 10, 100, 100); + FL_METHOD_CALLBACK_2(btn, Fl_String, str, insert, int, 2, const char*, "XX"); + btn->do_callback(); + EXPECT_STREQ(str->c_str(), "FLXXTK"); + delete btn; + delete str; + return true; +} + +int cb3a = 0, cb3b = 0; +TEST(Fl_Callback_Macros, FL_INLINE_CALLBACK) { + Fl_Group::current(NULL); + Fl_Button *btn = new Fl_Button(10, 10, 100, 100); + FL_INLINE_CALLBACK_2(btn, + int, a, 42, int, b, 16, + { cb3a = a; cb3b = b; } + ); + btn->do_callback(); + EXPECT_EQ(cb3a, 42); + EXPECT_EQ(cb3b, 16); + delete btn; + return true; +} + // //------- test aspects of the FLTK core library ---------- //