Optimize automation-event process splitting
[ardour.git] / gtk2_ardour / lua_script_manager.cc
1 /*
2  * Copyright (C) 2016 Robin Gareus <robin@gareus.org>
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public License
6  * as published by the Free Software Foundation; either version 2
7  * of the License, or (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
17  */
18
19 #include <gtkmm/box.h>
20 #include <gtkmm/frame.h>
21 #include <gtkmm/messagedialog.h>
22
23 #include "gtkmm2ext/gui_thread.h"
24 #include "gtkmm2ext/utils.h"
25
26 #include "ardour/session.h"
27
28 #include "LuaBridge/LuaBridge.h"
29
30 #include "ardour_ui.h"
31 #include "lua_script_manager.h"
32 #include "luawindow.h"
33 #include "script_selector.h"
34 #include "pbd/i18n.h"
35
36 using namespace std;
37 using namespace Gtk;
38 using namespace ARDOUR;
39
40 LuaScriptManager::LuaScriptManager ()
41         : ArdourWindow (_("Script Manager"))
42         , _a_set_button (_("Add/Set"))
43         , _a_del_button (_("Remove"))
44         , _a_edit_button (_("Edit"))
45         , _a_call_button (_("Call"))
46         , _c_add_button (_("New Hook"))
47         , _c_del_button (_("Remove"))
48         , _s_add_button (_("Load"))
49         , _s_del_button (_("Remove"))
50 {
51         /* action script page */
52         _a_store = ListStore::create (_a_model);
53         _a_view.set_model (_a_store);
54         _a_view.append_column (_("Action"), _a_model.action);
55         _a_view.append_column (_("Name"), _a_model.name);
56         _a_view.get_column(0)->set_resizable (true);
57         _a_view.get_column(0)->set_expand (true);
58         _a_view.get_column(1)->set_resizable (true);
59         _a_view.get_column(1)->set_expand (true);
60
61         Frame* f;
62         Gtk::Label* doc;
63
64         Gtk::HBox* edit_box = manage (new Gtk::HBox);
65         edit_box->set_spacing(3);
66
67         edit_box->pack_start (_a_set_button, true, true);
68         edit_box->pack_start (_a_del_button, true, true);
69         edit_box->pack_start (_a_edit_button, true, true);
70         edit_box->pack_start (_a_call_button, true, true);
71
72         _a_set_button.signal_clicked().connect (sigc::mem_fun(*this, &LuaScriptManager::set_action_btn_clicked));
73         _a_del_button.signal_clicked().connect (sigc::mem_fun(*this, &LuaScriptManager::del_action_btn_clicked));
74         _a_edit_button.signal_clicked().connect (sigc::mem_fun(*this, &LuaScriptManager::edit_action_btn_clicked));
75         _a_call_button.signal_clicked().connect (sigc::mem_fun(*this, &LuaScriptManager::call_action_btn_clicked));
76         _a_view.get_selection()->signal_changed().connect (sigc::mem_fun (*this, &LuaScriptManager::action_selection_changed));
77
78         LuaInstance::instance()->ActionChanged.connect (sigc::mem_fun (*this, &LuaScriptManager::set_action_script_name));
79         LuaInstance::instance()->SlotChanged.connect (sigc::mem_fun (*this, &LuaScriptManager::set_callback_script_name));
80
81         f = manage (new Frame (_("Description")));
82         doc = manage (new Label (
83                                 _("Action Scripts are user initiated actions (menu, shortcuts, toolbar-button) for batch processing or customized tasks.")
84                                 ));
85         doc->set_padding (5, 5);
86         doc->set_line_wrap();
87         f->add (*doc);
88
89         Gtk::VBox *vbox = manage (new VBox());
90         vbox->pack_start (_a_view, false, false);
91         vbox->pack_end (*edit_box, false, false);
92         vbox->pack_end (*f, false, false);
93         vbox->show_all ();
94
95         pages.pages ().push_back (Notebook_Helpers::TabElem (*vbox, "Action Scripts"));
96
97         /* action hooks page */
98
99         _c_store = ListStore::create (_c_model);
100         _c_view.set_model (_c_store);
101         _c_view.append_column (_("Name"), _c_model.name);
102         _c_view.append_column (_("Signal(s)"), _c_model.signals);
103         _c_view.get_column(0)->set_resizable (true);
104         _c_view.get_column(0)->set_expand (true);
105         _c_view.get_column(1)->set_resizable (true);
106         _c_view.get_column(1)->set_expand (true);
107         Gtk::CellRendererText* r = dynamic_cast<Gtk::CellRendererText*>(_c_view.get_column_cell_renderer (1));
108         r->property_ellipsize () = Pango::ELLIPSIZE_MIDDLE;
109
110         edit_box = manage (new Gtk::HBox);
111         edit_box->set_spacing(3);
112         edit_box->pack_start (_c_add_button, true, true);
113         edit_box->pack_start (_c_del_button, true, true);
114
115         _c_add_button.signal_clicked().connect (sigc::mem_fun(*this, &LuaScriptManager::add_callback_btn_clicked));
116         _c_del_button.signal_clicked().connect (sigc::mem_fun(*this, &LuaScriptManager::del_callback_btn_clicked));
117         _c_view.get_selection()->signal_changed().connect (sigc::mem_fun (*this, &LuaScriptManager::callback_selection_changed));
118
119         f = manage (new Frame (_("Description")));
120         doc = manage (new Label (
121                                 _("Lua action hooks are event-triggered callbacks for the Editor/Mixer GUI. Once a script is registered it is automatically triggered by events to perform some task.")
122                                 ));
123         doc->set_padding (5, 5);
124         doc->set_line_wrap();
125         f->add (*doc);
126
127         vbox = manage (new VBox());
128         vbox->pack_start (_c_view, false, false);
129         vbox->pack_end (*edit_box, false, false);
130         vbox->pack_end (*f, false, false);
131         vbox->show_all ();
132
133         pages.pages ().push_back (Notebook_Helpers::TabElem (*vbox, "Action Hooks"));
134
135         /* session script page */
136
137         _s_store = ListStore::create (_s_model);
138         _s_view.set_model (_s_store);
139         _s_view.append_column (_("Name"), _s_model.name);
140         _s_view.get_column(0)->set_resizable (true);
141         _s_view.get_column(0)->set_expand (true);
142
143         edit_box = manage (new Gtk::HBox);
144         edit_box->set_spacing(3);
145         edit_box->pack_start (_s_add_button, true, true);
146         edit_box->pack_start (_s_del_button, true, true);
147
148         _s_add_button.signal_clicked().connect (sigc::mem_fun(*this, &LuaScriptManager::add_sess_btn_clicked));
149         _s_del_button.signal_clicked().connect (sigc::mem_fun(*this, &LuaScriptManager::del_sess_btn_clicked));
150         _s_view.get_selection()->signal_changed().connect (sigc::mem_fun (*this, &LuaScriptManager::session_script_selection_changed));
151
152         f = manage (new Frame (_("Description")));
153         doc = manage (new Label (
154                                 _("Lua session scripts are loaded into processing engine and run in realtime. They are called periodically at the start of every audio cycle in the realtime process context before any processing takes place.")
155                                 ));
156         doc->set_padding (5, 5);
157         doc->set_line_wrap();
158         f->add (*doc);
159
160         vbox = manage (new VBox());
161         vbox->pack_start (_s_view, false, false);
162         vbox->pack_end (*edit_box, false, false);
163         vbox->pack_end (*f, false, false);
164         vbox->show_all ();
165
166         pages.pages ().push_back (Notebook_Helpers::TabElem (*vbox, "Session Scripts"));
167
168         /* global layout */
169
170         add (pages);
171         pages.show();
172
173         setup_actions ();
174         setup_callbacks ();
175         setup_session_scripts ();
176
177         action_selection_changed ();
178         callback_selection_changed ();
179         session_script_selection_changed ();
180 }
181
182 void
183 LuaScriptManager::set_session (ARDOUR::Session *s)
184 {
185         ArdourWindow::set_session (s);
186         setup_session_scripts ();
187         if (!_session) {
188                 return;
189         }
190
191         _session->LuaScriptsChanged.connect (_session_script_connection,  invalidator (*this), boost::bind (&LuaScriptManager::setup_session_scripts, this), gui_context());
192         setup_session_scripts ();
193 }
194
195 void
196 LuaScriptManager::session_going_away ()
197 {
198         ArdourWindow::session_going_away ();
199         _session_script_connection.disconnect ();
200         hide_all();
201 }
202
203 void
204 LuaScriptManager::setup_actions ()
205 {
206         LuaInstance *li = LuaInstance::instance();
207         for (int i = 0; i < MAX_LUA_ACTION_SCRIPTS; ++i) {
208                 std::string name;
209                 TreeModel::Row r = *_a_store->append ();
210                 r[_a_model.id] = i;
211                 r[_a_model.action] = string_compose (_("Action %1"), i + 1);
212                 if (li->lua_action_name (i, name)) {
213                         r[_a_model.name] = name;
214                         r[_a_model.enabled] = true;
215                 } else {
216                         r[_a_model.name] = _("Unset");
217                         r[_a_model.enabled] = false;
218                 }
219         }
220 }
221
222 void
223 LuaScriptManager::action_selection_changed ()
224 {
225         TreeModel::Row row = *(_a_view.get_selection()->get_selected());
226         if (row) {
227                 _a_set_button.set_sensitive (true);
228         }
229         else {
230                 _a_set_button.set_sensitive (false);
231         }
232
233         if (row && row[_a_model.enabled]) {
234                 _a_del_button.set_sensitive (true);
235                 _a_edit_button.set_sensitive (true);
236                 _a_call_button.set_sensitive (true);
237         } else {
238                 _a_del_button.set_sensitive (false);
239                 _a_edit_button.set_sensitive (false);
240                 _a_call_button.set_sensitive (false);
241         }
242 }
243
244 void
245 LuaScriptManager::set_action_btn_clicked ()
246 {
247         TreeModel::Row row = *(_a_view.get_selection()->get_selected());
248         assert (row);
249         LuaInstance *li = LuaInstance::instance();
250         li->interactive_add (LuaScriptInfo::EditorAction, row[_a_model.id]);
251 }
252
253 void
254 LuaScriptManager::del_action_btn_clicked ()
255 {
256         TreeModel::Row row = *(_a_view.get_selection()->get_selected());
257         assert (row);
258         LuaInstance *li = LuaInstance::instance();
259         if (!li->remove_lua_action (row[_a_model.id])) {
260                 // error
261         }
262 }
263
264 void
265 LuaScriptManager::call_action_btn_clicked ()
266 {
267         TreeModel::Row row = *(_a_view.get_selection()->get_selected());
268         assert (row && row[_a_model.enabled]);
269         LuaInstance *li = LuaInstance::instance();
270         li->call_action (row[_a_model.id]);
271 }
272
273 void
274 LuaScriptManager::edit_action_btn_clicked ()
275 {
276         TreeModel::Row row = *(_a_view.get_selection()->get_selected());
277         assert (row);
278         int id = row[_a_model.id];
279         LuaInstance *li = LuaInstance::instance();
280         std::string name, script;
281         LuaScriptParamList args;
282         if (!li->lua_action (id, name, script, args)) {
283                 return;
284         }
285         LuaWindow::instance()->edit_script (name, script);
286 }
287
288 void
289 LuaScriptManager::set_action_script_name (int i, const std::string& name)
290 {
291         typedef Gtk::TreeModel::Children type_children;
292         type_children children = _a_store->children();
293         for(type_children::iterator iter = children.begin(); iter != children.end(); ++iter) {
294                 Gtk::TreeModel::Row row = *iter;
295                 if (row[_a_model.id] == i) {
296                         if (name.empty()) {
297                                 row[_a_model.enabled] = false;
298                                 row[_a_model.name] = _("Unset");
299                         } else {
300                                 row[_a_model.enabled] = true;
301                                 row[_a_model.name] = name;
302                         }
303                         break;
304                 }
305         }
306         action_selection_changed ();
307 }
308
309
310 void
311 LuaScriptManager::setup_callbacks ()
312 {
313         LuaInstance *li = LuaInstance::instance();
314         std::vector<PBD::ID> ids = li->lua_slots();
315         for (std::vector<PBD::ID>::const_iterator i = ids.begin(); i != ids.end(); ++i) {
316                 std::string name;
317                 std::string script;
318                 ActionHook ah;
319                 LuaScriptParamList lsp;
320                 if (li->lua_slot (*i, name, script, ah, lsp)) {
321                         set_callback_script_name (*i, name, ah);
322                 }
323         }
324 }
325
326 void
327 LuaScriptManager::callback_selection_changed ()
328 {
329         TreeModel::Row row = *(_c_view.get_selection()->get_selected());
330         if (row) {
331                 _c_del_button.set_sensitive (true);
332         } else {
333                 _c_del_button.set_sensitive (false);
334         }
335 }
336
337 void
338 LuaScriptManager::add_callback_btn_clicked ()
339 {
340         LuaInstance *li = LuaInstance::instance();
341         li->interactive_add (LuaScriptInfo::EditorHook, -1);
342 }
343
344 void
345 LuaScriptManager::del_callback_btn_clicked ()
346 {
347         TreeModel::Row row = *(_c_view.get_selection()->get_selected());
348         assert (row);
349         LuaInstance *li = LuaInstance::instance();
350         if (!li->unregister_lua_slot (row[_c_model.id])) {
351                 // error
352         }
353 }
354
355 void
356 LuaScriptManager::set_callback_script_name (PBD::ID id, const std::string& name, const ActionHook& ah)
357 {
358         if (name.empty()) {
359                 typedef Gtk::TreeModel::Children type_children;
360                 type_children children = _c_store->children();
361                 for(type_children::iterator iter = children.begin(); iter != children.end(); ++iter) {
362                         Gtk::TreeModel::Row row = *iter;
363                         PBD::ID i = row[_c_model.id];
364                         if (i == id) {
365                                 _c_store->erase (iter);
366                                 break;
367                         }
368                 }
369         } else {
370                 TreeModel::Row r = *_c_store->append ();
371                 r[_c_model.id] = id;
372                 r[_c_model.name] = name;
373                 string sig;
374                 for (uint32_t i = 0; i < LuaSignal::LAST_SIGNAL; ++i) {
375                         if (ah[i]) {
376                                 if (!sig.empty()) sig += ", ";
377                                 sig += enum2str (LuaSignal::LuaSignal (i));
378                         }
379                 }
380                 r[_c_model.signals] = sig;
381         }
382         callback_selection_changed ();
383 }
384
385
386 void
387 LuaScriptManager::setup_session_scripts ()
388 {
389         _s_store->clear ();
390         if (!_session) {
391                 return;
392         }
393         std::vector<std::string> reg = _session->registered_lua_functions ();
394         for (std::vector<string>::const_iterator i = reg.begin(); i != reg.end(); ++i) {
395                 TreeModel::Row r = *_s_store->append ();
396                 r[_s_model.name] = *i;
397         }
398         session_script_selection_changed ();
399 }
400
401 void
402 LuaScriptManager::session_script_selection_changed ()
403 {
404         if (!_session) {
405                 _s_del_button.set_sensitive (false);
406                 _s_add_button.set_sensitive (false);
407                 return;
408         }
409         TreeModel::Row row = *(_s_view.get_selection()->get_selected());
410         if (row) {
411                 _s_del_button.set_sensitive (true);
412         } else {
413                 _s_del_button.set_sensitive (false);
414         }
415         _s_add_button.set_sensitive (true);
416 }
417
418 void
419 LuaScriptManager::add_sess_btn_clicked ()
420 {
421         if (!_session) {
422                 return;
423         }
424         LuaInstance *li = LuaInstance::instance();
425         li->interactive_add (LuaScriptInfo::Session, -1);
426 }
427
428 void
429 LuaScriptManager::del_sess_btn_clicked ()
430 {
431         assert (_session);
432         TreeModel::Row row = *(_s_view.get_selection()->get_selected());
433         const std::string& name = row[_s_model.name];
434         try {
435                 _session->unregister_lua_function (name);
436         } catch (luabridge::LuaException const& e) {
437                 string msg = string_compose (_("Session script '%1' removal failed: %2"), name, e.what ());
438                 MessageDialog am (msg);
439                 am.run ();
440         }
441 }