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 <glibmm/fileutils.h>
29 #include <gtkmm/messagedialog.h>
31 #include "pbd/basename.h"
32 #include "pbd/file_utils.h"
35 #include "gtkmm2ext/gtk_ui.h"
36 #include "gtkmm2ext/utils.h"
37 #include "gtkmm2ext/window_title.h"
39 #include "ardour/luabindings.h"
40 #include "LuaBridge/LuaBridge.h"
42 #include "ardour_ui.h"
43 #include "gui_thread.h"
44 #include "luainstance.h"
45 #include "luawindow.h"
46 #include "public_editor.h"
52 using namespace ARDOUR;
53 using namespace ARDOUR_UI_UTILS;
57 using namespace Gtkmm2ext;
61 inline LuaWindow::BufferFlags operator| (const LuaWindow::BufferFlags& a, const LuaWindow::BufferFlags& b) {
62 return static_cast<LuaWindow::BufferFlags> (static_cast <int>(a) | static_cast<int> (b));
65 inline LuaWindow::BufferFlags operator|= (LuaWindow::BufferFlags& a, const LuaWindow::BufferFlags& b) {
66 return a = 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 LuaWindow* LuaWindow::_instance = 0;
76 LuaWindow::instance ()
79 _instance = new LuaWindow;
85 LuaWindow::LuaWindow ()
86 : Window (Gtk::WINDOW_TOPLEVEL)
87 , VisibilityTracker (*((Gtk::Window*) this))
93 , _btn_clear (_("Clear Outtput"))
94 , _btn_open (_("Import"))
95 , _btn_save (_("Save"))
96 , _btn_delete (_("Delete"))
102 set_wmclass (X_("ardour_mixer"), PROGRAM_NAME);
104 script_select.disable_scrolling ();
106 set_border_width (0);
108 outtext.set_editable (false);
109 outtext.set_wrap_mode (Gtk::WRAP_WORD);
110 outtext.set_cursor_visible (false);
112 signal_delete_event().connect (sigc::mem_fun (*this, &LuaWindow::hide_window));
113 signal_configure_event().connect (sigc::mem_fun (*ARDOUR_UI::instance(), &ARDOUR_UI::configure_handler));
115 _btn_run.signal_clicked.connect (sigc::mem_fun(*this, &LuaWindow::run_script));
116 _btn_clear.signal_clicked.connect (sigc::mem_fun(*this, &LuaWindow::clear_output));
117 _btn_open.signal_clicked.connect (sigc::mem_fun(*this, &LuaWindow::import_script));
118 _btn_save.signal_clicked.connect (sigc::mem_fun(*this, &LuaWindow::save_script));
119 _btn_delete.signal_clicked.connect (sigc::mem_fun(*this, &LuaWindow::delete_script));
121 _btn_open.set_sensitive (false); // TODO
122 _btn_save.set_sensitive (false);
123 _btn_delete.set_sensitive (false); // TODO
127 Gtk::ScrolledWindow *scrollin = manage (new Gtk::ScrolledWindow);
128 scrollin->set_policy (Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC);
129 scrollin->add (entry);
130 scrollout.set_policy (Gtk::POLICY_NEVER, Gtk::POLICY_ALWAYS);
131 scrollout.add (outtext);
133 Gtk::HBox *hbox = manage (new HBox());
135 hbox->pack_start (_btn_run, false, false, 2);
136 hbox->pack_start (_btn_clear, false, false, 2);
137 hbox->pack_start (_btn_open, false, false, 2);
138 hbox->pack_start (_btn_save, false, false, 2);
139 hbox->pack_start (_btn_delete, false, false, 2);
140 hbox->pack_start (script_select, false, false, 2);
142 Gtk::VBox *vbox = manage (new VBox());
143 vbox->pack_start (*scrollin, true, true, 0);
144 vbox->pack_start (*hbox, false, false, 2);
146 Gtk::VPaned *vpane = manage (new Gtk::VPaned ());
147 vpane->pack1 (*vbox, true, false);
148 vpane->pack2 (scrollout, false, true);
152 set_size_request (640, 480); // XXX
154 lua.Print.connect (sigc::mem_fun (*this, &LuaWindow::append_text));
156 lua_State* L = lua.getState();
157 LuaInstance::register_classes (L);
158 luabridge::push <PublicEditor *> (L, &PublicEditor::instance());
159 lua_setglobal (L, "Editor");
161 ARDOUR_UI_UTILS::set_tooltip (script_select, _("Select Editor Buffer"));
164 LuaScripting::instance().scripts_changed.connect (*this, invalidator (*this), boost::bind (&LuaWindow::refresh_scriptlist, this), gui_context());
166 Glib::RefPtr<Gtk::TextBuffer> tb (entry.get_buffer());
168 _script_changed_connection = tb->signal_changed().connect (sigc::mem_fun(*this, &LuaWindow::script_changed));
171 LuaWindow::~LuaWindow ()
176 LuaWindow::show_window ()
183 LuaWindow::hide_window (GdkEventAny *ev)
185 if (!_visible) return 0;
187 return just_hide_it (ev, static_cast<Gtk::Window *>(this));
190 void LuaWindow::set_session (Session* s)
192 SessionHandlePtr::set_session (s);
198 _session->DirtyChanged.connect (_session_connections, invalidator (*this), boost::bind (&LuaWindow::update_title, this), gui_context());
200 // expose "Session" point directly
201 lua_State* L = lua.getState();
202 LuaBindings::set_session (L, _session);
206 LuaWindow::session_going_away ()
208 ENSURE_GUI_THREAD (*this, &LuaWindow::session_going_away);
209 lua.do_command ("collectgarbage();");
210 //TODO: re-init lua-engine (drop all references) ??
212 SessionHandlePtr::session_going_away ();
216 lua_State* L = lua.getState();
217 LuaBindings::set_session (L, _session);
221 LuaWindow::update_title ()
226 if (_session->snap_name() != _session->name()) {
227 n = _session->snap_name ();
229 n = _session->name ();
232 if (_session->dirty ()) {
236 WindowTitle title (n);
237 title += S_("Window|Lua");
238 title += Glib::get_application_name ();
239 set_title (title.get_string());
242 WindowTitle title (S_("Window|Lua"));
243 title += Glib::get_application_name ();
244 set_title (title.get_string());
249 LuaWindow::scroll_to_bottom ()
251 Gtk::Adjustment *adj;
252 adj = scrollout.get_vadjustment();
253 adj->set_value (MAX(0,(adj->get_upper() - adj->get_page_size())));
257 LuaWindow::run_script ()
259 Glib::RefPtr<Gtk::TextBuffer> tb (entry.get_buffer());
260 std::string script = tb->get_text();
261 const std::string& bytecode = LuaScripting::get_factory_bytecode (script);
262 if (bytecode.empty()) {
263 // plain script or faulty script -- run directly
265 lua.do_command ("function ardour () end");
266 if (0 == lua.do_command (script)) {
267 append_text ("> OK");
269 } catch (luabridge::LuaException const& e) {
270 append_text (string_compose (_("LuaException: %1"), e.what()));
273 // script with factory method
275 lua_State* L = lua.getState();
276 lua.do_command ("function ardour () end");
278 LuaScriptParamList args = LuaScriptParams::script_params (script, "action_param", false);
279 luabridge::LuaRef tbl_arg (luabridge::newTable(L));
280 LuaScriptParams::params_to_ref (&tbl_arg, args);
281 lua.do_command (script); // register "factory"
282 luabridge::LuaRef lua_factory = luabridge::getGlobal (L, "factory");
283 if (lua_factory.isFunction()) {
284 lua_factory(tbl_arg)();
286 lua.do_command ("factory = nil;");
287 } catch (luabridge::LuaException const& e) {
288 append_text (string_compose (_("LuaException: %1"), e.what()));
294 LuaWindow::append_text (std::string s)
296 Glib::RefPtr<Gtk::TextBuffer> tb (outtext.get_buffer());
297 tb->insert (tb->end(), s + "\n");
302 LuaWindow::clear_output ()
304 Glib::RefPtr<Gtk::TextBuffer> tb (outtext.get_buffer());
309 LuaWindow::new_script ()
312 Glib::RefPtr<Gtk::TextBuffer> tb (entry.get_buffer());
318 LuaWindow::delete_script ()
321 Glib::RefPtr<Gtk::TextBuffer> tb (entry.get_buffer());
327 LuaWindow::import_script ()
332 LuaWindow::save_script ()
334 Glib::RefPtr<Gtk::TextBuffer> tb (entry.get_buffer());
335 std::string script = tb->get_text();
336 std::string msg = "Unknown error";
339 LuaScriptInfoPtr lsi = LuaScripting::script_info (script);
340 ScriptBuffer & sb (*_current_buffer);
342 assert (sb.flags & Buffer_Dirty);
344 // 1) check if it has a valid header and factory
345 const std::string& bytecode = LuaScripting::get_factory_bytecode (script);
346 if (bytecode.empty()) {
347 msg = _("Missing script header.\nThe script requires an '{ardour}' info table and a 'factory' function.");
351 if (!LuaScripting::try_compile (script, LuaScriptParams::script_params (script, "action_param", false))) {
352 msg = _("Script fails to compile.");
356 // 2) check script name & type
357 lsi = LuaScripting::script_info (script);
359 msg = _("Invalid or missing script-name or script-type.");
363 if (lsi->type != LuaScriptInfo::Snippet && lsi->type != LuaScriptInfo::EditorAction) {
364 msg = _("Invalid script-type.\nValid types are 'EditorAction' and 'Snippet'.");
368 // 3) if there's already a writable file,...
369 if ((sb.flags & Buffer_HasFile) && !(sb.flags & Buffer_ReadOnly)) {
371 Glib::file_set_contents (sb.path, script);
372 sb.flags &= BufferFlags(~Buffer_Dirty);
373 update_gui_state (); // XXX here?
374 append_text (X_("> ") + string_compose (_("Saved as %1"), sb.path));
376 } catch (Glib::FileError e) {
377 msg = string_compose (_("Error saving file: %1"), e.what());
382 // 4) check if the name is unique for the given type; locally at least
383 if (true /*sb.flags & Buffer_HasFile*/) {
384 LuaScriptList& lsl (LuaScripting::instance ().scripts (lsi->type));
385 for (LuaScriptList::const_iterator s = lsl.begin(); s != lsl.end(); ++s) {
386 if ((*s)->name == lsi->name) {
387 msg = string_compose (_("Script with given name '%1' already exists.\nUse a different name in the descriptor."), lsi->name);
393 // 5) construct filename -- TODO ask user for name, ask to replace file.
397 struct tm * timeinfo = localtime (&t);
398 strftime (buf, sizeof(buf), "%s%d", timeinfo);
399 sprintf (buf, "%s%d", buf, random ()); // is this valid?
401 std::string fn = md5.digestString (buf);
404 case LuaScriptInfo::EditorAction:
407 case LuaScriptInfo::Snippet:
413 path = Glib::build_filename (LuaScripting::user_script_dir (), fn.substr(0, 11) + ".lua");
414 } while (Glib::file_test (path, Glib::FILE_TEST_EXISTS));
417 Glib::file_set_contents (path, script);
419 sb.flags |= Buffer_HasFile;
420 sb.flags &= BufferFlags(~Buffer_Dirty);
421 update_gui_state (); // XXX here?
422 LuaScripting::instance().refresh (true);
423 append_text (X_("> ") + string_compose (_("Saved as %1"), path));
425 } catch (Glib::FileError e) {
426 msg = string_compose (_("Error saving file: %1"), e.what());
431 MessageDialog am (msg);
436 LuaWindow::setup_buffers ()
438 if (script_buffers.size() > 0) {
441 script_buffers.push_back (ScriptBufferPtr (new LuaWindow::ScriptBuffer("#1")));
442 script_buffers.push_back (ScriptBufferPtr (new LuaWindow::ScriptBuffer("#2"))); // XXX
443 _current_buffer = script_buffers.front();
445 refresh_scriptlist ();
450 LuaWindow::count_scratch_buffers () const
456 LuaWindow::refresh_scriptlist ()
458 for (ScriptBufferList::iterator i = script_buffers.begin (); i != script_buffers.end ();) {
459 if ((*i)->flags & Buffer_Scratch) {
463 i = script_buffers.erase (i);
465 LuaScriptList& lsa (LuaScripting::instance ().scripts (LuaScriptInfo::EditorAction));
466 for (LuaScriptList::const_iterator s = lsa.begin(); s != lsa.end(); ++s) {
467 script_buffers.push_back (ScriptBufferPtr ( new LuaWindow::ScriptBuffer(*s)));
470 LuaScriptList& lss (LuaScripting::instance ().scripts (LuaScriptInfo::Snippet));
471 for (LuaScriptList::const_iterator s = lss.begin(); s != lss.end(); ++s) {
472 script_buffers.push_back (ScriptBufferPtr ( new LuaWindow::ScriptBuffer(*s)));
478 LuaWindow::rebuild_menu ()
480 using namespace Menu_Helpers;
482 _menu_scratch = manage (new Menu);
483 _menu_snippet = manage (new Menu);
484 _menu_actions = manage (new Menu);
486 MenuList& items_scratch (_menu_scratch->items());
487 MenuList& items_snippet (_menu_snippet->items());
488 MenuList& items_actions (_menu_actions->items());
491 Menu_Helpers::MenuElem elem = Gtk::Menu_Helpers::MenuElem(_("New"),
492 sigc::mem_fun(*this, &LuaWindow::new_script));
493 items_scratch.push_back(elem);
496 for (ScriptBufferList::const_iterator i = script_buffers.begin (); i != script_buffers.end (); ++i) {
497 Menu_Helpers::MenuElem elem = Gtk::Menu_Helpers::MenuElem((*i)->name,
498 sigc::bind(sigc::mem_fun(*this, &LuaWindow::script_selection_changed), (*i)));
500 if ((*i)->flags & Buffer_Scratch) {
501 items_scratch.push_back(elem);
503 else if ((*i)->type == LuaScriptInfo::EditorAction) {
504 items_actions.push_back(elem);
506 else if ((*i)->type == LuaScriptInfo::Snippet) {
507 items_snippet.push_back(elem);
511 script_select.clear_items ();
512 script_select.AddMenuElem (Menu_Helpers::MenuElem ("Scratch", *_menu_scratch));
513 script_select.AddMenuElem (Menu_Helpers::MenuElem ("Snippets", *_menu_snippet));
514 script_select.AddMenuElem (Menu_Helpers::MenuElem ("Actions", *_menu_actions));
518 LuaWindow::script_selection_changed (ScriptBufferPtr n)
520 if (n == _current_buffer) {
524 Glib::RefPtr<Gtk::TextBuffer> tb (entry.get_buffer());
525 _current_buffer->script = tb->get_text();
527 if (!(n->flags & Buffer_Valid)) {
529 append_text ("! Failed to load buffer.");
533 if (n->flags & Buffer_Valid) {
535 _script_changed_connection.block ();
536 tb->set_text (n->script);
537 _script_changed_connection.unblock ();
539 append_text ("! Failed to switch buffer.");
545 LuaWindow::update_gui_state ()
547 const ScriptBuffer & sb (*_current_buffer);
549 if (sb.flags & Buffer_Scratch) {
550 name = string_compose (_("Scratch Buffer %1"), sb.name);
551 } else if (sb.type == LuaScriptInfo::EditorAction) {
552 name = string_compose (_("Action: '%1'"), sb.name);
553 } else if (sb.type == LuaScriptInfo::Snippet) {
554 name = string_compose (_("Snippet: %1"), sb.name);
556 cerr << "Invalid Script type\n";
560 if (sb.flags & Buffer_Dirty) {
563 script_select.set_text(name);
565 _btn_save.set_sensitive (sb.flags & Buffer_Dirty);
566 _btn_delete.set_sensitive (sb.flags & Buffer_Scratch); // TODO allow to remove user-scripts
570 LuaWindow::script_changed () {
571 if (_current_buffer->flags & Buffer_Dirty) {
574 _current_buffer->flags |= Buffer_Dirty;
578 LuaWindow::ScriptBuffer::ScriptBuffer (const std::string& n)
580 , flags (Buffer_Scratch | Buffer_Valid)
584 LuaWindow::ScriptBuffer::ScriptBuffer (LuaScriptInfoPtr p)
587 , flags (Buffer_HasFile)
590 if (!PBD::exists_and_writable (path)) {
591 flags |= Buffer_ReadOnly;
596 LuaWindow::ScriptBuffer::ScriptBuffer (const ScriptBuffer& other)
597 : script (other.script)
600 , flags (other.flags)
606 LuaWindow::ScriptBuffer::~ScriptBuffer ()
611 LuaWindow::ScriptBuffer::load ()
613 if (!(flags & Buffer_HasFile)) return false;
614 if (flags & Buffer_Valid) return true;
616 script = Glib::file_get_contents (path);
617 flags |= Buffer_Valid;
618 } catch (Glib::FileError e) {