diff --git a/gtk2_ardour/ardour.menus.in b/gtk2_ardour/ardour.menus.in index 1eab195b76..8ed6ae70b5 100644 --- a/gtk2_ardour/ardour.menus.in +++ b/gtk2_ardour/ardour.menus.in @@ -280,7 +280,24 @@ + + + + + + + + + + + + + + +#ifndef GTKOSX + +#endif diff --git a/gtk2_ardour/ardour_ui.cc b/gtk2_ardour/ardour_ui.cc index a40d192e72..4c1c001873 100644 --- a/gtk2_ardour/ardour_ui.cc +++ b/gtk2_ardour/ardour_ui.cc @@ -126,6 +126,7 @@ typedef uint64_t microseconds_t; #include "keyboard.h" #include "keyeditor.h" #include "location_ui.h" +#include "lua_script_manager.h" #include "luawindow.h" #include "main_clock.h" #include "missing_file_dialog.h" @@ -276,6 +277,7 @@ ARDOUR_UI::ARDOUR_UI (int *argcp, char **argvp[], const char* localedir) , route_params (X_("inspector"), _("Tracks and Busses")) , audio_midi_setup (X_("audio-midi-setup"), _("Audio/MIDI Setup")) , export_video_dialog (X_("video-export"), _("Video Export Dialog")) + , lua_script_window (X_("script-manager"), _("Script Manager")) , session_option_editor (X_("session-options-editor"), _("Properties"), boost::bind (&ARDOUR_UI::create_session_option_editor, this)) , add_video_dialog (X_("add-video"), _("Add Video"), boost::bind (&ARDOUR_UI::create_add_video_dialog, this)) , bundle_manager (X_("bundle-manager"), _("Bundle Manager"), boost::bind (&ARDOUR_UI::create_bundle_manager, this)) @@ -427,6 +429,7 @@ ARDOUR_UI::ARDOUR_UI (int *argcp, char **argvp[], const char* localedir) audio_port_matrix.set_state (*ui_xml, 0); midi_port_matrix.set_state (*ui_xml, 0); export_video_dialog.set_state (*ui_xml, 0); + lua_script_window.set_state (*ui_xml, 0); } /* Separate windows */ @@ -440,6 +443,7 @@ ARDOUR_UI::ARDOUR_UI (int *argcp, char **argvp[], const char* localedir) WM::Manager::instance().register_window (&route_params); WM::Manager::instance().register_window (&audio_midi_setup); WM::Manager::instance().register_window (&export_video_dialog); + WM::Manager::instance().register_window (&lua_script_window); WM::Manager::instance().register_window (&bundle_manager); WM::Manager::instance().register_window (&location_ui); WM::Manager::instance().register_window (&big_clock_window); diff --git a/gtk2_ardour/ardour_ui_dialogs.cc b/gtk2_ardour/ardour_ui_dialogs.cc index 25f8243ec9..2e2361a379 100644 --- a/gtk2_ardour/ardour_ui_dialogs.cc +++ b/gtk2_ardour/ardour_ui_dialogs.cc @@ -49,6 +49,7 @@ #include "gui_thread.h" #include "keyeditor.h" #include "location_ui.h" +#include "lua_script_manager.h" #include "luawindow.h" #include "main_clock.h" #include "meterbridge.h" @@ -846,3 +847,9 @@ ARDOUR_UI::toggle_monitor_section_visibility () mixer->show_monitor_section (tact->get_active()); } } + +void +ARDOUR_UI::lua_script_manager () +{ + lua_script_window.show (); +} diff --git a/gtk2_ardour/editor.cc b/gtk2_ardour/editor.cc index 0cb7b05dcb..0afc797dc1 100644 --- a/gtk2_ardour/editor.cc +++ b/gtk2_ardour/editor.cc @@ -106,6 +106,7 @@ #include "gui_object.h" #include "gui_thread.h" #include "keyboard.h" +#include "luainstance.h" #include "marker.h" #include "midi_region_view.h" #include "midi_time_axis.h" @@ -832,6 +833,9 @@ Editor::Editor () setup_fade_images (); + LuaInstance::instance(); // instantiate + LuaInstance::instance()->ActionChanged.connect (sigc::mem_fun (*this, &Editor::set_script_action_name)); + instant_save (); } @@ -1400,6 +1404,8 @@ Editor::set_session (Session *t) ActionManager::ui_manager->signal_pre_activate().connect (sigc::mem_fun (*this, &Editor::action_pre_activated)); + LuaInstance::instance()->set_session(_session); + start_updating_meters (); } @@ -2402,7 +2408,7 @@ Editor::set_state (const XMLNode& node, int version) } } - return 0; + return LuaInstance::instance()->set_state(node); } XMLNode& @@ -2482,6 +2488,9 @@ Editor::get_state () snprintf (buf, sizeof (buf), "%" PRId64, nudge_clock->current_duration()); node->add_property ("nudge-clock-value", buf); + node->add_child_nocopy (LuaInstance::instance()->get_action_state()); + node->add_child_nocopy (LuaInstance::instance()->get_hook_state()); + return *node; } @@ -5616,6 +5625,30 @@ Editor::session_going_away () SessionHandlePtr::session_going_away (); } +void +Editor::manage_action_scripts () +{ + ARDOUR_UI::instance()->lua_script_manager(); +} + +void +Editor::trigger_script (int i) +{ + LuaInstance::instance()-> call_action (i); +} + +void +Editor::set_script_action_name (int i, const std::string& n) +{ + string const a = string_compose (X_("script-action-%1"), i + 1); + Glib::RefPtr act = ActionManager::get_action(X_("Editor"), a.c_str()); + assert (act); + if (n.empty ()) { + act->set_label (string_compose (_("Unset #%1"), i + 1)); + } else { + act->set_label (n); + } +} void Editor::show_editor_list (bool yn) diff --git a/gtk2_ardour/editor.h b/gtk2_ardour/editor.h index 7e74bbedbf..d0d84b1a6e 100644 --- a/gtk2_ardour/editor.h +++ b/gtk2_ardour/editor.h @@ -633,6 +633,7 @@ class Editor : public PublicEditor, public PBD::ScopedConnectionList, public ARD void remove_marker (ArdourCanvas::Item&, GdkEvent*); gint really_remove_marker (ARDOUR::Location* loc); void goto_nth_marker (int nth); + void trigger_script (int nth); void toggle_marker_lines (); void set_marker_line_visibility (bool); @@ -1614,6 +1615,9 @@ class Editor : public PublicEditor, public PBD::ScopedConnectionList, public ARD void initialize_canvas (); + void manage_action_scripts (); + void set_script_action_name (int i, const std::string&); + /* display control */ bool _show_measures; diff --git a/gtk2_ardour/editor_actions.cc b/gtk2_ardour/editor_actions.cc index 663ed9468b..046d9b94e1 100644 --- a/gtk2_ardour/editor_actions.cc +++ b/gtk2_ardour/editor_actions.cc @@ -147,6 +147,7 @@ Editor::register_actions () myactions.register_action (editor_menu_actions, X_("View"), _("View")); myactions.register_action (editor_menu_actions, X_("ZoomFocus"), _("Zoom Focus")); myactions.register_action (editor_menu_actions, X_("ZoomMenu"), _("Zoom")); + myactions.register_action (editor_menu_actions, X_("ActionScripts"), _("Scripted Actions")); register_region_actions (); @@ -246,6 +247,7 @@ Editor::register_actions () reg_sens (editor_actions, a.c_str(), n.c_str(), sigc::bind (sigc::mem_fun (*this, &Editor::goto_nth_marker), i - 1)); } + reg_sens (editor_actions, "jump-forward-to-mark", _("Jump to Next Mark"), sigc::mem_fun(*this, &Editor::jump_forward_to_mark)); reg_sens (editor_actions, "alternate-jump-forward-to-mark", _("Jump to Next Mark"), sigc::mem_fun(*this, &Editor::jump_forward_to_mark)); reg_sens (editor_actions, "jump-backward-to-mark", _("Jump to Previous Mark"), sigc::mem_fun(*this, &Editor::jump_backward_to_mark)); @@ -473,6 +475,13 @@ Editor::register_actions () myactions.register_action (editor_actions, X_("cycle-zoom-focus"), _("Next Zoom Focus"), sigc::mem_fun (*this, &Editor::cycle_zoom_focus)); + act = reg_sens (editor_actions, "manage-action-scripts", _("Manage"), + sigc::mem_fun(*this, &Editor::manage_action_scripts)); + for (int i = 1; i <= 9; ++i) { + string const a = string_compose (X_("script-action-%1"), i); + string const n = string_compose (_("Unset #%1"), i); + reg_sens (editor_actions, a.c_str(), n.c_str(), sigc::bind (sigc::mem_fun (*this, &Editor::trigger_script), i - 1)); + } Glib::RefPtr mouse_mode_actions = myactions.create_action_group (X_("MouseMode")); RadioAction::Group mouse_mode_group; diff --git a/gtk2_ardour/lua_script_manager.cc b/gtk2_ardour/lua_script_manager.cc new file mode 100644 index 0000000000..1917a7e4c7 --- /dev/null +++ b/gtk2_ardour/lua_script_manager.cc @@ -0,0 +1,312 @@ +/* + * Copyright (C) 2016 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 "gtkmm2ext/utils.h" + +#include "lua_script_manager.h" +#include "script_selector.h" +#include "i18n.h" + +using namespace std; +using namespace Gtk; +using namespace ARDOUR; + +LuaScriptManager::LuaScriptManager () + : ArdourWindow (_("Script Manager")) + , _a_set_button (_("Add/Set")) + , _a_del_button (_("Remove")) + , _a_edit_button (_("Edit")) + , _a_call_button (_("Call")) + , _c_add_button (_("New Hook")) + , _c_del_button (_("Remove")) +{ + /* action script page */ + _a_store = ListStore::create (_a_model); + _a_view.set_model (_a_store); + _a_view.append_column (_("Action"), _a_model.action); + _a_view.append_column (_("Name"), _a_model.name); + _a_view.get_column(0)->set_resizable (true); + _a_view.get_column(0)->set_expand (true); + + Gtk::HBox* edit_box = manage (new Gtk::HBox); + edit_box->set_spacing(3); + + edit_box->pack_start (_a_set_button, true, true); + edit_box->pack_start (_a_del_button, true, true); + edit_box->pack_start (_a_edit_button, true, true); + edit_box->pack_start (_a_call_button, true, true); + + _a_set_button.signal_clicked().connect (sigc::mem_fun(*this, &LuaScriptManager::set_action_btn_clicked)); + _a_del_button.signal_clicked().connect (sigc::mem_fun(*this, &LuaScriptManager::del_action_btn_clicked)); + _a_edit_button.signal_clicked().connect (sigc::mem_fun(*this, &LuaScriptManager::edit_action_btn_clicked)); + _a_call_button.signal_clicked().connect (sigc::mem_fun(*this, &LuaScriptManager::call_action_btn_clicked)); + _a_view.get_selection()->signal_changed().connect (sigc::mem_fun (*this, &LuaScriptManager::action_selection_changed)); + + LuaInstance::instance()->ActionChanged.connect (sigc::mem_fun (*this, &LuaScriptManager::set_action_script_name)); + LuaInstance::instance()->SlotChanged.connect (sigc::mem_fun (*this, &LuaScriptManager::set_callback_script_name)); + + Gtk::VBox *vbox = manage (new VBox()); + vbox->pack_start (_a_view, false, false); + vbox->pack_end (*edit_box, false, false); + vbox->show_all (); + + pages.pages ().push_back (Notebook_Helpers::TabElem (*vbox, "Action Scripts")); + + /* action hooks page */ + + _c_store = ListStore::create (_c_model); + _c_view.set_model (_c_store); + _c_view.append_column (_("Name"), _c_model.name); + _c_view.get_column(0)->set_resizable (true); + _c_view.get_column(0)->set_expand (true); + _c_view.append_column (_("Signal(s)"), _c_model.signals); + + edit_box = manage (new Gtk::HBox); + edit_box->set_spacing(3); + edit_box->pack_start (_c_add_button, true, true); + edit_box->pack_start (_c_del_button, true, true); + + _c_add_button.signal_clicked().connect (sigc::mem_fun(*this, &LuaScriptManager::add_callback_btn_clicked)); + _c_del_button.signal_clicked().connect (sigc::mem_fun(*this, &LuaScriptManager::del_callback_btn_clicked)); + _c_view.get_selection()->signal_changed().connect (sigc::mem_fun (*this, &LuaScriptManager::callback_selection_changed)); + + vbox = manage (new VBox()); + vbox->pack_start (_c_view, false, false); + vbox->pack_end (*edit_box, false, false); + vbox->show_all (); + + pages.pages ().push_back (Notebook_Helpers::TabElem (*vbox, "Action Hooks")); + + + add (pages); + pages.show(); + + setup_actions (); + setup_callbacks (); + + action_selection_changed (); + callback_selection_changed (); +} + +bool +LuaScriptManager::on_delete_event (GdkEventAny*) +{ + return false; +} + +void +LuaScriptManager::session_going_away () +{ + ArdourWindow::session_going_away (); + hide_all(); +} + +void +LuaScriptManager::setup_actions () +{ + LuaInstance *li = LuaInstance::instance(); + for (int i = 0; i < 9; ++i) { + std::string name; + TreeModel::Row r = *_a_store->append (); + r[_a_model.id] = i; + r[_a_model.action] = string_compose (_("Action %1"), i + 1); + if (li->lua_action_name (i, name)) { + r[_a_model.name] = name; + r[_a_model.enabled] = true; + } else { + r[_a_model.name] = _("Unset"); + r[_a_model.enabled] = false; + } + } +} + +void +LuaScriptManager::action_selection_changed () +{ + TreeModel::Row row = *(_a_view.get_selection()->get_selected()); + if (row) { + _a_set_button.set_sensitive (true); + } + else { + _a_set_button.set_sensitive (false); + } + + if (row && row[_a_model.enabled]) { + _a_del_button.set_sensitive (true); + _a_edit_button.set_sensitive (false); // TODO + _a_call_button.set_sensitive (true); + } else { + _a_del_button.set_sensitive (false); + _a_edit_button.set_sensitive (false); + _a_call_button.set_sensitive (false); + } +} + +void +LuaScriptManager::set_action_btn_clicked () +{ + TreeModel::Row row = *(_a_view.get_selection()->get_selected()); + assert (row); + LuaInstance *li = LuaInstance::instance(); + li->interactive_add (LuaScriptInfo::EditorAction, row[_a_model.id]); +} + +void +LuaScriptManager::del_action_btn_clicked () +{ + TreeModel::Row row = *(_a_view.get_selection()->get_selected()); + assert (row); + LuaInstance *li = LuaInstance::instance(); + if (!li->remove_lua_action (row[_a_model.id])) { + // error + } +} + +void +LuaScriptManager::call_action_btn_clicked () +{ + TreeModel::Row row = *(_a_view.get_selection()->get_selected()); + assert (row && row[_a_model.enabled]); + LuaInstance *li = LuaInstance::instance(); + li->call_action (row[_a_model.id]); +} + +void +LuaScriptManager::edit_action_btn_clicked () +{ + TreeModel::Row row = *(_a_view.get_selection()->get_selected()); + assert (row); + int id = row[_a_model.id]; + LuaInstance *li = LuaInstance::instance(); + std::string name, script; + LuaScriptParamList args; + if (!li->lua_action (id, name, script, args)) { + return; + } + + // TODO text-editor window, update script directly + + if (!LuaScripting::try_compile (script, args)) { + // compilation failed, keep editing + return; + } + + if (li->set_lua_action (id, name, script, args)) { + // OK + } else { + // load failed, keep editing.. + } + action_selection_changed (); +} + +void +LuaScriptManager::set_action_script_name (int i, const std::string& name) +{ + typedef Gtk::TreeModel::Children type_children; + type_children children = _a_store->children(); + for(type_children::iterator iter = children.begin(); iter != children.end(); ++iter) { + Gtk::TreeModel::Row row = *iter; + if (row[_a_model.id] == i) { + if (name.empty()) { + row[_a_model.enabled] = false; + row[_a_model.name] = _("Unset"); + } else { + row[_a_model.enabled] = true; + row[_a_model.name] = name; + } + break; + } + } + action_selection_changed (); +} + + +void +LuaScriptManager::setup_callbacks () +{ + LuaInstance *li = LuaInstance::instance(); + std::vector ids = li->lua_slots(); + for (std::vector::const_iterator i = ids.begin(); i != ids.end(); ++i) { + std::string name; + std::string script; + ActionHook ah; + LuaScriptParamList lsp; + if (li->lua_slot (*i, name, script, ah, lsp)) { + set_callback_script_name (*i, name, ah); + } + } +} + +void +LuaScriptManager::callback_selection_changed () +{ + TreeModel::Row row = *(_c_view.get_selection()->get_selected()); + if (row) { + _c_del_button.set_sensitive (true); + } else { + _c_del_button.set_sensitive (false); + } +} + +void +LuaScriptManager::add_callback_btn_clicked () +{ + LuaInstance *li = LuaInstance::instance(); + li->interactive_add (LuaScriptInfo::EditorHook, -1); +} + +void +LuaScriptManager::del_callback_btn_clicked () +{ + TreeModel::Row row = *(_c_view.get_selection()->get_selected()); + assert (row); + LuaInstance *li = LuaInstance::instance(); + if (!li->unregister_lua_slot (row[_c_model.id])) { + // error + } +} + +void +LuaScriptManager::set_callback_script_name (PBD::ID id, const std::string& name, const ActionHook& ah) +{ + if (name.empty()) { + typedef Gtk::TreeModel::Children type_children; + type_children children = _c_store->children(); + for(type_children::iterator iter = children.begin(); iter != children.end(); ++iter) { + Gtk::TreeModel::Row row = *iter; + PBD::ID i = row[_c_model.id]; + if (i == id) { + _c_store->erase (iter); + break; + } + } + } else { + TreeModel::Row r = *_c_store->append (); + r[_c_model.id] = id; + r[_c_model.name] = name; + string sig; + for (uint32_t i = 0; i < LuaSignal::LAST_SIGNAL; ++i) { + if (ah[i]) { + if (!sig.empty()) sig += ", "; + sig += enum2str (LuaSignal::LuaSignal (i)); + } + } + r[_c_model.signals] = sig; + } + callback_selection_changed (); +} diff --git a/gtk2_ardour/lua_script_manager.h b/gtk2_ardour/lua_script_manager.h new file mode 100644 index 0000000000..a01813fe6a --- /dev/null +++ b/gtk2_ardour/lua_script_manager.h @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2016 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/luascripting.h" + +#include "ardour_window.h" +#include "luainstance.h" + +class LuaScriptManager : public ArdourWindow +{ +public: + LuaScriptManager (); + +protected: + bool on_delete_event (GdkEventAny*); + void session_going_away(); + +private: + Gtk::Notebook pages; + + /* action scripts */ + void setup_actions (); + void action_selection_changed (); + void set_action_script_name (int, const std::string&); + + void set_action_btn_clicked (); + void del_action_btn_clicked (); + void edit_action_btn_clicked (); + void call_action_btn_clicked (); + + class LuaActionScriptModelColumns : public Gtk::TreeModelColumnRecord + { + public: + LuaActionScriptModelColumns () + { + add (id); + add (action); + add (name); + add (enabled); + } + + Gtk::TreeModelColumn id; + Gtk::TreeModelColumn action; + Gtk::TreeModelColumn name; + Gtk::TreeModelColumn enabled; + }; + + Gtk::Button _a_set_button; + Gtk::Button _a_del_button; + Gtk::Button _a_edit_button; + Gtk::Button _a_call_button; + + Glib::RefPtr _a_store; + LuaActionScriptModelColumns _a_model; + Gtk::TreeView _a_view; + + /* action callback hooks */ + void setup_callbacks (); + void callback_selection_changed (); + void set_callback_script_name (PBD::ID, const std::string&, const ActionHook& ah); + + void add_callback_btn_clicked (); + void del_callback_btn_clicked (); + + class LuaCallbackScriptModelColumns : public Gtk::TreeModelColumnRecord + { + public: + LuaCallbackScriptModelColumns () + { + add (id); + add (name); + add (signals); + } + + Gtk::TreeModelColumn id; + Gtk::TreeModelColumn name; + Gtk::TreeModelColumn signals; + }; + + Glib::RefPtr _c_store; + LuaCallbackScriptModelColumns _c_model; + Gtk::TreeView _c_view; + + Gtk::Button _c_add_button; + Gtk::Button _c_del_button; +}; diff --git a/gtk2_ardour/public_editor.h b/gtk2_ardour/public_editor.h index 86a23ec455..d6aabaf229 100644 --- a/gtk2_ardour/public_editor.h +++ b/gtk2_ardour/public_editor.h @@ -205,6 +205,7 @@ class PublicEditor : public Gtkmm2ext::Tabbable { virtual void play_with_preroll () = 0; virtual void maybe_locate_with_edit_preroll (framepos_t location) = 0; virtual void goto_nth_marker (int nth) = 0; + virtual void trigger_script (int nth) = 0; virtual void add_location_from_playhead_cursor () = 0; virtual void remove_location_at_playhead_cursor () = 0; virtual void set_show_measures (bool yn) = 0; diff --git a/gtk2_ardour/wscript b/gtk2_ardour/wscript index de73078287..4faee4f944 100644 --- a/gtk2_ardour/wscript +++ b/gtk2_ardour/wscript @@ -122,6 +122,7 @@ gtk2_ardour_sources = [ 'led.cc', 'level_meter.cc', 'location_ui.cc', + 'lua_script_manager.cc', 'luainstance.cc', 'luawindow.cc', 'main.cc',