towards a proper lua script console UI
authorRobin Gareus <robin@gareus.org>
Wed, 23 Mar 2016 22:44:35 +0000 (23:44 +0100)
committerRobin Gareus <robin@gareus.org>
Wed, 23 Mar 2016 22:44:35 +0000 (23:44 +0100)
gtk2_ardour/luawindow.cc
gtk2_ardour/luawindow.h
gtk2_ardour/script_selector.cc

index 5fda4f2e242b7a5c30c3610a7e7cf211709f87b1..6b0ca8e08b42f1bc72ee4d5fe9654e9147de2921 100644 (file)
 
 */
 
+#ifdef PLATFORM_WINDOWS
+#define random() rand()
+#endif
+
 #ifdef WAF_BUILD
 #include "gtk2ardour-config.h"
 #endif
 
-#include <gtkmm2ext/gtk_ui.h>
-#include <gtkmm2ext/utils.h>
-#include <gtkmm2ext/window_title.h>
+#include <glibmm/fileutils.h>
+#include <gtkmm/messagedialog.h>
+
+#include "pbd/basename.h"
+#include "pbd/file_utils.h"
+#include "pbd/md5.h"
+
+#include "gtkmm2ext/gtk_ui.h"
+#include "gtkmm2ext/utils.h"
+#include "gtkmm2ext/window_title.h"
+
+#include "ardour/luabindings.h"
+#include "LuaBridge/LuaBridge.h"
 
 #include "ardour_ui.h"
 #include "gui_thread.h"
 #include "luainstance.h"
 #include "luawindow.h"
 #include "public_editor.h"
+#include "tooltips.h"
 #include "utils.h"
 
-#include "ardour/luabindings.h"
-#include "LuaBridge/LuaBridge.h"
-
 #include "i18n.h"
 
 using namespace ARDOUR;
@@ -46,6 +58,18 @@ using namespace Gtkmm2ext;
 using namespace std;
 
 
+inline LuaWindow::BufferFlags operator| (const LuaWindow::BufferFlags& a, const LuaWindow::BufferFlags& b) {
+       return static_cast<LuaWindow::BufferFlags> (static_cast <int>(a) | static_cast<int> (b));
+}
+
+inline LuaWindow::BufferFlags operator|= (LuaWindow::BufferFlags& a, const LuaWindow::BufferFlags& b) {
+       return a = static_cast<LuaWindow::BufferFlags> (static_cast <int>(a) | static_cast<int> (b));
+}
+
+inline LuaWindow::BufferFlags operator&= (LuaWindow::BufferFlags& a, const LuaWindow::BufferFlags& b) {
+       return a = static_cast<LuaWindow::BufferFlags> (static_cast <int>(a) & static_cast<int> (b));
+}
+
 LuaWindow* LuaWindow::_instance = 0;
 
 LuaWindow*
@@ -62,54 +86,86 @@ LuaWindow::LuaWindow ()
        : Window (Gtk::WINDOW_TOPLEVEL)
        , VisibilityTracker (*((Gtk::Window*) this))
        , _visible (false)
+       , _menu_scratch (0)
+       , _menu_snippet (0)
+       , _menu_actions (0)
+       , _btn_run (_("Run"))
+       , _btn_clear (_("Clear Outtput"))
+       , _btn_open (_("Import"))
+       , _btn_save (_("Save"))
+       , _btn_delete (_("Delete"))
+       , _current_buffer (0)
 {
        set_name ("Lua");
 
        update_title ();
        set_wmclass (X_("ardour_mixer"), PROGRAM_NAME);
 
+       script_select.disable_scrolling ();
+
        set_border_width (0);
 
        outtext.set_editable (false);
        outtext.set_wrap_mode (Gtk::WRAP_WORD);
+       outtext.set_cursor_visible (false);
 
        signal_delete_event().connect (sigc::mem_fun (*this, &LuaWindow::hide_window));
        signal_configure_event().connect (sigc::mem_fun (*ARDOUR_UI::instance(), &ARDOUR_UI::configure_handler));
 
-       scrollwin.set_policy (Gtk::POLICY_NEVER, Gtk::POLICY_ALWAYS);
-       scrollwin.add (outtext);
+       _btn_run.signal_clicked.connect (sigc::mem_fun(*this, &LuaWindow::run_script));
+       _btn_clear.signal_clicked.connect (sigc::mem_fun(*this, &LuaWindow::clear_output));
+       _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_open.set_sensitive (false); // TODO
+       _btn_save.set_sensitive (false);
+       _btn_delete.set_sensitive (false); // TODO
+
+       // layout
 
-       Gtk::Button *btn_clr = manage (new Button ("Clear"));
-       btn_clr->signal_clicked().connect (sigc::mem_fun(*this, &LuaWindow::clear_output));
+       Gtk::ScrolledWindow *scrollin = manage (new Gtk::ScrolledWindow);
+       scrollin->set_policy (Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC);
+       scrollin->add (entry);
+       scrollout.set_policy (Gtk::POLICY_NEVER, Gtk::POLICY_ALWAYS);
+       scrollout.add (outtext);
 
        Gtk::HBox *hbox = manage (new HBox());
 
-       hbox->pack_start (entry, true, true, 2);
-       hbox->pack_start (*btn_clr, false, false, 0);
+       hbox->pack_start (_btn_run, false, false, 2);
+       hbox->pack_start (_btn_clear, false, false, 2);
+       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 (script_select, false, false, 2);
 
        Gtk::VBox *vbox = manage (new VBox());
-       vbox->pack_start (scrollwin, true, true, 0);
+       vbox->pack_start (*scrollin, true, true, 0);
        vbox->pack_start (*hbox, false, false, 2);
 
-       entry.signal_activate().connect (sigc::mem_fun (*this, &LuaWindow::entry_activated));
+       Gtk::VPaned *vpane = manage (new Gtk::VPaned ());
+       vpane->pack1 (*vbox, true, false);
+       vpane->pack2 (scrollout, false, true);
 
-       lua.Print.connect (sigc::mem_fun (*this, &LuaWindow::append_text));
-
-       vbox->show_all ();
-       add (*vbox);
+       vpane->show_all ();
+       add (*vpane);
        set_size_request (640, 480); // XXX
 
-       LuaInstance::register_classes (lua.getState());
-       // TODO register some callback functions.
+       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");
-       // TODO
-       // - allow to load files
-       // - allow to run files directly
-       // - history buffer
-       // - multi-line input ??
+
+       ARDOUR_UI_UTILS::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());
+
+       Glib::RefPtr<Gtk::TextBuffer> tb (entry.get_buffer());
+
+       _script_changed_connection = tb->signal_changed().connect (sigc::mem_fun(*this, &LuaWindow::script_changed));
 }
 
 LuaWindow::~LuaWindow ()
@@ -193,18 +249,44 @@ void
 LuaWindow::scroll_to_bottom ()
 {
        Gtk::Adjustment *adj;
-       adj = scrollwin.get_vadjustment();
+       adj = scrollout.get_vadjustment();
        adj->set_value (MAX(0,(adj->get_upper() - adj->get_page_size())));
 }
 
 void
-LuaWindow::entry_activated ()
+LuaWindow::run_script ()
 {
-       std::string cmd = entry.get_text();
-       append_text ("> " + cmd);
-
-       if (0 == lua.do_command (cmd)) {
-               entry.set_text("");
+       Glib::RefPtr<Gtk::TextBuffer> tb (entry.get_buffer());
+       std::string script = tb->get_text();
+       const std::string& bytecode = LuaScripting::get_factory_bytecode (script);
+       if (bytecode.empty()) {
+               // plain script or faulty script -- run directly
+               try {
+                       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()));
+               }
+       } else {
+               // script with factory method
+               try {
+                       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"
+                       luabridge::LuaRef lua_factory = luabridge::getGlobal (L, "factory");
+                       if (lua_factory.isFunction()) {
+                               lua_factory(tbl_arg)();
+                       }
+                       lua.do_command ("factory = nil;");
+               } catch (luabridge::LuaException const& e) {
+                       append_text (string_compose (_("LuaException: %1"), e.what()));
+               }
        }
 }
 
@@ -222,3 +304,319 @@ LuaWindow::clear_output ()
        Glib::RefPtr<Gtk::TextBuffer> tb (outtext.get_buffer());
        tb->set_text ("");
 }
+
+void
+LuaWindow::new_script ()
+{
+#if 0
+       Glib::RefPtr<Gtk::TextBuffer> tb (entry.get_buffer());
+       tb->set_text ("");
+#endif
+}
+
+void
+LuaWindow::delete_script ()
+{
+#if 0
+       Glib::RefPtr<Gtk::TextBuffer> tb (entry.get_buffer());
+       tb->set_text ("");
+#endif
+}
+
+void
+LuaWindow::import_script ()
+{
+}
+
+void
+LuaWindow::save_script ()
+{
+       Glib::RefPtr<Gtk::TextBuffer> tb (entry.get_buffer());
+       std::string script = tb->get_text();
+       std::string msg = "Unknown error";
+
+       std::string path;
+       LuaScriptInfoPtr lsi = LuaScripting::script_info (script);
+       ScriptBuffer & sb (*_current_buffer);
+
+       assert (sb.flags & Buffer_Dirty);
+
+       // 1) check if it has a valid header and factory
+       const std::string& bytecode = LuaScripting::get_factory_bytecode (script);
+       if (bytecode.empty()) {
+               msg = _("Missing script header.\nThe script requires an '{ardour}' info table and a 'factory' function.");
+               goto errorout;
+       }
+
+       if (!LuaScripting::try_compile (script, LuaScriptParams::script_params (script, "action_param", false))) {
+               msg = _("Script fails to compile.");
+               goto errorout;
+       }
+
+       // 2) check script name & type
+       lsi = LuaScripting::script_info (script);
+       if (!lsi) {
+               msg = _("Invalid or missing script-name or script-type.");
+               goto errorout;
+       }
+
+       if (lsi->type != LuaScriptInfo::Snippet && lsi->type != LuaScriptInfo::EditorAction) {
+               msg = _("Invalid script-type.\nValid types are 'EditorAction' and 'Snippet'.");
+               goto errorout;
+       }
+
+       // 3) if there's already a writable file,...
+       if ((sb.flags & Buffer_HasFile) && !(sb.flags & Buffer_ReadOnly)) {
+               try {
+                       Glib::file_set_contents (sb.path, script);
+                       sb.flags &= BufferFlags(~Buffer_Dirty);
+                       update_gui_state (); // XXX here?
+                       append_text (X_("> ") + string_compose (_("Saved as %1"), sb.path));
+                       return; // OK
+               } catch (Glib::FileError e) {
+                       msg = string_compose (_("Error saving file: %1"), e.what());
+                       goto errorout;
+               }
+       }
+
+       // 4) check if the name is unique for the given type; locally at least
+       if (true /*sb.flags & Buffer_HasFile*/) {
+               LuaScriptList& lsl (LuaScripting::instance ().scripts (lsi->type));
+               for (LuaScriptList::const_iterator s = lsl.begin(); s != lsl.end(); ++s) {
+                       if ((*s)->name == lsi->name) {
+                               msg = string_compose (_("Script with given name '%1' already exists.\nUse a different name in the descriptor."), lsi->name);
+                               goto errorout;
+                       }
+               }
+       }
+
+       // 5) construct filename -- TODO ask user for name, ask to replace file.
+       do {
+               char buf[80];
+               time_t t = time(0);
+               struct tm * timeinfo = localtime (&t);
+               strftime (buf, sizeof(buf), "%s%d", timeinfo);
+               sprintf (buf, "%s%d", buf, random ()); // is this valid?
+               MD5 md5;
+               std::string fn = md5.digestString (buf);
+
+               switch (lsi->type) {
+                       case LuaScriptInfo::EditorAction:
+                               fn = "a_" + fn;
+                               break;
+                       case LuaScriptInfo::Snippet:
+                               fn = "s_" + fn;
+                               break;
+                       default:
+                               break;
+               }
+               path = Glib::build_filename (LuaScripting::user_script_dir (), fn.substr(0, 11) + ".lua");
+       } while (Glib::file_test (path, Glib::FILE_TEST_EXISTS));
+
+       try {
+               Glib::file_set_contents (path, script);
+               sb.path = path;
+               sb.flags |= Buffer_HasFile;
+               sb.flags &= BufferFlags(~Buffer_Dirty);
+               update_gui_state (); // XXX here?
+               LuaScripting::instance().refresh (true);
+               append_text (X_("> ") + string_compose (_("Saved as %1"), path));
+               return; // OK
+       } catch (Glib::FileError e) {
+               msg = string_compose (_("Error saving file: %1"), e.what());
+               goto errorout;
+       }
+
+errorout:
+               MessageDialog am (msg);
+               am.run ();
+}
+
+void
+LuaWindow::setup_buffers ()
+{
+       if (script_buffers.size() > 0) {
+               return;
+       }
+       script_buffers.push_back (ScriptBufferPtr (new LuaWindow::ScriptBuffer("#1")));
+       script_buffers.push_back (ScriptBufferPtr (new LuaWindow::ScriptBuffer("#2"))); // XXX
+       _current_buffer = script_buffers.front();
+
+       refresh_scriptlist ();
+       update_gui_state ();
+}
+
+uint32_t
+LuaWindow::count_scratch_buffers () const
+{
+       return 0;
+}
+
+void
+LuaWindow::refresh_scriptlist ()
+{
+       for (ScriptBufferList::iterator i = script_buffers.begin (); i != script_buffers.end ();) {
+               if ((*i)->flags & Buffer_Scratch) {
+                       ++i;
+                       continue;
+               }
+               i = script_buffers.erase (i);
+       }
+       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)));
+       }
+
+       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)));
+       }
+       rebuild_menu ();
+}
+
+void
+LuaWindow::rebuild_menu ()
+{
+       using namespace Menu_Helpers;
+
+       _menu_scratch = manage (new Menu);
+       _menu_snippet = manage (new Menu);
+       _menu_actions = manage (new Menu);
+
+       MenuList& items_scratch (_menu_scratch->items());
+       MenuList& items_snippet (_menu_snippet->items());
+       MenuList& items_actions (_menu_actions->items());
+
+       {
+               Menu_Helpers::MenuElem elem = Gtk::Menu_Helpers::MenuElem(_("New"),
+                               sigc::mem_fun(*this, &LuaWindow::new_script));
+               items_scratch.push_back(elem);
+       }
+
+       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)));
+
+               if ((*i)->flags & Buffer_Scratch) {
+                       items_scratch.push_back(elem);
+               }
+               else if ((*i)->type == LuaScriptInfo::EditorAction) {
+                               items_actions.push_back(elem);
+               }
+               else if ((*i)->type == LuaScriptInfo::Snippet) {
+                               items_snippet.push_back(elem);
+               }
+       }
+
+       script_select.clear_items ();
+       script_select.AddMenuElem (Menu_Helpers::MenuElem ("Scratch", *_menu_scratch));
+       script_select.AddMenuElem (Menu_Helpers::MenuElem ("Snippets", *_menu_snippet));
+       script_select.AddMenuElem (Menu_Helpers::MenuElem ("Actions", *_menu_actions));
+}
+
+void
+LuaWindow::script_selection_changed (ScriptBufferPtr n)
+{
+       if (n == _current_buffer) {
+               return;
+       }
+
+       Glib::RefPtr<Gtk::TextBuffer> tb (entry.get_buffer());
+       _current_buffer->script = tb->get_text();
+
+       if (!(n->flags & Buffer_Valid)) {
+               if (!n->load()) {
+                       append_text ("! Failed to load buffer.");
+               }
+       }
+
+       if (n->flags & Buffer_Valid) {
+               _current_buffer = n;
+               _script_changed_connection.block ();
+               tb->set_text (n->script);
+               _script_changed_connection.unblock ();
+       } else {
+               append_text ("! Failed to switch buffer.");
+       }
+       update_gui_state ();
+}
+
+void
+LuaWindow::update_gui_state ()
+{
+       const ScriptBuffer & sb (*_current_buffer);
+       std::string name;
+       if (sb.flags & Buffer_Scratch) {
+               name = string_compose (_("Scratch Buffer %1"), sb.name);
+       } else if (sb.type == LuaScriptInfo::EditorAction) {
+               name = string_compose (_("Action: '%1'"), sb.name);
+       } else if (sb.type == LuaScriptInfo::Snippet) {
+               name = string_compose (_("Snippet: %1"), sb.name);
+       } else {
+               cerr << "Invalid Script type\n";
+               assert (0);
+               return;
+       }
+       if (sb.flags & Buffer_Dirty) {
+               name += " *";
+       }
+       script_select.set_text(name);
+
+       _btn_save.set_sensitive (sb.flags & Buffer_Dirty);
+       _btn_delete.set_sensitive (sb.flags & Buffer_Scratch); // TODO allow to remove user-scripts
+}
+
+void
+LuaWindow::script_changed () {
+       if (_current_buffer->flags & Buffer_Dirty) {
+               return;
+       }
+       _current_buffer->flags |= Buffer_Dirty;
+       update_gui_state ();
+}
+
+LuaWindow::ScriptBuffer::ScriptBuffer (const std::string& n)
+       : name (n)
+       , flags (Buffer_Scratch | Buffer_Valid)
+{
+}
+
+LuaWindow::ScriptBuffer::ScriptBuffer (LuaScriptInfoPtr p)
+       : name (p->name)
+       , path (p->path)
+       , flags (Buffer_HasFile)
+       , type (p->type)
+{
+       if (!PBD::exists_and_writable (path)) {
+               flags |= Buffer_ReadOnly;
+       }
+}
+
+#if 0
+LuaWindow::ScriptBuffer::ScriptBuffer (const ScriptBuffer& other)
+       : script (other.script)
+       , name (other.name)
+       , path (other.path)
+       , flags (other.flags)
+       , type (other.type)
+{
+}
+#endif
+
+LuaWindow::ScriptBuffer::~ScriptBuffer ()
+{
+}
+
+bool
+LuaWindow::ScriptBuffer::load ()
+{
+       if (!(flags & Buffer_HasFile)) return false;
+       if (flags & Buffer_Valid) return true;
+       try {
+               script = Glib::file_get_contents (path);
+               flags |= Buffer_Valid;
+       } catch (Glib::FileError e) {
+               return false;
+       }
+       return true;
+}
index 9a7f281f3efc2671032fb3184a9c3ff956588873..8faec694a5035c5aef3e8d5a0692c4bc123903d9 100644 (file)
 #include <gtkmm/label.h>
 #include <gtkmm/window.h>
 
+#include "pbd/signals.h"
+#include "pbd/stateful.h"
+
 #include "ardour/ardour.h"
-#include "ardour/types.h"
+#include "ardour/luascripting.h"
 #include "ardour/session_handle.h"
-
-#include "pbd/stateful.h"
-#include "pbd/signals.h"
+#include "ardour/types.h"
 
 #include "gtkmm2ext/visibility_tracker.h"
 
 #include "lua/luastate.h"
 
+#include "ardour_button.h"
+#include "ardour_dropdown.h"
+
 class LuaWindow :
        public Gtk::Window,
        public PBD::ScopedConnectionList,
@@ -52,27 +56,85 @@ class LuaWindow :
 
        void set_session (ARDOUR::Session* s);
 
+       typedef enum {
+               Buffer_NOFLAG     = 0x00,
+               Buffer_Valid      = 0x01, ///< script is loaded
+               Buffer_HasFile    = 0x02,
+               Buffer_ReadOnly   = 0x04,
+               Buffer_Dirty      = 0x08,
+               Buffer_Scratch    = 0x10,
+       } BufferFlags;
+
+       class ScriptBuffer {
+       public:
+               ScriptBuffer (const std::string&);
+               ScriptBuffer (ARDOUR::LuaScriptInfoPtr);
+               //ScriptBuffer (const ScriptBuffer& other);
+               ~ScriptBuffer ();
+
+               bool load ();
+
+               std::string script;
+               std::string name;
+               std::string path;
+               BufferFlags flags;
+               ARDOUR::LuaScriptInfo::ScriptType type;
+       };
+
   private:
        LuaWindow ();
        static LuaWindow* _instance;
 
+       LuaState lua;
        bool _visible;
-       Gtk::VBox global_vpacker;
+
+       Gtk::Menu* _menu_scratch;
+       Gtk::Menu* _menu_snippet;
+       Gtk::Menu* _menu_actions;
+
+       sigc::connection _script_changed_connection;
+
+       Gtk::TextView entry;
+       Gtk::TextView outtext;
+       Gtk::ScrolledWindow scrollout;
+
+       ArdourButton _btn_run;
+       ArdourButton _btn_clear;
+       ArdourButton _btn_open;
+       ArdourButton _btn_save;
+       ArdourButton _btn_delete;
+
+       ArdourDropdown script_select;
+
+       typedef boost::shared_ptr<ScriptBuffer> ScriptBufferPtr;
+       typedef std::vector<ScriptBufferPtr> ScriptBufferList;
+
+       ScriptBufferList script_buffers;
+       ScriptBufferPtr _current_buffer;
 
        void session_going_away ();
        void update_title ();
 
-       Gtk::Entry entry;
-       Gtk::TextView outtext;
-       Gtk::ScrolledWindow scrollwin;
+       void setup_buffers ();
+       void refresh_scriptlist ();
+       void rebuild_menu ();
+       uint32_t count_scratch_buffers () const;
+
+       void script_changed ();
+       void script_selection_changed (ScriptBufferPtr n);
+       void update_gui_state ();
 
        void append_text (std::string s);
        void scroll_to_bottom ();
        void clear_output ();
 
-       void entry_activated ();
+       void run_script ();
 
-       LuaState lua;
+       void new_script ();
+       void delete_script ();
+       void import_script ();
+       void save_script ();
 };
 
+
 #endif
index a8a1b6e9578cee45a9062636f69873084b9eaf1d..3fab40a5e74d5a8d2dbc03d740d61da9868c4984 100644 (file)
@@ -112,6 +112,7 @@ ScriptSelector::script_combo_changed ()
 void
 ScriptSelector::refresh ()
 {
+       LuaScripting::instance ().refresh ();
        _scripts = LuaScripting::instance ().scripts (_script_type);
        setup_list ();
 }
@@ -166,10 +167,12 @@ ScriptParameterDialog::ScriptParameterDialog (std::string title,
        t->attach (_name_entry, 1, 2, ty, ty+1);
        ++ty;
 
-       l = manage (new Label (_("<b>Parameters:</b>"), Gtk::ALIGN_CENTER, Gtk::ALIGN_CENTER, false));
-       l->set_use_markup ();
-       t->attach (_name_entry, 0, 2, ty, ty+1);
-       ++ty;
+       if (_lsp.size () > 0) {
+               l = manage (new Label (_("<b>Instance Parameters</b>"), Gtk::ALIGN_LEFT, Gtk::ALIGN_CENTER, false));
+               l->set_use_markup ();
+               t->attach (*l, 0, 2, ty, ty+1);
+               ++ty;
+       }
 
        for (size_t i = 0; i < _lsp.size (); ++i) {
                CheckButton* c = manage (new CheckButton (_lsp[i]->title));