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