From f5995661d0b652a1907ca440dce6e1fcc2bf3855 Mon Sep 17 00:00:00 2001 From: Robin Gareus Date: Mon, 24 Apr 2017 22:12:09 +0200 Subject: [PATCH] Lua: Action Script Dialog & MessageBox --- gtk2_ardour/luadialog.cc | 563 +++++++++++++++++++++++++++++++++++++ gtk2_ardour/luadialog.h | 88 ++++++ gtk2_ardour/luainstance.cc | 47 ++++ gtk2_ardour/luainstance.h | 1 + gtk2_ardour/wscript | 1 + 5 files changed, 700 insertions(+) create mode 100644 gtk2_ardour/luadialog.cc create mode 100644 gtk2_ardour/luadialog.h diff --git a/gtk2_ardour/luadialog.cc b/gtk2_ardour/luadialog.cc new file mode 100644 index 0000000000..62a1e4d682 --- /dev/null +++ b/gtk2_ardour/luadialog.cc @@ -0,0 +1,563 @@ +/* + * Copyright (C) 2017 Robin Gareus + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include + +#include "ardour/dB.h" +#include "ardour/rc_configuration.h" + +#include "gtkmm2ext/slider_controller.h" +#include "gtkmm2ext/utils.h" + +#include "ardour_dialog.h" +#include "ardour_dropdown.h" +#include "luadialog.h" +#include "utils.h" + +using namespace LuaDialog; + +/******************************************************************************* + * Simple Message Dialog + */ +Message::Message (std::string const& title, std::string const& msg, Message::MessageType mt, Message::ButtonType bt) + : _message_dialog (msg, false, to_gtk_mt (mt), to_gtk_bt (bt), true) +{ + _message_dialog.set_title (title); +} + +int +Message::run () +{ + int rv = _message_dialog.run (); + _message_dialog.hide (); + switch (rv) { + case Gtk::RESPONSE_OK: + return 0; + case Gtk::RESPONSE_CANCEL: + return 1; + case Gtk::RESPONSE_CLOSE: + return 2; + case Gtk::RESPONSE_YES: + return 3; + case Gtk::RESPONSE_NO: + return 4; + default: + break; + } + return -1; +} + +Gtk::ButtonsType +Message::to_gtk_bt (ButtonType bt) +{ + switch (bt) { + case OK: + return Gtk::BUTTONS_OK; + case Close: + return Gtk::BUTTONS_CLOSE; + case Cancel: + return Gtk::BUTTONS_CANCEL; + case Yes_No: + return Gtk::BUTTONS_YES_NO; + case OK_Cancel: + return Gtk::BUTTONS_OK_CANCEL; + } + assert (0); + return Gtk::BUTTONS_OK; +} + +Gtk::MessageType +Message::to_gtk_mt (MessageType mt) +{ + switch (mt) { + case Info: + return Gtk::MESSAGE_INFO; + case Warning: + return Gtk::MESSAGE_WARNING; + case Question: + return Gtk::MESSAGE_QUESTION; + case Error: + return Gtk::MESSAGE_ERROR; + } + assert (0); + return Gtk::MESSAGE_INFO; +} + + +/* ***************************************************************************** + * Lua Dialog Widgets + */ + +class LuaDialogCheckbox : public LuaDialogWidget +{ +public: + LuaDialogCheckbox (std::string const& key, std::string const& title, bool on) + : LuaDialogWidget (key, "") + , _cb (title) + { + _cb.set_active (on); + } + + Gtk::Widget* widget () + { + return &_cb; + } + + void assign (luabridge::LuaRef* rv) const + { + (*rv)[_key] = _cb.get_active (); + } + +protected: + Gtk::CheckButton _cb; +}; + +class LuaDialogEntry : public LuaDialogWidget +{ +public: + LuaDialogEntry (std::string const& key, std::string const& title, std::string const& dflt) + : LuaDialogWidget (key, title) + { + _entry.set_text (dflt); + } + + Gtk::Widget* widget () + { + return &_entry; + } + + void assign (luabridge::LuaRef* rv) const + { + (*rv)[_key] = std::string (_entry.get_text ()); + } + +protected: + Gtk::Entry _entry; +}; + +class LuaDialogFader : public LuaDialogWidget +{ +public: + LuaDialogFader (std::string const& key, std::string const& title, double dflt) + : LuaDialogWidget (key, title) + , _db_adjustment (ARDOUR::gain_to_slider_position_with_max (1.0, ARDOUR::Config->get_max_gain()), 0, 1, 0.01, 0.1) + { + _db_slider = Gtk::manage (new Gtkmm2ext::HSliderController (&_db_adjustment, boost::shared_ptr(), 220, 18)); + + _fader_centering_box.pack_start (*_db_slider, true, false); + + _box.set_spacing (4); + _box.set_homogeneous (false); + _box.pack_start (_fader_centering_box, false, false); + _box.pack_start (_db_display, false, false); + _box.pack_start (*Gtk::manage (new Gtk::Label ("dB")), false, false); + + Gtkmm2ext::set_size_request_to_display_given_text (_db_display, "-99.00", 12, 0); + + _db_adjustment.signal_value_changed().connect (sigc::mem_fun (*this, &LuaDialogFader::db_changed)); + _db_display.signal_activate().connect (sigc::mem_fun (*this, &LuaDialogFader::on_activate)); + _db_display.signal_key_press_event().connect (sigc::mem_fun (*this, &LuaDialogFader::on_key_press), false); + + double coeff_val = dB_to_coefficient (dflt); + _db_adjustment.set_value (ARDOUR::gain_to_slider_position_with_max (coeff_val, ARDOUR::Config->get_max_gain ())); + db_changed (); + } + + Gtk::Widget* widget () + { + return &_box; + } + + void assign (luabridge::LuaRef* rv) const + { + double const val = ARDOUR::slider_position_to_gain_with_max (_db_adjustment.get_value (), ARDOUR::Config->get_max_gain()); + (*rv)[_key] = accurate_coefficient_to_dB (val); + } + +protected: + void db_changed () + { + double const val = ARDOUR::slider_position_to_gain_with_max (_db_adjustment.get_value (), ARDOUR::Config->get_max_gain()); + char buf[16]; + if (val == 0.0) { + snprintf (buf, sizeof (buf), "-inf"); + } else { + snprintf (buf, sizeof (buf), "%.2f", accurate_coefficient_to_dB (val)); + } + _db_display.set_text (buf); + } + + void on_activate () + { + float db_val = atof (_db_display.get_text ().c_str ()); + double coeff_val = dB_to_coefficient (db_val); + _db_adjustment.set_value (ARDOUR::gain_to_slider_position_with_max (coeff_val, ARDOUR::Config->get_max_gain ())); + } + + bool on_key_press (GdkEventKey* ev) + { + if (ARDOUR_UI_UTILS::key_is_legal_for_numeric_entry (ev->keyval)) { + return false; + } + return true; + } + + Gtk::Adjustment _db_adjustment; + Gtkmm2ext::HSliderController* _db_slider; + Gtk::Entry _db_display; + Gtk::HBox _box; + Gtk::VBox _fader_centering_box; +}; + +class LuaDialogSlider : public LuaDialogWidget +{ +public: + LuaDialogSlider (std::string const& key, std::string const& title, double lower, double upper, double dflt, int digits, luabridge::LuaRef scalepoints) + : LuaDialogWidget (key, title) + , _adj (dflt, lower, upper, 1, (upper - lower) / 20, 0) + , _hscale (_adj) + { + _hscale.set_digits (digits); + _hscale.set_draw_value (true); + _hscale.set_value_pos (Gtk::POS_TOP); + + if (!scalepoints.isTable ()) { + return; + } + + for (luabridge::Iterator i (scalepoints); !i.isNil (); ++i) { + if (!i.key ().isNumber ()) { continue; } + if (!i.value ().isString ()) { continue; } + _hscale.add_mark (i.key ().cast (), Gtk::POS_BOTTOM, i.value ().cast ()); + } + } + + Gtk::Widget* widget () + { + return &_hscale; + } + + void assign (luabridge::LuaRef* rv) const + { + (*rv)[_key] = _adj.get_value (); + } + +protected: + Gtk::Adjustment _adj; + Gtk::HScale _hscale; +}; + +class LuaDialogSpinBox : public LuaDialogWidget +{ +public: + LuaDialogSpinBox (std::string const& key, std::string const& title, double lower, double upper, double dflt, double step, int digits) + : LuaDialogWidget (key, title) + , _adj (dflt, lower, upper, step, step, 0) + , _spin (_adj) + { + _spin.set_digits(digits); + } + + Gtk::Widget* widget () + { + return &_spin; + } + + void assign (luabridge::LuaRef* rv) const + { + (*rv)[_key] = _adj.get_value (); + } + +protected: + Gtk::Adjustment _adj; + Gtk::SpinButton _spin; +}; + +class LuaDialogRadio : public LuaDialogWidget +{ +public: + LuaDialogRadio (std::string const& key, std::string const& title, luabridge::LuaRef values, std::string const& dflt) + : LuaDialogWidget (key, title) + , _rv (0) + { + for (luabridge::Iterator i (values); !i.isNil (); ++i) { + if (!i.key ().isString ()) { continue; } + std::string key = i.key ().cast (); + Gtk::RadioButton* rb = Gtk::manage (new Gtk::RadioButton (_group, key)); + _hbox.pack_start (*rb); + luabridge::LuaRef* ref = new luabridge::LuaRef (i.value ()); + _refs.push_back (ref); + if (!_rv) { _rv = ref; } + rb->signal_toggled ().connect (sigc::bind ( + sigc::mem_fun (*this, &LuaDialogRadio::rb_toggled), rb, ref + ) , false); + + if (key == dflt) { + rb->set_active (); + } + } + } + + ~LuaDialogRadio () + { + for (std::vector::const_iterator i = _refs.begin (); i != _refs.end (); ++i) { + delete *i; + } + _refs.clear (); + } + + Gtk::Widget* widget () + { + return &_hbox; + } + + void assign (luabridge::LuaRef* rv) const + { + if (_rv) { + (*rv)[_key] = *_rv; + } else { + (*rv)[_key] = luabridge::Nil (); + } + } + +protected: + LuaDialogRadio (LuaDialogRadio const&); // prevent cc + void rb_toggled (Gtk::RadioButton* b, luabridge::LuaRef* rv) { + if (b->get_active ()) { + _rv = rv; + } + } + + Gtk::HBox _hbox; + Gtk::RadioButtonGroup _group; + std::vector _refs; + luabridge::LuaRef* _rv; +}; + +class LuaDialogDropDown : public LuaDialogWidget +{ +public: + LuaDialogDropDown (std::string const& key, std::string const& title, luabridge::LuaRef values, std::string const& dflt) + : LuaDialogWidget (key, title) + , _rv (0) + { + populate (_dd.items(), values, dflt); + } + + ~LuaDialogDropDown () + { + for (std::vector::const_iterator i = _refs.begin (); i != _refs.end (); ++i) { + delete *i; + } + _refs.clear (); + } + + Gtk::Widget* widget () + { + return &_dd; + } + + void assign (luabridge::LuaRef* rv) const + { + if (_rv) { + (*rv)[_key] = *_rv; + } else { + (*rv)[_key] = luabridge::Nil (); + } + } + +protected: + void populate (Gtk::Menu_Helpers::MenuList& items, luabridge::LuaRef values, std::string const& dflt) + { + using namespace Gtk::Menu_Helpers; + for (luabridge::Iterator i (values); !i.isNil (); ++i) { + if (!i.key ().isString ()) { continue; } + std::string key = i.key ().cast (); + if (i.value ().isTable ()) { + Gtk::Menu* menu = Gtk::manage (new Gtk::Menu); + items.push_back (MenuElem (key, *menu)); + populate (menu->items(), i.value (), dflt); + continue; + } + luabridge::LuaRef* ref = new luabridge::LuaRef (i.value ()); + _refs.push_back (ref); + items.push_back (MenuElem (i.key ().cast (), + sigc::bind (sigc::mem_fun(*this, &LuaDialogDropDown::dd_select), key, ref))); + + if (!_rv || key == dflt) { + _rv = ref; + _dd.set_text (key); + } + } + } + + void dd_select (std::string const& key, luabridge::LuaRef* rv) { + _dd.set_text (key); + _rv = rv; + } + + ArdourDropdown _dd; + std::vector _refs; + luabridge::LuaRef* _rv; +}; + + +/******************************************************************************* + * Lua Parameter Dialog + */ +Dialog::Dialog (std::string const& title, luabridge::LuaRef lr) + :_ad (title, true, false) + , _title (title) +{ + if (!lr.isTable ()) { + return; + } + for (luabridge::Iterator i (lr); !i.isNil (); ++i) { + if (!i.key ().isNumber ()) { continue; } + if (!i.value ().isTable ()) { continue; } + if (!i.value ()["key"].isString ()) { continue; } + if (!i.value ()["title"].isString ()) { continue; } + if (!i.value ()["type"].isString ()) { continue; } + + std::string key = i.value ()["key"].cast (); + std::string title = i.value ()["title"].cast (); + std::string type = i.value ()["type"].cast (); + + if (type == "checkbox") { + bool dflt = false; + if (i.value ()["default"].isBoolean ()) { + dflt = i.value ()["default"].cast (); + } + _widgets.push_back (new LuaDialogCheckbox (key, title, dflt)); + } else if (type == "entry") { + std::string dflt; + if (i.value ()["default"].isString ()) { + dflt = i.value ()["default"].cast (); + } + _widgets.push_back (new LuaDialogEntry (key, title, dflt)); + } else if (type == "radio") { + std::string dflt; + if (!i.value ()["values"].isTable ()) { + continue; + } + if (i.value ()["default"].isString ()) { + dflt = i.value ()["default"].cast (); + } + _widgets.push_back (new LuaDialogRadio (key, title, i.value ()["values"], dflt)); + } else if (type == "fader") { + double dflt = 0; + if (i.value ()["default"].isNumber ()) { + dflt = i.value ()["default"].cast (); + } + _widgets.push_back (new LuaDialogFader (key, title, dflt)); + } else if (type == "slider") { + double lower, upper, dflt; + int digits = 0; + if (!i.value ()["min"].isNumber ()) { continue; } + if (!i.value ()["max"].isNumber ()) { continue; } + lower = i.value ()["min"].cast (); + upper = i.value ()["max"].cast (); + if (i.value ()["default"].isNumber ()) { + dflt = i.value ()["default"].cast (); + } else { + dflt = lower; + } + if (i.value ()["digits"].isNumber ()) { + digits = i.value ()["digits"].cast (); + } + _widgets.push_back (new LuaDialogSlider (key, title, lower, upper, dflt, digits, i.value ()["scalepoints"])); + } else if (type == "number") { + double lower, upper, dflt, step; + int digits = 0; + if (!i.value ()["min"].isNumber ()) { continue; } + if (!i.value ()["max"].isNumber ()) { continue; } + lower = i.value ()["min"].cast (); + upper = i.value ()["max"].cast (); + if (i.value ()["default"].isNumber ()) { + dflt = i.value ()["default"].cast (); + } else { + dflt = lower; + } + if (i.value ()["step"].isNumber ()) { + step = i.value ()["step"].cast (); + } else { + step = 1.0; + } + if (i.value ()["digits"].isNumber ()) { + digits = i.value ()["digits"].cast (); + } + _widgets.push_back (new LuaDialogSpinBox (key, title, lower, upper, dflt, step, digits)); + } else if (type == "dropdown") { + std::string dflt; + if (!i.value ()["values"].isTable ()) { + continue; + } + if (i.value ()["default"].isString ()) { + dflt = i.value ()["default"].cast (); + } + _widgets.push_back (new LuaDialogDropDown (key, title, i.value ()["values"], dflt)); + } + } + + _ad.add_button (Gtk::Stock::OK, Gtk::RESPONSE_ACCEPT); + _ad.add_button (Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL); + + Gtk::Table* table = Gtk::manage (new Gtk::Table()); + table->set_spacings (6); + _ad.get_vbox ()->pack_start (*table); + int row = 0; + + for (DialogWidgets::const_iterator i = _widgets.begin (); i != _widgets.end () ; ++i) { + std::string const& label = (*i)->label (); + if (!label.empty ()) { + Gtk::Label* lbl = Gtk::manage (new Gtk::Label ("" + label + ":", Gtk::ALIGN_END, Gtk::ALIGN_CENTER, false)); + lbl->set_use_markup (); + table->attach (*lbl, 0, 1, row, row + 1, Gtk::FILL | Gtk::EXPAND, Gtk::SHRINK); + } + table->attach (*((*i)->widget ()), 1, 2, row, row + 1, Gtk::FILL | Gtk::EXPAND, Gtk::SHRINK); + ++row; + } +} + +Dialog::~Dialog () +{ + for (DialogWidgets::const_iterator i = _widgets.begin (); i != _widgets.end () ; ++i) { + delete *i; + } + _widgets.clear (); +} + +int +Dialog::run (lua_State *L) +{ + _ad.get_vbox ()->show_all (); + switch (_ad.run ()) { + case Gtk::RESPONSE_ACCEPT: + break; + default: + lua_pushnil (L); + return 1; + } + + luabridge::LuaRef rv (luabridge::newTable (L)); + for (DialogWidgets::const_iterator i = _widgets.begin (); i != _widgets.end () ; ++i) { + (*i)->assign (&rv); + } + luabridge::push (L, rv); + return 1; +} diff --git a/gtk2_ardour/luadialog.h b/gtk2_ardour/luadialog.h new file mode 100644 index 0000000000..96d79846fb --- /dev/null +++ b/gtk2_ardour/luadialog.h @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2017 Robin Gareus + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef _gtk2ardour_luadialog_h_ +#define _gtk2ardour_luadialog_h_ + +#include +#include +#include + +#include "LuaBridge/LuaBridge.h" + +namespace LuaDialog { + +class Message { +public: + + enum MessageType { + Info, Warning, Question, Error + }; + + enum ButtonType { + OK, Close, Cancel, Yes_No, OK_Cancel + }; + + Message (std::string const&, std::string const&, Message::MessageType, Message::ButtonType); + + int run (); + +private: + Message (Message const&); // prevent copy construction + + static Gtk::ButtonsType to_gtk_bt (ButtonType bt); + static Gtk::MessageType to_gtk_mt (MessageType mt); + + Gtk::MessageDialog _message_dialog; +}; + +class LuaDialogWidget { +public: + LuaDialogWidget (std::string const& key, std::string const& label) + : _key (key), _label (label) + {} + + virtual ~LuaDialogWidget () {} + + virtual Gtk::Widget* widget () = 0; + virtual void assign (luabridge::LuaRef* rv) const = 0; + std::string const& label () const { return _label; } + +protected: + std::string _key; + std::string _label; +}; + + +class Dialog { +public: + Dialog (std::string const&, luabridge::LuaRef); + ~Dialog (); + int run (lua_State *L); + +private: + Dialog (Dialog const&); // prevent copy construction + ArdourDialog _ad; + typedef std::vector DialogWidgets; + DialogWidgets _widgets; + std::string _title; +}; + +}; // namespace + +#endif diff --git a/gtk2_ardour/luainstance.cc b/gtk2_ardour/luainstance.cc index 691473963b..e1c04bf435 100644 --- a/gtk2_ardour/luainstance.cc +++ b/gtk2_ardour/luainstance.cc @@ -34,6 +34,7 @@ #include "ardour_ui.h" #include "public_editor.h" #include "region_selection.h" +#include "luadialog.h" #include "luainstance.h" #include "luasignal.h" #include "marker.h" @@ -548,6 +549,51 @@ LuaInstance::bind_cairo (lua_State* L) } +void +LuaInstance::bind_dialog (lua_State* L) +{ + luabridge::getGlobalNamespace (L) + .beginNamespace ("LuaDialog") + + .beginClass ("Message") + .addConstructor () + .addFunction ("run", &LuaDialog::Message::run) + .endClass () + + .beginClass ("Dialog") + .addConstructor () + .addCFunction ("run", &LuaDialog::Dialog::run) + .endClass () + + /* enums */ + .beginNamespace ("MessageType") + .addConst ("Info", LuaDialog::Message::Info) + .addConst ("Warning", LuaDialog::Message::Warning) + .addConst ("Question", LuaDialog::Message::Question) + .addConst ("Error", LuaDialog::Message::Error) + .endNamespace () + + .beginNamespace ("ButtonType") + .addConst ("OK", LuaDialog::Message::OK) + .addConst ("Close", LuaDialog::Message::Close) + .addConst ("Cancel", LuaDialog::Message::Cancel) + .addConst ("Yes_No", LuaDialog::Message::Yes_No) + .addConst ("OK_Cancel", LuaDialog::Message::OK_Cancel) + .endNamespace () + + .beginNamespace ("Response") + .addConst ("OK", 0) + .addConst ("Cancel", 1) + .addConst ("Close", 2) + .addConst ("Yes", 3) + .addConst ("No", 4) + .addConst ("None", -1) + .endNamespace () + + .endNamespace (); + +} + void LuaInstance::register_classes (lua_State* L) { @@ -557,6 +603,7 @@ LuaInstance::register_classes (lua_State* L) LuaBindings::osc (L); bind_cairo (L); + bind_dialog (L); register_hooks (L); luabridge::getGlobalNamespace (L) diff --git a/gtk2_ardour/luainstance.h b/gtk2_ardour/luainstance.h index 4d3d0b1973..8ffc1d887e 100644 --- a/gtk2_ardour/luainstance.h +++ b/gtk2_ardour/luainstance.h @@ -87,6 +87,7 @@ public: static void register_classes (lua_State* L); static void register_hooks (lua_State* L); static void bind_cairo (lua_State* L); + static void bind_dialog (lua_State* L); static void render_action_icon (cairo_t* cr, int w, int h, uint32_t c, void* i); diff --git a/gtk2_ardour/wscript b/gtk2_ardour/wscript index 2d64249246..646ae17d6b 100644 --- a/gtk2_ardour/wscript +++ b/gtk2_ardour/wscript @@ -135,6 +135,7 @@ gtk2_ardour_sources = [ 'level_meter.cc', 'location_ui.cc', 'lua_script_manager.cc', + 'luadialog.cc', 'luainstance.cc', 'luawindow.cc', 'main.cc',