13
0
livetrax/gtk2_ardour/template_dialog.cc
Johannes Mueller 0da2d7bf8a Inflate template archives rather in .config/ardour5/ than in ...
`.config/ardour5/(templates|route_templates)`.

We put as toplevel directory of the archive `templates` or
`route_templates`. Then no matter if the user imports a session template or a
route template archives, we always put them into the correct folder.

As now the user can also import route templates while the
SessionTemplateManager is visible and vice versa, we need to signal the
successful import to the corresponding template manager. Therfor we introduce
the signal TemplatesImported.
2017-07-15 11:37:15 +02:00

605 lines
17 KiB
C++

/*
Copyright (C) 2010 Paul Davis
Author: Johannes Mueller
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include <map>
#include <glib/gstdio.h>
#include <gtkmm/filechooserdialog.h>
#include <gtkmm/notebook.h>
#include <gtkmm/separator.h>
#include <gtkmm/scrolledwindow.h>
#include <gtkmm/stock.h>
#include <gtkmm/treeiter.h>
#include "pbd/basename.h"
#include "pbd/error.h"
#include "pbd/file_archive.h"
#include "pbd/file_utils.h"
#include "pbd/i18n.h"
#include "pbd/xml++.h"
#include "gtkmm2ext/gui_thread.h"
#include "ardour/filesystem_paths.h"
#include "ardour/template_utils.h"
#include "template_dialog.h"
using namespace std;
using namespace Gtk;
using namespace PBD;
using namespace ARDOUR;
TemplateDialog::TemplateDialog ()
: ArdourDialog ("Manage Templates")
{
Notebook* nb = manage (new Notebook);
SessionTemplateManager* session_tm = manage (new SessionTemplateManager);
nb->append_page (*session_tm, _("Session Templates"));
RouteTemplateManager* route_tm = manage (new RouteTemplateManager);
nb->append_page (*route_tm, _("Track Templates"));
get_vbox()->pack_start (*nb);
add_button (_("Ok"), Gtk::RESPONSE_OK);
get_vbox()->show_all();
session_tm->init ();
route_tm->init ();
session_tm->TemplatesImported.connect (*this, invalidator (*this), boost::bind (&RouteTemplateManager::init, route_tm), gui_context ());
route_tm->TemplatesImported.connect (*this, invalidator (*this), boost::bind (&SessionTemplateManager::init, session_tm), gui_context ());
}
TemplateManager::TemplateManager ()
: HBox ()
, ProgressReporter ()
, _remove_button (_("Remove"))
, _rename_button (_("Rename"))
, _export_all_templates_button (_("Export all"))
, _import_template_set_button (_("Import"))
{
_template_model = ListStore::create (_template_columns);
_template_treeview.set_model (_template_model);
_validated_column.set_title (_("Template Name"));
_validated_column.pack_start (_validating_cellrenderer);
_template_treeview.append_column (_validated_column);
_validating_cellrenderer.property_editable() = true;
_validated_column.set_cell_data_func (_validating_cellrenderer, sigc::mem_fun (*this, &TemplateManager::render_template_names));
_validating_cellrenderer.signal_edited().connect (sigc::mem_fun (*this, &TemplateManager::validate_edit));
_template_treeview.signal_cursor_changed().connect (sigc::mem_fun (*this, &TemplateManager::row_selection_changed));
_template_treeview.signal_key_press_event().connect (sigc::mem_fun (*this, &TemplateManager::key_event));
ScrolledWindow* sw = manage (new ScrolledWindow);
sw->property_hscrollbar_policy() = POLICY_AUTOMATIC;
sw->add (_template_treeview);
sw->set_size_request (300, 200);
VBox* vb_btns = manage (new VBox);
vb_btns->set_spacing (4);
vb_btns->pack_start (_rename_button, false, false);
vb_btns->pack_start (_remove_button, false, false);
_rename_button.set_sensitive (false);
_rename_button.signal_clicked().connect (sigc::mem_fun (*this, &TemplateManager::start_edit));
_remove_button.set_sensitive (false);
_remove_button.signal_clicked().connect (sigc::mem_fun (*this, &TemplateManager::delete_selected_template));
vb_btns->pack_start (*(manage (new VSeparator ())));
vb_btns->pack_start (_export_all_templates_button, false, false);
vb_btns->pack_start (_import_template_set_button, false, false);
_export_all_templates_button.set_sensitive (true);
_export_all_templates_button.signal_clicked().connect (sigc::mem_fun (*this, &TemplateManager::export_all_templates));
_import_template_set_button.set_sensitive (true);
_import_template_set_button.signal_clicked().connect (sigc::mem_fun (*this, &TemplateManager::import_template_set));
set_spacing (6);
VBox* vb = manage (new VBox);
vb->pack_start (*sw);
vb->pack_start (_progress_bar);
pack_start (*vb);
pack_start (*vb_btns);
show_all_children ();
_progress_bar.hide ();
}
void
TemplateManager::setup_model (const vector<TemplateInfo>& templates)
{
_template_model->clear ();
for (vector<TemplateInfo>::const_iterator it = templates.begin(); it != templates.end(); ++it) {
TreeModel::Row row;
row = *(_template_model->append ());
row[_template_columns.name] = it->name;
row[_template_columns.path] = it->path;
}
}
void
TemplateManager::row_selection_changed ()
{
bool has_selection = false;
if (_template_treeview.get_selection()->count_selected_rows () != 0) {
Gtk::TreeModel::const_iterator it = _template_treeview.get_selection()->get_selected ();
if (it) {
has_selection = true;
}
}
_rename_button.set_sensitive (has_selection);
_remove_button.set_sensitive (has_selection);
}
void
TemplateManager::render_template_names (Gtk::CellRenderer*, const Gtk::TreeModel::iterator& it)
{
if (it) {
_validating_cellrenderer.property_text () = it->get_value (_template_columns.name);
}
}
void
TemplateManager::validate_edit (const Glib::ustring& path_string, const Glib::ustring& new_name)
{
const TreePath path (path_string);
TreeModel::iterator current = _template_model->get_iter (path);
if (current->get_value (_template_columns.name) == new_name) {
return;
}
TreeModel::Children rows = _template_model->children ();
bool found = false;
for (TreeModel::Children::const_iterator it = rows.begin(); it != rows.end(); ++it) {
if (it->get_value (_template_columns.name) == new_name) {
found = true;
break;
}
}
if (found) {
error << string_compose (_("Template of name \"%1\" already exists"), new_name) << endmsg;
return;
}
rename_template (current, new_name);
}
void
TemplateManager::start_edit ()
{
TreeModel::Path path;
TreeViewColumn* col;
_template_treeview.get_cursor (path, col);
_template_treeview.set_cursor (path, *col, /*set_editing =*/ true);
}
bool
TemplateManager::key_event (GdkEventKey* ev)
{
if (ev->keyval == GDK_KEY_F2) {
start_edit ();
return true;
}
if (ev->keyval == GDK_KEY_Delete) {
delete_selected_template ();
return true;
}
return false;
}
static
bool accept_all_files (string const &, void *)
{
return true;
}
static void _set_progress (Progress* p, size_t n, size_t t)
{
p->set_progress (float (n) / float(t));
}
void
TemplateManager::export_all_templates ()
{
GError* err = NULL;
char* td = g_dir_make_tmp ("ardour-templates-XXXXXX", &err);
if (!td) {
error << string_compose(_("Could not make tmpdir: %1"), err->message) << endmsg;
return;
}
const string tmpdir (td);
g_free (td);
g_clear_error (&err);
FileChooserDialog dialog(_("Save Exported Template Archive"), FILE_CHOOSER_ACTION_SAVE);
dialog.set_filename (X_("templates.tar.xz"));
dialog.add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL);
dialog.add_button(Gtk::Stock::OK, Gtk::RESPONSE_OK);
FileFilter archive_filter;
archive_filter.add_pattern (X_("*.tar.xz"));
archive_filter.set_name (_("Template archives"));
dialog.add_filter (archive_filter);
int result = dialog.run ();
if (result != RESPONSE_OK || !dialog.get_filename().length()) {
return;
}
PBD::copy_recurse (templates_dir (), Glib::build_filename (tmpdir, Glib::path_get_basename (templates_dir ())));
vector<string> files;
PBD::find_files_matching_regex (files, tmpdir, string ("\\.template$"), /* recurse = */ true);
vector<string>::const_iterator it;
for (it = files.begin(); it != files.end(); ++it) {
const string bn = PBD::basename_nosuffix (*it);
const string old_path = Glib::build_filename (templates_dir (), bn);
const string new_path = Glib::build_filename ("$TEMPLATEDIR", bn);
XMLTree tree;
if (!tree.read (*it)) {
continue;
}
if (adjust_xml_tree (tree, old_path, new_path)) {
tree.write (*it);
}
}
find_files_matching_filter (files, tmpdir, accept_all_files, 0, false, true, true);
std::map<std::string, std::string> filemap;
for (it = files.begin(); it != files.end(); ++it) {
filemap[*it] = it->substr (tmpdir.size()+1, it->size() - tmpdir.size() - 1);
}
_current_action = _("Exporting templates");
PBD::FileArchive ar (dialog.get_filename());
PBD::ScopedConnectionList progress_connection;
ar.progress.connect_same_thread (progress_connection, boost::bind (&_set_progress, this, _1, _2));
ar.create (filemap);
PBD::remove_directory (tmpdir);
}
void
TemplateManager::import_template_set ()
{
FileChooserDialog dialog (_("Import template archives"));
dialog.add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL);
dialog.add_button(Gtk::Stock::OK, Gtk::RESPONSE_OK);
FileFilter archive_filter;
archive_filter.add_pattern (X_("*.tar.xz"));
archive_filter.set_name (_("Template archives"));
dialog.add_filter (archive_filter);
int result = dialog.run ();
if (result != RESPONSE_OK || !dialog.get_filename().length()) {
return;
}
_current_action = _("Importing templates");
FileArchive ar (dialog.get_filename ());
PBD::ScopedConnectionList progress_connection;
ar.progress.connect_same_thread (progress_connection, boost::bind (&_set_progress, this, _1, _2));
ar.inflate (user_config_directory ());
vector<string> files;
PBD::find_files_matching_regex (files, templates_dir (), string ("\\.template$"), /* recurse = */ true);
vector<string>::const_iterator it;
for (it = files.begin(); it != files.end(); ++it) {
const string bn = PBD::basename_nosuffix (*it);
const string old_path = Glib::build_filename ("$TEMPLATEDIR", bn);
const string new_path = Glib::build_filename (templates_dir (), bn);
XMLTree tree;
if (!tree.read (*it)) {
continue;
}
if (adjust_xml_tree (tree, old_path, new_path)) {
tree.write (*it);
}
}
init ();
TemplatesImported (); /* emit signal */
}
bool
TemplateManager::adjust_plugin_paths (XMLNode* node, const string& name, const string& new_name) const
{
bool adjusted = false;
const XMLNodeList& procs = node->children (X_("Processor"));
XMLNodeConstIterator pit;
for (pit = procs.begin(); pit != procs.end(); ++pit) {
XMLNode* lv2_node = (*pit)->child (X_("lv2"));
if (!lv2_node) {
continue;
}
string template_dir;
if (!lv2_node->get_property (X_("template-dir"), template_dir)) {
continue;
}
const int suffix_pos = template_dir.size() - name.size();
if (suffix_pos < 0) {
cerr << "Template name\"" << name << "\" longer than template-dir \"" << template_dir << "\", WTH?" << endl;
continue;
}
if (template_dir.compare (suffix_pos, template_dir.size(), name)) {
cerr << "Template name \"" << name << "\" no suffix of template-dir \"" << template_dir << "\"" << endl;
continue;
}
const string new_template_dir = template_dir.substr (0, suffix_pos) + new_name;
lv2_node->set_property (X_("template-dir"), new_template_dir);
adjusted = true;
}
return adjusted;
}
void
TemplateManager::update_progress_gui (float p)
{
if (p >= 1.0) {
_progress_bar.hide ();
return;
}
_progress_bar.show ();
_progress_bar.set_text (_current_action);
_progress_bar.set_fraction (p);
}
void SessionTemplateManager::init ()
{
vector<TemplateInfo> templates;
find_session_templates (templates);
setup_model (templates);
_progress_bar.hide ();
}
void RouteTemplateManager::init ()
{
vector<TemplateInfo> templates;
find_route_templates (templates);
setup_model (templates);
_progress_bar.hide ();
}
void
SessionTemplateManager::rename_template (TreeModel::iterator& item, const Glib::ustring& new_name_)
{
const string old_path = item->get_value (_template_columns.path);
const string old_name = item->get_value (_template_columns.name);
const string new_name = string (new_name_);
const string old_file_old_path = Glib::build_filename (old_path, old_name+".template");
XMLTree tree;
if (!tree.read(old_file_old_path)) {
error << string_compose (_("Could not parse template file \"%1\"."), old_file_old_path) << endmsg;
return;
}
XMLNode* root = tree.root();
const XMLNode* const routes_node = root->child (X_("Routes"));
if (routes_node) {
const XMLNodeList& routes = routes_node->children (X_("Route"));
XMLNodeConstIterator rit;
for (rit = routes.begin(); rit != routes.end(); ++rit) {
adjust_plugin_paths (*rit, old_name, new_name);
}
}
const string new_file_old_path = Glib::build_filename (old_path, new_name+".template");
tree.set_filename (new_file_old_path);
if (!tree.write ()) {
error << string_compose(_("Could not write to new template file \"%1\"."), new_file_old_path);
return;
}
const string new_path = Glib::build_filename (user_template_directory (), new_name);
if (g_rename (old_path.c_str(), new_path.c_str()) != 0) {
error << string_compose (_("Could not rename template directory from \"%1\" to \"%2\": %3"),
old_path, new_path, strerror (errno)) << endmsg;
g_unlink (new_file_old_path.c_str());
return;
}
const string old_file_new_path = Glib::build_filename (new_path, old_name+".template");
if (g_unlink (old_file_new_path.c_str())) {
error << string_compose (X_("Could not delete old template file \"%1\": %2"),
old_file_new_path, strerror (errno)) << endmsg;
}
item->set_value (_template_columns.name, new_name);
item->set_value (_template_columns.path, new_path);
}
void
SessionTemplateManager::delete_selected_template ()
{
if (_template_treeview.get_selection()->count_selected_rows() == 0) {
return;
}
Gtk::TreeModel::const_iterator it = _template_treeview.get_selection()->get_selected();
if (!it) {
return;
}
PBD::remove_directory (it->get_value (_template_columns.path));
_template_model->erase (it);
row_selection_changed ();
}
string
SessionTemplateManager::templates_dir () const
{
return user_template_directory ();
}
bool
SessionTemplateManager::adjust_xml_tree (XMLTree& tree, const std::string& old_name, const std::string& new_name) const
{
bool adjusted = false;
XMLNode* root = tree.root();
const XMLNode* const routes_node = root->child (X_("Routes"));
if (routes_node) {
const XMLNodeList& routes = routes_node->children (X_("Route"));
XMLNodeConstIterator rit;
for (rit = routes.begin(); rit != routes.end(); ++rit) {
if (adjust_plugin_paths (*rit, old_name, new_name)) {
adjusted = true;
}
}
}
return adjusted;
}
void
RouteTemplateManager::rename_template (TreeModel::iterator& item, const Glib::ustring& new_name)
{
const string old_name = item->get_value (_template_columns.name);
const string old_filepath = item->get_value (_template_columns.path);
const string new_filepath = Glib::build_filename (user_route_template_directory(), new_name+".template");
XMLTree tree;
if (!tree.read (old_filepath)) {
error << string_compose (_("Could not parse template file \"%1\"."), old_filepath) << endmsg;
return;
}
tree.root()->children().front()->set_property (X_("name"), new_name);
const bool adjusted = adjust_plugin_paths (tree.root(), old_name, string (new_name));
const string old_state_dir = Glib::build_filename (user_route_template_directory(), old_name);
const string new_state_dir = Glib::build_filename (user_route_template_directory(), new_name);
if (adjusted) {
if (g_rename (old_state_dir.c_str(), new_state_dir.c_str()) != 0) {
error << string_compose (_("Could not rename state dir \"%1\" to \"%22\": %3"), old_state_dir, new_state_dir, strerror (errno)) << endmsg;
return;
}
}
tree.set_filename (new_filepath);
if (!tree.write ()) {
error << string_compose(_("Could not write new template file \"%1\"."), new_filepath) << endmsg;
if (adjusted) {
g_rename (new_state_dir.c_str(), old_state_dir.c_str());
}
return;
}
if (g_unlink (old_filepath.c_str()) != 0) {
error << string_compose (_("Could not remove old template file \"%1\": %2"), old_filepath, strerror (errno)) << endmsg;
}
item->set_value (_template_columns.name, string (new_name));
item->set_value (_template_columns.path, new_filepath);
}
void
RouteTemplateManager::delete_selected_template ()
{
if (_template_treeview.get_selection()->count_selected_rows() == 0) {
return;
}
Gtk::TreeModel::const_iterator it = _template_treeview.get_selection()->get_selected();
if (!it) {
return;
}
const string file_path = it->get_value (_template_columns.path);
if (g_unlink (file_path.c_str()) != 0) {
error << string_compose(_("Could not delete template file \"%1\": %2"), file_path, strerror (errno)) << endmsg;
return;
}
PBD::remove_directory (Glib::build_filename (user_route_template_directory (), it->get_value (_template_columns.name)));
_template_model->erase (it);
row_selection_changed ();
}
string
RouteTemplateManager::templates_dir () const
{
return user_route_template_directory ();
}
bool
RouteTemplateManager::adjust_xml_tree (XMLTree& tree, const std::string& old_name, const std::string& new_name) const
{
return adjust_plugin_paths (tree.root(), old_name, string (new_name));
}