1 /*
2  * Copyright (C) 2016-2017 Robin Gareus <robin@gareus.org>
3  * Copyright (C) 2016-2018 Paul Davis <paul@linuxaudiosystems.com>
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License along
16  * with this program; if not, write to the Free Software Foundation, Inc.,
17  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18  */
19 
20 #ifdef PLATFORM_WINDOWS
21 #define random() rand()
22 #endif
23 
24 #ifdef WAF_BUILD
25 #include "gtk2ardour-config.h"
26 #endif
27 
28 #include "pbd/gstdio_compat.h"
29 #include <glibmm/fileutils.h>
30 #include <gtkmm/messagedialog.h>
31 
32 #include "pbd/basename.h"
33 #include "pbd/file_utils.h"
34 #include "pbd/md5.h"
35 
36 #include "gtkmm2ext/gtk_ui.h"
37 #include "gtkmm2ext/utils.h"
38 #include "gtkmm2ext/window_title.h"
39 
40 #include "widgets/pane.h"
41 #include "widgets/tooltips.h"
42 
43 #include "ardour/filesystem_paths.h"
44 #include "ardour/luabindings.h"
45 #include "LuaBridge/LuaBridge.h"
46 
47 #include "ardour_ui.h"
48 #include "gui_thread.h"
49 #include "luainstance.h"
50 #include "luawindow.h"
51 #include "public_editor.h"
52 #include "utils.h"
53 #include "ui_config.h"
54 #include "utils_videotl.h"
55 
56 #include "pbd/i18n.h"
57 
58 using namespace ARDOUR;
59 using namespace PBD;
60 using namespace Gtk;
61 using namespace Glib;
62 using namespace Gtkmm2ext;
63 using namespace std;
64 
65 
operator |(const LuaWindow::BufferFlags & a,const LuaWindow::BufferFlags & b)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));
68 }
69 
operator |=(LuaWindow::BufferFlags & a,const LuaWindow::BufferFlags & 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));
72 }
73 
operator &=(LuaWindow::BufferFlags & a,const LuaWindow::BufferFlags & 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));
76 }
77 
78 LuaWindow* LuaWindow::_instance = 0;
79 
80 LuaWindow*
instance()81 LuaWindow::instance ()
82 {
83 	if (!_instance) {
84 		_instance  = new LuaWindow;
85 	}
86 
87 	return _instance;
88 }
89 
LuaWindow()90 LuaWindow::LuaWindow ()
91 	: Window (Gtk::WINDOW_TOPLEVEL)
92 	, VisibilityTracker (*((Gtk::Window*) this))
93 	, lua (0)
94 	, _visible (false)
95 	, _menu_scratch (0)
96 	, _menu_snippet (0)
97 	, _menu_actions (0)
98 	, _btn_run (_("Run"))
99 	, _btn_clear (_("Clear Output"))
100 	, _btn_open (_("Import"))
101 	, _btn_save (_("Save"))
102 	, _btn_delete (_("Delete"))
103 	, _btn_revert (_("Revert"))
104 	, _current_buffer ()
105 {
106 	set_name ("Lua");
107 
108 	reinit_lua ();
109 	update_title ();
110 	set_wmclass (X_("ardour_mixer"), PROGRAM_NAME);
111 
112 #ifdef __APPLE__
113 	set_type_hint (Gdk::WINDOW_TYPE_HINT_DIALOG);
114 #else
115 	if (UIConfiguration::instance().get_all_floating_windows_are_dialogs()) {
116 		set_type_hint (Gdk::WINDOW_TYPE_HINT_DIALOG);
117 	} else {
118 		set_type_hint (Gdk::WINDOW_TYPE_HINT_UTILITY);
119 	}
120 #endif
121 
122 	script_select.disable_scrolling ();
123 
124 	set_border_width (0);
125 
126 	outtext.set_editable (false);
127 	outtext.set_wrap_mode (Gtk::WRAP_WORD);
128 	outtext.set_cursor_visible (false);
129 
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));
132 
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));
139 
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);
144 
145 	// layout
146 
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);
152 
153 	entry.set_name ("ArdourLuaEntry");
154 	outtext.set_name ("ArdourLuaEntry");
155 
156 	Gtk::HBox *hbox = manage (new HBox());
157 
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);
165 
166 	Gtk::VBox *vbox = manage (new VBox());
167 	vbox->pack_start (*scrollin, true, true, 0);
168 	vbox->pack_start (*hbox, false, false, 2);
169 
170 	ArdourWidgets::VPane *vpane = manage (new ArdourWidgets::VPane ());
171 	vpane->add (*vbox);
172 	vpane->add (scrollout);
173 	vpane->set_divider (0, 0.75);
174 
175 	vpane->show_all ();
176 	add (*vpane);
177 	set_size_request (640, 480); // XXX
178 	ArdourWidgets::set_tooltip (script_select, _("Select Editor Buffer"));
179 
180 	setup_buffers ();
181 	LuaScripting::instance().scripts_changed.connect (*this, invalidator (*this), boost::bind (&LuaWindow::refresh_scriptlist, this), gui_context());
182 
183 	Glib::RefPtr<Gtk::TextBuffer> tb (entry.get_buffer());
184 	_script_changed_connection = tb->signal_changed().connect (sigc::mem_fun(*this, &LuaWindow::script_changed));
185 }
186 
~LuaWindow()187 LuaWindow::~LuaWindow ()
188 {
189 	delete lua;
190 }
191 
192 void
show_window()193 LuaWindow::show_window ()
194 {
195 	present();
196 	_visible = true;
197 }
198 
199 bool
hide_window(GdkEventAny * ev)200 LuaWindow::hide_window (GdkEventAny *ev)
201 {
202 	if (!_visible) return 0;
203 	_visible = false;
204 	return ARDOUR_UI_UTILS::just_hide_it (ev, static_cast<Gtk::Window *>(this));
205 }
206 
reinit_lua()207 void LuaWindow::reinit_lua ()
208 {
209 	ENSURE_GUI_THREAD (*this, &LuaWindow::session_going_away);
210 	delete lua;
211 	lua = new LuaState();
212 	lua->Print.connect (sigc::mem_fun (*this, &LuaWindow::append_text));
213 	lua->sandbox (false);
214 
215 	lua_State* L = lua->getState();
216 	LuaInstance::register_classes (L);
217 	luabridge::push <PublicEditor *> (L, &PublicEditor::instance());
218 	lua_setglobal (L, "Editor");
219 }
220 
set_session(Session * s)221 void LuaWindow::set_session (Session* s)
222 {
223 	SessionHandlePtr::set_session (s);
224 	if (!_session) {
225 		return;
226 	}
227 
228 	update_title ();
229 	_session->DirtyChanged.connect (_session_connections, invalidator (*this), boost::bind (&LuaWindow::update_title, this), gui_context());
230 
231 	lua_State* L = lua->getState();
232 	LuaBindings::set_session (L, _session);
233 }
234 
235 void
session_going_away()236 LuaWindow::session_going_away ()
237 {
238 	ENSURE_GUI_THREAD (*this, &LuaWindow::session_going_away);
239 	reinit_lua (); // drop state (all variables, session references)
240 
241 	SessionHandlePtr::session_going_away ();
242 	_session = 0;
243 	update_title ();
244 
245 	lua_State* L = lua->getState();
246 	LuaBindings::set_session (L, _session);
247 }
248 
249 void
update_title()250 LuaWindow::update_title ()
251 {
252 	if (_session) {
253 		string n;
254 
255 		if (_session->snap_name() != _session->name()) {
256 			n = _session->snap_name ();
257 		} else {
258 			n = _session->name ();
259 		}
260 
261 		if (_session->dirty ()) {
262 			n = "*" + n;
263 		}
264 
265 		WindowTitle title (n);
266 		title += S_("Window|Lua");
267 		title += Glib::get_application_name ();
268 		set_title (title.get_string());
269 
270 	} else {
271 		WindowTitle title (S_("Window|Lua"));
272 		title += Glib::get_application_name ();
273 		set_title (title.get_string());
274 	}
275 }
276 
277 void
scroll_to_bottom()278 LuaWindow::scroll_to_bottom ()
279 {
280 	Gtk::Adjustment *adj;
281 	adj = scrollout.get_vadjustment();
282 	adj->set_value (MAX(0,(adj->get_upper() - adj->get_page_size())));
283 }
284 
285 void
run_script()286 LuaWindow::run_script ()
287 {
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
293 		try {
294 			lua->do_command ("function ardour () end");
295 			if (0 == lua->do_command (script)) {
296 				append_text ("> OK");
297 			}
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()));
304 		} catch (...) {
305 			append_text (string_compose (_("C++ Exception: %1"), "..."));
306 		}
307 	} else {
308 		// script with factory method
309 		try {
310 			lua_State* L = lua->getState();
311 			lua->do_command ("function ardour () end");
312 
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)();
320 			}
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()));
328 		} catch (...) {
329 			append_text (string_compose (_("C++ Exception: %1"), "..."));
330 		}
331 	}
332 	lua->collect_garbage ();
333 }
334 
335 void
append_text(std::string s)336 LuaWindow::append_text (std::string s)
337 {
338 	Glib::RefPtr<Gtk::TextBuffer> tb (outtext.get_buffer());
339 	tb->insert (tb->end(), s + "\n");
340 	scroll_to_bottom ();
341 	Gtkmm2ext::UI::instance()->flush_pending (0.05);
342 }
343 
344 void
clear_output()345 LuaWindow::clear_output ()
346 {
347 	Glib::RefPtr<Gtk::TextBuffer> tb (outtext.get_buffer());
348 	tb->set_text ("");
349 }
350 
351 void
edit_script(const std::string & name,const std::string & script)352 LuaWindow::edit_script (const std::string& name, const std::string& script)
353 {
354 	ScriptBuffer* sb = new LuaWindow::ScriptBuffer (name);
355 	sb->script = script;
356 	script_buffers.push_back (ScriptBufferPtr (sb));
357 	script_selection_changed (script_buffers.back ());
358 	refresh_scriptlist ();
359 	show_window ();
360 }
361 
362 void
new_script()363 LuaWindow::new_script ()
364 {
365 	char buf[32];
366 	snprintf (buf, sizeof (buf), "#%d", count_scratch_buffers () + 1);
367 	script_buffers.push_back (ScriptBufferPtr (new LuaWindow::ScriptBuffer (buf)));
368 	script_selection_changed (script_buffers.back ());
369 	refresh_scriptlist ();
370 }
371 
372 void
delete_script()373 LuaWindow::delete_script ()
374 {
375 	assert ((_current_buffer->flags & Buffer_Scratch) || !(_current_buffer->flags & Buffer_ReadOnly));
376 	bool refresh = false;
377 	bool neednew = true;
378 	if (_current_buffer->flags & Buffer_HasFile) {
379 		if (0 == ::g_unlink (_current_buffer->path.c_str())) {
380 			append_text (X_("> ") + string_compose (_("Deleted %1"), _current_buffer->path));
381 			refresh = true;
382 		} else {
383 			append_text (X_("> ") + string_compose (_("Failed to delete %1"), _current_buffer->path));
384 		}
385 	}
386 	for (ScriptBufferList::iterator i = script_buffers.begin (); i != script_buffers.end (); ++i) {
387 		if ((*i) == _current_buffer) {
388 			script_buffers.erase (i);
389 			break;
390 		}
391 	}
392 
393 	for (ScriptBufferList::const_iterator i = script_buffers.begin (); i != script_buffers.end (); ++i) {
394 		if ((*i)->flags & Buffer_Scratch) {
395 			script_selection_changed (*i);
396 			neednew = false;
397 		}
398 	}
399 	if (neednew) {
400 		new_script ();
401 	}
402 	if (refresh) {
403 		LuaScripting::instance ().refresh (true);
404 	}
405 }
406 
407 void
revert_script()408 LuaWindow::revert_script ()
409 {
410 	_current_buffer->flags &= BufferFlags(~Buffer_Valid);
411 	script_selection_changed (_current_buffer, true);
412 }
413 
414 void
import_script()415 LuaWindow::import_script ()
416 {
417 	// TODO: dialog to select file or enter URL
418 	// TODO convert a few URL (eg. pastebin) to raw.
419 #if 0
420 	char *url = "http://pastebin.com/raw/3UMkZ6nV";
421 	char *rv = ArdourCurl::http_get (url, 0. true);
422 	if (rv) {
423 		new_script ();
424 		Glib::RefPtr<Gtk::TextBuffer> tb (entry.get_buffer());
425 		tb->set_text (rv);
426 		_current_buffer->flags &= BufferFlags(~Buffer_Dirty);
427 		update_gui_state ();
428 	}
429 	free (rv);
430 #endif
431 }
432 
433 void
save_script()434 LuaWindow::save_script ()
435 {
436 	Glib::RefPtr<Gtk::TextBuffer> tb (entry.get_buffer());
437 	std::string script = tb->get_text();
438 	std::string msg = "Unknown error";
439 
440 	std::string path;
441 	LuaScriptInfoPtr lsi = LuaScripting::script_info (script);
442 	ScriptBuffer & sb (*_current_buffer);
443 
444 	assert (sb.flags & Buffer_Dirty);
445 
446 	// 1) check if it has a valid header and factory
447 	const std::string& bytecode = LuaScripting::get_factory_bytecode (script);
448 	if (bytecode.empty()) {
449 		msg = _("Missing script header.\nThe script requires an '{ardour}' info table and a 'factory' function.");
450 		goto errorout;
451 	}
452 
453 	if (!LuaScripting::try_compile (script, LuaScriptParams::script_params (script, "action_param", false))) {
454 		msg = _("Script fails to compile.");
455 		goto errorout;
456 	}
457 
458 	// 2) check script name & type
459 	lsi = LuaScripting::script_info (script);
460 	if (!lsi) {
461 		msg = _("Invalid or missing script-name or script-type.");
462 		goto errorout;
463 	}
464 
465 	if (lsi->type != LuaScriptInfo::Snippet && lsi->type != LuaScriptInfo::EditorAction) {
466 		msg = _("Invalid script-type.\nValid types are 'EditorAction' and 'Snippet'.");
467 		goto errorout;
468 	}
469 
470 	// 3) if there's already a writable file,...
471 	if ((sb.flags & Buffer_HasFile) && !(sb.flags & Buffer_ReadOnly)) {
472 		try {
473 			Glib::file_set_contents (sb.path, script);
474 			sb.name = lsi->name;
475 			sb.flags &= BufferFlags(~Buffer_Dirty);
476 			update_gui_state (); // XXX here?
477 			append_text (X_("> ") + string_compose (_("Saved as %1"), sb.path));
478 			return; // OK
479 		} catch (Glib::FileError const& e) {
480 			msg = string_compose (_("Error saving file: %1"), e.what());
481 			goto errorout;
482 		}
483 	}
484 
485 	// 4) check if the name is unique for the given type; locally at least
486 	if (true /*sb.flags & Buffer_HasFile*/) {
487 		LuaScriptList& lsl (LuaScripting::instance ().scripts (lsi->type));
488 		for (LuaScriptList::const_iterator s = lsl.begin(); s != lsl.end(); ++s) {
489 			if ((*s)->name == lsi->name) {
490 				msg = string_compose (_("Script with given name '%1' already exists.\nUse a different name in the descriptor."), lsi->name);
491 				goto errorout;
492 			}
493 		}
494 	}
495 
496 	// 5) construct filename -- TODO ask user for name, ask to replace file.
497 	do {
498 		char tme[80];
499 		char buf[100];
500 		time_t t = time(0);
501 		struct tm * timeinfo = localtime (&t);
502 		strftime (tme, sizeof(tme), "%s", timeinfo);
503 		snprintf (buf, sizeof(buf), "%s%ld", tme, random ());
504 		MD5 md5;
505 		std::string fn = md5.digestString (buf);
506 
507 		switch (lsi->type) {
508 			case LuaScriptInfo::EditorAction:
509 				fn = "a_" + fn;
510 				break;
511 			case LuaScriptInfo::Snippet:
512 				fn = "s_" + fn;
513 				break;
514 			default:
515 				break;
516 		}
517 		path = Glib::build_filename (LuaScripting::user_script_dir (), fn.substr(0, 11) + ".lua");
518 	} while (Glib::file_test (path, Glib::FILE_TEST_EXISTS));
519 
520 	try {
521 		Glib::file_set_contents (path, script);
522 		sb.path = path;
523 		sb.name = lsi->name;
524 		sb.flags |= Buffer_HasFile;
525 		sb.flags &= BufferFlags(~Buffer_Dirty);
526 		sb.flags &= BufferFlags(~Buffer_ReadOnly);
527 		update_gui_state (); // XXX here? .refresh (true) may trigger this, too
528 		LuaScripting::instance().refresh (true);
529 		append_text (X_("> ") + string_compose (_("Saved as %1"), path));
530 		return; // OK
531 	} catch (Glib::FileError const& e) {
532 		msg = string_compose (_("Error saving file: %1"), e.what());
533 		goto errorout;
534 	}
535 
536 errorout:
537 		MessageDialog am (msg);
538 		am.run ();
539 }
540 
541 void
setup_buffers()542 LuaWindow::setup_buffers ()
543 {
544 	if (script_buffers.size() > 0) {
545 		return;
546 	}
547 	script_buffers.push_back (ScriptBufferPtr (new LuaWindow::ScriptBuffer("#1")));
548 	_current_buffer = script_buffers.front();
549 
550 	Glib::RefPtr<Gtk::TextBuffer> tb (entry.get_buffer());
551 	tb->set_text (_current_buffer->script);
552 
553 	refresh_scriptlist ();
554 	update_gui_state ();
555 }
556 
557 uint32_t
count_scratch_buffers() const558 LuaWindow::count_scratch_buffers () const
559 {
560 	uint32_t n = 0;
561 	for (ScriptBufferList::const_iterator i = script_buffers.begin (); i != script_buffers.end (); ++i) {
562 		if ((*i)->flags & Buffer_Scratch) {
563 			++n;
564 		}
565 	}
566 	return n;
567 }
568 
569 void
refresh_scriptlist()570 LuaWindow::refresh_scriptlist ()
571 {
572 	for (ScriptBufferList::iterator i = script_buffers.begin (); i != script_buffers.end ();) {
573 		if ((*i)->flags & Buffer_Scratch) {
574 			++i;
575 			continue;
576 		}
577 		i = script_buffers.erase (i);
578 	}
579 	LuaScriptList& lsa (LuaScripting::instance ().scripts (LuaScriptInfo::EditorAction));
580 	for (LuaScriptList::const_iterator s = lsa.begin(); s != lsa.end(); ++s) {
581 		script_buffers.push_back (ScriptBufferPtr (new LuaWindow::ScriptBuffer(*s)));
582 	}
583 
584 	LuaScriptList& lss (LuaScripting::instance ().scripts (LuaScriptInfo::Snippet));
585 	for (LuaScriptList::const_iterator s = lss.begin(); s != lss.end(); ++s) {
586 		script_buffers.push_back (ScriptBufferPtr (new LuaWindow::ScriptBuffer(*s)));
587 	}
588 	rebuild_menu ();
589 }
590 
591 void
rebuild_menu()592 LuaWindow::rebuild_menu ()
593 {
594 	using namespace Menu_Helpers;
595 
596 	_menu_scratch = manage (new Menu);
597 	_menu_snippet = manage (new Menu);
598 	_menu_actions = manage (new Menu);
599 
600 	MenuList& items_scratch (_menu_scratch->items());
601 	MenuList& items_snippet (_menu_snippet->items());
602 	MenuList& items_actions (_menu_actions->items());
603 
604 	{
605 		Menu_Helpers::MenuElem elem = Gtk::Menu_Helpers::MenuElem(_("New"),
606 				sigc::mem_fun(*this, &LuaWindow::new_script));
607 		items_scratch.push_back(elem);
608 	}
609 
610 	items_scratch.push_back(SeparatorElem());
611 
612 	for (ScriptBufferList::const_iterator i = script_buffers.begin (); i != script_buffers.end (); ++i) {
613 		std::string name;
614 		if ((*i)->flags & Buffer_ReadOnly) {
615 			name = "[R] " + (*i)->name;
616 		} else {
617 			name = (*i)->name;
618 		}
619 		Menu_Helpers::MenuElem elem = Gtk::Menu_Helpers::MenuElem(name,
620 				sigc::bind(sigc::mem_fun(*this, &LuaWindow::script_selection_changed), (*i), false));
621 
622 		if ((*i)->flags & Buffer_Scratch) {
623 			items_scratch.push_back(elem);
624 		}
625 		else if ((*i)->type == LuaScriptInfo::EditorAction) {
626 				items_actions.push_back(elem);
627 		}
628 		else if ((*i)->type == LuaScriptInfo::Snippet) {
629 				items_snippet.push_back(elem);
630 		}
631 	}
632 
633 	script_select.clear_items ();
634 	script_select.AddMenuElem (Menu_Helpers::MenuElem ("Scratch", *_menu_scratch));
635 	script_select.AddMenuElem (Menu_Helpers::MenuElem ("Snippets", *_menu_snippet));
636 	script_select.AddMenuElem (Menu_Helpers::MenuElem ("Actions", *_menu_actions));
637 }
638 
639 void
script_selection_changed(ScriptBufferPtr n,bool force)640 LuaWindow::script_selection_changed (ScriptBufferPtr n, bool force)
641 {
642 	if (n == _current_buffer && !force) {
643 		return;
644 	}
645 
646 	Glib::RefPtr<Gtk::TextBuffer> tb (entry.get_buffer());
647 
648 	if (_current_buffer->flags & Buffer_Valid) {
649 		_current_buffer->script = tb->get_text();
650 	}
651 
652 	if (!(n->flags & Buffer_Valid)) {
653 		if (!n->load()) {
654 			append_text ("! Failed to load buffer.");
655 		}
656 	}
657 
658 	if (n->flags & Buffer_Valid) {
659 		_current_buffer = n;
660 		_script_changed_connection.block ();
661 		tb->set_text (n->script);
662 		_script_changed_connection.unblock ();
663 	} else {
664 		append_text ("! Failed to switch buffer.");
665 	}
666 	update_gui_state ();
667 }
668 
669 void
update_gui_state()670 LuaWindow::update_gui_state ()
671 {
672 	const ScriptBuffer & sb (*_current_buffer);
673 	std::string name;
674 	if (sb.flags & Buffer_Scratch) {
675 		name = string_compose (_("Scratch Buffer %1"), sb.name);
676 	} else if (sb.type == LuaScriptInfo::EditorAction) {
677 		name = string_compose (_("Action: '%1'"), sb.name);
678 	} else if (sb.type == LuaScriptInfo::Snippet) {
679 		name = string_compose (_("Snippet: %1"), sb.name);
680 	} else {
681 		cerr << "Invalid Script type\n";
682 		assert (0);
683 		return;
684 	}
685 	if (sb.flags & Buffer_Dirty) {
686 		name += " *";
687 	}
688 	script_select.set_text(name);
689 
690 	if (sb.flags & Buffer_ReadOnly) {
691 		_btn_save.set_text (_("Save as"));
692 	} else {
693 		_btn_save.set_text (_("Save"));
694 	}
695 	_btn_save.set_sensitive (sb.flags & Buffer_Dirty);
696 	_btn_delete.set_sensitive (sb.flags & Buffer_Scratch || ((sb.flags & (Buffer_ReadOnly | Buffer_HasFile)) == Buffer_HasFile));
697 	_btn_revert.set_sensitive ((sb.flags & Buffer_Dirty) && (sb.flags & Buffer_HasFile));
698 }
699 
700 void
script_changed()701 LuaWindow::script_changed () {
702 	if (_current_buffer->flags & Buffer_Dirty) {
703 		return;
704 	}
705 	_current_buffer->flags |= Buffer_Dirty;
706 	update_gui_state ();
707 }
708 
ScriptBuffer(const std::string & n)709 LuaWindow::ScriptBuffer::ScriptBuffer (const std::string& n)
710 	: name (n)
711 	, flags (Buffer_Scratch | Buffer_Valid)
712 {
713 	script =
714 		"---- this header is (only) required to save the script\n"
715 		"-- ardour { [\"type\"] = \"Snippet\", name = \"\" }\n"
716 		"-- function factory () return function () -- -- end end\n";
717 }
718 
ScriptBuffer(LuaScriptInfoPtr p)719 LuaWindow::ScriptBuffer::ScriptBuffer (LuaScriptInfoPtr p)
720 	: name (p->name)
721 	, path (p->path)
722 	, flags (Buffer_HasFile)
723 	, type (p->type)
724 {
725 	if (!PBD::exists_and_writable (path)) {
726 		flags |= Buffer_ReadOnly;
727 	}
728 	if (path.find (user_config_directory ()) != 0) {
729 		// mark non-user scripts as read-only
730 		flags |= Buffer_ReadOnly;
731 	}
732 }
733 
734 #if 0
735 LuaWindow::ScriptBuffer::ScriptBuffer (const ScriptBuffer& other)
736 	: script (other.script)
737 	, name (other.name)
738 	, path (other.path)
739 	, flags (other.flags)
740 	, type (other.type)
741 {
742 }
743 #endif
744 
~ScriptBuffer()745 LuaWindow::ScriptBuffer::~ScriptBuffer ()
746 {
747 }
748 
749 bool
load()750 LuaWindow::ScriptBuffer::load ()
751 {
752 	assert (!(flags & Buffer_Valid));
753 	if (!(flags & Buffer_HasFile)) return false;
754 	try {
755 		script = Glib::file_get_contents (path);
756 		flags |= Buffer_Valid;
757 		flags &= BufferFlags(~Buffer_Dirty);
758 	} catch (Glib::FileError const& e) {
759 		return false;
760 	}
761 	return true;
762 }
763