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