Skip to content

Commit 7e08970

Browse files
committed
WIP lua: Add on_change callback for dialogs
1 parent f1ab114 commit 7e08970

6 files changed

Lines changed: 119 additions & 18 deletions

File tree

libaegisub/common/dispatch.cpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ namespace {
2929
boost::asio::io_service *service;
3030
std::function<void (agi::dispatch::Thunk)> invoke_main;
3131
std::atomic<uint_fast32_t> threads_running;
32+
thread_local bool is_main_thread;
3233

3334
class MainQueue final : public agi::dispatch::Queue {
3435
void DoInvoke(agi::dispatch::Thunk thunk) override {
@@ -78,10 +79,12 @@ void Init(std::function<void (Thunk)> invoke_main) {
7879
static IOServiceThreadPool thread_pool;
7980
::service = &thread_pool.io_service;
8081
::invoke_main = invoke_main;
82+
::is_main_thread = true;
8183

8284
thread_pool.threads.reserve(std::max<unsigned>(4, std::thread::hardware_concurrency()));
8385
for (size_t i = 0; i < thread_pool.threads.capacity(); ++i) {
8486
thread_pool.threads.emplace_back([]{
87+
::is_main_thread = false;
8588
++threads_running;
8689
agi::util::SetThreadName("Dispatch Worker");
8790
service->run();
@@ -123,6 +126,14 @@ void Queue::Sync(Thunk thunk) {
123126
if (e) std::rethrow_exception(e);
124127
}
125128

129+
void EnsureMain(Thunk thunk) {
130+
if (::is_main_thread) {
131+
thunk();
132+
} else {
133+
Main().Sync(thunk);
134+
}
135+
}
136+
126137
Queue& Main() {
127138
static MainQueue q;
128139
return q;

libaegisub/include/libaegisub/dispatch.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,11 @@ namespace agi {
4141
/// Get the main queue, which runs on the GUI thread
4242
Queue& Main();
4343

44+
45+
/// Ensure that the thunk is run on the main thread, without deadlocking
46+
/// when already on the main thread
47+
void EnsureMain(Thunk thunk);
48+
4449
/// Get the generic background queue, which runs thunks in parallel
4550
Queue& Background();
4651

src/auto4_base.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,7 @@ namespace Automation4 {
208208

209209
void ProgressSink::ShowDialog(ScriptDialog *config_dialog)
210210
{
211-
agi::dispatch::Main().Sync([=] {
211+
agi::dispatch::EnsureMain([=] {
212212
wxDialog w; // container dialog box
213213
w.SetExtraStyle(wxWS_EX_VALIDATE_RECURSIVELY);
214214
w.Create(bsr->GetParentWindow(), -1, to_wx(bsr->GetTitle()));

src/auto4_lua.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ namespace {
128128
const char *clipboard_get()
129129
{
130130
std::string data;
131-
agi::dispatch::Main().Sync([&] { data = GetClipboard(); });
131+
agi::dispatch::EnsureMain([&] { data = GetClipboard(); });
132132
if (data.empty())
133133
return nullptr;
134134
return strndup(data);
@@ -138,7 +138,7 @@ namespace {
138138
{
139139
bool succeeded = false;
140140

141-
agi::dispatch::Main().Sync([&] {
141+
agi::dispatch::EnsureMain([&] {
142142
wxClipboard &cb = *wxTheClipboard;
143143
if (cb.Open()) {
144144
succeeded = cb.SetData(new wxTextDataObject(wxString::FromUTF8(str)));
@@ -1138,7 +1138,7 @@ namespace {
11381138

11391139
// config
11401140
if (has_config && config_dialog) {
1141-
int results_produced = config_dialog->LuaReadBack(L);
1141+
int results_produced = config_dialog->LuaReadBack();
11421142
assert(results_produced == 1);
11431143
(void) results_produced; // avoid warning on release builds
11441144
// TODO, write back stored options here

src/auto4_lua_dialog.cpp

Lines changed: 87 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -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\nLua 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)

src/auto4_lua_progresssink.cpp

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -189,11 +189,21 @@ namespace Automation4 {
189189
{
190190
ProgressSink *ps = GetObjPointer(L, lua_upvalueindex(1));
191191

192-
LuaDialog dlg(L, true); // magically creates the config dialog structure etc
192+
bool cancelled = false;
193+
LuaDialog dlg(L, true, [&] (std::string const& error, bool cancel) {
194+
ps->Log(error);
195+
cancelled = cancelled || cancel;
196+
}); // magically creates the config dialog structure etc
197+
193198
ps->ShowDialog(&dlg);
194199

200+
if (cancelled) {
201+
lua_pushnil(L);
202+
throw error_tag();
203+
}
204+
195205
// more magic: puts two values on stack: button pushed and table with control results
196-
return dlg.LuaReadBack(L);
206+
return dlg.LuaReadBack();
197207
}
198208

199209
int LuaProgressSink::LuaDisplayOpenDialog(lua_State *L)

0 commit comments

Comments
 (0)