Fix DSP load sorting with inactive plugins
[ardour.git] / gtk2_ardour / lua_script_manager.cc
1 /*
2  * Copyright (C) 2016-2018 Robin Gareus <robin@gareus.org>
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 2 of the License, or
7  * (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 along
15  * with this program; if not, write to the Free Software Foundation, Inc.,
16  * 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::ScrolledWindow *scroller = manage (new Gtk::ScrolledWindow());
90         scroller->set_policy (Gtk::POLICY_NEVER, Gtk::POLICY_AUTOMATIC);
91         scroller->add (_a_view);
92         Gtk::VBox *vbox = manage (new VBox());
93         vbox->pack_start (*scroller, true, true);
94         vbox->pack_end (*edit_box, false, false);
95         vbox->pack_end (*f, false, false);
96         vbox->show_all ();
97
98         pages.pages ().push_back (Notebook_Helpers::TabElem (*vbox, "Action Scripts"));
99
100         /* action hooks page */
101
102         _c_store = ListStore::create (_c_model);
103         _c_view.set_model (_c_store);
104         _c_view.append_column (_("Name"), _c_model.name);
105         _c_view.append_column (_("Signal(s)"), _c_model.signals);
106         _c_view.get_column(0)->set_resizable (true);
107         _c_view.get_column(0)->set_expand (true);
108         _c_view.get_column(1)->set_resizable (true);
109         _c_view.get_column(1)->set_expand (true);
110         Gtk::CellRendererText* r = dynamic_cast<Gtk::CellRendererText*>(_c_view.get_column_cell_renderer (1));
111         r->property_ellipsize () = Pango::ELLIPSIZE_MIDDLE;
112
113         edit_box = manage (new Gtk::HBox);
114         edit_box->set_spacing(3);
115         edit_box->pack_start (_c_add_button, true, true);
116         edit_box->pack_start (_c_del_button, true, true);
117
118         _c_add_button.signal_clicked().connect (sigc::mem_fun(*this, &LuaScriptManager::add_callback_btn_clicked));
119         _c_del_button.signal_clicked().connect (sigc::mem_fun(*this, &LuaScriptManager::del_callback_btn_clicked));
120         _c_view.get_selection()->signal_changed().connect (sigc::mem_fun (*this, &LuaScriptManager::callback_selection_changed));
121
122         f = manage (new Frame (_("Description")));
123         doc = manage (new Label (
124                                 _("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.")
125                                 ));
126         doc->set_padding (5, 5);
127         doc->set_line_wrap();
128         f->add (*doc);
129
130         scroller = manage (new Gtk::ScrolledWindow());
131         scroller->set_policy (Gtk::POLICY_NEVER, Gtk::POLICY_AUTOMATIC);
132         scroller->add (_c_view);
133         vbox = manage (new VBox());
134         vbox->pack_start (*scroller, true, true);
135         vbox->pack_end (*edit_box, false, false);
136         vbox->pack_end (*f, false, false);
137         vbox->show_all ();
138
139         pages.pages ().push_back (Notebook_Helpers::TabElem (*vbox, "Action Hooks"));
140
141         /* session script page */
142
143         _s_store = ListStore::create (_s_model);
144         _s_view.set_model (_s_store);
145         _s_view.append_column (_("Name"), _s_model.name);
146         _s_view.get_column(0)->set_resizable (true);
147         _s_view.get_column(0)->set_expand (true);
148
149         edit_box = manage (new Gtk::HBox);
150         edit_box->set_spacing(3);
151         edit_box->pack_start (_s_add_button, true, true);
152         edit_box->pack_start (_s_del_button, true, true);
153
154         _s_add_button.signal_clicked().connect (sigc::mem_fun(*this, &LuaScriptManager::add_sess_btn_clicked));
155         _s_del_button.signal_clicked().connect (sigc::mem_fun(*this, &LuaScriptManager::del_sess_btn_clicked));
156         _s_view.get_selection()->signal_changed().connect (sigc::mem_fun (*this, &LuaScriptManager::session_script_selection_changed));
157
158         f = manage (new Frame (_("Description")));
159         doc = manage (new Label (
160                                 _("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.")
161                                 ));
162         doc->set_padding (5, 5);
163         doc->set_line_wrap();
164         f->add (*doc);
165
166         scroller = manage (new Gtk::ScrolledWindow());
167         scroller->set_policy (Gtk::POLICY_NEVER, Gtk::POLICY_AUTOMATIC);
168         scroller->add (_s_view);
169         vbox = manage (new VBox());
170         vbox->pack_start (*scroller, true, true);
171         vbox->pack_end (*edit_box, false, false);
172         vbox->pack_end (*f, false, false);
173         vbox->show_all ();
174
175         pages.pages ().push_back (Notebook_Helpers::TabElem (*vbox, "Session Scripts"));
176
177         /* global layout */
178
179         add (pages);
180         pages.show();
181
182         setup_actions ();
183         setup_callbacks ();
184         setup_session_scripts ();
185
186         action_selection_changed ();
187         callback_selection_changed ();
188         session_script_selection_changed ();
189 }
190
191 void
192 LuaScriptManager::set_session (ARDOUR::Session *s)
193 {
194         ArdourWindow::set_session (s);
195         setup_session_scripts ();
196         if (!_session) {
197                 return;
198         }
199
200         _session->LuaScriptsChanged.connect (_session_script_connection,  invalidator (*this), boost::bind (&LuaScriptManager::setup_session_scripts, this), gui_context());
201         setup_session_scripts ();
202 }
203
204 void
205 LuaScriptManager::session_going_away ()
206 {
207         ArdourWindow::session_going_away ();
208         _session_script_connection.disconnect ();
209         hide_all();
210 }
211
212 void
213 LuaScriptManager::setup_actions ()
214 {
215         LuaInstance *li = LuaInstance::instance();
216         for (int i = 0; i < MAX_LUA_ACTION_SCRIPTS; ++i) {
217                 std::string name;
218                 TreeModel::Row r = *_a_store->append ();
219                 r[_a_model.id] = i;
220                 r[_a_model.action] = string_compose (_("Action %1"), i + 1);
221                 if (li->lua_action_name (i, name)) {
222                         r[_a_model.name] = name;
223                         r[_a_model.enabled] = true;
224                 } else {
225                         r[_a_model.name] = _("Unset");
226                         r[_a_model.enabled] = false;
227                 }
228         }
229 }
230
231 void
232 LuaScriptManager::action_selection_changed ()
233 {
234         TreeModel::Row row = *(_a_view.get_selection()->get_selected());
235         if (row) {
236                 _a_set_button.set_sensitive (true);
237         }
238         else {
239                 _a_set_button.set_sensitive (false);
240         }
241
242         if (row && row[_a_model.enabled]) {
243                 _a_del_button.set_sensitive (true);
244                 _a_edit_button.set_sensitive (true);
245                 _a_call_button.set_sensitive (true);
246         } else {
247                 _a_del_button.set_sensitive (false);
248                 _a_edit_button.set_sensitive (false);
249                 _a_call_button.set_sensitive (false);
250         }
251 }
252
253 void
254 LuaScriptManager::set_action_btn_clicked ()
255 {
256         TreeModel::Row row = *(_a_view.get_selection()->get_selected());
257         assert (row);
258         LuaInstance *li = LuaInstance::instance();
259         li->interactive_add (LuaScriptInfo::EditorAction, row[_a_model.id]);
260 }
261
262 void
263 LuaScriptManager::del_action_btn_clicked ()
264 {
265         TreeModel::Row row = *(_a_view.get_selection()->get_selected());
266         assert (row);
267         LuaInstance *li = LuaInstance::instance();
268         if (!li->remove_lua_action (row[_a_model.id])) {
269                 // error
270         }
271 }
272
273 void
274 LuaScriptManager::call_action_btn_clicked ()
275 {
276         TreeModel::Row row = *(_a_view.get_selection()->get_selected());
277         assert (row && row[_a_model.enabled]);
278         LuaInstance *li = LuaInstance::instance();
279         li->call_action (row[_a_model.id]);
280 }
281
282 void
283 LuaScriptManager::edit_action_btn_clicked ()
284 {
285         TreeModel::Row row = *(_a_view.get_selection()->get_selected());
286         assert (row);
287         int id = row[_a_model.id];
288         LuaInstance *li = LuaInstance::instance();
289         std::string name, script;
290         LuaScriptParamList args;
291         if (!li->lua_action (id, name, script, args)) {
292                 return;
293         }
294         LuaWindow::instance()->edit_script (name, script);
295 }
296
297 void
298 LuaScriptManager::set_action_script_name (int i, const std::string& name)
299 {
300         typedef Gtk::TreeModel::Children type_children;
301         type_children children = _a_store->children();
302         for(type_children::iterator iter = children.begin(); iter != children.end(); ++iter) {
303                 Gtk::TreeModel::Row row = *iter;
304                 if (row[_a_model.id] == i) {
305                         if (name.empty()) {
306                                 row[_a_model.enabled] = false;
307                                 row[_a_model.name] = _("Unset");
308                         } else {
309                                 row[_a_model.enabled] = true;
310                                 row[_a_model.name] = name;
311                         }
312                         break;
313                 }
314         }
315         action_selection_changed ();
316 }
317
318
319 void
320 LuaScriptManager::setup_callbacks ()
321 {
322         LuaInstance *li = LuaInstance::instance();
323         std::vector<PBD::ID> ids = li->lua_slots();
324         for (std::vector<PBD::ID>::const_iterator i = ids.begin(); i != ids.end(); ++i) {
325                 std::string name;
326                 std::string script;
327                 ActionHook ah;
328                 LuaScriptParamList lsp;
329                 if (li->lua_slot (*i, name, script, ah, lsp)) {
330                         set_callback_script_name (*i, name, ah);
331                 }
332         }
333 }
334
335 void
336 LuaScriptManager::callback_selection_changed ()
337 {
338         TreeModel::Row row = *(_c_view.get_selection()->get_selected());
339         if (row) {
340                 _c_del_button.set_sensitive (true);
341         } else {
342                 _c_del_button.set_sensitive (false);
343         }
344 }
345
346 void
347 LuaScriptManager::add_callback_btn_clicked ()
348 {
349         LuaInstance *li = LuaInstance::instance();
350         li->interactive_add (LuaScriptInfo::EditorHook, -1);
351 }
352
353 void
354 LuaScriptManager::del_callback_btn_clicked ()
355 {
356         TreeModel::Row row = *(_c_view.get_selection()->get_selected());
357         assert (row);
358         LuaInstance *li = LuaInstance::instance();
359         if (!li->unregister_lua_slot (row[_c_model.id])) {
360                 // error
361         }
362 }
363
364 void
365 LuaScriptManager::set_callback_script_name (PBD::ID id, const std::string& name, const ActionHook& ah)
366 {
367         if (name.empty()) {
368                 typedef Gtk::TreeModel::Children type_children;
369                 type_children children = _c_store->children();
370                 for(type_children::iterator iter = children.begin(); iter != children.end(); ++iter) {
371                         Gtk::TreeModel::Row row = *iter;
372                         PBD::ID i = row[_c_model.id];
373                         if (i == id) {
374                                 _c_store->erase (iter);
375                                 break;
376                         }
377                 }
378         } else {
379                 TreeModel::Row r = *_c_store->append ();
380                 r[_c_model.id] = id;
381                 r[_c_model.name] = name;
382                 string sig;
383                 for (uint32_t i = 0; i < LuaSignal::LAST_SIGNAL; ++i) {
384                         if (ah[i]) {
385                                 if (!sig.empty()) sig += ", ";
386                                 sig += enum2str (LuaSignal::LuaSignal (i));
387                         }
388                 }
389                 r[_c_model.signals] = sig;
390         }
391         callback_selection_changed ();
392 }
393
394
395 void
396 LuaScriptManager::setup_session_scripts ()
397 {
398         _s_store->clear ();
399         if (!_session) {
400                 return;
401         }
402         std::vector<std::string> reg = _session->registered_lua_functions ();
403         for (std::vector<string>::const_iterator i = reg.begin(); i != reg.end(); ++i) {
404                 TreeModel::Row r = *_s_store->append ();
405                 r[_s_model.name] = *i;
406         }
407         session_script_selection_changed ();
408 }
409
410 void
411 LuaScriptManager::session_script_selection_changed ()
412 {
413         if (!_session) {
414                 _s_del_button.set_sensitive (false);
415                 _s_add_button.set_sensitive (false);
416                 return;
417         }
418         TreeModel::Row row = *(_s_view.get_selection()->get_selected());
419         if (row) {
420                 _s_del_button.set_sensitive (true);
421         } else {
422                 _s_del_button.set_sensitive (false);
423         }
424         _s_add_button.set_sensitive (true);
425 }
426
427 void
428 LuaScriptManager::add_sess_btn_clicked ()
429 {
430         if (!_session) {
431                 return;
432         }
433         LuaInstance *li = LuaInstance::instance();
434         li->interactive_add (LuaScriptInfo::Session, -1);
435 }
436
437 void
438 LuaScriptManager::del_sess_btn_clicked ()
439 {
440         assert (_session);
441         TreeModel::Row row = *(_s_view.get_selection()->get_selected());
442         const std::string& name = row[_s_model.name];
443         try {
444                 _session->unregister_lua_function (name);
445         } catch (luabridge::LuaException const& e) {
446                 string msg = string_compose (_("Session script '%1' removal failed: %2"), name, e.what ());
447                 MessageDialog am (msg);
448                 am.run ();
449         }
450 }