1 /*
2  * Copyright (C) 2017-2018 Johannes Mueller <github@johannes-mueller.org>
3  * Copyright (C) 2017-2019 Robin Gareus <robin@gareus.org>
4  * Copyright (C) 2017 Paul Davis <paul@linuxaudiosystems.com>
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License along
17  * with this program; if not, write to the Free Software Foundation, Inc.,
18  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19  */
20 
21 #include <map>
22 #include <vector>
23 #include <cerrno>
24 
25 #include <glib/gstdio.h>
26 
27 #include <gtkmm/filechooserdialog.h>
28 #include <gtkmm/frame.h>
29 #include <gtkmm/liststore.h>
30 #include <gtkmm/notebook.h>
31 #include <gtkmm/progressbar.h>
32 #include <gtkmm/separator.h>
33 #include <gtkmm/scrolledwindow.h>
34 #include <gtkmm/stock.h>
35 #include <gtkmm/textview.h>
36 #include <gtkmm/treeiter.h>
37 #include <gtkmm/treeview.h>
38 
39 #include "pbd/basename.h"
40 #include "pbd/error.h"
41 #include "pbd/file_archive.h"
42 #include "pbd/file_utils.h"
43 #include "pbd/xml++.h"
44 
45 #include "gtkmm2ext/gui_thread.h"
46 #include "gtkmm2ext/utils.h"
47 
48 #include "ardour/directory_names.h"
49 #include "ardour/filename_extensions.h"
50 #include "ardour/filesystem_paths.h"
51 #include "ardour/template_utils.h"
52 
53 #include "progress_reporter.h"
54 #include "template_dialog.h"
55 
56 #include "pbd/i18n.h"
57 
58 using namespace std;
59 using namespace Gtk;
60 using namespace PBD;
61 using namespace ARDOUR;
62 
63 class TemplateManager : public Gtk::HBox,
64 			public ProgressReporter
65 {
66 public:
~TemplateManager()67 	virtual ~TemplateManager () {}
68 
69 	virtual void init () = 0;
70 	void handle_dirty_description ();
71 
72 	PBD::Signal0<void> TemplatesImported;
73 
74 protected:
75 	TemplateManager ();
76 
77 	Gtk::TextView _description_editor;
78 	Gtk::Button _save_desc;
79 
80 	void setup_model (const std::vector<ARDOUR::TemplateInfo>& templates);
81 
82 	void row_selection_changed ();
83 
84 	virtual void delete_selected_template () = 0;
85 	bool adjust_plugin_paths (XMLNode* node, const std::string& name, const std::string& new_name) const;
86 
87 	struct SessionTemplateColumns : public Gtk::TreeModel::ColumnRecord {
SessionTemplateColumnsTemplateManager::SessionTemplateColumns88 		SessionTemplateColumns () {
89 			add (name);
90 			add (path);
91 			add (description);
92 		}
93 
94 		Gtk::TreeModelColumn<std::string> name;
95 		Gtk::TreeModelColumn<std::string> path;
96 		Gtk::TreeModelColumn<std::string> description;
97 	};
98 
99 	Glib::RefPtr<Gtk::ListStore>  _template_model;
100 	SessionTemplateColumns _template_columns;
101 
102 	Gtk::TreeModel::const_iterator _current_selection;
103 
104 	Gtk::ProgressBar _progress_bar;
105 	std::string _current_action;
106 
107 private:
108 	void render_template_names (Gtk::CellRenderer* rnd, const Gtk::TreeModel::iterator& it);
109 	void validate_edit (const Glib::ustring& path_string, const Glib::ustring& new_name);
110 	void start_edit ();
111 
112 	void set_desc_dirty ();
113 
114 	bool key_event (GdkEventKey* ev);
115 
116 	virtual void get_templates (vector<TemplateInfo>& templates) const = 0;
117 
118 	virtual void rename_template (Gtk::TreeModel::iterator& item, const Glib::ustring& new_name) = 0;
119 
120 	virtual void save_template_desc ();
121 
122 	void export_all_templates ();
123 	void import_template_set ();
124 
125 	virtual std::string templates_dir () const = 0;
126 	virtual std::string templates_dir_basename () const = 0;
127 	virtual std::string template_file (const Gtk::TreeModel::const_iterator& item) const = 0;
128 
129 	virtual bool adjust_xml_tree (XMLTree& tree, const std::string& old_name, const std::string& new_name) const = 0;
130 
131 	Gtk::TreeView _template_treeview;
132 	Gtk::CellRendererText _validating_cellrenderer;
133 	Gtk::TreeView::Column _validated_column;
134 
135 	bool _desc_dirty;
136 
137 	Gtk::Button _remove_button;
138 	Gtk::Button _rename_button;
139 
140 	Gtk::Button _export_all_templates_button;
141 	Gtk::Button _import_template_set_button;
142 
143 	sigc::connection _cursor_changed_connection;
144 
145 	void update_progress_gui (float p);
146 };
147 
148 class SessionTemplateManager : public TemplateManager
149 {
150 public:
SessionTemplateManager()151 	SessionTemplateManager () : TemplateManager () {}
~SessionTemplateManager()152 	~SessionTemplateManager () {}
153 
154 	void init ();
155 
156 	void get_templates (vector<TemplateInfo>& templates) const;
157 
158 private:
159 	void rename_template (Gtk::TreeModel::iterator& item, const Glib::ustring& new_name);
160 	void delete_selected_template ();
161 
162 	std::string templates_dir () const;
163 	virtual std::string templates_dir_basename () const;
164 	std::string template_file (const Gtk::TreeModel::const_iterator& item) const;
165 
166 	bool adjust_xml_tree (XMLTree& tree, const std::string& old_name, const std::string& new_name) const;
167 };
168 
169 
170 class RouteTemplateManager : public TemplateManager
171 {
172 public:
RouteTemplateManager()173 	RouteTemplateManager () : TemplateManager () {}
~RouteTemplateManager()174 	~RouteTemplateManager () {}
175 
176 	void init ();
177 
178 	void get_templates (vector<TemplateInfo>& templates) const;
179 
180 private:
181 	void rename_template (Gtk::TreeModel::iterator& item, const Glib::ustring& new_name);
182 	void delete_selected_template ();
183 
184 	std::string templates_dir () const;
185 	virtual std::string templates_dir_basename () const;
186 	std::string template_file (const Gtk::TreeModel::const_iterator& item) const;
187 
188 	bool adjust_xml_tree (XMLTree& tree, const std::string& old_name, const std::string& new_name) const;
189 };
190 
191 
TemplateDialog()192 TemplateDialog::TemplateDialog ()
193 	: ArdourDialog ("Manage Templates")
194 {
195 	Notebook* nb = manage (new Notebook);
196 
197 	SessionTemplateManager* session_tm = manage (new SessionTemplateManager);
198 	nb->append_page (*session_tm, _("Session Templates"));
199 
200 	RouteTemplateManager* route_tm = manage (new RouteTemplateManager);
201 	nb->append_page (*route_tm, _("Track Templates"));
202 
203 	get_vbox()->pack_start (*nb);
204 	add_button (_("Done"), Gtk::RESPONSE_OK);
205 
206 	get_vbox()->show_all();
207 
208 	session_tm->init ();
209 	route_tm->init ();
210 
211 	session_tm->TemplatesImported.connect (*this, invalidator (*this), boost::bind (&RouteTemplateManager::init, route_tm), gui_context ());
212 	route_tm->TemplatesImported.connect (*this, invalidator (*this), boost::bind (&SessionTemplateManager::init, session_tm), gui_context ());
213 
214 	signal_hide().connect (sigc::mem_fun (session_tm, &TemplateManager::handle_dirty_description));
215 	signal_hide().connect (sigc::mem_fun (route_tm, &TemplateManager::handle_dirty_description));
216 	nb->signal_switch_page().connect (sigc::hide (sigc::hide (sigc::mem_fun (session_tm, &TemplateManager::handle_dirty_description))));
217 	nb->signal_switch_page().connect (sigc::hide (sigc::hide (sigc::mem_fun (route_tm, &TemplateManager::handle_dirty_description))));
218 }
219 
TemplateManager()220 TemplateManager::TemplateManager ()
221 	: HBox ()
222 	, ProgressReporter ()
223 	, _save_desc (_("Save Description"))
224 	, _desc_dirty (false)
225 	, _remove_button (_("Remove"))
226 	, _rename_button (_("Rename"))
227 	, _export_all_templates_button (_("Export all"))
228 	, _import_template_set_button (_("Import"))
229 {
230 	_template_model = ListStore::create (_template_columns);
231 	_template_treeview.set_model (_template_model);
232 
233 	_validated_column.set_title (_("Template Name"));
234 	_validated_column.pack_start (_validating_cellrenderer);
235 	_template_treeview.append_column (_validated_column);
236 	_validating_cellrenderer.property_editable() = true;
237 
238 	_validated_column.set_cell_data_func (_validating_cellrenderer, sigc::mem_fun (*this, &TemplateManager::render_template_names));
239 	_validating_cellrenderer.signal_edited().connect (sigc::mem_fun (*this, &TemplateManager::validate_edit));
240 	_cursor_changed_connection = _template_treeview.signal_cursor_changed().connect (sigc::mem_fun (*this, &TemplateManager::row_selection_changed));
241 	_template_treeview.signal_key_press_event().connect (sigc::mem_fun (*this, &TemplateManager::key_event));
242 
243 	ScrolledWindow* sw = manage (new ScrolledWindow);
244 	sw->property_hscrollbar_policy() = POLICY_AUTOMATIC;
245 	sw->add (_template_treeview);
246 	sw->set_size_request (300, 200);
247 
248 	VBox* vb_btns = manage (new VBox);
249 	vb_btns->set_spacing (4);
250 	vb_btns->pack_start (_rename_button, false, false);
251 	vb_btns->pack_start (_remove_button, false, false);
252 	vb_btns->pack_start (_save_desc, false, false);
253 
254 	_rename_button.set_sensitive (false);
255 	_rename_button.signal_clicked().connect (sigc::mem_fun (*this, &TemplateManager::start_edit));
256 	_remove_button.set_sensitive (false);
257 	_remove_button.signal_clicked().connect (sigc::mem_fun (*this, &TemplateManager::delete_selected_template));
258 
259 	vb_btns->pack_start (*(manage (new VSeparator ())));
260 
261 	vb_btns->pack_start (_export_all_templates_button, false, false);
262 	vb_btns->pack_start (_import_template_set_button, false, false);
263 
264 	_export_all_templates_button.set_sensitive (false);
265 	_export_all_templates_button.signal_clicked().connect (sigc::mem_fun (*this, &TemplateManager::export_all_templates));
266 
267 	_import_template_set_button.set_sensitive (true);
268 	_import_template_set_button.signal_clicked().connect (sigc::mem_fun (*this, &TemplateManager::import_template_set));
269 
270 	set_spacing (6);
271 
272 	VBox* vb = manage (new VBox);
273 	vb->pack_start (*sw);
274 	vb->pack_start (_progress_bar);
275 
276 	Frame* desc_frame = manage (new Frame (_("Description")));
277 
278 	_description_editor.set_wrap_mode (Gtk::WRAP_WORD);
279 	_description_editor.set_size_request (300,400);
280 	_description_editor.set_border_width (6);
281 
282 	_save_desc.set_sensitive (false);
283 	_save_desc.signal_clicked().connect (sigc::mem_fun (*this, &TemplateManager::save_template_desc));
284 
285 	_description_editor.get_buffer()->signal_changed().connect (sigc::mem_fun (*this, &TemplateManager::set_desc_dirty));
286 
287 	desc_frame->add (_description_editor);
288 
289 	pack_start (*vb);
290 	pack_start (*desc_frame);
291 	pack_start (*vb_btns);
292 
293 	show_all_children ();
294 	_progress_bar.hide ();
295 }
296 
297 void
setup_model(const vector<TemplateInfo> & templates)298 TemplateManager::setup_model (const vector<TemplateInfo>& templates)
299 {
300 	_template_model->clear ();
301 
302 	for (vector<TemplateInfo>::const_iterator it = templates.begin(); it != templates.end(); ++it) {
303 		TreeModel::Row row;
304 		row = *(_template_model->append ());
305 
306 		row[_template_columns.name] = it->name;
307 		row[_template_columns.path] = it->path;
308 		row[_template_columns.description] = it->description;
309 	}
310 
311 	_export_all_templates_button.set_sensitive (!templates.empty ());
312 }
313 
314 void
handle_dirty_description()315 TemplateManager::handle_dirty_description ()
316 {
317 	if (_desc_dirty && _current_selection) {
318 		ArdourDialog dlg (_("Description not saved"), true);
319 		const string name = _current_selection->get_value (_template_columns.name);
320 		Label msg (string_compose (_("The description of template \"%1\" has been modified but has not been saved yet.\n"
321 					     "Do you want to save it?"), name));
322 		dlg.get_vbox()->pack_start (msg);
323 		msg.show ();
324 		dlg.add_button (_("Save"), RESPONSE_ACCEPT);
325 		dlg.add_button (_("Discard"), RESPONSE_REJECT);
326 		dlg.set_default_response (RESPONSE_REJECT);
327 
328 		int response = dlg.run ();
329 
330 		if (response == RESPONSE_ACCEPT) {
331 			save_template_desc ();
332 		} else {
333 			_description_editor.get_buffer()->set_text (_current_selection->get_value (_template_columns.description));
334 		}
335 		_desc_dirty = false;
336 	}
337 }
338 
339 void
row_selection_changed()340 TemplateManager::row_selection_changed ()
341 {
342 	if (_current_selection) {
343 		handle_dirty_description ();
344 	} else {
345 		_description_editor.get_buffer()->set_text ("");
346 	}
347 
348 	_current_selection = _template_treeview.get_selection()->get_selected ();
349 	if (_current_selection) {
350 		const string desc = _current_selection->get_value (_template_columns.description);
351 		_description_editor.get_buffer()->set_text (desc);
352 	}
353 
354 	_desc_dirty = false;
355 	_save_desc.set_sensitive (false);
356 
357 	_description_editor.set_sensitive (_current_selection);
358 	_rename_button.set_sensitive (_current_selection);
359 	_remove_button.set_sensitive (_current_selection);
360 }
361 
362 void
render_template_names(Gtk::CellRenderer *,const Gtk::TreeModel::iterator & it)363 TemplateManager::render_template_names (Gtk::CellRenderer*, const Gtk::TreeModel::iterator& it)
364 {
365 	if (it) {
366 		_validating_cellrenderer.property_text () = it->get_value (_template_columns.name);
367 	}
368 }
369 
370 void
validate_edit(const Glib::ustring & path_string,const Glib::ustring & new_name)371 TemplateManager::validate_edit (const Glib::ustring& path_string, const Glib::ustring& new_name)
372 {
373 	const TreePath path (path_string);
374 	TreeModel::iterator current = _template_model->get_iter (path);
375 
376 	if (current->get_value (_template_columns.name) == new_name) {
377 		return;
378 	}
379 
380 	TreeModel::Children rows = _template_model->children ();
381 
382 	bool found = false;
383 	for (TreeModel::Children::const_iterator it = rows.begin(); it != rows.end(); ++it) {
384 		if (it->get_value (_template_columns.name) == new_name) {
385 			found = true;
386 			break;
387 		}
388 	}
389 
390 	if (found) {
391 		error << string_compose (_("Template of name \"%1\" already exists"), new_name) << endmsg;
392 		return;
393 	}
394 
395 
396 	rename_template (current, new_name);
397 }
398 
399 void
start_edit()400 TemplateManager::start_edit ()
401 {
402 	TreeModel::Path path;
403 	TreeViewColumn* col;
404 	_template_treeview.get_cursor (path, col);
405 	_cursor_changed_connection.block ();
406 	_template_treeview.set_cursor (path, *col, /*set_editing =*/ true);
407 	_cursor_changed_connection.unblock ();
408 }
409 
410 void
set_desc_dirty()411 TemplateManager::set_desc_dirty ()
412 {
413 	_desc_dirty = true;
414 	_save_desc.set_sensitive (true);
415 }
416 
417 void
save_template_desc()418 TemplateManager::save_template_desc ()
419 {
420 	const string file_path = template_file (_current_selection);
421 
422 	string desc_txt = _description_editor.get_buffer()->get_text ();
423 	string::reverse_iterator wss = desc_txt.rbegin();
424 	while (wss != desc_txt.rend() && isspace (*wss)) {
425 		desc_txt.erase (--(wss++).base());
426 	}
427 
428 	_current_selection->set_value (_template_columns.description, desc_txt);
429 
430 	XMLTree tree;
431 
432 	if (!tree.read(file_path)) {
433 		error << string_compose (_("Could not parse template file \"%1\"."), file_path) << endmsg;
434 		return;
435 	}
436 
437 	tree.root()->remove_nodes_and_delete (X_("description"));
438 
439 	if (!desc_txt.empty ()) {
440 		XMLNode* desc = new XMLNode (X_("description"));
441 		XMLNode* dn = new XMLNode (X_("content"), desc_txt);
442 		desc->add_child_nocopy (*dn);
443 		tree.root()->add_child_nocopy (*desc);
444 	}
445 
446 	if (!tree.write ()) {
447 		error << string_compose(X_("Could not write to template file \"%1\"."), file_path) << endmsg;
448 		return;
449 	}
450 
451 	_save_desc.set_sensitive (false);
452 	_desc_dirty = false;
453 }
454 
455 bool
key_event(GdkEventKey * ev)456 TemplateManager::key_event (GdkEventKey* ev)
457 {
458 	if (ev->keyval == GDK_KEY_F2) {
459 		start_edit ();
460 		return true;
461 	}
462 	if (ev->keyval == GDK_KEY_Delete) {
463 		delete_selected_template ();
464 		return true;
465 	}
466 
467 	return false;
468 }
469 
470 static bool
accept_all_files(string const &,void *)471 accept_all_files (string const &, void *)
472 {
473 	return true;
474 }
475 
476 static void
_set_progress(Progress * p,size_t n,size_t t)477 _set_progress (Progress* p, size_t n, size_t t)
478 {
479 	p->set_progress (float (n) / float(t));
480 }
481 
482 
483 void
export_all_templates()484 TemplateManager::export_all_templates ()
485 {
486 	GError* err = NULL;
487 	char* td = g_dir_make_tmp ("ardour-templates-XXXXXX", &err);
488 
489 	if (!td) {
490 		error << string_compose(_("Could not make tmpdir: %1"), err->message) << endmsg;
491 		return;
492 	}
493 	const string tmpdir = PBD::canonical_path (td);
494 	g_free (td);
495 	g_clear_error (&err);
496 
497 	FileChooserDialog dialog(_("Save Exported Template Archive"), FILE_CHOOSER_ACTION_SAVE);
498 	Gtkmm2ext::add_volume_shortcuts (dialog);
499 	dialog.set_filename (X_("templates"));
500 
501 	dialog.add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL);
502 	dialog.add_button(Gtk::Stock::OK, Gtk::RESPONSE_OK);
503 
504 	FileFilter archive_filter;
505 	archive_filter.add_pattern (string_compose(X_("*%1"), ARDOUR::template_archive_suffix));
506 	archive_filter.set_name (_("Template archives"));
507 	dialog.add_filter (archive_filter);
508 
509 	int result = dialog.run ();
510 
511 	if (result != RESPONSE_OK || !dialog.get_filename().length()) {
512 		PBD::remove_directory (tmpdir);
513 		return;
514 	}
515 
516 	string filename = dialog.get_filename ();
517 	filename += ARDOUR::template_archive_suffix;
518 
519 	if (g_file_test (filename.c_str(), G_FILE_TEST_EXISTS)) {
520 		ArdourDialog dlg (_("File exists"), true);
521 		Label msg (string_compose (_("The file %1 already exists."), filename));
522 		dlg.get_vbox()->pack_start (msg);
523 		msg.show ();
524 		dlg.add_button (_("Overwrite"), RESPONSE_ACCEPT);
525 		dlg.add_button (_("Cancel"), RESPONSE_REJECT);
526 		dlg.set_default_response (RESPONSE_REJECT);
527 
528 		result = dlg.run ();
529 
530 		if (result == RESPONSE_REJECT) {
531 			PBD::remove_directory (tmpdir);
532 			return;
533 		}
534 	}
535 
536 	PBD::copy_recurse (templates_dir (), Glib::build_filename (tmpdir, Glib::path_get_basename (templates_dir ())));
537 
538 	vector<string> files;
539 	PBD::find_files_matching_regex (files, tmpdir, string ("\\.template$"), /* recurse = */ true);
540 
541 	vector<string>::const_iterator it;
542 	for (it = files.begin(); it != files.end(); ++it) {
543 		const string bn = PBD::basename_nosuffix (*it);
544 		const string old_path = Glib::build_filename (templates_dir (), bn);
545 		const string new_path = Glib::build_filename ("$TEMPLATEDIR", bn);
546 
547 		XMLTree tree;
548 		if (!tree.read (*it)) {
549 			continue;
550 		}
551 		if (adjust_xml_tree (tree, old_path, new_path)) {
552 			tree.write (*it);
553 		}
554 	}
555 
556 	find_files_matching_filter (files, tmpdir, accept_all_files, 0, false, true, true);
557 
558 	std::map<std::string, std::string> filemap;
559 	for (it = files.begin(); it != files.end(); ++it) {
560 		filemap[*it] = it->substr (tmpdir.size()+1, it->size() - tmpdir.size() - 1);
561 	}
562 
563 	_current_action = _("Exporting templates");
564 
565 	PBD::FileArchive ar (filename);
566 	PBD::ScopedConnectionList progress_connection;
567 	ar.progress.connect_same_thread (progress_connection, boost::bind (&_set_progress, this, _1, _2));
568 	ar.create (filemap);
569 
570 	PBD::remove_directory (tmpdir);
571 }
572 
573 void
import_template_set()574 TemplateManager::import_template_set ()
575 {
576 	FileChooserDialog dialog (_("Import template archives"));
577 	dialog.add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL);
578 	dialog.add_button(Gtk::Stock::OK, Gtk::RESPONSE_OK);
579 
580 	FileFilter archive_filter;
581 	archive_filter.add_pattern (string_compose(X_("*%1"), ARDOUR::template_archive_suffix));
582 	archive_filter.add_pattern (X_("*.tar.xz")); // template archives from 5.x
583 	archive_filter.set_name (_("Template archives"));
584 	dialog.add_filter (archive_filter);
585 
586 	int result = dialog.run ();
587 
588 	if (result != RESPONSE_OK || !dialog.get_filename().length()) {
589 		return;
590 	}
591 
592 	_current_action = _("Importing templates");
593 
594 	FileArchive ar (dialog.get_filename ());
595 	PBD::ScopedConnectionList progress_connection;
596 	ar.progress.connect_same_thread (progress_connection, boost::bind (&_set_progress, this, _1, _2));
597 
598 	for (std::string fn = ar.next_file_name(); !fn.empty(); fn = ar.next_file_name()) {
599 		const size_t pos = fn.find (templates_dir_basename ());
600 		if (pos == string::npos) {
601 			continue;
602 		}
603 		const std::string dest = Glib::build_filename (user_config_directory(), fn.substr (pos));
604 		ar.extract_current_file (dest);
605 	}
606 	vector<string> files;
607 	PBD::find_files_matching_regex (files, templates_dir (), string ("\\.template$"), /* recurse = */ true);
608 
609 	vector<string>::const_iterator it;
610 	for (it = files.begin(); it != files.end(); ++it) {
611 		const string bn = PBD::basename_nosuffix (*it);
612 		const string old_path = Glib::build_filename ("$TEMPLATEDIR", bn);
613 		const string new_path = Glib::build_filename (templates_dir (), bn);
614 
615 		XMLTree tree;
616 		if (!tree.read (*it)) {
617 			continue;
618 		}
619 		if (adjust_xml_tree (tree, old_path, new_path)) {
620 			tree.write (*it);
621 		}
622 	}
623 
624 	init ();
625 	TemplatesImported (); /* emit signal */
626 }
627 
628 bool
adjust_plugin_paths(XMLNode * node,const string & name,const string & new_name) const629 TemplateManager::adjust_plugin_paths (XMLNode* node, const string& name, const string& new_name) const
630 {
631 	bool adjusted = false;
632 
633 	const XMLNodeList& procs = node->children (X_("Processor"));
634 	XMLNodeConstIterator pit;
635 	for (pit = procs.begin(); pit != procs.end(); ++pit) {
636 		XMLNode* lv2_node = (*pit)->child (X_("lv2"));
637 		if (!lv2_node) {
638 			continue;
639 		}
640 		string template_dir;
641 
642 		if (!lv2_node->get_property (X_("template-dir"), template_dir)) {
643 			continue;
644 		}
645 
646 		const int suffix_pos = template_dir.size() - name.size();
647 		if (suffix_pos < 0) {
648 			cerr << "Template name\"" << name << "\" longer than template-dir \"" << template_dir << "\", WTH?" << endl;
649 			continue;
650 		}
651 
652 		if (template_dir.compare (suffix_pos, template_dir.size(), name)) {
653 			cerr << "Template name \"" << name << "\" no suffix of template-dir \"" << template_dir << "\"" << endl;
654 			continue;
655 		}
656 
657 		const string new_template_dir = template_dir.substr (0, suffix_pos) + new_name;
658 		lv2_node->set_property (X_("template-dir"), new_template_dir);
659 
660 		adjusted = true;
661 	}
662 
663 	return adjusted;
664 }
665 
666 void
update_progress_gui(float p)667 TemplateManager::update_progress_gui (float p)
668 {
669 	if (p >= 1.0) {
670 		_progress_bar.hide ();
671 		return;
672 	}
673 
674 	_progress_bar.show ();
675 	_progress_bar.set_text (_current_action);
676 	_progress_bar.set_fraction (p);
677 }
678 
679 void
init()680 SessionTemplateManager::init ()
681 {
682 	vector<TemplateInfo> templates;
683 	get_templates (templates);
684 	setup_model (templates);
685 
686 	_progress_bar.hide ();
687 	_description_editor.set_sensitive (false);
688 	_save_desc.set_sensitive (false);
689 }
690 
691 void
init()692 RouteTemplateManager::init ()
693 {
694 	vector<TemplateInfo> templates;
695 	get_templates (templates);
696 	setup_model (templates);
697 
698 	_progress_bar.hide ();
699 	_description_editor.set_sensitive (false);
700 	_save_desc.set_sensitive (false);
701 }
702 
703 void
get_templates(vector<TemplateInfo> & templates) const704 SessionTemplateManager::get_templates (vector<TemplateInfo>& templates) const
705 {
706 	find_session_templates (templates, /* read_xml = */ true);
707 }
708 
709 void
get_templates(vector<TemplateInfo> & templates) const710 RouteTemplateManager::get_templates (vector<TemplateInfo>& templates) const
711 {
712 	find_route_templates (templates);
713 }
714 
715 #include <cerrno>
716 
717 void
rename_template(TreeModel::iterator & item,const Glib::ustring & new_name_)718 SessionTemplateManager::rename_template (TreeModel::iterator& item, const Glib::ustring& new_name_)
719 {
720 	const string old_path = item->get_value (_template_columns.path);
721 	const string old_name = item->get_value (_template_columns.name);
722 	const string new_name = string (new_name_);
723 
724 	if (old_name == new_name) {
725 		return;
726 	}
727 
728 	const string old_file_old_path = Glib::build_filename (old_path, old_name+".template");
729 
730 	XMLTree tree;
731 
732 	if (!tree.read(old_file_old_path)) {
733 		error << string_compose (_("Could not parse template file \"%1\"."), old_file_old_path) << endmsg;
734 		return;
735 	}
736 	XMLNode* root = tree.root();
737 
738 	const XMLNode* const routes_node = root->child (X_("Routes"));
739 	if (routes_node) {
740 		const XMLNodeList& routes = routes_node->children (X_("Route"));
741 		XMLNodeConstIterator rit;
742 		for (rit = routes.begin(); rit != routes.end(); ++rit) {
743 			adjust_plugin_paths (*rit, old_name, new_name);
744 		}
745 	}
746 
747 	const string new_file_old_path = Glib::build_filename (old_path, new_name+".template");
748 
749 	tree.set_filename (new_file_old_path);
750 
751 	if (!tree.write ()) {
752 		error << string_compose(_("Could not write to new template file \"%1\"."), new_file_old_path);
753 		return;
754 	}
755 
756 	const string new_path = Glib::build_filename (user_template_directory (), new_name);
757 
758 	if (g_rename (old_path.c_str(), new_path.c_str()) != 0) {
759 		error << string_compose (_("Could not rename template directory from \"%1\" to \"%2\": %3"),
760 					 old_path, new_path, strerror (errno)) << endmsg;
761 		g_unlink (new_file_old_path.c_str());
762 		return;
763 	}
764 
765 	const string old_file_new_path = Glib::build_filename (new_path, old_name+".template");
766 	if (g_unlink (old_file_new_path.c_str())) {
767 		error << string_compose (X_("Could not delete old template file \"%1\": %2"),
768 					 old_file_new_path, strerror (errno)) << endmsg;
769 	}
770 
771 	item->set_value (_template_columns.name, new_name);
772 	item->set_value (_template_columns.path, new_path);
773 }
774 
775 void
delete_selected_template()776 SessionTemplateManager::delete_selected_template ()
777 {
778 	if (!_current_selection) {
779 		return;
780 	}
781 
782 	PBD::remove_directory (_current_selection->get_value (_template_columns.path));
783 
784 	_template_model->erase (_current_selection);
785 	_current_selection = TreeIter ();
786 	row_selection_changed ();
787 }
788 
789 string
templates_dir() const790 SessionTemplateManager::templates_dir () const
791 {
792 	return user_template_directory ();
793 }
794 
795 string
templates_dir_basename() const796 SessionTemplateManager::templates_dir_basename () const
797 {
798 	return string (templates_dir_name);
799 }
800 
801 
802 string
template_file(const TreeModel::const_iterator & item) const803 SessionTemplateManager::template_file (const TreeModel::const_iterator& item) const
804 {
805 	const string path = item->get_value (_template_columns.path);
806 	const string name = item->get_value (_template_columns.name);
807 	return Glib::build_filename (path, name+".template");
808 }
809 
810 bool
adjust_xml_tree(XMLTree & tree,const std::string & old_name,const std::string & new_name) const811 SessionTemplateManager::adjust_xml_tree (XMLTree& tree, const std::string& old_name, const std::string& new_name) const
812 {
813 	bool adjusted = false;
814 	XMLNode* root = tree.root();
815 
816 	const XMLNode* const routes_node = root->child (X_("Routes"));
817 	if (routes_node) {
818 		const XMLNodeList& routes = routes_node->children (X_("Route"));
819 		XMLNodeConstIterator rit;
820 		for (rit = routes.begin(); rit != routes.end(); ++rit) {
821 			if (adjust_plugin_paths (*rit, old_name, new_name)) {
822 				adjusted = true;
823 			}
824 		}
825 	}
826 
827 	return adjusted;
828 }
829 
830 
831 void
rename_template(TreeModel::iterator & item,const Glib::ustring & new_name)832 RouteTemplateManager::rename_template (TreeModel::iterator& item, const Glib::ustring& new_name)
833 {
834 	const string old_name = item->get_value (_template_columns.name);
835 	const string old_filepath = item->get_value (_template_columns.path);
836 	const string new_filepath = Glib::build_filename (user_route_template_directory(), new_name+".template");
837 
838 	if (old_name == new_name) {
839 		return;
840 	}
841 
842 	XMLTree tree;
843 	if (!tree.read (old_filepath)) {
844 		error << string_compose (_("Could not parse template file \"%1\"."), old_filepath) << endmsg;
845 		return;
846 	}
847 	tree.root()->set_property (X_("name"), new_name);
848 	tree.root()->children().front()->set_property (X_("name"), new_name);
849 
850 	const bool adjusted = adjust_plugin_paths (tree.root(), old_name, string (new_name));
851 
852 	const string old_state_dir = Glib::build_filename (user_route_template_directory(), old_name);
853 	const string new_state_dir = Glib::build_filename (user_route_template_directory(), new_name);
854 
855 	if (adjusted) {
856 		if (g_file_test (old_state_dir.c_str(), G_FILE_TEST_EXISTS)) {
857 			if (g_rename (old_state_dir.c_str(), new_state_dir.c_str()) != 0) {
858 				error << string_compose (_("Could not rename state dir \"%1\" to \"%2\": %3"), old_state_dir, new_state_dir, strerror (errno)) << endmsg;
859 				return;
860 			}
861 		}
862 	}
863 
864 	tree.set_filename (new_filepath);
865 
866 	if (!tree.write ()) {
867 		error << string_compose(_("Could not write new template file \"%1\"."), new_filepath) << endmsg;
868 		if (adjusted) {
869 			g_rename (new_state_dir.c_str(), old_state_dir.c_str());
870 		}
871 		return;
872 	}
873 
874 	if (g_unlink (old_filepath.c_str()) != 0) {
875 		error << string_compose (_("Could not remove old template file \"%1\": %2"), old_filepath, strerror (errno)) << endmsg;
876 	}
877 
878 	item->set_value (_template_columns.name, string (new_name));
879 	item->set_value (_template_columns.path, new_filepath);
880 }
881 
882 void
delete_selected_template()883 RouteTemplateManager::delete_selected_template ()
884 {
885 	if (!_current_selection) {
886 		return;
887 	}
888 
889 	const string file_path = _current_selection->get_value (_template_columns.path);
890 
891 	if (g_unlink (file_path.c_str()) != 0) {
892 		error << string_compose(_("Could not delete template file \"%1\": %2"), file_path, strerror (errno)) << endmsg;
893 		return;
894 	}
895 	PBD::remove_directory (Glib::build_filename (user_route_template_directory (),
896 						     _current_selection->get_value (_template_columns.name)));
897 
898 	_template_model->erase (_current_selection);
899 	row_selection_changed ();
900 }
901 
902 string
templates_dir() const903 RouteTemplateManager::templates_dir () const
904 {
905 	return user_route_template_directory ();
906 }
907 
908 string
templates_dir_basename() const909 RouteTemplateManager::templates_dir_basename () const
910 {
911 	return string (route_templates_dir_name);
912 }
913 
914 
915 string
template_file(const TreeModel::const_iterator & item) const916 RouteTemplateManager::template_file (const TreeModel::const_iterator& item) const
917 {
918 	return item->get_value (_template_columns.path);
919 }
920 
921 bool
adjust_xml_tree(XMLTree & tree,const std::string & old_name,const std::string & new_name) const922 RouteTemplateManager::adjust_xml_tree (XMLTree& tree, const std::string& old_name, const std::string& new_name) const
923 {
924 	return adjust_plugin_paths (tree.root(), old_name, string (new_name));
925 }
926