1 /*
2  * Copyright (C) 2008-2009 Sakari Bergen <sakari.bergen@beatwaves.net>
3  * Copyright (C) 2009-2010 Carl Hetherington <carl@carlh.net>
4  * Copyright (C) 2009-2011 David Robillard <d@drobilla.net>
5  * Copyright (C) 2009-2017 Paul Davis <paul@linuxaudiosystems.com>
6  * Copyright (C) 2015-2018 Robin Gareus <robin@gareus.org>
7  *
8  * This program is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation; either version 2 of the License, or
11  * (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License along
19  * with this program; if not, write to the Free Software Foundation, Inc.,
20  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21  */
22 
23 #include <gtkmm/messagedialog.h>
24 #include <gtkmm/stock.h>
25 
26 #include "pbd/failed_constructor.h"
27 
28 #include "ardour/audio_region_importer.h"
29 #include "ardour/audio_playlist_importer.h"
30 #include "ardour/audio_track_importer.h"
31 #include "ardour/filename_extensions.h"
32 #include "ardour/location_importer.h"
33 #include "ardour/tempo_map_importer.h"
34 
35 #include "gtkmm2ext/utils.h"
36 #include "widgets/prompter.h"
37 
38 #include "ardour_message.h"
39 #include "gui_thread.h"
40 #include "session_import_dialog.h"
41 #include "ui_config.h"
42 
43 #include "pbd/i18n.h"
44 
45 using namespace std;
46 using namespace ARDOUR;
47 using namespace PBD;
48 using namespace Gtk;
49 
SessionImportDialog(ARDOUR::Session * target)50 SessionImportDialog::SessionImportDialog (ARDOUR::Session* target) :
51   ArdourDialog (_("Import from Session")),
52   file_browse_button (_("Browse"))
53 {
54 	set_session (target);
55 
56 	// File entry
57 	file_entry.set_name ("ImportFileNameEntry");
58 	file_entry.set_text ("/");
59 	Gtkmm2ext::set_size_request_to_display_given_text (file_entry, X_("Kg/quite/a/reasonable/size/for/files/i/think"), 5, 8);
60 
61 	file_browse_button.set_name ("EditorGTKButton");
62 	file_browse_button.signal_clicked().connect (sigc::mem_fun(*this, &SessionImportDialog::browse));
63 
64 	file_hbox.set_spacing (5);
65 	file_hbox.set_border_width (5);
66 	file_hbox.pack_start (file_entry, true, true);
67 	file_hbox.pack_start (file_browse_button, false, false);
68 
69 	file_frame.add (file_hbox);
70 	file_frame.set_border_width (5);
71 	file_frame.set_name ("ImportFrom");
72 	file_frame.set_label (_("Import from Session"));
73 
74 	get_vbox()->pack_start (file_frame, false, false);
75 
76 	// Session browser
77 	session_tree = TreeStore::create (sb_cols);
78 	session_browser.set_model (session_tree);
79 
80 	session_browser.set_name ("SessionBrowser");
81 	session_browser.append_column (_("Elements"), sb_cols.name);
82 	session_browser.append_column_editable (_("Import"), sb_cols.queued);
83 	session_browser.get_column(0)->set_min_width (180);
84 	session_browser.get_column(1)->set_min_width (40);
85 	session_browser.get_column(1)->set_sizing (TREE_VIEW_COLUMN_AUTOSIZE);
86 	if (UIConfiguration::instance().get_use_tooltips()) {
87 		session_browser.set_tooltip_column (3);
88 	}
89 
90 	session_scroll.set_policy (POLICY_AUTOMATIC, POLICY_AUTOMATIC);
91 	session_scroll.add (session_browser);
92 	session_scroll.set_size_request (220, 400);
93 
94 	// Connect signals
95 	CellRendererToggle *toggle = dynamic_cast<CellRendererToggle *> (session_browser.get_column_cell_renderer (1));
96 	toggle->signal_toggled().connect(sigc::mem_fun (*this, &SessionImportDialog::update));
97 	session_browser.signal_row_activated().connect(sigc::mem_fun (*this, &SessionImportDialog::show_info));
98 
99 	get_vbox()->pack_start (session_scroll, false, false);
100 
101 	// Buttons
102 	cancel_button = add_button (Stock::CANCEL, RESPONSE_CANCEL);
103 	cancel_button->signal_clicked().connect (sigc::mem_fun (*this, &SessionImportDialog::end_dialog));
104 	ok_button = add_button (_("Import"), RESPONSE_ACCEPT);
105 	ok_button->signal_clicked().connect (sigc::mem_fun (*this, &SessionImportDialog::do_merge));
106 
107 	// prompt signals XXX: problem - handlers to be in the same thread since they return values
108 	ElementImporter::Rename.connect_same_thread (connections, boost::bind (&SessionImportDialog::open_rename_dialog, this, _1, _2));
109 	ElementImporter::Prompt.connect_same_thread (connections, boost::bind (&SessionImportDialog::open_prompt_dialog, this, _1));
110 
111 	// Finalize
112 	show_all();
113 }
114 
115 void
load_session(const string & filename)116 SessionImportDialog::load_session (const string& filename)
117 {
118 	if (_session) {
119 		if (tree.read (filename)) {
120 			error << string_compose (_("Cannot load XML for session from %1"), filename) << endmsg;
121 			return;
122 		}
123 		boost::shared_ptr<AudioRegionImportHandler> region_handler (new AudioRegionImportHandler (tree, *_session));
124 		boost::shared_ptr<AudioPlaylistImportHandler> pl_handler (new AudioPlaylistImportHandler (tree, *_session, *region_handler));
125 
126 		handlers.push_back (boost::static_pointer_cast<ElementImportHandler> (region_handler));
127 		handlers.push_back (boost::static_pointer_cast<ElementImportHandler> (pl_handler));
128 		handlers.push_back (HandlerPtr(new UnusedAudioPlaylistImportHandler (tree, *_session, *region_handler)));
129 		handlers.push_back (HandlerPtr(new AudioTrackImportHandler (tree, *_session, *pl_handler)));
130 		handlers.push_back (HandlerPtr(new LocationImportHandler (tree, *_session)));
131 		handlers.push_back (HandlerPtr(new TempoMapImportHandler (tree, *_session)));
132 
133 		fill_list();
134 
135 		if (ElementImportHandler::dirty()) {
136 			// Warn user
137 			string txt = _("Some elements had errors in them. Please see the log for details");
138 			ArdourMessageDialog msg (txt, false, MESSAGE_WARNING, BUTTONS_OK, true);
139 			msg.run();
140 		}
141 	}
142 }
143 
144 void
fill_list()145 SessionImportDialog::fill_list ()
146 {
147 	session_tree->clear();
148 
149 	// Loop through element types
150 	for (HandlerList::iterator handler = handlers.begin(); handler != handlers.end(); ++handler) {
151 		TreeModel::iterator iter = session_tree->append();
152 		TreeModel::Row row = *iter;
153 		row[sb_cols.name] = (*handler)->get_info();
154 		row[sb_cols.queued] = false;
155 		row[sb_cols.element] = ElementPtr(); // "Null" pointer
156 
157 		// Loop through elements
158 		ElementList &elements = (*handler)->elements;
159 		for (ElementList::iterator element = elements.begin(); element != elements.end(); ++element) {
160 			iter = session_tree->append(row.children());
161 			TreeModel::Row child = *iter;
162 			child[sb_cols.name] = (*element)->get_name();
163 			child[sb_cols.queued] = false;
164 			child[sb_cols.element] = *element;
165 			child[sb_cols.info] = (*element)->get_info();
166 		}
167 	}
168 }
169 
170 void
browse()171 SessionImportDialog::browse ()
172 {
173 	FileChooserDialog dialog(_("Import from session"), browse_action());
174 	dialog.set_transient_for(*this);
175 	dialog.set_filename (file_entry.get_text());
176 
177 	FileFilter session_filter;
178 	session_filter.add_pattern (string_compose(X_("*%1"), ARDOUR::statefile_suffix));
179 	session_filter.set_name (string_compose (_("%1 sessions"), PROGRAM_NAME));
180 	dialog.add_filter (session_filter);
181 	dialog.set_filter (session_filter);
182 
183 	dialog.add_button(Stock::CANCEL, RESPONSE_CANCEL);
184 	dialog.add_button(Stock::OK, RESPONSE_OK);
185 
186 	int result = dialog.run();
187 
188 	if (result == RESPONSE_OK) {
189 		string filename = dialog.get_filename();
190 
191 		if (filename.length()) {
192 			file_entry.set_text (filename);
193 			load_session (filename);
194 		}
195 	}
196 }
197 
198 void
do_merge()199 SessionImportDialog::do_merge ()
200 {
201 
202 	// element types
203 	TreeModel::Children types = session_browser.get_model()->children();
204 	TreeModel::Children::iterator ti;
205 	for (ti = types.begin(); ti != types.end(); ++ti) {
206 		// elements
207 		TreeModel::Children elements = ti->children();
208 		TreeModel::Children::iterator ei;
209 		for (ei = elements.begin(); ei != elements.end(); ++ei) {
210 			if ((*ei)[sb_cols.queued]) {
211 				ElementPtr element = (*ei)[sb_cols.element];
212 				element->move();
213 			}
214 		}
215 	}
216 
217 	end_dialog();
218 
219 	if (ElementImportHandler::errors()) {
220 		// Warn user
221 		string txt = _("Some elements had errors in them. Please see the log for details");
222 		ArdourMessageDialog msg (txt, false, MESSAGE_WARNING, BUTTONS_OK, true);
223 		msg.run();
224 	}
225 }
226 
227 
228 void
update(string path)229 SessionImportDialog::update (string path)
230 {
231 	TreeModel::iterator cell = session_browser.get_model()->get_iter (path);
232 
233 	// Select all elements if element type is selected
234 	if (path.size() == 1) {
235 		{
236 			// Prompt user for verification
237 			string txt = _("This will select all elements of this type!");
238 			ArdourMessageDialog msg (txt, false, MESSAGE_QUESTION, BUTTONS_OK_CANCEL, true);
239 			switch (msg.run()) {
240 				case Gtk::RESPONSE_ACCEPT:
241 				case Gtk::RESPONSE_OK:
242 					break;
243 				default:
244 					(*cell)[sb_cols.queued] = false;
245 					return;
246 			}
247 		}
248 
249 		TreeModel::Children elements = cell->children();
250 		TreeModel::Children::iterator ei;
251 		for (ei = elements.begin(); ei != elements.end(); ++ei) {
252 			ElementPtr element = (*ei)[sb_cols.element];
253 			if (element->prepare_move()) {
254 				(*ei)[sb_cols.queued] = true;
255 			} else {
256 				(*cell)[sb_cols.queued] = false; // Not all are selected
257 			}
258 		}
259 		return;
260 	}
261 
262 	ElementPtr element = (*cell)[sb_cols.element];
263 	if ((*cell)[sb_cols.queued]) {
264 		if (!element->prepare_move()) {
265 			(*cell)[sb_cols.queued] = false;
266 		}
267 	} else {
268 		element->cancel_move();
269 	}
270 }
271 
272 void
show_info(const TreeModel::Path & path,TreeViewColumn *)273 SessionImportDialog::show_info(const TreeModel::Path& path, TreeViewColumn*)
274 {
275 	if (path.size() == 1) {
276 		return;
277 	}
278 
279 	TreeModel::iterator cell = session_browser.get_model()->get_iter (path);
280 	string info = (*cell)[sb_cols.info];
281 
282 	ArdourMessageDialog msg (info, false, MESSAGE_INFO, BUTTONS_OK, true);
283 	msg.run();
284 }
285 
286 void
end_dialog()287 SessionImportDialog::end_dialog ()
288 {
289 	hide_all();
290 
291 	set_modal (false);
292 	ok_button->set_sensitive(true);
293 }
294 
295 std::pair<bool, string>
open_rename_dialog(string text,string name)296 SessionImportDialog::open_rename_dialog (string text, string name)
297 {
298 	ArdourWidgets::Prompter prompter(true);
299 	string new_name;
300 
301 	prompter.set_name ("Prompter");
302 	prompter.add_button (Stock::SAVE, RESPONSE_ACCEPT);
303 	prompter.set_prompt (text);
304 	prompter.set_initial_text (name);
305 
306 	if (prompter.run() == RESPONSE_ACCEPT) {
307 		prompter.get_result (new_name);
308 		if (new_name.length()) {
309 			name = new_name;
310 		}
311 		return std::make_pair (true, new_name);
312 	}
313 	return std::make_pair (false, new_name);
314 }
315 
316 bool
open_prompt_dialog(string text)317 SessionImportDialog::open_prompt_dialog (string text)
318 {
319 	ArdourMessageDialog msg (text, false, MESSAGE_QUESTION, BUTTONS_OK_CANCEL, true);
320 	if (msg.run() == RESPONSE_OK) {
321 		return true;
322 	}
323 	return false;
324 }
325