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