@@ -141,7 +141,7 @@ namespace Automation4 {
141141 public:
142142 Label (lua_State *L) : LuaDialogControl(L), label(get_field(L, " label" )) { }
143143
144- wxControl *Create (wxWindow *parent) override {
144+ wxControl *Create (wxWindow *parent, ChangeCallback callback ) override {
145145 return new wxStaticText (parent, -1 , to_wx (label));
146146 }
147147
@@ -174,11 +174,12 @@ namespace Automation4 {
174174 std::string SerialiseValue () const override { return inline_string_encode (text); }
175175 void UnserialiseValue (const std::string &serialised) override { text = inline_string_decode (serialised); }
176176
177- wxControl *Create (wxWindow *parent) override {
177+ wxControl *Create (wxWindow *parent, ChangeCallback callback ) override {
178178 cw = new wxTextCtrl (parent, -1 , to_wx (text));
179179 cw->SetMaxLength (0 );
180180 cw->SetValidator (StringBinder (&text));
181181 cw->SetToolTip (to_wx (hint));
182+ cw->Bind (wxEVT_TEXT, [=](wxCommandEvent&) { callback (); });
182183 return cw;
183184 }
184185
@@ -204,8 +205,9 @@ namespace Automation4 {
204205 std::string SerialiseValue () const override { return inline_string_encode (color.GetHexFormatted (alpha)); }
205206 void UnserialiseValue (const std::string &serialised) override { color = inline_string_decode (serialised); }
206207
207- wxControl *Create (wxWindow *parent) override {
208+ wxControl *Create (wxWindow *parent, ChangeCallback callback ) override {
208209 wxControl *cw = new ColourButton (parent, wxSize (50 *width,10 *height), alpha, color, ColorValidator (&color));
210+ cw->Bind (EVT_COLOR , [=](ValueEvent<agi::Color>&) { callback (); });
209211 cw->SetToolTip (to_wx (hint));
210212 return cw;
211213 }
@@ -221,10 +223,11 @@ namespace Automation4 {
221223 Textbox (lua_State *L) : Edit(L) { }
222224
223225 // Same serialisation interface as single-line edit
224- wxControl *Create (wxWindow *parent) override {
226+ wxControl *Create (wxWindow *parent, ChangeCallback callback ) override {
225227 cw = new wxTextCtrl (parent, -1 , " " , wxDefaultPosition, wxDefaultSize, wxTE_MULTILINE, StringBinder (&text));
226228 cw->SetMinSize (wxSize (0 , 30 ));
227229 cw->SetToolTip (to_wx (hint));
230+ cw->Bind (wxEVT_TEXT, [=](wxCommandEvent&) { callback (); });
228231 return cw;
229232 }
230233 };
@@ -252,10 +255,12 @@ namespace Automation4 {
252255 std::string SerialiseValue () const override { return std::to_string (value); }
253256 void UnserialiseValue (const std::string &serialised) override { value = atoi (serialised.c_str ()); }
254257
255- wxControl *Create (wxWindow *parent) override {
258+ wxControl *Create (wxWindow *parent, ChangeCallback callback ) override {
256259 cw = new wxSpinCtrl (parent, -1 , " " , wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS, min, max, value);
257260 cw->SetValidator (wxGenericValidator (&value));
258261 cw->SetToolTip (to_wx (hint));
262+ cw->Bind (wxEVT_SPINCTRL, [=](wxCommandEvent&) { callback (); });
263+ cw->Bind (wxEVT_TEXT, [=](wxCommandEvent&) { callback (); });
259264 return cw;
260265 }
261266
@@ -294,17 +299,20 @@ namespace Automation4 {
294299 std::string SerialiseValue () const override { return std::to_string (value); }
295300 void UnserialiseValue (const std::string &serialised) override { value = atof (serialised.c_str ()); }
296301
297- wxControl *Create (wxWindow *parent) override {
302+ wxControl *Create (wxWindow *parent, ChangeCallback callback ) override {
298303 if (step > 0 ) {
299304 scd = new wxSpinCtrlDouble (parent, -1 , " " , wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS, min, max, value, step);
300305 scd->SetValidator (DoubleSpinValidator (&value));
301306 scd->SetToolTip (to_wx (hint));
307+ scd->Bind (wxEVT_SPINCTRLDOUBLE, [=](wxCommandEvent&) { callback (); });
308+ scd->Bind (wxEVT_TEXT, [=](wxCommandEvent&) { callback (); });
302309 return scd;
303310 }
304311
305312 DoubleValidator val (&value, min, max);
306313 cw = new wxTextCtrl (parent, -1 , " " , wxDefaultPosition, wxDefaultSize, 0 , val);
307314 cw->SetToolTip (to_wx (hint));
315+ cw->Bind (wxEVT_TEXT, [=](wxCommandEvent&) { callback (); });
308316 return cw;
309317 }
310318
@@ -332,9 +340,10 @@ namespace Automation4 {
332340 std::string SerialiseValue () const override { return inline_string_encode (value); }
333341 void UnserialiseValue (const std::string &serialised) override { value = inline_string_decode (serialised); }
334342
335- wxControl *Create (wxWindow *parent) override {
343+ wxControl *Create (wxWindow *parent, ChangeCallback callback ) override {
336344 cw = new wxComboBox (parent, -1 , to_wx (value), wxDefaultPosition, wxDefaultSize, to_wx (items), wxCB_READONLY, StringBinder (&value));
337345 cw->SetToolTip (to_wx (hint));
346+ cw->Bind (wxEVT_COMBOBOX, [=](wxCommandEvent&) { callback (); });
338347 return cw;
339348 }
340349
@@ -360,11 +369,12 @@ namespace Automation4 {
360369 std::string SerialiseValue () const override { return value ? " 1" : " 0" ; }
361370 void UnserialiseValue (const std::string &serialised) override { value = serialised != " 0" ; }
362371
363- wxControl *Create (wxWindow *parent) override {
372+ wxControl *Create (wxWindow *parent, ChangeCallback callback ) override {
364373 cw = new wxCheckBox (parent, -1 , to_wx (label));
365374 cw->SetValidator (wxGenericValidator (&value));
366375 cw->SetToolTip (to_wx (hint));
367376 cw->SetValue (value);
377+ cw->Bind (wxEVT_CHECKBOX, [=](wxCommandEvent&) { callback (); });
368378 return cw;
369379 }
370380
@@ -375,15 +385,20 @@ namespace Automation4 {
375385 }
376386
377387 // LuaDialog
378- LuaDialog::LuaDialog (lua_State *L, bool include_buttons)
388+ LuaDialog::LuaDialog (lua_State *L, bool include_buttons, ErrorLogger logger )
379389 : use_buttons(include_buttons)
390+ , L(L)
391+ , error_logger(logger)
380392 {
381393 LOG_D (" automation/lua/dialog" ) << " creating LuaDialoug, addr: " << this ;
382394
383395 // assume top of stack now contains a dialog table
384396 if (!lua_istable (L, 1 ))
385397 error (L, " Cannot create config dialog from something non-table" );
386398
399+ lua_createtable (L, 0 , 1 );
400+ myid = luaL_ref (L, LUA_REGISTRYINDEX );
401+
387402 // Ok, so there is a table with controls
388403 lua_pushvalue (L, 1 );
389404 lua_for_each (L, [&] {
@@ -442,14 +457,75 @@ namespace Automation4 {
442457 btn->first = id;
443458 });
444459 }
460+
461+ if (include_buttons && lua_istable (L, 4 )) {
462+ lua_pushvalue (L, 4 );
463+ lua_for_each (L, [&]{
464+ std::string key = check_string (L, -2 );
465+ if (key == " on_change" ) {
466+ if (!lua_isfunction (L, -1 ))
467+ error (L, " The dialog change callback must be a function" );
468+
469+ lua_rawgeti (L, LUA_REGISTRYINDEX , myid);
470+ lua_pushvalue (L, -2 );
471+ lua_setfield (L, -2 , " dialog_change_callback" );
472+ lua_pop (L, 1 );
473+ has_callback = true ;
474+ }
475+ // Don't error on invalid keys to be somewhat forward-compatible
476+ });
477+ }
478+ }
479+
480+ LuaDialog::~LuaDialog () {
481+ luaL_unref (L, LUA_REGISTRYINDEX , myid);
445482 }
446483
447484 wxWindow* LuaDialog::CreateWindow (wxWindow *parent) {
485+ auto dialog = static_cast <wxDialog *>(parent);
448486 window = new wxPanel (parent);
449487
488+ // Some hacks to prevent false positive events when first showing the dialog
489+ window->Bind (wxEVT_ENTER_WINDOW, [&, this ](wxEvent&) { this ->can_call_callback = true ; });
490+ window->Bind (wxEVT_MOTION, [&, this ](wxEvent&) { this ->can_call_callback = true ; });
491+ LuaDialogControl::ChangeCallback cb = [&, dialog, this ] {
492+ if (has_callback && can_call_callback) {
493+ LuaStackcheck stackcheck (L);
494+
495+ window->TransferDataFromWindow ();
496+
497+ lua_pushcclosure (L, add_stack_trace, 0 );
498+
499+ lua_rawgeti (L, LUA_REGISTRYINDEX , myid);
500+ lua_getfield (L, -1 , " dialog_change_callback" );
501+ lua_remove (L, -2 );
502+ assert (lua_isfunction (L, -1 ));
503+
504+ LuaReadBack ();
505+ lua_remove (L, -2 ); // Remove button
506+
507+ if (lua_pcall (L, 1 , 0 , -3 )) {
508+ if (!lua_isnil (L, -1 ) && error_logger != nullptr ) {
509+ // if the call failed, log the error here
510+ error_logger (" \n\n Lua reported a runtime error in dialog change callback:\n " , false );
511+ error_logger (get_string_or_default (L, -1 ), false );
512+ } else { // Cancel
513+ error_logger (" " , true );
514+ dialog->EndModal (0 );
515+ }
516+ lua_pop (L, 2 );
517+ return ;
518+ }
519+
520+ lua_pop (L, 1 ); // pop error handler again
521+
522+ stackcheck.check_stack (0 );
523+ }
524+ };
525+
450526 auto s = new wxGridBagSizer (4 , 4 );
451527 for (auto & c : controls)
452- s->Add (c->Create (window), wxGBPosition (c->y , c->x ),
528+ s->Add (c->Create (window, cb ), wxGBPosition (c->y , c->x ),
453529 wxGBSpan (c->height , c->width ), c->GetSizerFlags ());
454530
455531 if (!use_buttons) {
@@ -462,7 +538,6 @@ namespace Automation4 {
462538 buttons.emplace_back (wxID_CANCEL, " " );
463539 }
464540
465- auto dialog = static_cast <wxDialog *>(parent);
466541 auto bs = new wxStdDialogButtonSizer;
467542
468543 auto make_button = [&](wxWindowID id, int button_pushed, std::string const & text) -> wxButton *{
@@ -502,7 +577,7 @@ namespace Automation4 {
502577 return window;
503578 }
504579
505- int LuaDialog::LuaReadBack (lua_State *L ) {
580+ int LuaDialog::LuaReadBack () {
506581 // First read back which button was pressed, if any
507582 if (use_buttons) {
508583 if (button_pushed == -1 || buttons[button_pushed].first == wxID_CANCEL)
0 commit comments