a-fluidsynth: implement LV2_BANKPATCH__notify
[ardour.git] / gtk2_ardour / luawindow.cc
index aed565f7ce4e430f63eb086b8d9440cef09b4747..89a1d387aa490cfdb1ea058c80a803842d8d1cfc 100644 (file)
@@ -25,6 +25,7 @@
 #include "gtk2ardour-config.h"
 #endif
 
+#include "pbd/gstdio_compat.h"
 #include <glibmm/fileutils.h>
 #include <gtkmm/messagedialog.h>
 
 #include "gtkmm2ext/utils.h"
 #include "gtkmm2ext/window_title.h"
 
+#include "widgets/pane.h"
+#include "widgets/tooltips.h"
+
+#include "ardour/filesystem_paths.h"
 #include "ardour/luabindings.h"
 #include "LuaBridge/LuaBridge.h"
 
 #include "luainstance.h"
 #include "luawindow.h"
 #include "public_editor.h"
-#include "tooltips.h"
 #include "utils.h"
 #include "utils_videotl.h"
 
-#include "i18n.h"
+#include "pbd/i18n.h"
 
 using namespace ARDOUR;
-using namespace ARDOUR_UI_UTILS;
 using namespace PBD;
 using namespace Gtk;
 using namespace Glib;
@@ -86,19 +89,22 @@ LuaWindow::instance ()
 LuaWindow::LuaWindow ()
        : Window (Gtk::WINDOW_TOPLEVEL)
        , VisibilityTracker (*((Gtk::Window*) this))
+       , lua (0)
        , _visible (false)
        , _menu_scratch (0)
        , _menu_snippet (0)
        , _menu_actions (0)
        , _btn_run (_("Run"))
-       , _btn_clear (_("Clear Outtput"))
+       , _btn_clear (_("Clear Output"))
        , _btn_open (_("Import"))
        , _btn_save (_("Save"))
        , _btn_delete (_("Delete"))
+       , _btn_revert (_("Revert"))
        , _current_buffer ()
 {
        set_name ("Lua");
 
+       reinit_lua ();
        update_title ();
        set_wmclass (X_("ardour_mixer"), PROGRAM_NAME);
 
@@ -118,10 +124,12 @@ LuaWindow::LuaWindow ()
        _btn_open.signal_clicked.connect (sigc::mem_fun(*this, &LuaWindow::import_script));
        _btn_save.signal_clicked.connect (sigc::mem_fun(*this, &LuaWindow::save_script));
        _btn_delete.signal_clicked.connect (sigc::mem_fun(*this, &LuaWindow::delete_script));
+       _btn_revert.signal_clicked.connect (sigc::mem_fun(*this, &LuaWindow::revert_script));
 
        _btn_open.set_sensitive (false); // TODO
        _btn_save.set_sensitive (false);
        _btn_delete.set_sensitive (false);
+       _btn_revert.set_sensitive (false);
 
        // layout
 
@@ -131,6 +139,9 @@ LuaWindow::LuaWindow ()
        scrollout.set_policy (Gtk::POLICY_NEVER, Gtk::POLICY_ALWAYS);
        scrollout.add (outtext);
 
+       entry.set_name ("ArdourLuaEntry");
+       outtext.set_name ("ArdourLuaEntry");
+
        Gtk::HBox *hbox = manage (new HBox());
 
        hbox->pack_start (_btn_run, false, false, 2);
@@ -138,28 +149,22 @@ LuaWindow::LuaWindow ()
        hbox->pack_start (_btn_open, false, false, 2);
        hbox->pack_start (_btn_save, false, false, 2);
        hbox->pack_start (_btn_delete, false, false, 2);
+       hbox->pack_start (_btn_revert, false, false, 2);
        hbox->pack_start (script_select, false, false, 2);
 
        Gtk::VBox *vbox = manage (new VBox());
        vbox->pack_start (*scrollin, true, true, 0);
        vbox->pack_start (*hbox, false, false, 2);
 
-       Gtk::VPaned *vpane = manage (new Gtk::VPaned ());
-       vpane->pack1 (*vbox, true, false);
-       vpane->pack2 (scrollout, false, true);
+       ArdourWidgets::VPane *vpane = manage (new ArdourWidgets::VPane ());
+       vpane->add (*vbox);
+       vpane->add (scrollout);
+       vpane->set_divider (0, 0.75);
 
        vpane->show_all ();
        add (*vpane);
        set_size_request (640, 480); // XXX
-
-       lua.Print.connect (sigc::mem_fun (*this, &LuaWindow::append_text));
-
-       lua_State* L = lua.getState();
-       LuaInstance::register_classes (L);
-       luabridge::push <PublicEditor *> (L, &PublicEditor::instance());
-       lua_setglobal (L, "Editor");
-
-       ARDOUR_UI_UTILS::set_tooltip (script_select, _("Select Editor Buffer"));
+       ArdourWidgets::set_tooltip (script_select, _("Select Editor Buffer"));
 
        setup_buffers ();
        LuaScripting::instance().scripts_changed.connect (*this, invalidator (*this), boost::bind (&LuaWindow::refresh_scriptlist, this), gui_context());
@@ -170,6 +175,7 @@ LuaWindow::LuaWindow ()
 
 LuaWindow::~LuaWindow ()
 {
+       delete lua;
 }
 
 void
@@ -184,7 +190,21 @@ LuaWindow::hide_window (GdkEventAny *ev)
 {
        if (!_visible) return 0;
        _visible = false;
-       return just_hide_it (ev, static_cast<Gtk::Window *>(this));
+       return ARDOUR_UI_UTILS::just_hide_it (ev, static_cast<Gtk::Window *>(this));
+}
+
+void LuaWindow::reinit_lua ()
+{
+       ENSURE_GUI_THREAD (*this, &LuaWindow::session_going_away);
+       delete lua;
+       lua = new LuaState();
+       lua->Print.connect (sigc::mem_fun (*this, &LuaWindow::append_text));
+       lua->sandbox (false);
+
+       lua_State* L = lua->getState();
+       LuaInstance::register_classes (L);
+       luabridge::push <PublicEditor *> (L, &PublicEditor::instance());
+       lua_setglobal (L, "Editor");
 }
 
 void LuaWindow::set_session (Session* s)
@@ -197,7 +217,7 @@ void LuaWindow::set_session (Session* s)
        update_title ();
        _session->DirtyChanged.connect (_session_connections, invalidator (*this), boost::bind (&LuaWindow::update_title, this), gui_context());
 
-       lua_State* L = lua.getState();
+       lua_State* L = lua->getState();
        LuaBindings::set_session (L, _session);
 }
 
@@ -205,14 +225,13 @@ void
 LuaWindow::session_going_away ()
 {
        ENSURE_GUI_THREAD (*this, &LuaWindow::session_going_away);
-       lua.do_command ("collectgarbage();");
-       //TODO: re-init lua-engine (drop all references)
+       reinit_lua (); // drop state (all variables, session references)
 
        SessionHandlePtr::session_going_away ();
        _session = 0;
        update_title ();
 
-       lua_State* L = lua.getState();
+       lua_State* L = lua->getState();
        LuaBindings::set_session (L, _session);
 }
 
@@ -261,30 +280,42 @@ LuaWindow::run_script ()
        if (bytecode.empty()) {
                // plain script or faulty script -- run directly
                try {
-                       lua.do_command ("function ardour () end");
-                       if (0 == lua.do_command (script)) {
+                       lua->do_command ("function ardour () end");
+                       if (0 == lua->do_command (script)) {
                                append_text ("> OK");
                        }
                } catch (luabridge::LuaException const& e) {
                        append_text (string_compose (_("LuaException: %1"), e.what()));
+               } catch (Glib::Exception const& e) {
+                       append_text (string_compose (_("Glib Exception: %1"), e.what()));
+               } catch (std::exception const& e) {
+                       append_text (string_compose (_("C++ Exception: %1"), e.what()));
+               } catch (...) {
+                       append_text (string_compose (_("C++ Exception: %1"), "..."));
                }
        } else {
                // script with factory method
                try {
-                       lua_State* L = lua.getState();
-                       lua.do_command ("function ardour () end");
+                       lua_State* L = lua->getState();
+                       lua->do_command ("function ardour () end");
 
                        LuaScriptParamList args = LuaScriptParams::script_params (script, "action_param", false);
                        luabridge::LuaRef tbl_arg (luabridge::newTable(L));
                        LuaScriptParams::params_to_ref (&tbl_arg, args);
-                       lua.do_command (script); // register "factory"
+                       lua->do_command (script); // register "factory"
                        luabridge::LuaRef lua_factory = luabridge::getGlobal (L, "factory");
                        if (lua_factory.isFunction()) {
                                lua_factory(tbl_arg)();
                        }
-                       lua.do_command ("factory = nil;");
+                       lua->do_command ("factory = nil;");
                } catch (luabridge::LuaException const& e) {
                        append_text (string_compose (_("LuaException: %1"), e.what()));
+               } catch (Glib::Exception const& e) {
+                       append_text (string_compose (_("Glib Exception: %1"), e.what()));
+               } catch (std::exception const& e) {
+                       append_text (string_compose (_("C++ Exception: %1"), e.what()));
+               } catch (...) {
+                       append_text (string_compose (_("C++ Exception: %1"), "..."));
                }
        }
 }
@@ -295,6 +326,7 @@ LuaWindow::append_text (std::string s)
        Glib::RefPtr<Gtk::TextBuffer> tb (outtext.get_buffer());
        tb->insert (tb->end(), s + "\n");
        scroll_to_bottom ();
+       Gtkmm2ext::UI::instance()->flush_pending (0.05);
 }
 
 void
@@ -304,6 +336,17 @@ LuaWindow::clear_output ()
        tb->set_text ("");
 }
 
+void
+LuaWindow::edit_script (const std::string& name, const std::string& script)
+{
+       ScriptBuffer* sb = new LuaWindow::ScriptBuffer (name);
+       sb->script = script;
+       script_buffers.push_back (ScriptBufferPtr (sb));
+       script_selection_changed (script_buffers.back ());
+       refresh_scriptlist ();
+       show_window ();
+}
+
 void
 LuaWindow::new_script ()
 {
@@ -317,7 +360,17 @@ LuaWindow::new_script ()
 void
 LuaWindow::delete_script ()
 {
-       assert (_current_buffer->flags & Buffer_Scratch);
+       assert ((_current_buffer->flags & Buffer_Scratch) || !(_current_buffer->flags & Buffer_ReadOnly));
+       bool refresh = false;
+       bool neednew = true;
+       if (_current_buffer->flags & Buffer_HasFile) {
+               if (0 == ::g_unlink (_current_buffer->path.c_str())) {
+                       append_text (X_("> ") + string_compose (_("Deleted %1"), _current_buffer->path));
+                       refresh = true;
+               } else {
+                       append_text (X_("> ") + string_compose (_("Failed to delete %1"), _current_buffer->path));
+               }
+       }
        for (ScriptBufferList::iterator i = script_buffers.begin (); i != script_buffers.end (); ++i) {
                if ((*i) == _current_buffer) {
                        script_buffers.erase (i);
@@ -328,10 +381,22 @@ LuaWindow::delete_script ()
        for (ScriptBufferList::const_iterator i = script_buffers.begin (); i != script_buffers.end (); ++i) {
                if ((*i)->flags & Buffer_Scratch) {
                        script_selection_changed (*i);
-                       return;
+                       neednew = false;
                }
        }
-       new_script ();
+       if (neednew) {
+               new_script ();
+       }
+       if (refresh) {
+               LuaScripting::instance ().refresh (true);
+       }
+}
+
+void
+LuaWindow::revert_script ()
+{
+       _current_buffer->flags &= BufferFlags(~Buffer_Valid);
+       script_selection_changed (_current_buffer, true);
 }
 
 void
@@ -341,7 +406,7 @@ LuaWindow::import_script ()
        // TODO convert a few URL (eg. pastebin) to raw.
 #if 0
        char *url = "http://pastebin.com/raw/3UMkZ6nV";
-       char *rv = a3_curl_http_get (url, 0);
+       char *rv = ArdourCurl::http_get (url, 0);
        if (rv) {
                new_script ();
                Glib::RefPtr<Gtk::TextBuffer> tb (entry.get_buffer());
@@ -394,6 +459,7 @@ LuaWindow::save_script ()
        if ((sb.flags & Buffer_HasFile) && !(sb.flags & Buffer_ReadOnly)) {
                try {
                        Glib::file_set_contents (sb.path, script);
+                       sb.name = lsi->name;
                        sb.flags &= BufferFlags(~Buffer_Dirty);
                        update_gui_state (); // XXX here?
                        append_text (X_("> ") + string_compose (_("Saved as %1"), sb.path));
@@ -441,9 +507,11 @@ LuaWindow::save_script ()
        try {
                Glib::file_set_contents (path, script);
                sb.path = path;
+               sb.name = lsi->name;
                sb.flags |= Buffer_HasFile;
                sb.flags &= BufferFlags(~Buffer_Dirty);
-               update_gui_state (); // XXX here?
+               sb.flags &= BufferFlags(~Buffer_ReadOnly);
+               update_gui_state (); // XXX here? .refresh (true) may trigger this, too
                LuaScripting::instance().refresh (true);
                append_text (X_("> ") + string_compose (_("Saved as %1"), path));
                return; // OK
@@ -466,6 +534,9 @@ LuaWindow::setup_buffers ()
        script_buffers.push_back (ScriptBufferPtr (new LuaWindow::ScriptBuffer("#1")));
        _current_buffer = script_buffers.front();
 
+       Glib::RefPtr<Gtk::TextBuffer> tb (entry.get_buffer());
+       tb->set_text (_current_buffer->script);
+
        refresh_scriptlist ();
        update_gui_state ();
 }
@@ -494,12 +565,12 @@ LuaWindow::refresh_scriptlist ()
        }
        LuaScriptList& lsa (LuaScripting::instance ().scripts (LuaScriptInfo::EditorAction));
        for (LuaScriptList::const_iterator s = lsa.begin(); s != lsa.end(); ++s) {
-               script_buffers.push_back (ScriptBufferPtr ( new LuaWindow::ScriptBuffer(*s)));
+               script_buffers.push_back (ScriptBufferPtr (new LuaWindow::ScriptBuffer(*s)));
        }
 
        LuaScriptList& lss (LuaScripting::instance ().scripts (LuaScriptInfo::Snippet));
        for (LuaScriptList::const_iterator s = lss.begin(); s != lss.end(); ++s) {
-               script_buffers.push_back (ScriptBufferPtr ( new LuaWindow::ScriptBuffer(*s)));
+               script_buffers.push_back (ScriptBufferPtr (new LuaWindow::ScriptBuffer(*s)));
        }
        rebuild_menu ();
 }
@@ -523,9 +594,17 @@ LuaWindow::rebuild_menu ()
                items_scratch.push_back(elem);
        }
 
+       items_scratch.push_back(SeparatorElem());
+
        for (ScriptBufferList::const_iterator i = script_buffers.begin (); i != script_buffers.end (); ++i) {
-               Menu_Helpers::MenuElem elem = Gtk::Menu_Helpers::MenuElem((*i)->name,
-                               sigc::bind(sigc::mem_fun(*this, &LuaWindow::script_selection_changed), (*i)));
+               std::string name;
+               if ((*i)->flags & Buffer_ReadOnly) {
+                       name = "[R] " + (*i)->name;
+               } else {
+                       name = (*i)->name;
+               }
+               Menu_Helpers::MenuElem elem = Gtk::Menu_Helpers::MenuElem(name,
+                               sigc::bind(sigc::mem_fun(*this, &LuaWindow::script_selection_changed), (*i), false));
 
                if ((*i)->flags & Buffer_Scratch) {
                        items_scratch.push_back(elem);
@@ -545,14 +624,17 @@ LuaWindow::rebuild_menu ()
 }
 
 void
-LuaWindow::script_selection_changed (ScriptBufferPtr n)
+LuaWindow::script_selection_changed (ScriptBufferPtr n, bool force)
 {
-       if (n == _current_buffer) {
+       if (n == _current_buffer && !force) {
                return;
        }
 
        Glib::RefPtr<Gtk::TextBuffer> tb (entry.get_buffer());
-       _current_buffer->script = tb->get_text();
+
+       if (_current_buffer->flags & Buffer_Valid) {
+               _current_buffer->script = tb->get_text();
+       }
 
        if (!(n->flags & Buffer_Valid)) {
                if (!n->load()) {
@@ -592,8 +674,14 @@ LuaWindow::update_gui_state ()
        }
        script_select.set_text(name);
 
+       if (sb.flags & Buffer_ReadOnly) {
+               _btn_save.set_text (_("Save as"));
+       } else {
+               _btn_save.set_text (_("Save"));
+       }
        _btn_save.set_sensitive (sb.flags & Buffer_Dirty);
-       _btn_delete.set_sensitive (sb.flags & Buffer_Scratch); // TODO allow to remove user-scripts
+       _btn_delete.set_sensitive (sb.flags & Buffer_Scratch || ((sb.flags & (Buffer_ReadOnly | Buffer_HasFile)) == Buffer_HasFile));
+       _btn_revert.set_sensitive ((sb.flags & Buffer_Dirty) && (sb.flags & Buffer_HasFile));
 }
 
 void
@@ -609,6 +697,10 @@ LuaWindow::ScriptBuffer::ScriptBuffer (const std::string& n)
        : name (n)
        , flags (Buffer_Scratch | Buffer_Valid)
 {
+       script =
+               "---- this header is (only) required to save the script\n"
+               "-- ardour { [\"type\"] = \"Snippet\", name = \"\" }\n"
+               "-- function factory () return function () -- -- end end\n";
 }
 
 LuaWindow::ScriptBuffer::ScriptBuffer (LuaScriptInfoPtr p)
@@ -620,6 +712,10 @@ LuaWindow::ScriptBuffer::ScriptBuffer (LuaScriptInfoPtr p)
        if (!PBD::exists_and_writable (path)) {
                flags |= Buffer_ReadOnly;
        }
+       if (path.find (user_config_directory ()) != 0) {
+               // mark non-user scripts as read-only
+               flags |= Buffer_ReadOnly;
+       }
 }
 
 #if 0
@@ -640,11 +736,12 @@ LuaWindow::ScriptBuffer::~ScriptBuffer ()
 bool
 LuaWindow::ScriptBuffer::load ()
 {
+       assert (!(flags & Buffer_Valid));
        if (!(flags & Buffer_HasFile)) return false;
-       if (flags & Buffer_Valid) return true;
        try {
                script = Glib::file_get_contents (path);
                flags |= Buffer_Valid;
+               flags &= BufferFlags(~Buffer_Dirty);
        } catch (Glib::FileError e) {
                return false;
        }