Add support for using standard button IDs from automation
Some example uses: -- ~special snowflake~ OK/Cancel aegisub.dialog.display(config, {ok='Accept', cancel='Cancel'}) -- On OS X the 'Help' button will be just a left-aligned ? aegisub.dialog.display(config, {ok='OK', cancel='Cancel', help='Help'}) -- Each button in its own subtable to preserve passed order -- Unnecessary when using only IDed buttons since the passed order will -- be ignored in favor of the platform-standard order aegisub.dialog.display(config, {{ok='Accept'}, {cancel='Cancel'}, {help='Help'}, 'Another Button'}) In some cases the passed labels will be ignored in favor of the platform-standard labels. Available IDs: ok yes save apply close no cancel help context_help Note that many combinations of button IDs do not make sense and may have strange effects. Buttons with an ID of 'cancel' return false, as if ESC was pressed. A button with an ID of 'close' results in that button being triggered on ESC rather than cancel. Buttons with an ID of 'ok', 'yes' and 'save' are set as the default affirmative button for the dialog. Closes #1609.
This commit is contained in:
parent
657726b9ec
commit
8e491a9ecc
2 changed files with 109 additions and 73 deletions
|
@ -187,8 +187,8 @@ namespace Automation4 {
|
||||||
class LuaDialog : public ScriptDialog {
|
class LuaDialog : public ScriptDialog {
|
||||||
/// Controls in this dialog
|
/// Controls in this dialog
|
||||||
std::vector<LuaDialogControl*> controls;
|
std::vector<LuaDialogControl*> controls;
|
||||||
/// The names of buttons in this dialog if non-default ones were used
|
/// The names and IDs of buttons in this dialog if non-default ones were used
|
||||||
std::vector<std::string> buttons;
|
std::vector<std::pair<int, std::string>> buttons;
|
||||||
|
|
||||||
/// Does the dialog contain any buttons
|
/// Does the dialog contain any buttons
|
||||||
bool use_buttons;
|
bool use_buttons;
|
||||||
|
@ -198,8 +198,6 @@ namespace Automation4 {
|
||||||
|
|
||||||
wxWindow *window;
|
wxWindow *window;
|
||||||
|
|
||||||
void OnButtonPush(wxCommandEvent &evt);
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
LuaDialog(lua_State *L, bool include_buttons);
|
LuaDialog(lua_State *L, bool include_buttons);
|
||||||
~LuaDialog();
|
~LuaDialog();
|
||||||
|
|
|
@ -46,9 +46,12 @@
|
||||||
#include <libaegisub/log.h>
|
#include <libaegisub/log.h>
|
||||||
|
|
||||||
#include <boost/algorithm/string/case_conv.hpp>
|
#include <boost/algorithm/string/case_conv.hpp>
|
||||||
|
#include <boost/range/adaptors.hpp>
|
||||||
|
#include <boost/range/algorithm.hpp>
|
||||||
#include <boost/tokenizer.hpp>
|
#include <boost/tokenizer.hpp>
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
#include <cfloat>
|
#include <cfloat>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
#include <wx/button.h>
|
#include <wx/button.h>
|
||||||
#include <wx/checkbox.h>
|
#include <wx/checkbox.h>
|
||||||
|
@ -98,15 +101,39 @@ namespace {
|
||||||
return get_field(L, name, std::string());
|
return get_field(L, name, std::string());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template<typename Func>
|
||||||
|
void lua_for_each(lua_State *L, Func&& func) {
|
||||||
|
lua_pushnil(L); // initial key
|
||||||
|
while (lua_next(L, -2)) {
|
||||||
|
func();
|
||||||
|
lua_pop(L, 1); // pop value, leave key
|
||||||
|
}
|
||||||
|
lua_pop(L, 1); // pop key
|
||||||
|
}
|
||||||
|
|
||||||
template<class T>
|
template<class T>
|
||||||
void read_string_array(lua_State *L, T &cont) {
|
void read_string_array(lua_State *L, T &cont) {
|
||||||
lua_pushnil(L);
|
lua_for_each(L, [&] {
|
||||||
while (lua_next(L, -2)) {
|
|
||||||
if (lua_isstring(L, -1))
|
if (lua_isstring(L, -1))
|
||||||
cont.push_back(lua_tostring(L, -1));
|
cont.push_back(lua_tostring(L, -1));
|
||||||
lua_pop(L, 1);
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
int string_to_wx_id(std::string const& str) {
|
||||||
|
static std::unordered_map<std::string, int> ids;
|
||||||
|
if (ids.empty()) {
|
||||||
|
ids["ok"] = wxID_OK;
|
||||||
|
ids["yes"] = wxID_YES;
|
||||||
|
ids["save"] = wxID_SAVE;
|
||||||
|
ids["apply"] = wxID_APPLY;
|
||||||
|
ids["close"] = wxID_CLOSE;
|
||||||
|
ids["no"] = wxID_NO;
|
||||||
|
ids["cancel"] = wxID_CANCEL;
|
||||||
|
ids["help"] = wxID_HELP;
|
||||||
|
ids["context_help"] = wxID_CONTEXT_HELP;
|
||||||
}
|
}
|
||||||
lua_pop(L, 1);
|
auto it = ids.find(str);
|
||||||
|
return it == end(ids) ? -1 : it->second;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -449,7 +476,31 @@ namespace Automation4 {
|
||||||
|
|
||||||
if (include_buttons && lua_istable(L, 2)) {
|
if (include_buttons && lua_istable(L, 2)) {
|
||||||
lua_pushvalue(L, 2);
|
lua_pushvalue(L, 2);
|
||||||
read_string_array(L, buttons);
|
lua_for_each(L, [&]{
|
||||||
|
// String key: key is button ID, value is button label
|
||||||
|
// lua_isstring actually checks "is convertible to string"
|
||||||
|
if (lua_type(L, -2) == LUA_TSTRING)
|
||||||
|
buttons.emplace_back(
|
||||||
|
string_to_wx_id(lua_tostring(L, -2)),
|
||||||
|
luaL_checkstring(L, -1));
|
||||||
|
|
||||||
|
// Number key, string value: value is label
|
||||||
|
else if (lua_isstring(L, -1))
|
||||||
|
buttons.emplace_back(-1, lua_tostring(L, -1));
|
||||||
|
|
||||||
|
// Table value: Is a subtable that needs to be flatten.
|
||||||
|
// Used for ordered key-value pairs
|
||||||
|
else if (lua_istable(L, -1)) {
|
||||||
|
lua_pushvalue(L, -1);
|
||||||
|
lua_for_each(L, [&]{
|
||||||
|
buttons.emplace_back(
|
||||||
|
string_to_wx_id(luaL_checkstring(L, -2)),
|
||||||
|
luaL_checkstring(L, -1));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
luaL_error(L, "Invalid entry in buttons table");
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -460,58 +511,68 @@ namespace Automation4 {
|
||||||
wxWindow* LuaDialog::CreateWindow(wxWindow *parent) {
|
wxWindow* LuaDialog::CreateWindow(wxWindow *parent) {
|
||||||
window = new wxPanel(parent);
|
window = new wxPanel(parent);
|
||||||
|
|
||||||
wxGridBagSizer *s = new wxGridBagSizer(4, 4);
|
auto s = new wxGridBagSizer(4, 4);
|
||||||
for (auto c : controls)
|
for (auto c : controls)
|
||||||
s->Add(c->Create(window), wxGBPosition(c->y, c->x), wxGBSpan(c->height, c->width), c->GetSizerFlags());
|
s->Add(c->Create(window), wxGBPosition(c->y, c->x),
|
||||||
|
wxGBSpan(c->height, c->width), c->GetSizerFlags());
|
||||||
|
|
||||||
if (!use_buttons)
|
if (!use_buttons) {
|
||||||
window->SetSizerAndFit(s);
|
window->SetSizerAndFit(s);
|
||||||
else {
|
return window;
|
||||||
wxStdDialogButtonSizer *bs = new wxStdDialogButtonSizer;
|
|
||||||
if (buttons.size() > 0) {
|
|
||||||
LOG_D("automation/lua/dialog") << "creating user buttons";
|
|
||||||
for (size_t i = 0; i < buttons.size(); ++i) {
|
|
||||||
LOG_D("automation/lua/dialog") << "button '" << buttons[i] << "' gets id " << 1001+(wxWindowID)i;
|
|
||||||
bs->Add(new wxButton(window, 1001+(wxWindowID)i, to_wx(buttons[i])));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
LOG_D("automation/lua/dialog") << "creating default buttons";
|
|
||||||
bs->Add(new wxButton(window, wxID_OK));
|
|
||||||
bs->Add(new wxButton(window, wxID_CANCEL));
|
|
||||||
}
|
|
||||||
bs->Realize();
|
|
||||||
|
|
||||||
window->Bind(wxEVT_COMMAND_BUTTON_CLICKED, &LuaDialog::OnButtonPush, this);
|
|
||||||
|
|
||||||
wxBoxSizer *ms = new wxBoxSizer(wxVERTICAL);
|
|
||||||
ms->Add(s, 0, wxBOTTOM, 5);
|
|
||||||
ms->Add(bs);
|
|
||||||
window->SetSizerAndFit(ms);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (buttons.size() == 0) {
|
||||||
|
buttons.emplace_back(wxID_OK, "");
|
||||||
|
buttons.emplace_back(wxID_CANCEL, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
auto dialog = static_cast<wxDialog *>(parent);
|
||||||
|
auto bs = new wxStdDialogButtonSizer;
|
||||||
|
|
||||||
|
auto make_button = [&](wxWindowID id, int button_pushed, wxString const& text) -> wxButton *{
|
||||||
|
auto button = new wxButton(window, id, text);
|
||||||
|
button->Bind(wxEVT_COMMAND_BUTTON_CLICKED, [=](wxCommandEvent &evt) {
|
||||||
|
this->button_pushed = button_pushed;
|
||||||
|
dialog->EndModal(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (id == wxID_OK || id == wxID_YES || id == wxID_SAVE) {
|
||||||
|
button->SetFocus();
|
||||||
|
button->SetDefault();
|
||||||
|
dialog->SetAffirmativeId(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (id == wxID_CLOSE || id == wxID_NO)
|
||||||
|
dialog->SetEscapeId(id);
|
||||||
|
|
||||||
|
return button;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (boost::count(buttons | boost::adaptors::map_keys, -1) == 0) {
|
||||||
|
for (size_t i = 0; i < buttons.size(); ++i)
|
||||||
|
bs->AddButton(make_button(buttons[i].first, i, wxEmptyString));
|
||||||
|
bs->Realize();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
for (size_t i = 0; i < buttons.size(); ++i)
|
||||||
|
bs->Add(make_button(buttons[i].first, i, buttons[i].second));
|
||||||
|
}
|
||||||
|
|
||||||
|
auto ms = new wxBoxSizer(wxVERTICAL);
|
||||||
|
ms->Add(s, 0, wxBOTTOM, 5);
|
||||||
|
ms->Add(bs);
|
||||||
|
window->SetSizerAndFit(ms);
|
||||||
|
|
||||||
return window;
|
return window;
|
||||||
}
|
}
|
||||||
|
|
||||||
int LuaDialog::LuaReadBack(lua_State *L) {
|
int LuaDialog::LuaReadBack(lua_State *L) {
|
||||||
// First read back which button was pressed, if any
|
// First read back which button was pressed, if any
|
||||||
if (use_buttons) {
|
if (use_buttons) {
|
||||||
LOG_D("automation/lua/dialog") << "reading back button_pushed";
|
if (buttons[button_pushed].first == wxID_CANCEL)
|
||||||
int btn = button_pushed;
|
lua_pushboolean(L, false);
|
||||||
if (btn == 0) {
|
else
|
||||||
LOG_D("automation/lua/dialog") << "was zero, canceled";
|
lua_pushstring(L, buttons[button_pushed].second.c_str());
|
||||||
// Always cancel/closed
|
|
||||||
lua_pushboolean(L, 0);
|
|
||||||
}
|
|
||||||
else if (buttons.size() == 0 && btn == 1) {
|
|
||||||
LOG_D("automation/lua/dialog") << "default buttons, button 1 bushed, Ok button";
|
|
||||||
lua_pushboolean(L, 1);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
LOG_D("automation/lua/dialog") << "user button: " << buttons.at(btn-1);
|
|
||||||
// button_pushed is index+1 to reserve 0 for Cancel
|
|
||||||
lua_pushstring(L, buttons.at(btn-1).c_str());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Then read controls back
|
// Then read controls back
|
||||||
|
@ -555,27 +616,4 @@ namespace Automation4 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void LuaDialog::OnButtonPush(wxCommandEvent &evt) {
|
|
||||||
// Let button_pushed == 0 mean "cancelled", such that pushing Cancel or closing the dialog
|
|
||||||
// will both result in button_pushed == 0
|
|
||||||
if (evt.GetId() == wxID_OK) {
|
|
||||||
LOG_D("automation/lua/dialog") << "was wxID_OK";
|
|
||||||
button_pushed = 1;
|
|
||||||
}
|
|
||||||
else if (evt.GetId() == wxID_CANCEL) {
|
|
||||||
LOG_D("automation/lua/dialog") << "was wxID_CANCEL";
|
|
||||||
button_pushed = 0;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
LOG_D("automation/lua/dialog") << "was user button";
|
|
||||||
// Therefore, when buttons are numbered from 1001 to 1000+n, make sure to set it to i+1
|
|
||||||
button_pushed = evt.GetId() - 1000;
|
|
||||||
|
|
||||||
// hack to make sure the dialog will be closed
|
|
||||||
evt.SetId(wxID_OK);
|
|
||||||
}
|
|
||||||
LOG_D("automation/lua/dialog") << "button_pushed set to: " << button_pushed;
|
|
||||||
evt.Skip();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue