2 Copyright (C) 2016 Robin Gareus <robin@gareus.org>
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.
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.
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., 675 Mass Ave, Cambridge, MA 02139, USA.
20 #ifdef PLATFORM_WINDOWS
21 #define random() rand()
25 #include "gtk2ardour-config.h"
28 #include "pbd/gstdio_compat.h"
29 #include <glibmm/fileutils.h>
30 #include <gtkmm/messagedialog.h>
32 #include "pbd/basename.h"
33 #include "pbd/file_utils.h"
36 #include "gtkmm2ext/gtk_ui.h"
37 #include "gtkmm2ext/utils.h"
38 #include "gtkmm2ext/window_title.h"
40 #include "widgets/pane.h"
41 #include "widgets/tooltips.h"
43 #include "ardour/filesystem_paths.h"
44 #include "ardour/luabindings.h"
45 #include "LuaBridge/LuaBridge.h"
47 #include "ardour_ui.h"
48 #include "gui_thread.h"
49 #include "luainstance.h"
50 #include "luawindow.h"
51 #include "public_editor.h"
53 #include "utils_videotl.h"
57 using namespace ARDOUR;
61 using namespace Gtkmm2ext;
65 inline LuaWindow::BufferFlags operator| (const LuaWindow::BufferFlags& a, const LuaWindow::BufferFlags& b) {
66 return static_cast<LuaWindow::BufferFlags> (static_cast <int>(a) | static_cast<int> (b));
69 inline LuaWindow::BufferFlags operator|= (LuaWindow::BufferFlags& a, const LuaWindow::BufferFlags& b) {
70 return a = static_cast<LuaWindow::BufferFlags> (static_cast <int>(a) | static_cast<int> (b));
73 inline LuaWindow::BufferFlags operator&= (LuaWindow::BufferFlags& a, const LuaWindow::BufferFlags& b) {
74 return a = static_cast<LuaWindow::BufferFlags> (static_cast <int>(a) & static_cast<int> (b));
77 LuaWindow* LuaWindow::_instance = 0;
80 LuaWindow::instance ()
83 _instance = new LuaWindow;
89 LuaWindow::LuaWindow ()
90 : Window (Gtk::WINDOW_TOPLEVEL)
91 , VisibilityTracker (*((Gtk::Window*) this))
98 , _btn_clear (_("Clear Output"))
99 , _btn_open (_("Import"))
100 , _btn_save (_("Save"))
101 , _btn_delete (_("Delete"))
102 , _btn_revert (_("Revert"))
109 set_wmclass (X_("ardour_mixer"), PROGRAM_NAME);
111 script_select.disable_scrolling ();
113 set_border_width (0);
115 outtext.set_editable (false);
116 outtext.set_wrap_mode (Gtk::WRAP_WORD);
117 outtext.set_cursor_visible (false);
119 signal_delete_event().connect (sigc::mem_fun (*this, &LuaWindow::hide_window));
120 signal_configure_event().connect (sigc::mem_fun (*ARDOUR_UI::instance(), &ARDOUR_UI::configure_handler));
122 _btn_run.signal_clicked.connect (sigc::mem_fun(*this, &LuaWindow::run_script));
123 _btn_clear.signal_clicked.connect (sigc::mem_fun(*this, &LuaWindow::clear_output));
124 _btn_open.signal_clicked.connect (sigc::mem_fun(*this, &LuaWindow::import_script));
125 _btn_save.signal_clicked.connect (sigc::mem_fun(*this, &LuaWindow::save_script));
126 _btn_delete.signal_clicked.connect (sigc::mem_fun(*this, &LuaWindow::delete_script));
127 _btn_revert.signal_clicked.connect (sigc::mem_fun(*this, &LuaWindow::revert_script));
129 _btn_open.set_sensitive (false); // TODO
130 _btn_save.set_sensitive (false);
131 _btn_delete.set_sensitive (false);
132 _btn_revert.set_sensitive (false);
136 Gtk::ScrolledWindow *scrollin = manage (new Gtk::ScrolledWindow);
137 scrollin->set_policy (Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC);
138 scrollin->add (entry);
139 scrollout.set_policy (Gtk::POLICY_NEVER, Gtk::POLICY_ALWAYS);
140 scrollout.add (outtext);
142 entry.set_name ("ArdourLuaEntry");
143 outtext.set_name ("ArdourLuaEntry");
145 Gtk::HBox *hbox = manage (new HBox());
147 hbox->pack_start (_btn_run, false, false, 2);
148 hbox->pack_start (_btn_clear, false, false, 2);
149 hbox->pack_start (_btn_open, false, false, 2);
150 hbox->pack_start (_btn_save, false, false, 2);
151 hbox->pack_start (_btn_delete, false, false, 2);
152 hbox->pack_start (_btn_revert, false, false, 2);
153 hbox->pack_start (script_select, false, false, 2);
155 Gtk::VBox *vbox = manage (new VBox());
156 vbox->pack_start (*scrollin, true, true, 0);
157 vbox->pack_start (*hbox, false, false, 2);
159 ArdourWidgets::VPane *vpane = manage (new ArdourWidgets::VPane ());
161 vpane->add (scrollout);
162 vpane->set_divider (0, 0.75);
166 set_size_request (640, 480); // XXX
167 ArdourWidgets::set_tooltip (script_select, _("Select Editor Buffer"));
170 LuaScripting::instance().scripts_changed.connect (*this, invalidator (*this), boost::bind (&LuaWindow::refresh_scriptlist, this), gui_context());
172 Glib::RefPtr<Gtk::TextBuffer> tb (entry.get_buffer());
173 _script_changed_connection = tb->signal_changed().connect (sigc::mem_fun(*this, &LuaWindow::script_changed));
176 LuaWindow::~LuaWindow ()
182 LuaWindow::show_window ()
189 LuaWindow::hide_window (GdkEventAny *ev)
191 if (!_visible) return 0;
193 return ARDOUR_UI_UTILS::just_hide_it (ev, static_cast<Gtk::Window *>(this));
196 void LuaWindow::reinit_lua ()
198 ENSURE_GUI_THREAD (*this, &LuaWindow::session_going_away);
200 lua = new LuaState();
201 lua->Print.connect (sigc::mem_fun (*this, &LuaWindow::append_text));
202 lua->sandbox (false);
204 lua_State* L = lua->getState();
205 LuaInstance::register_classes (L);
206 luabridge::push <PublicEditor *> (L, &PublicEditor::instance());
207 lua_setglobal (L, "Editor");
210 void LuaWindow::set_session (Session* s)
212 SessionHandlePtr::set_session (s);
218 _session->DirtyChanged.connect (_session_connections, invalidator (*this), boost::bind (&LuaWindow::update_title, this), gui_context());
220 lua_State* L = lua->getState();
221 LuaBindings::set_session (L, _session);
225 LuaWindow::session_going_away ()
227 ENSURE_GUI_THREAD (*this, &LuaWindow::session_going_away);
228 reinit_lua (); // drop state (all variables, session references)
230 SessionHandlePtr::session_going_away ();
234 lua_State* L = lua->getState();
235 LuaBindings::set_session (L, _session);
239 LuaWindow::update_title ()
244 if (_session->snap_name() != _session->name()) {
245 n = _session->snap_name ();
247 n = _session->name ();
250 if (_session->dirty ()) {
254 WindowTitle title (n);
255 title += S_("Window|Lua");
256 title += Glib::get_application_name ();
257 set_title (title.get_string());
260 WindowTitle title (S_("Window|Lua"));
261 title += Glib::get_application_name ();
262 set_title (title.get_string());
267 LuaWindow::scroll_to_bottom ()
269 Gtk::Adjustment *adj;
270 adj = scrollout.get_vadjustment();
271 adj->set_value (MAX(0,(adj->get_upper() - adj->get_page_size())));
275 LuaWindow::run_script ()
277 Glib::RefPtr<Gtk::TextBuffer> tb (entry.get_buffer());
278 std::string script = tb->get_text();
279 const std::string& bytecode = LuaScripting::get_factory_bytecode (script);
280 if (bytecode.empty()) {
281 // plain script or faulty script -- run directly
283 lua->do_command ("function ardour () end");
284 if (0 == lua->do_command (script)) {
285 append_text ("> OK");
287 } catch (luabridge::LuaException const& e) {
288 append_text (string_compose (_("LuaException: %1"), e.what()));
289 } catch (Glib::Exception const& e) {
290 append_text (string_compose (_("Glib Exception: %1"), e.what()));
291 } catch (std::exception const& e) {
292 append_text (string_compose (_("C++ Exception: %1"), e.what()));
294 append_text (string_compose (_("C++ Exception: %1"), "..."));
297 // script with factory method
299 lua_State* L = lua->getState();
300 lua->do_command ("function ardour () end");
302 LuaScriptParamList args = LuaScriptParams::script_params (script, "action_param", false);
303 luabridge::LuaRef tbl_arg (luabridge::newTable(L));
304 LuaScriptParams::params_to_ref (&tbl_arg, args);
305 lua->do_command (script); // register "factory"
306 luabridge::LuaRef lua_factory = luabridge::getGlobal (L, "factory");
307 if (lua_factory.isFunction()) {
308 lua_factory(tbl_arg)();
310 lua->do_command ("factory = nil;");
311 } catch (luabridge::LuaException const& e) {
312 append_text (string_compose (_("LuaException: %1"), e.what()));
313 } catch (Glib::Exception const& e) {
314 append_text (string_compose (_("Glib Exception: %1"), e.what()));
315 } catch (std::exception const& e) {
316 append_text (string_compose (_("C++ Exception: %1"), e.what()));
318 append_text (string_compose (_("C++ Exception: %1"), "..."));
324 LuaWindow::append_text (std::string s)
326 Glib::RefPtr<Gtk::TextBuffer> tb (outtext.get_buffer());
327 tb->insert (tb->end(), s + "\n");
329 Gtkmm2ext::UI::instance()->flush_pending (0.05);
333 LuaWindow::clear_output ()
335 Glib::RefPtr<Gtk::TextBuffer> tb (outtext.get_buffer());
340 LuaWindow::edit_script (const std::string& name, const std::string& script)
342 ScriptBuffer* sb = new LuaWindow::ScriptBuffer (name);
344 script_buffers.push_back (ScriptBufferPtr (sb));
345 script_selection_changed (script_buffers.back ());
346 refresh_scriptlist ();
351 LuaWindow::new_script ()
354 snprintf (buf, sizeof (buf), "#%d", count_scratch_buffers () + 1);
355 script_buffers.push_back (ScriptBufferPtr (new LuaWindow::ScriptBuffer (buf)));
356 script_selection_changed (script_buffers.back ());
357 refresh_scriptlist ();
361 LuaWindow::delete_script ()
363 assert ((_current_buffer->flags & Buffer_Scratch) || !(_current_buffer->flags & Buffer_ReadOnly));
364 bool refresh = false;
366 if (_current_buffer->flags & Buffer_HasFile) {
367 if (0 == ::g_unlink (_current_buffer->path.c_str())) {
368 append_text (X_("> ") + string_compose (_("Deleted %1"), _current_buffer->path));
371 append_text (X_("> ") + string_compose (_("Failed to delete %1"), _current_buffer->path));
374 for (ScriptBufferList::iterator i = script_buffers.begin (); i != script_buffers.end (); ++i) {
375 if ((*i) == _current_buffer) {
376 script_buffers.erase (i);
381 for (ScriptBufferList::const_iterator i = script_buffers.begin (); i != script_buffers.end (); ++i) {
382 if ((*i)->flags & Buffer_Scratch) {
383 script_selection_changed (*i);
391 LuaScripting::instance ().refresh (true);
396 LuaWindow::revert_script ()
398 _current_buffer->flags &= BufferFlags(~Buffer_Valid);
399 script_selection_changed (_current_buffer, true);
403 LuaWindow::import_script ()
405 // TODO: dialog to select file or enter URL
406 // TODO convert a few URL (eg. pastebin) to raw.
408 char *url = "http://pastebin.com/raw/3UMkZ6nV";
409 char *rv = ArdourCurl::http_get (url, 0);
412 Glib::RefPtr<Gtk::TextBuffer> tb (entry.get_buffer());
414 _current_buffer->flags &= BufferFlags(~Buffer_Dirty);
422 LuaWindow::save_script ()
424 Glib::RefPtr<Gtk::TextBuffer> tb (entry.get_buffer());
425 std::string script = tb->get_text();
426 std::string msg = "Unknown error";
429 LuaScriptInfoPtr lsi = LuaScripting::script_info (script);
430 ScriptBuffer & sb (*_current_buffer);
432 assert (sb.flags & Buffer_Dirty);
434 // 1) check if it has a valid header and factory
435 const std::string& bytecode = LuaScripting::get_factory_bytecode (script);
436 if (bytecode.empty()) {
437 msg = _("Missing script header.\nThe script requires an '{ardour}' info table and a 'factory' function.");
441 if (!LuaScripting::try_compile (script, LuaScriptParams::script_params (script, "action_param", false))) {
442 msg = _("Script fails to compile.");
446 // 2) check script name & type
447 lsi = LuaScripting::script_info (script);
449 msg = _("Invalid or missing script-name or script-type.");
453 if (lsi->type != LuaScriptInfo::Snippet && lsi->type != LuaScriptInfo::EditorAction) {
454 msg = _("Invalid script-type.\nValid types are 'EditorAction' and 'Snippet'.");
458 // 3) if there's already a writable file,...
459 if ((sb.flags & Buffer_HasFile) && !(sb.flags & Buffer_ReadOnly)) {
461 Glib::file_set_contents (sb.path, script);
463 sb.flags &= BufferFlags(~Buffer_Dirty);
464 update_gui_state (); // XXX here?
465 append_text (X_("> ") + string_compose (_("Saved as %1"), sb.path));
467 } catch (Glib::FileError e) {
468 msg = string_compose (_("Error saving file: %1"), e.what());
473 // 4) check if the name is unique for the given type; locally at least
474 if (true /*sb.flags & Buffer_HasFile*/) {
475 LuaScriptList& lsl (LuaScripting::instance ().scripts (lsi->type));
476 for (LuaScriptList::const_iterator s = lsl.begin(); s != lsl.end(); ++s) {
477 if ((*s)->name == lsi->name) {
478 msg = string_compose (_("Script with given name '%1' already exists.\nUse a different name in the descriptor."), lsi->name);
484 // 5) construct filename -- TODO ask user for name, ask to replace file.
488 struct tm * timeinfo = localtime (&t);
489 strftime (buf, sizeof(buf), "%s%d", timeinfo);
490 sprintf (buf, "%s%ld", buf, random ()); // is this valid?
492 std::string fn = md5.digestString (buf);
495 case LuaScriptInfo::EditorAction:
498 case LuaScriptInfo::Snippet:
504 path = Glib::build_filename (LuaScripting::user_script_dir (), fn.substr(0, 11) + ".lua");
505 } while (Glib::file_test (path, Glib::FILE_TEST_EXISTS));
508 Glib::file_set_contents (path, script);
511 sb.flags |= Buffer_HasFile;
512 sb.flags &= BufferFlags(~Buffer_Dirty);
513 sb.flags &= BufferFlags(~Buffer_ReadOnly);
514 update_gui_state (); // XXX here? .refresh (true) may trigger this, too
515 LuaScripting::instance().refresh (true);
516 append_text (X_("> ") + string_compose (_("Saved as %1"), path));
518 } catch (Glib::FileError e) {
519 msg = string_compose (_("Error saving file: %1"), e.what());
524 MessageDialog am (msg);
529 LuaWindow::setup_buffers ()
531 if (script_buffers.size() > 0) {
534 script_buffers.push_back (ScriptBufferPtr (new LuaWindow::ScriptBuffer("#1")));
535 _current_buffer = script_buffers.front();
537 Glib::RefPtr<Gtk::TextBuffer> tb (entry.get_buffer());
538 tb->set_text (_current_buffer->script);
540 refresh_scriptlist ();
545 LuaWindow::count_scratch_buffers () const
548 for (ScriptBufferList::const_iterator i = script_buffers.begin (); i != script_buffers.end (); ++i) {
549 if ((*i)->flags & Buffer_Scratch) {
557 LuaWindow::refresh_scriptlist ()
559 for (ScriptBufferList::iterator i = script_buffers.begin (); i != script_buffers.end ();) {
560 if ((*i)->flags & Buffer_Scratch) {
564 i = script_buffers.erase (i);
566 LuaScriptList& lsa (LuaScripting::instance ().scripts (LuaScriptInfo::EditorAction));
567 for (LuaScriptList::const_iterator s = lsa.begin(); s != lsa.end(); ++s) {
568 script_buffers.push_back (ScriptBufferPtr (new LuaWindow::ScriptBuffer(*s)));
571 LuaScriptList& lss (LuaScripting::instance ().scripts (LuaScriptInfo::Snippet));
572 for (LuaScriptList::const_iterator s = lss.begin(); s != lss.end(); ++s) {
573 script_buffers.push_back (ScriptBufferPtr (new LuaWindow::ScriptBuffer(*s)));
579 LuaWindow::rebuild_menu ()
581 using namespace Menu_Helpers;
583 _menu_scratch = manage (new Menu);
584 _menu_snippet = manage (new Menu);
585 _menu_actions = manage (new Menu);
587 MenuList& items_scratch (_menu_scratch->items());
588 MenuList& items_snippet (_menu_snippet->items());
589 MenuList& items_actions (_menu_actions->items());
592 Menu_Helpers::MenuElem elem = Gtk::Menu_Helpers::MenuElem(_("New"),
593 sigc::mem_fun(*this, &LuaWindow::new_script));
594 items_scratch.push_back(elem);
597 items_scratch.push_back(SeparatorElem());
599 for (ScriptBufferList::const_iterator i = script_buffers.begin (); i != script_buffers.end (); ++i) {
601 if ((*i)->flags & Buffer_ReadOnly) {
602 name = "[R] " + (*i)->name;
606 Menu_Helpers::MenuElem elem = Gtk::Menu_Helpers::MenuElem(name,
607 sigc::bind(sigc::mem_fun(*this, &LuaWindow::script_selection_changed), (*i), false));
609 if ((*i)->flags & Buffer_Scratch) {
610 items_scratch.push_back(elem);
612 else if ((*i)->type == LuaScriptInfo::EditorAction) {
613 items_actions.push_back(elem);
615 else if ((*i)->type == LuaScriptInfo::Snippet) {
616 items_snippet.push_back(elem);
620 script_select.clear_items ();
621 script_select.AddMenuElem (Menu_Helpers::MenuElem ("Scratch", *_menu_scratch));
622 script_select.AddMenuElem (Menu_Helpers::MenuElem ("Snippets", *_menu_snippet));
623 script_select.AddMenuElem (Menu_Helpers::MenuElem ("Actions", *_menu_actions));
627 LuaWindow::script_selection_changed (ScriptBufferPtr n, bool force)
629 if (n == _current_buffer && !force) {
633 Glib::RefPtr<Gtk::TextBuffer> tb (entry.get_buffer());
635 if (_current_buffer->flags & Buffer_Valid) {
636 _current_buffer->script = tb->get_text();
639 if (!(n->flags & Buffer_Valid)) {
641 append_text ("! Failed to load buffer.");
645 if (n->flags & Buffer_Valid) {
647 _script_changed_connection.block ();
648 tb->set_text (n->script);
649 _script_changed_connection.unblock ();
651 append_text ("! Failed to switch buffer.");
657 LuaWindow::update_gui_state ()
659 const ScriptBuffer & sb (*_current_buffer);
661 if (sb.flags & Buffer_Scratch) {
662 name = string_compose (_("Scratch Buffer %1"), sb.name);
663 } else if (sb.type == LuaScriptInfo::EditorAction) {
664 name = string_compose (_("Action: '%1'"), sb.name);
665 } else if (sb.type == LuaScriptInfo::Snippet) {
666 name = string_compose (_("Snippet: %1"), sb.name);
668 cerr << "Invalid Script type\n";
672 if (sb.flags & Buffer_Dirty) {
675 script_select.set_text(name);
677 if (sb.flags & Buffer_ReadOnly) {
678 _btn_save.set_text (_("Save as"));
680 _btn_save.set_text (_("Save"));
682 _btn_save.set_sensitive (sb.flags & Buffer_Dirty);
683 _btn_delete.set_sensitive (sb.flags & Buffer_Scratch || ((sb.flags & (Buffer_ReadOnly | Buffer_HasFile)) == Buffer_HasFile));
684 _btn_revert.set_sensitive ((sb.flags & Buffer_Dirty) && (sb.flags & Buffer_HasFile));
688 LuaWindow::script_changed () {
689 if (_current_buffer->flags & Buffer_Dirty) {
692 _current_buffer->flags |= Buffer_Dirty;
696 LuaWindow::ScriptBuffer::ScriptBuffer (const std::string& n)
698 , flags (Buffer_Scratch | Buffer_Valid)
701 "---- this header is (only) required to save the script\n"
702 "-- ardour { [\"type\"] = \"Snippet\", name = \"\" }\n"
703 "-- function factory () return function () -- -- end end\n";
706 LuaWindow::ScriptBuffer::ScriptBuffer (LuaScriptInfoPtr p)
709 , flags (Buffer_HasFile)
712 if (!PBD::exists_and_writable (path)) {
713 flags |= Buffer_ReadOnly;
715 if (path.find (user_config_directory ()) != 0) {
716 // mark non-user scripts as read-only
717 flags |= Buffer_ReadOnly;
722 LuaWindow::ScriptBuffer::ScriptBuffer (const ScriptBuffer& other)
723 : script (other.script)
726 , flags (other.flags)
732 LuaWindow::ScriptBuffer::~ScriptBuffer ()
737 LuaWindow::ScriptBuffer::load ()
739 assert (!(flags & Buffer_Valid));
740 if (!(flags & Buffer_HasFile)) return false;
742 script = Glib::file_get_contents (path);
743 flags |= Buffer_Valid;
744 flags &= BufferFlags(~Buffer_Dirty);
745 } catch (Glib::FileError e) {