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