Ask user whether to save or discard unsaved template descriptions
[ardour.git] / gtk2_ardour / template_dialog.cc
1 /*
2     Copyright (C) 2010 Paul Davis
3     Author: Johannes Mueller
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
16     along with this program; if not, write to the Free Software
17     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18
19 */
20
21 #include <map>
22 #include <cerrno>
23
24 #include <glib/gstdio.h>
25
26 #include <gtkmm/filechooserdialog.h>
27 #include <gtkmm/frame.h>
28 #include <gtkmm/notebook.h>
29 #include <gtkmm/separator.h>
30 #include <gtkmm/scrolledwindow.h>
31 #include <gtkmm/stock.h>
32 #include <gtkmm/treeiter.h>
33
34 #include "pbd/basename.h"
35 #include "pbd/error.h"
36 #include "pbd/file_archive.h"
37 #include "pbd/file_utils.h"
38 #include "pbd/i18n.h"
39 #include "pbd/xml++.h"
40
41 #include "gtkmm2ext/gui_thread.h"
42
43 #include "ardour/filesystem_paths.h"
44 #include "ardour/template_utils.h"
45
46 #include "template_dialog.h"
47
48 using namespace std;
49 using namespace Gtk;
50 using namespace PBD;
51 using namespace ARDOUR;
52
53
54 TemplateDialog::TemplateDialog ()
55         : ArdourDialog ("Manage Templates")
56 {
57         Notebook* nb = manage (new Notebook);
58
59         SessionTemplateManager* session_tm = manage (new SessionTemplateManager);
60         nb->append_page (*session_tm, _("Session Templates"));
61
62         RouteTemplateManager* route_tm = manage (new RouteTemplateManager);
63         nb->append_page (*route_tm, _("Track Templates"));
64
65         get_vbox()->pack_start (*nb);
66         add_button (_("Ok"), Gtk::RESPONSE_OK);
67
68         get_vbox()->show_all();
69
70         session_tm->init ();
71         route_tm->init ();
72
73         session_tm->TemplatesImported.connect (*this, invalidator (*this), boost::bind (&RouteTemplateManager::init, route_tm), gui_context ());
74         route_tm->TemplatesImported.connect (*this, invalidator (*this), boost::bind (&SessionTemplateManager::init, session_tm), gui_context ());
75
76         signal_hide().connect (sigc::mem_fun (session_tm, &TemplateManager::handle_dirty_description));
77         signal_hide().connect (sigc::mem_fun (route_tm, &TemplateManager::handle_dirty_description));
78         nb->signal_switch_page().connect (boost::bind (&TemplateManager::handle_dirty_description, session_tm));
79         nb->signal_switch_page().connect (boost::bind (&TemplateManager::handle_dirty_description, route_tm));
80 }
81
82 TemplateManager::TemplateManager ()
83         : HBox ()
84         , ProgressReporter ()
85         , _save_desc (_("Save Description"))
86         , _desc_dirty (false)
87         , _remove_button (_("Remove"))
88         , _rename_button (_("Rename"))
89         , _export_all_templates_button (_("Export all"))
90         , _import_template_set_button (_("Import"))
91 {
92         _template_model = ListStore::create (_template_columns);
93         _template_treeview.set_model (_template_model);
94
95         _validated_column.set_title (_("Template Name"));
96         _validated_column.pack_start (_validating_cellrenderer);
97         _template_treeview.append_column (_validated_column);
98         _validating_cellrenderer.property_editable() = true;
99
100         _validated_column.set_cell_data_func (_validating_cellrenderer, sigc::mem_fun (*this, &TemplateManager::render_template_names));
101         _validating_cellrenderer.signal_edited().connect (sigc::mem_fun (*this, &TemplateManager::validate_edit));
102         _template_treeview.signal_cursor_changed().connect (sigc::mem_fun (*this, &TemplateManager::row_selection_changed));
103         _template_treeview.signal_key_press_event().connect (sigc::mem_fun (*this, &TemplateManager::key_event));
104
105         ScrolledWindow* sw = manage (new ScrolledWindow);
106         sw->property_hscrollbar_policy() = POLICY_AUTOMATIC;
107         sw->add (_template_treeview);
108         sw->set_size_request (300, 200);
109
110         VBox* vb_btns = manage (new VBox);
111         vb_btns->set_spacing (4);
112         vb_btns->pack_start (_rename_button, false, false);
113         vb_btns->pack_start (_remove_button, false, false);
114         vb_btns->pack_start (_save_desc, false, false);
115
116         _rename_button.set_sensitive (false);
117         _rename_button.signal_clicked().connect (sigc::mem_fun (*this, &TemplateManager::start_edit));
118         _remove_button.set_sensitive (false);
119         _remove_button.signal_clicked().connect (sigc::mem_fun (*this, &TemplateManager::delete_selected_template));
120
121         vb_btns->pack_start (*(manage (new VSeparator ())));
122
123         vb_btns->pack_start (_export_all_templates_button, false, false);
124         vb_btns->pack_start (_import_template_set_button, false, false);
125
126         _export_all_templates_button.set_sensitive (false);
127         _export_all_templates_button.signal_clicked().connect (sigc::mem_fun (*this, &TemplateManager::export_all_templates));
128
129         _import_template_set_button.set_sensitive (true);
130         _import_template_set_button.signal_clicked().connect (sigc::mem_fun (*this, &TemplateManager::import_template_set));
131
132         set_spacing (6);
133
134         VBox* vb = manage (new VBox);
135         vb->pack_start (*sw);
136         vb->pack_start (_progress_bar);
137
138         Frame* desc_frame = manage (new Frame (_("Description")));
139
140         _description_editor.set_wrap_mode (Gtk::WRAP_WORD);
141         _description_editor.set_size_request (300,400);
142         _description_editor.set_border_width (6);
143
144         _save_desc.set_sensitive (false);
145         _save_desc.signal_clicked().connect (sigc::mem_fun (*this, &TemplateManager::save_template_desc));
146
147         _description_editor.get_buffer()->signal_changed().connect (sigc::mem_fun (*this, &TemplateManager::set_desc_dirty));
148
149         desc_frame->add (_description_editor);
150
151         pack_start (*vb);
152         pack_start (*desc_frame);
153         pack_start (*vb_btns);
154
155         show_all_children ();
156         _progress_bar.hide ();
157 }
158
159 void
160 TemplateManager::setup_model (const vector<TemplateInfo>& templates)
161 {
162         _template_model->clear ();
163
164         for (vector<TemplateInfo>::const_iterator it = templates.begin(); it != templates.end(); ++it) {
165                 TreeModel::Row row;
166                 row = *(_template_model->append ());
167
168                 row[_template_columns.name] = it->name;
169                 row[_template_columns.path] = it->path;
170                 row[_template_columns.description] = it->description;
171         }
172
173         _export_all_templates_button.set_sensitive (!templates.empty ());
174 }
175
176 void
177 TemplateManager::handle_dirty_description ()
178 {
179         if (_desc_dirty && _current_selection) {
180                 ArdourDialog dlg (_("Description not saved"), true);
181                 const string name = _current_selection->get_value (_template_columns.name);
182                 Label msg (string_compose (_("The discription of template \"%1\" has been modfied but has not been saved yet.\n"
183                                              "Do you want to save it?"), name));
184                 dlg.get_vbox()->pack_start (msg);
185                 msg.show ();
186                 dlg.add_button (_("Save"), RESPONSE_ACCEPT);
187                 dlg.add_button (_("Discard"), RESPONSE_REJECT);
188                 dlg.set_default_response (RESPONSE_REJECT);
189
190                 int response = dlg.run ();
191
192                 if (response == RESPONSE_ACCEPT) {
193                         save_template_desc ();
194                 } else {
195                         _description_editor.get_buffer()->set_text (_current_selection->get_value (_template_columns.description));
196                 }
197
198                 _desc_dirty = false;
199         }
200 }
201
202 void
203 TemplateManager::row_selection_changed ()
204 {
205         handle_dirty_description ();
206
207         _current_selection = _template_treeview.get_selection()->get_selected ();
208         if (_current_selection) {
209                 const string desc = _current_selection->get_value (_template_columns.description);
210                 _description_editor.get_buffer()->set_text (desc);
211                 _desc_dirty = false;
212                 _save_desc.set_sensitive (false);
213         }
214
215         _rename_button.set_sensitive (_current_selection);
216         _remove_button.set_sensitive (_current_selection);
217 }
218
219 void
220 TemplateManager::render_template_names (Gtk::CellRenderer*, const Gtk::TreeModel::iterator& it)
221 {
222         if (it) {
223                 _validating_cellrenderer.property_text () = it->get_value (_template_columns.name);
224         }
225 }
226
227 void
228 TemplateManager::validate_edit (const Glib::ustring& path_string, const Glib::ustring& new_name)
229 {
230         const TreePath path (path_string);
231         TreeModel::iterator current = _template_model->get_iter (path);
232
233         if (current->get_value (_template_columns.name) == new_name) {
234                 return;
235         }
236
237         TreeModel::Children rows = _template_model->children ();
238
239         bool found = false;
240         for (TreeModel::Children::const_iterator it = rows.begin(); it != rows.end(); ++it) {
241                 if (it->get_value (_template_columns.name) == new_name) {
242                         found = true;
243                         break;
244                 }
245         }
246
247         if (found) {
248                 error << string_compose (_("Template of name \"%1\" already exists"), new_name) << endmsg;
249                 return;
250         }
251
252
253         rename_template (current, new_name);
254 }
255
256 void
257 TemplateManager::start_edit ()
258 {
259         TreeModel::Path path;
260         TreeViewColumn* col;
261         _template_treeview.get_cursor (path, col);
262         _template_treeview.set_cursor (path, *col, /*set_editing =*/ true);
263 }
264
265 void
266 TemplateManager::set_desc_dirty ()
267 {
268         _desc_dirty = true;
269         _save_desc.set_sensitive (true);
270 }
271
272 void
273 TemplateManager::save_template_desc ()
274 {
275         const string file_path = template_file (_current_selection);
276
277         const string desc_txt = _description_editor.get_buffer()->get_text ();
278         _current_selection->set_value (_template_columns.description, desc_txt);
279
280         XMLTree tree;
281
282         if (!tree.read(file_path)) {
283                 error << string_compose (_("Could not parse template file \"%1\"."), file_path) << endmsg;
284                 return;
285         }
286
287         tree.root()->remove_nodes (X_("description"));
288         XMLNode* desc = new XMLNode (X_("description"));
289
290         XMLNode* dn = new XMLNode (X_("content"), desc_txt);
291         desc->add_child_nocopy (*dn);
292
293         tree.root()->add_child_nocopy (*desc);
294
295         if (!tree.write ()) {
296                 error << string_compose(X_("Could not write to template file \"%1\"."), file_path) << endmsg;
297                 return;
298         }
299
300         _save_desc.set_sensitive (false);
301         _desc_dirty = false;
302 }
303
304 bool
305 TemplateManager::key_event (GdkEventKey* ev)
306 {
307         if (ev->keyval == GDK_KEY_F2) {
308                 start_edit ();
309                 return true;
310         }
311         if (ev->keyval == GDK_KEY_Delete) {
312                 delete_selected_template ();
313                 return true;
314         }
315
316         return false;
317 }
318
319 static
320 bool accept_all_files (string const &, void *)
321 {
322         return true;
323 }
324
325 static void _set_progress (Progress* p, size_t n, size_t t)
326 {
327         p->set_progress (float (n) / float(t));
328 }
329
330
331 void
332 TemplateManager::export_all_templates ()
333 {
334         GError* err = NULL;
335         char* td = g_dir_make_tmp ("ardour-templates-XXXXXX", &err);
336
337         if (!td) {
338                 error << string_compose(_("Could not make tmpdir: %1"), err->message) << endmsg;
339                 return;
340         }
341         const string tmpdir (td);
342         g_free (td);
343         g_clear_error (&err);
344
345         FileChooserDialog dialog(_("Save Exported Template Archive"), FILE_CHOOSER_ACTION_SAVE);
346         dialog.set_filename (X_("templates.tar.xz"));
347
348         dialog.add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL);
349         dialog.add_button(Gtk::Stock::OK, Gtk::RESPONSE_OK);
350
351         FileFilter archive_filter;
352         archive_filter.add_pattern (X_("*.tar.xz"));
353         archive_filter.set_name (_("Template archives"));
354         dialog.add_filter (archive_filter);
355
356         int result = dialog.run ();
357
358         if (result != RESPONSE_OK || !dialog.get_filename().length()) {
359                 PBD::remove_directory (tmpdir);
360                 return;
361         }
362
363         string filename = dialog.get_filename ();
364         if (filename.compare (filename.size () - 7, 7, ".tar.xz")) {
365                 filename += ".tar.xz";
366         }
367
368         if (g_file_test (filename.c_str(), G_FILE_TEST_EXISTS)) {
369                 ArdourDialog dlg (_("File exists"), true);
370                 Label msg (string_compose (_("The file %1 already exists."), filename));
371                 dlg.get_vbox()->pack_start (msg);
372                 msg.show ();
373                 dlg.add_button (_("Overwrite"), RESPONSE_ACCEPT);
374                 dlg.add_button (_("Cancel"), RESPONSE_REJECT);
375                 dlg.set_default_response (RESPONSE_REJECT);
376
377                 result = dlg.run ();
378
379                 if (result == RESPONSE_REJECT) {
380                         PBD::remove_directory (tmpdir);
381                         return;
382                 }
383         }
384
385         PBD::copy_recurse (templates_dir (), Glib::build_filename (tmpdir, Glib::path_get_basename (templates_dir ())));
386
387         vector<string> files;
388         PBD::find_files_matching_regex (files, tmpdir, string ("\\.template$"), /* recurse = */ true);
389
390         vector<string>::const_iterator it;
391         for (it = files.begin(); it != files.end(); ++it) {
392                 const string bn = PBD::basename_nosuffix (*it);
393                 const string old_path = Glib::build_filename (templates_dir (), bn);
394                 const string new_path = Glib::build_filename ("$TEMPLATEDIR", bn);
395
396                 XMLTree tree;
397                 if (!tree.read (*it)) {
398                         continue;
399                 }
400                 if (adjust_xml_tree (tree, old_path, new_path)) {
401                         tree.write (*it);
402                 }
403         }
404
405         find_files_matching_filter (files, tmpdir, accept_all_files, 0, false, true, true);
406
407         std::map<std::string, std::string> filemap;
408         for (it = files.begin(); it != files.end(); ++it) {
409                 filemap[*it] = it->substr (tmpdir.size()+1, it->size() - tmpdir.size() - 1);
410         }
411
412         _current_action = _("Exporting templates");
413
414         PBD::FileArchive ar (filename);
415         PBD::ScopedConnectionList progress_connection;
416         ar.progress.connect_same_thread (progress_connection, boost::bind (&_set_progress, this, _1, _2));
417         ar.create (filemap);
418
419         PBD::remove_directory (tmpdir);
420 }
421
422 void
423 TemplateManager::import_template_set ()
424 {
425         FileChooserDialog dialog (_("Import template archives"));
426         dialog.add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL);
427         dialog.add_button(Gtk::Stock::OK, Gtk::RESPONSE_OK);
428
429         FileFilter archive_filter;
430         archive_filter.add_pattern (X_("*.tar.xz"));
431         archive_filter.set_name (_("Template archives"));
432         dialog.add_filter (archive_filter);
433
434         int result = dialog.run ();
435
436         if (result != RESPONSE_OK || !dialog.get_filename().length()) {
437                 return;
438         }
439
440         _current_action = _("Importing templates");
441
442         FileArchive ar (dialog.get_filename ());
443         PBD::ScopedConnectionList progress_connection;
444         ar.progress.connect_same_thread (progress_connection, boost::bind (&_set_progress, this, _1, _2));
445         ar.inflate (user_config_directory ());
446
447         vector<string> files;
448         PBD::find_files_matching_regex (files, templates_dir (), string ("\\.template$"), /* recurse = */ true);
449
450         vector<string>::const_iterator it;
451         for (it = files.begin(); it != files.end(); ++it) {
452                 const string bn = PBD::basename_nosuffix (*it);
453                 const string old_path = Glib::build_filename ("$TEMPLATEDIR", bn);
454                 const string new_path = Glib::build_filename (templates_dir (), bn);
455
456                 XMLTree tree;
457                 if (!tree.read (*it)) {
458                         continue;
459                 }
460                 if (adjust_xml_tree (tree, old_path, new_path)) {
461                         tree.write (*it);
462                 }
463         }
464
465         init ();
466         TemplatesImported (); /* emit signal */
467 }
468
469 bool
470 TemplateManager::adjust_plugin_paths (XMLNode* node, const string& name, const string& new_name) const
471 {
472         bool adjusted = false;
473
474         const XMLNodeList& procs = node->children (X_("Processor"));
475         XMLNodeConstIterator pit;
476         for (pit = procs.begin(); pit != procs.end(); ++pit) {
477                 XMLNode* lv2_node = (*pit)->child (X_("lv2"));
478                 if (!lv2_node) {
479                         continue;
480                 }
481                 string template_dir;
482
483                 if (!lv2_node->get_property (X_("template-dir"), template_dir)) {
484                         continue;
485                 }
486
487                 const int suffix_pos = template_dir.size() - name.size();
488                 if (suffix_pos < 0) {
489                         cerr << "Template name\"" << name << "\" longer than template-dir \"" << template_dir << "\", WTH?" << endl;
490                         continue;
491                 }
492
493                 if (template_dir.compare (suffix_pos, template_dir.size(), name)) {
494                         cerr << "Template name \"" << name << "\" no suffix of template-dir \"" << template_dir << "\"" << endl;
495                         continue;
496                 }
497
498                 const string new_template_dir = template_dir.substr (0, suffix_pos) + new_name;
499                 lv2_node->set_property (X_("template-dir"), new_template_dir);
500
501                 adjusted = true;
502         }
503
504         return adjusted;
505 }
506
507 void
508 TemplateManager::update_progress_gui (float p)
509 {
510         if (p >= 1.0) {
511                 _progress_bar.hide ();
512                 return;
513         }
514
515         _progress_bar.show ();
516         _progress_bar.set_text (_current_action);
517         _progress_bar.set_fraction (p);
518 }
519
520
521 void SessionTemplateManager::init ()
522 {
523         vector<TemplateInfo> templates;
524         find_session_templates (templates, /* read_xml = */ true);
525         setup_model (templates);
526
527         _progress_bar.hide ();
528 }
529
530 void RouteTemplateManager::init ()
531 {
532         vector<TemplateInfo> templates;
533         find_route_templates (templates);
534         setup_model (templates);
535
536         _progress_bar.hide ();
537 }
538
539 #include <cerrno>
540
541 void
542 SessionTemplateManager::rename_template (TreeModel::iterator& item, const Glib::ustring& new_name_)
543 {
544         const string old_path = item->get_value (_template_columns.path);
545         const string old_name = item->get_value (_template_columns.name);
546         const string new_name = string (new_name_);
547
548         const string old_file_old_path = Glib::build_filename (old_path, old_name+".template");
549
550         XMLTree tree;
551
552         if (!tree.read(old_file_old_path)) {
553                 error << string_compose (_("Could not parse template file \"%1\"."), old_file_old_path) << endmsg;
554                 return;
555         }
556         XMLNode* root = tree.root();
557
558         const XMLNode* const routes_node = root->child (X_("Routes"));
559         if (routes_node) {
560                 const XMLNodeList& routes = routes_node->children (X_("Route"));
561                 XMLNodeConstIterator rit;
562                 for (rit = routes.begin(); rit != routes.end(); ++rit) {
563                         adjust_plugin_paths (*rit, old_name, new_name);
564                 }
565         }
566
567         const string new_file_old_path = Glib::build_filename (old_path, new_name+".template");
568
569         tree.set_filename (new_file_old_path);
570
571         if (!tree.write ()) {
572                 error << string_compose(_("Could not write to new template file \"%1\"."), new_file_old_path);
573                 return;
574         }
575
576         const string new_path = Glib::build_filename (user_template_directory (), new_name);
577
578         if (g_rename (old_path.c_str(), new_path.c_str()) != 0) {
579                 error << string_compose (_("Could not rename template directory from \"%1\" to \"%2\": %3"),
580                                          old_path, new_path, strerror (errno)) << endmsg;
581                 g_unlink (new_file_old_path.c_str());
582                 return;
583         }
584
585         const string old_file_new_path = Glib::build_filename (new_path, old_name+".template");
586         if (g_unlink (old_file_new_path.c_str())) {
587                 error << string_compose (X_("Could not delete old template file \"%1\": %2"),
588                                          old_file_new_path, strerror (errno)) << endmsg;
589         }
590
591         item->set_value (_template_columns.name, new_name);
592         item->set_value (_template_columns.path, new_path);
593 }
594
595 void
596 SessionTemplateManager::delete_selected_template ()
597 {
598         if (!_current_selection) {
599                 return;
600         }
601
602         PBD::remove_directory (_current_selection->get_value (_template_columns.path));
603
604         _template_model->erase (_current_selection);
605         row_selection_changed ();
606 }
607
608 string
609 SessionTemplateManager::templates_dir () const
610 {
611         return user_template_directory ();
612 }
613
614
615 string
616 SessionTemplateManager::template_file (const TreeModel::const_iterator& item) const
617 {
618         const string path = item->get_value (_template_columns.path);
619         const string name = item->get_value (_template_columns.name);
620         return Glib::build_filename (path, name+".template");
621 }
622
623 bool
624 SessionTemplateManager::adjust_xml_tree (XMLTree& tree, const std::string& old_name, const std::string& new_name) const
625 {
626         bool adjusted = false;
627         XMLNode* root = tree.root();
628
629         const XMLNode* const routes_node = root->child (X_("Routes"));
630         if (routes_node) {
631                 const XMLNodeList& routes = routes_node->children (X_("Route"));
632                 XMLNodeConstIterator rit;
633                 for (rit = routes.begin(); rit != routes.end(); ++rit) {
634                         if (adjust_plugin_paths (*rit, old_name, new_name)) {
635                                 adjusted = true;
636                         }
637                 }
638         }
639
640         return adjusted;
641 }
642
643
644 void
645 RouteTemplateManager::rename_template (TreeModel::iterator& item, const Glib::ustring& new_name)
646 {
647         const string old_name = item->get_value (_template_columns.name);
648         const string old_filepath = item->get_value (_template_columns.path);
649         const string new_filepath = Glib::build_filename (user_route_template_directory(), new_name+".template");
650
651         XMLTree tree;
652         if (!tree.read (old_filepath)) {
653                 error << string_compose (_("Could not parse template file \"%1\"."), old_filepath) << endmsg;
654                 return;
655         }
656         tree.root()->set_property (X_("name"), new_name);
657         tree.root()->children().front()->set_property (X_("name"), new_name);
658
659         const bool adjusted = adjust_plugin_paths (tree.root(), old_name, string (new_name));
660
661         const string old_state_dir = Glib::build_filename (user_route_template_directory(), old_name);
662         const string new_state_dir = Glib::build_filename (user_route_template_directory(), new_name);
663
664         if (adjusted) {
665                 if (g_file_test (old_state_dir.c_str(), G_FILE_TEST_EXISTS)) {
666                         if (g_rename (old_state_dir.c_str(), new_state_dir.c_str()) != 0) {
667                                 error << string_compose (_("Could not rename state dir \"%1\" to \"%22\": %3"), old_state_dir, new_state_dir, strerror (errno)) << endmsg;
668                                 return;
669                         }
670                 }
671         }
672
673         tree.set_filename (new_filepath);
674
675         if (!tree.write ()) {
676                 error << string_compose(_("Could not write new template file \"%1\"."), new_filepath) << endmsg;
677                 if (adjusted) {
678                         g_rename (new_state_dir.c_str(), old_state_dir.c_str());
679                 }
680                 return;
681         }
682
683         if (g_unlink (old_filepath.c_str()) != 0) {
684                 error << string_compose (_("Could not remove old template file \"%1\": %2"), old_filepath, strerror (errno)) << endmsg;
685         }
686
687         item->set_value (_template_columns.name, string (new_name));
688         item->set_value (_template_columns.path, new_filepath);
689 }
690
691 void
692 RouteTemplateManager::delete_selected_template ()
693 {
694         if (!_current_selection) {
695                 return;
696         }
697
698         const string file_path = _current_selection->get_value (_template_columns.path);
699
700         if (g_unlink (file_path.c_str()) != 0) {
701                 error << string_compose(_("Could not delete template file \"%1\": %2"), file_path, strerror (errno)) << endmsg;
702                 return;
703         }
704         PBD::remove_directory (Glib::build_filename (user_route_template_directory (),
705                                                      _current_selection->get_value (_template_columns.name)));
706
707         _template_model->erase (_current_selection);
708         row_selection_changed ();
709 }
710
711 string
712 RouteTemplateManager::templates_dir () const
713 {
714         return user_route_template_directory ();
715 }
716
717
718 string
719 RouteTemplateManager::template_file (const TreeModel::const_iterator& item) const
720 {
721         return item->get_value (_template_columns.path);
722 }
723
724 bool
725 RouteTemplateManager::adjust_xml_tree (XMLTree& tree, const std::string& old_name, const std::string& new_name) const
726 {
727         return adjust_plugin_paths (tree.root(), old_name, string (new_name));
728 }