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 "ui_config.h"
54 #include "utils_videotl.h"
58 using namespace ARDOUR;
62 using namespace Gtkmm2ext;
66 inline LuaWindow::BufferFlags operator| (const LuaWindow::BufferFlags& a, const LuaWindow::BufferFlags& b) {
67 return static_cast<LuaWindow::BufferFlags> (static_cast <int>(a) | static_cast<int> (b));
70 inline LuaWindow::BufferFlags operator|= (LuaWindow::BufferFlags& a, const LuaWindow::BufferFlags& b) {
71 return a = static_cast<LuaWindow::BufferFlags> (static_cast <int>(a) | static_cast<int> (b));
74 inline LuaWindow::BufferFlags operator&= (LuaWindow::BufferFlags& a, const LuaWindow::BufferFlags& b) {
75 return a = static_cast<LuaWindow::BufferFlags> (static_cast <int>(a) & static_cast<int> (b));
78 LuaWindow* LuaWindow::_instance = 0;
81 LuaWindow::instance ()
84 _instance = new LuaWindow;
90 LuaWindow::LuaWindow ()
91 : Window (Gtk::WINDOW_TOPLEVEL)
92 , VisibilityTracker (*((Gtk::Window*) this))
99 , _btn_clear (_("Clear Output"))
100 , _btn_open (_("Import"))
101 , _btn_save (_("Save"))
102 , _btn_delete (_("Delete"))
103 , _btn_revert (_("Revert"))
110 set_wmclass (X_("ardour_mixer"), PROGRAM_NAME);
113 set_type_hint (Gdk::WINDOW_TYPE_HINT_DIALOG);
115 if (UIConfiguration::instance().get_all_floating_windows_are_dialogs()) {
116 set_type_hint (Gdk::WINDOW_TYPE_HINT_DIALOG);
118 set_type_hint (Gdk::WINDOW_TYPE_HINT_UTILITY);
122 script_select.disable_scrolling ();
124 set_border_width (0);
126 outtext.set_editable (false);
127 outtext.set_wrap_mode (Gtk::WRAP_WORD);
128 outtext.set_cursor_visible (false);
130 signal_delete_event().connect (sigc::mem_fun (*this, &LuaWindow::hide_window));
131 signal_configure_event().connect (sigc::mem_fun (*ARDOUR_UI::instance(), &ARDOUR_UI::configure_handler));
133 _btn_run.signal_clicked.connect (sigc::mem_fun(*this, &LuaWindow::run_script));
134 _btn_clear.signal_clicked.connect (sigc::mem_fun(*this, &LuaWindow::clear_output));
135 _btn_open.signal_clicked.connect (sigc::mem_fun(*this, &LuaWindow::import_script));
136 _btn_save.signal_clicked.connect (sigc::mem_fun(*this, &LuaWindow::save_script));
137 _btn_delete.signal_clicked.connect (sigc::mem_fun(*this, &LuaWindow::delete_script));
138 _btn_revert.signal_clicked.connect (sigc::mem_fun(*this, &LuaWindow::revert_script));
140 _btn_open.set_sensitive (false); // TODO
141 _btn_save.set_sensitive (false);
142 _btn_delete.set_sensitive (false);
143 _btn_revert.set_sensitive (false);
147 Gtk::ScrolledWindow *scrollin = manage (new Gtk::ScrolledWindow);
148 scrollin->set_policy (Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC);
149 scrollin->add (entry);
150 scrollout.set_policy (Gtk::POLICY_NEVER, Gtk::POLICY_ALWAYS);
151 scrollout.add (outtext);
153 entry.set_name ("ArdourLuaEntry");
154 outtext.set_name ("ArdourLuaEntry");
156 Gtk::HBox *hbox = manage (new HBox());
158 hbox->pack_start (_btn_run, false, false, 2);
159 hbox->pack_start (_btn_clear, false, false, 2);
160 hbox->pack_start (_btn_open, false, false, 2);
161 hbox->pack_start (_btn_save, false, false, 2);
162 hbox->pack_start (_btn_delete, false, false, 2);
163 hbox->pack_start (_btn_revert, false, false, 2);
164 hbox->pack_start (script_select, false, false, 2);
166 Gtk::VBox *vbox = manage (new VBox());
167 vbox->pack_start (*scrollin, true, true, 0);
168 vbox->pack_start (*hbox, false, false, 2);
170 ArdourWidgets::VPane *vpane = manage (new ArdourWidgets::VPane ());
172 vpane->add (scrollout);
173 vpane->set_divider (0, 0.75);
177 set_size_request (640, 480); // XXX
178 ArdourWidgets::set_tooltip (script_select, _("Select Editor Buffer"));
181 LuaScripting::instance().scripts_changed.connect (*this, invalidator (*this), boost::bind (&LuaWindow::refresh_scriptlist, this), gui_context());
183 Glib::RefPtr<Gtk::TextBuffer> tb (entry.get_buffer());
184 _script_changed_connection = tb->signal_changed().connect (sigc::mem_fun(*this, &LuaWindow::script_changed));
187 LuaWindow::~LuaWindow ()
193 LuaWindow::show_window ()
200 LuaWindow::hide_window (GdkEventAny *ev)
202 if (!_visible) return 0;
204 return ARDOUR_UI_UTILS::just_hide_it (ev, static_cast<Gtk::Window *>(this));
207 void LuaWindow::reinit_lua ()
209 ENSURE_GUI_THREAD (*this, &LuaWindow::session_going_away);
211 lua = new LuaState();
212 lua->Print.connect (sigc::mem_fun (*this, &LuaWindow::append_text));
213 lua->sandbox (false);
215 lua_State* L = lua->getState();
216 LuaInstance::register_classes (L);
217 luabridge::push <PublicEditor *> (L, &PublicEditor::instance());
218 lua_setglobal (L, "Editor");
221 void LuaWindow::set_session (Session* s)
223 SessionHandlePtr::set_session (s);
229 _session->DirtyChanged.connect (_session_connections, invalidator (*this), boost::bind (&LuaWindow::update_title, this), gui_context());
231 lua_State* L = lua->getState();
232 LuaBindings::set_session (L, _session);
236 LuaWindow::session_going_away ()
238 ENSURE_GUI_THREAD (*this, &LuaWindow::session_going_away);
239 reinit_lua (); // drop state (all variables, session references)
241 SessionHandlePtr::session_going_away ();
245 lua_State* L = lua->getState();
246 LuaBindings::set_session (L, _session);
250 LuaWindow::update_title ()
255 if (_session->snap_name() != _session->name()) {
256 n = _session->snap_name ();
258 n = _session->name ();
261 if (_session->dirty ()) {
265 WindowTitle title (n);
266 title += S_("Window|Lua");
267 title += Glib::get_application_name ();
268 set_title (title.get_string());
271 WindowTitle title (S_("Window|Lua"));
272 title += Glib::get_application_name ();
273 set_title (title.get_string());
278 LuaWindow::scroll_to_bottom ()
280 Gtk::Adjustment *adj;
281 adj = scrollout.get_vadjustment();
282 adj->set_value (MAX(0,(adj->get_upper() - adj->get_page_size())));
286 LuaWindow::run_script ()
288 Glib::RefPtr<Gtk::TextBuffer> tb (entry.get_buffer());
289 std::string script = tb->get_text();
290 const std::string& bytecode = LuaScripting::get_factory_bytecode (script);
291 if (bytecode.empty()) {
292 // plain script or faulty script -- run directly
294 lua->do_command ("function ardour () end");
295 if (0 == lua->do_command (script)) {
296 append_text ("> OK");
298 } catch (luabridge::LuaException const& e) {
299 append_text (string_compose (_("LuaException: %1"), e.what()));
300 } catch (Glib::Exception const& e) {
301 append_text (string_compose (_("Glib Exception: %1"), e.what()));
302 } catch (std::exception const& e) {
303 append_text (string_compose (_("C++ Exception: %1"), e.what()));
305 append_text (string_compose (_("C++ Exception: %1"), "..."));
308 // script with factory method
310 lua_State* L = lua->getState();
311 lua->do_command ("function ardour () end");
313 LuaScriptParamList args = LuaScriptParams::script_params (script, "action_param", false);
314 luabridge::LuaRef tbl_arg (luabridge::newTable(L));
315 LuaScriptParams::params_to_ref (&tbl_arg, args);
316 lua->do_command (script); // register "factory"
317 luabridge::LuaRef lua_factory = luabridge::getGlobal (L, "factory");
318 if (lua_factory.isFunction()) {
319 lua_factory(tbl_arg)();
321 lua->do_command ("factory = nil;");
322 } catch (luabridge::LuaException const& e) {
323 append_text (string_compose (_("LuaException: %1"), e.what()));
324 } catch (Glib::Exception const& e) {
325 append_text (string_compose (_("Glib Exception: %1"), e.what()));
326 } catch (std::exception const& e) {
327 append_text (string_compose (_("C++ Exception: %1"), e.what()));
329 append_text (string_compose (_("C++ Exception: %1"), "..."));
335 LuaWindow::append_text (std::string s)
337 Glib::RefPtr<Gtk::TextBuffer> tb (outtext.get_buffer());
338 tb->insert (tb->end(), s + "\n");
340 Gtkmm2ext::UI::instance()->flush_pending (0.05);
344 LuaWindow::clear_output ()
346 Glib::RefPtr<Gtk::TextBuffer> tb (outtext.get_buffer());
351 LuaWindow::edit_script (const std::string& name, const std::string& script)
353 ScriptBuffer* sb = new LuaWindow::ScriptBuffer (name);
355 script_buffers.push_back (ScriptBufferPtr (sb));
356 script_selection_changed (script_buffers.back ());
357 refresh_scriptlist ();
362 LuaWindow::new_script ()
365 snprintf (buf, sizeof (buf), "#%d", count_scratch_buffers () + 1);
366 script_buffers.push_back (ScriptBufferPtr (new LuaWindow::ScriptBuffer (buf)));
367 script_selection_changed (script_buffers.back ());
368 refresh_scriptlist ();
372 LuaWindow::delete_script ()
374 assert ((_current_buffer->flags & Buffer_Scratch) || !(_current_buffer->flags & Buffer_ReadOnly));
375 bool refresh = false;
377 if (_current_buffer->flags & Buffer_HasFile) {
378 if (0 == ::g_unlink (_current_buffer->path.c_str())) {
379 append_text (X_("> ") + string_compose (_("Deleted %1"), _current_buffer->path));
382 append_text (X_("> ") + string_compose (_("Failed to delete %1"), _current_buffer->path));
385 for (ScriptBufferList::iterator i = script_buffers.begin (); i != script_buffers.end (); ++i) {
386 if ((*i) == _current_buffer) {
387 script_buffers.erase (i);
392 for (ScriptBufferList::const_iterator i = script_buffers.begin (); i != script_buffers.end (); ++i) {
393 if ((*i)->flags & Buffer_Scratch) {
394 script_selection_changed (*i);
402 LuaScripting::instance ().refresh (true);
407 LuaWindow::revert_script ()
409 _current_buffer->flags &= BufferFlags(~Buffer_Valid);
410 script_selection_changed (_current_buffer, true);
414 LuaWindow::import_script ()
416 // TODO: dialog to select file or enter URL
417 // TODO convert a few URL (eg. pastebin) to raw.
419 char *url = "http://pastebin.com/raw/3UMkZ6nV";
420 char *rv = ArdourCurl::http_get (url, 0. true);
423 Glib::RefPtr<Gtk::TextBuffer> tb (entry.get_buffer());
425 _current_buffer->flags &= BufferFlags(~Buffer_Dirty);
433 LuaWindow::save_script ()
435 Glib::RefPtr<Gtk::TextBuffer> tb (entry.get_buffer());
436 std::string script = tb->get_text();
437 std::string msg = "Unknown error";
440 LuaScriptInfoPtr lsi = LuaScripting::script_info (script);
441 ScriptBuffer & sb (*_current_buffer);
443 assert (sb.flags & Buffer_Dirty);
445 // 1) check if it has a valid header and factory
446 const std::string& bytecode = LuaScripting::get_factory_bytecode (script);
447 if (bytecode.empty()) {
448 msg = _("Missing script header.\nThe script requires an '{ardour}' info table and a 'factory' function.");
452 if (!LuaScripting::try_compile (script, LuaScriptParams::script_params (script, "action_param", false))) {
453 msg = _("Script fails to compile.");
457 // 2) check script name & type
458 lsi = LuaScripting::script_info (script);
460 msg = _("Invalid or missing script-name or script-type.");
464 if (lsi->type != LuaScriptInfo::Snippet && lsi->type != LuaScriptInfo::EditorAction) {
465 msg = _("Invalid script-type.\nValid types are 'EditorAction' and 'Snippet'.");
469 // 3) if there's already a writable file,...
470 if ((sb.flags & Buffer_HasFile) && !(sb.flags & Buffer_ReadOnly)) {
472 Glib::file_set_contents (sb.path, script);
474 sb.flags &= BufferFlags(~Buffer_Dirty);
475 update_gui_state (); // XXX here?
476 append_text (X_("> ") + string_compose (_("Saved as %1"), sb.path));
478 } catch (Glib::FileError const& e) {
479 msg = string_compose (_("Error saving file: %1"), e.what());
484 // 4) check if the name is unique for the given type; locally at least
485 if (true /*sb.flags & Buffer_HasFile*/) {
486 LuaScriptList& lsl (LuaScripting::instance ().scripts (lsi->type));
487 for (LuaScriptList::const_iterator s = lsl.begin(); s != lsl.end(); ++s) {
488 if ((*s)->name == lsi->name) {
489 msg = string_compose (_("Script with given name '%1' already exists.\nUse a different name in the descriptor."), lsi->name);
495 // 5) construct filename -- TODO ask user for name, ask to replace file.
500 struct tm * timeinfo = localtime (&t);
501 strftime (tme, sizeof(tme), "%s", timeinfo);
502 snprintf (buf, sizeof(buf), "%s%ld", tme, random ());
504 std::string fn = md5.digestString (buf);
507 case LuaScriptInfo::EditorAction:
510 case LuaScriptInfo::Snippet:
516 path = Glib::build_filename (LuaScripting::user_script_dir (), fn.substr(0, 11) + ".lua");
517 } while (Glib::file_test (path, Glib::FILE_TEST_EXISTS));
520 Glib::file_set_contents (path, script);
523 sb.flags |= Buffer_HasFile;
524 sb.flags &= BufferFlags(~Buffer_Dirty);
525 sb.flags &= BufferFlags(~Buffer_ReadOnly);
526 update_gui_state (); // XXX here? .refresh (true) may trigger this, too
527 LuaScripting::instance().refresh (true);
528 append_text (X_("> ") + string_compose (_("Saved as %1"), path));
530 } catch (Glib::FileError const& e) {
531 msg = string_compose (_("Error saving file: %1"), e.what());
536 MessageDialog am (msg);
541 LuaWindow::setup_buffers ()
543 if (script_buffers.size() > 0) {
546 script_buffers.push_back (ScriptBufferPtr (new LuaWindow::ScriptBuffer("#1")));
547 _current_buffer = script_buffers.front();
549 Glib::RefPtr<Gtk::TextBuffer> tb (entry.get_buffer());
550 tb->set_text (_current_buffer->script);
552 refresh_scriptlist ();
557 LuaWindow::count_scratch_buffers () const
560 for (ScriptBufferList::const_iterator i = script_buffers.begin (); i != script_buffers.end (); ++i) {
561 if ((*i)->flags & Buffer_Scratch) {
569 LuaWindow::refresh_scriptlist ()
571 for (ScriptBufferList::iterator i = script_buffers.begin (); i != script_buffers.end ();) {
572 if ((*i)->flags & Buffer_Scratch) {
576 i = script_buffers.erase (i);
578 LuaScriptList& lsa (LuaScripting::instance ().scripts (LuaScriptInfo::EditorAction));
579 for (LuaScriptList::const_iterator s = lsa.begin(); s != lsa.end(); ++s) {
580 script_buffers.push_back (ScriptBufferPtr (new LuaWindow::ScriptBuffer(*s)));
583 LuaScriptList& lss (LuaScripting::instance ().scripts (LuaScriptInfo::Snippet));
584 for (LuaScriptList::const_iterator s = lss.begin(); s != lss.end(); ++s) {
585 script_buffers.push_back (ScriptBufferPtr (new LuaWindow::ScriptBuffer(*s)));
591 LuaWindow::rebuild_menu ()
593 using namespace Menu_Helpers;
595 _menu_scratch = manage (new Menu);
596 _menu_snippet = manage (new Menu);
597 _menu_actions = manage (new Menu);
599 MenuList& items_scratch (_menu_scratch->items());
600 MenuList& items_snippet (_menu_snippet->items());
601 MenuList& items_actions (_menu_actions->items());
604 Menu_Helpers::MenuElem elem = Gtk::Menu_Helpers::MenuElem(_("New"),
605 sigc::mem_fun(*this, &LuaWindow::new_script));
606 items_scratch.push_back(elem);
609 items_scratch.push_back(SeparatorElem());
611 for (ScriptBufferList::const_iterator i = script_buffers.begin (); i != script_buffers.end (); ++i) {
613 if ((*i)->flags & Buffer_ReadOnly) {
614 name = "[R] " + (*i)->name;
618 Menu_Helpers::MenuElem elem = Gtk::Menu_Helpers::MenuElem(name,
619 sigc::bind(sigc::mem_fun(*this, &LuaWindow::script_selection_changed), (*i), false));
621 if ((*i)->flags & Buffer_Scratch) {
622 items_scratch.push_back(elem);
624 else if ((*i)->type == LuaScriptInfo::EditorAction) {
625 items_actions.push_back(elem);
627 else if ((*i)->type == LuaScriptInfo::Snippet) {
628 items_snippet.push_back(elem);
632 script_select.clear_items ();
633 script_select.AddMenuElem (Menu_Helpers::MenuElem ("Scratch", *_menu_scratch));
634 script_select.AddMenuElem (Menu_Helpers::MenuElem ("Snippets", *_menu_snippet));
635 script_select.AddMenuElem (Menu_Helpers::MenuElem ("Actions", *_menu_actions));
639 LuaWindow::script_selection_changed (ScriptBufferPtr n, bool force)
641 if (n == _current_buffer && !force) {
645 Glib::RefPtr<Gtk::TextBuffer> tb (entry.get_buffer());
647 if (_current_buffer->flags & Buffer_Valid) {
648 _current_buffer->script = tb->get_text();
651 if (!(n->flags & Buffer_Valid)) {
653 append_text ("! Failed to load buffer.");
657 if (n->flags & Buffer_Valid) {
659 _script_changed_connection.block ();
660 tb->set_text (n->script);
661 _script_changed_connection.unblock ();
663 append_text ("! Failed to switch buffer.");
669 LuaWindow::update_gui_state ()
671 const ScriptBuffer & sb (*_current_buffer);
673 if (sb.flags & Buffer_Scratch) {
674 name = string_compose (_("Scratch Buffer %1"), sb.name);
675 } else if (sb.type == LuaScriptInfo::EditorAction) {
676 name = string_compose (_("Action: '%1'"), sb.name);
677 } else if (sb.type == LuaScriptInfo::Snippet) {
678 name = string_compose (_("Snippet: %1"), sb.name);
680 cerr << "Invalid Script type\n";
684 if (sb.flags & Buffer_Dirty) {
687 script_select.set_text(name);
689 if (sb.flags & Buffer_ReadOnly) {
690 _btn_save.set_text (_("Save as"));
692 _btn_save.set_text (_("Save"));
694 _btn_save.set_sensitive (sb.flags & Buffer_Dirty);
695 _btn_delete.set_sensitive (sb.flags & Buffer_Scratch || ((sb.flags & (Buffer_ReadOnly | Buffer_HasFile)) == Buffer_HasFile));
696 _btn_revert.set_sensitive ((sb.flags & Buffer_Dirty) && (sb.flags & Buffer_HasFile));
700 LuaWindow::script_changed () {
701 if (_current_buffer->flags & Buffer_Dirty) {
704 _current_buffer->flags |= Buffer_Dirty;
708 LuaWindow::ScriptBuffer::ScriptBuffer (const std::string& n)
710 , flags (Buffer_Scratch | Buffer_Valid)
713 "---- this header is (only) required to save the script\n"
714 "-- ardour { [\"type\"] = \"Snippet\", name = \"\" }\n"
715 "-- function factory () return function () -- -- end end\n";
718 LuaWindow::ScriptBuffer::ScriptBuffer (LuaScriptInfoPtr p)
721 , flags (Buffer_HasFile)
724 if (!PBD::exists_and_writable (path)) {
725 flags |= Buffer_ReadOnly;
727 if (path.find (user_config_directory ()) != 0) {
728 // mark non-user scripts as read-only
729 flags |= Buffer_ReadOnly;
734 LuaWindow::ScriptBuffer::ScriptBuffer (const ScriptBuffer& other)
735 : script (other.script)
738 , flags (other.flags)
744 LuaWindow::ScriptBuffer::~ScriptBuffer ()
749 LuaWindow::ScriptBuffer::load ()
751 assert (!(flags & Buffer_Valid));
752 if (!(flags & Buffer_HasFile)) return false;
754 script = Glib::file_get_contents (path);
755 flags |= Buffer_Valid;
756 flags &= BufferFlags(~Buffer_Dirty);
757 } catch (Glib::FileError const& e) {