mirror of https://github.com/fltk/fltk
Improved, yet compatible, widget callback system using macros (#729)
* adds FL/fl_callback.macros.H * adds FL_FUNCTION_CALLBACK_n(widget, function, [type, data]) * adds FL_METHOD_CALLBACK_n(widget, class, instance, method, [type, data]) * adds FL_INLINE_CALLBACK_n(widget, [type, name, data], callback_body) * adds `examples/callback` * full documentation
This commit is contained in:
parent
e6440ca0a8
commit
10d9010ed9
|
@ -121,6 +121,8 @@ Changes in FLTK 1.4.0 Released: ??? ?? 2022
|
||||||
- Added Fl_Surface_Device::push_current(new_surface) and
|
- Added Fl_Surface_Device::push_current(new_surface) and
|
||||||
Fl_Surface_Device::pop_current() to set/unset the current surface
|
Fl_Surface_Device::pop_current() to set/unset the current surface
|
||||||
receiving graphics commands.
|
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)
|
New Configuration Options (ABI Version)
|
||||||
|
|
||||||
|
|
|
@ -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.
|
/** 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)
|
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)
|
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)
|
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)
|
NO_OVERLAY = 1<<15, ///< window not using a hardware overlay plane (Fl_Menu_Window)
|
||||||
GROUP_RELATIVE = 1<<16, ///< Reserved, not implemented. DO NOT USE.
|
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
|
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()
|
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
|
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
|
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).
|
// 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_;}
|
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.
|
Each widget has a single callback.
|
||||||
\param[in] cb new callback
|
\param[in] cb new callback
|
||||||
\param[in] p user data
|
\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.
|
/** Sets the current callback function for the widget.
|
||||||
Each widget has a single callback.
|
Each widget has a single callback.
|
||||||
|
@ -695,7 +729,7 @@ public:
|
||||||
*/
|
*/
|
||||||
void callback(Fl_Callback1* cb, long p = 0) {
|
void callback(Fl_Callback1* cb, long p = 0) {
|
||||||
callback_ = (Fl_Callback*)(fl_intptr_t)(cb);
|
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.
|
/** Gets the user data for this widget.
|
||||||
|
@ -704,11 +738,11 @@ public:
|
||||||
*/
|
*/
|
||||||
void* user_data() const {return user_data_;}
|
void* user_data() const {return user_data_;}
|
||||||
|
|
||||||
/** Sets the user data for this widget.
|
/** \brief Sets the user data for this widget. */
|
||||||
Sets the new user data (void *) argument that is passed to the callback function.
|
void user_data(void* v);
|
||||||
\param[in] v new user data
|
|
||||||
*/
|
/** \brief Sets the user data for this widget. */
|
||||||
void user_data(void* v) {user_data_ = v;}
|
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.
|
/** Gets the current user data (long) argument that is passed to the callback function.
|
||||||
|
|
||||||
|
@ -727,7 +761,7 @@ public:
|
||||||
|
|
||||||
\see argument()
|
\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.
|
/** Returns the conditions under which the callback is called.
|
||||||
|
|
||||||
|
|
|
@ -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 <stdlib.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
\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/fl_callback_macros.H>
|
||||||
|
...
|
||||||
|
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/fl_callback_macros.H>
|
||||||
|
...
|
||||||
|
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/fl_callback_macros.H>
|
||||||
|
...
|
||||||
|
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_ */
|
|
@ -550,6 +550,72 @@ int xyz_data;
|
||||||
button->callback(xyz_callback, &xyz_data);
|
button->callback(xyz_callback, &xyz_data);
|
||||||
\endcode
|
\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/fl_callback_macros.H>
|
||||||
|
...
|
||||||
|
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
|
Normally callbacks are performed only when the value of the
|
||||||
widget changes. You can change this using the Fl_Widget::when()
|
widget changes. You can change this using the Fl_Widget::when()
|
||||||
method:
|
method:
|
||||||
|
@ -564,45 +630,13 @@ button->when(FL_WHEN_ENTER_KEY_ALWAYS);
|
||||||
button->when(FL_WHEN_CHANGED | FL_WHEN_NOT_CHANGED);
|
button->when(FL_WHEN_CHANGED | FL_WHEN_NOT_CHANGED);
|
||||||
\endcode
|
\endcode
|
||||||
|
|
||||||
<CENTER><TABLE WIDTH="80%" BORDER="1" CELLPADDING="5" CELLSPACING="0" BGCOLOR="#cccccc">
|
Within the callback, you can query why the callback was called using
|
||||||
<TR>
|
Fl::callback_reason(). For example, setting
|
||||||
<TD><B>Note:</B>
|
|
||||||
|
|
||||||
You cannot delete a widget inside a callback, as the
|
\code myInput->when(FL_WHEN_RELEASE|FL_WHEN_CHANGED) \endcode
|
||||||
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.
|
|
||||||
|
|
||||||
<B>Hint:</B>
|
for a text input field may return \ref FL_REASON_LOST_FOCUS or
|
||||||
|
\ref FL_REASON_CHANGED as a callback reason.
|
||||||
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 <tt>this</tt> 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
|
|
||||||
</TD>
|
|
||||||
</TR>
|
|
||||||
</TABLE></CENTER>
|
|
||||||
|
|
||||||
\section common_shortcuts Shortcuts
|
\section common_shortcuts Shortcuts
|
||||||
|
|
||||||
|
|
|
@ -32,6 +32,7 @@ file (MAKE_DIRECTORY ${EXECUTABLE_OUTPUT_PATH})
|
||||||
|
|
||||||
set (SIMPLE_SOURCES
|
set (SIMPLE_SOURCES
|
||||||
chart-simple
|
chart-simple
|
||||||
|
callbacks
|
||||||
browser-simple
|
browser-simple
|
||||||
draggable-group
|
draggable-group
|
||||||
howto-add_fd-and-popen
|
howto-add_fd-and-popen
|
||||||
|
|
|
@ -28,6 +28,7 @@ ALL = animgifimage$(EXEEXT) \
|
||||||
animgifimage-resize$(EXEEXT) \
|
animgifimage-resize$(EXEEXT) \
|
||||||
browser-simple$(EXEEXT) \
|
browser-simple$(EXEEXT) \
|
||||||
cairo-draw-x$(EXEEXT) \
|
cairo-draw-x$(EXEEXT) \
|
||||||
|
callbacks$(EXEEXT) \
|
||||||
chart-simple$(EXEEXT) \
|
chart-simple$(EXEEXT) \
|
||||||
draggable-group$(EXEEXT) \
|
draggable-group$(EXEEXT) \
|
||||||
howto-add_fd-and-popen$(EXEEXT) \
|
howto-add_fd-and-popen$(EXEEXT) \
|
||||||
|
|
|
@ -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 <stdio.h>
|
||||||
|
#include <FL/Fl.H>
|
||||||
|
#include <FL/Fl_Window.H>
|
||||||
|
#include <FL/Fl_Box.H>
|
||||||
|
#include <FL/Fl_Button.H>
|
||||||
|
#include <FL/Fl_String.H>
|
||||||
|
#include <FL/fl_ask.H>
|
||||||
|
#include <FL/fl_callback_macros.H>
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
|
@ -178,6 +178,8 @@ Fl_Widget::~Fl_Widget() {
|
||||||
fl_throw_focus(this);
|
fl_throw_focus(this);
|
||||||
// remove stale entries from default callback queue (Fl::readqueue())
|
// remove stale entries from default callback queue (Fl::readqueue())
|
||||||
if (callback_ == default_callback) cleanup_readqueue(this);
|
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)
|
if (callback_ != default_callback)
|
||||||
clear_changed();
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,8 +17,10 @@
|
||||||
#include "unittests.h"
|
#include "unittests.h"
|
||||||
|
|
||||||
#include <FL/Fl_Group.H>
|
#include <FL/Fl_Group.H>
|
||||||
|
#include <FL/Fl_Button.H>
|
||||||
#include <FL/Fl_Simple_Terminal.H>
|
#include <FL/Fl_Simple_Terminal.H>
|
||||||
#include <FL/Fl_String.H>
|
#include <FL/Fl_String.H>
|
||||||
|
#include <FL/fl_callback_macros.H>
|
||||||
|
|
||||||
/* Test Fl_String constructor and assignment. */
|
/* Test Fl_String constructor and assignment. */
|
||||||
TEST(Fl_String, Assignment) {
|
TEST(Fl_String, Assignment) {
|
||||||
|
@ -216,6 +218,68 @@ TEST(Fl_Preferences, Strings) {
|
||||||
return true;
|
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 ----------
|
//------- test aspects of the FLTK core library ----------
|
||||||
//
|
//
|
||||||
|
|
Loading…
Reference in New Issue