1 /*
2  * Copyright (C) 2009-2011 David Robillard <d@drobilla.net>
3  * Copyright (C) 2009-2012 Carl Hetherington <carl@carlh.net>
4  * Copyright (C) 2009-2016 Paul Davis <paul@linuxaudiosystems.com>
5  * Copyright (C) 2012-2015 Tim Mayberry <mojofunk@gmail.com>
6  * Copyright (C) 2012-2019 Robin Gareus <robin@gareus.org>
7  * Copyright (C) 2013 Colin Fletcher <colin.m.fletcher@googlemail.com>
8  * Copyright (C) 2013 Michael R. Fisher <mfisher@bketech.com>
9  * Copyright (C) 2015 John Emmas <john@creativepost.co.uk>
10  *
11  * This program is free software; you can redistribute it and/or modify
12  * it under the terms of the GNU General Public License as published by
13  * the Free Software Foundation; either version 2 of the License, or
14  * (at your option) any later version.
15  *
16  * This program is distributed in the hope that it will be useful,
17  * but WITHOUT ANY WARRANTY; without even the implied warranty of
18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19  * GNU General Public License for more details.
20  *
21  * You should have received a copy of the GNU General Public License along
22  * with this program; if not, write to the Free Software Foundation, Inc.,
23  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
24  */
25 
26 #ifdef WAF_BUILD
27 #include "gtk2ardour-config.h"
28 #include "gtk2ardour-version.h"
29 #endif
30 
31 #include <algorithm>
32 #include <fcntl.h>
33 
34 #include "pbd/gstdio_compat.h"
35 
36 #include <gtkmm.h>
37 
38 #include "pbd/basename.h"
39 #include "pbd/failed_constructor.h"
40 #include "pbd/scoped_file_descriptor.h"
41 #include "pbd/file_utils.h"
42 #include "pbd/replace_all.h"
43 #include "pbd/whitespace.h"
44 #include "pbd/openuri.h"
45 
46 #include "ardour/audioengine.h"
47 #include "ardour/filesystem_paths.h"
48 #include "ardour/filename_extensions.h"
49 #include "ardour/plugin_manager.h"
50 #include "ardour/recent_sessions.h"
51 #include "ardour/session.h"
52 #include "ardour/session_state_utils.h"
53 #include "ardour/template_utils.h"
54 #include "ardour/profile.h"
55 
56 #include "gtkmm2ext/utils.h"
57 
58 #include "new_user_wizard.h"
59 #include "opts.h"
60 #include "ui_config.h"
61 #include "pbd/i18n.h"
62 #include "utils.h"
63 
64 using namespace std;
65 using namespace Gtk;
66 using namespace Gdk;
67 using namespace Glib;
68 using namespace PBD;
69 using namespace ARDOUR;
70 using namespace ARDOUR_UI_UTILS;
71 
NewUserWizard()72 NewUserWizard::NewUserWizard ()
73 	: config_modified (false)
74 	, default_dir_chooser (0)
75 	, monitor_via_hardware_button (string_compose (_("Use an external mixer or the hardware mixer of your audio interface.\n"
76 							 "%1 will play NO role in monitoring"), PROGRAM_NAME))
77 	, monitor_via_ardour_button (string_compose (_("Ask %1 to play back material as it is being recorded"), PROGRAM_NAME))
78 	, audio_page_index (-1)
79 	, new_user_page_index (-1)
80 	, default_folder_page_index (-1)
81 	, monitoring_page_index (-1)
82 	, final_page_index (-1)
83 {
84 	set_position (WIN_POS_CENTER);
85 	set_border_width (12);
86 
87 	if (! (icon_pixbuf = ::get_icon (PROGRAM_NAME "-icon_48px"))) {
88 		throw failed_constructor();
89 	}
90 
91 	list<Glib::RefPtr<Gdk::Pixbuf> > window_icons;
92 	Glib::RefPtr<Gdk::Pixbuf> icon;
93 
94 	if ((icon = ::get_icon (PROGRAM_NAME "-icon_16px"))) {
95 		window_icons.push_back (icon);
96 	}
97 	if ((icon = ::get_icon (PROGRAM_NAME "-icon_22px"))) {
98 		window_icons.push_back (icon);
99 	}
100 	if ((icon = ::get_icon (PROGRAM_NAME "-icon_32px"))) {
101 		window_icons.push_back (icon);
102 	}
103 	if ((icon = ::get_icon (PROGRAM_NAME "-icon_48px"))) {
104 		window_icons.push_back (icon);
105 	}
106 	if (!window_icons.empty ()) {
107 		set_default_icon_list (window_icons);
108 	}
109 
110 	setup_new_user_page ();
111 	setup_first_time_config_page ();
112 	setup_monitoring_choice_page ();
113 	setup_monitor_section_choice_page ();
114 	setup_final_page ();
115 }
116 
~NewUserWizard()117 NewUserWizard::~NewUserWizard ()
118 {
119 }
120 
121 bool
required()122 NewUserWizard::required ()
123 {
124 	if (Glib::file_test (ARDOUR::been_here_before_path (), Glib::FILE_TEST_EXISTS)) {
125 		return false;
126 	}
127 
128 	return true;
129 }
130 
131 void
setup_new_user_page()132 NewUserWizard::setup_new_user_page ()
133 {
134 	Label* foomatic = manage (new Label);
135 
136 	foomatic->set_markup (string_compose (_("\
137 <span size=\"larger\">%1 is a digital audio workstation. You can use it to \
138 record, edit and mix multi-track audio. You can produce your \
139 own CDs, mix video soundtracks, or experiment with new \
140 ideas about music and sound. \
141 \n\n\
142 There are a few things that need to be configured before you start \
143 using the program.</span> \
144 "), PROGRAM_NAME));
145 	foomatic->set_justify (JUSTIFY_FILL);
146 	foomatic->set_line_wrap ();
147 
148 	VBox* vbox = manage (new VBox);
149 	vbox->set_border_width (24);
150 	vbox->pack_start (*foomatic, true, true, 12);
151 
152 #ifndef __APPLE__
153 	Label* barmatic = manage (new Label);
154 	barmatic->set_text (_("GUI and Font scaling:"));
155 
156 	Label* bazmatic = manage (new Label);
157 	bazmatic->set_markup (_("<small><i>This can later be changed in Preferences &gt; Appearance.</i></small>"));
158 
159 	ui_font_scale.append_text (_("100%"));
160 	ui_font_scale.append_text (_("150%"));
161 	ui_font_scale.append_text (_("200%"));
162 	ui_font_scale.append_text (_("250%"));
163 	ui_font_scale.set_active_text (_("100%"));
164 
165 	HBox* hbox = manage (new HBox);
166 	HBox* cbox = manage (new HBox);
167 
168 	hbox->pack_start (*barmatic, false, false);
169 	hbox->pack_start (ui_font_scale, false, false);
170 	cbox->pack_start (*hbox, true, false);
171 
172 	vbox->pack_start (*cbox, false, false, 2);
173 	vbox->pack_start (*bazmatic, false, false);
174 
175 	ui_font_scale.show ();
176 	barmatic->show ();
177 	bazmatic->show ();
178 	hbox->show ();
179 	cbox->show ();
180 
181 	guess_default_ui_scale ();
182 	ui_font_scale.signal_changed ().connect (sigc::mem_fun (*this, &NewUserWizard::rescale_ui));
183 #endif
184 
185 	foomatic->show ();
186 	vbox->show ();
187 
188 	new_user_page_index = append_page (*vbox);
189 	set_page_type (*vbox, ASSISTANT_PAGE_INTRO);
190 	set_page_title (*vbox, string_compose (_("Welcome to %1"), PROGRAM_NAME));
191 	set_page_header_image (*vbox, icon_pixbuf);
192 	set_page_complete (*vbox, true);
193 }
194 
195 void
rescale_ui()196 NewUserWizard::rescale_ui ()
197 {
198 	int rn = ui_font_scale.get_active_row_number ();
199 	if (rn < 0 ) {
200 		return;
201 	}
202 	float ui_scale = 100 + rn * 50;
203 	UIConfiguration::instance ().set_font_scale (1024 * ui_scale);
204 	UIConfiguration::instance ().reset_dpi ();
205 }
206 
207 void
guess_default_ui_scale()208 NewUserWizard::guess_default_ui_scale ()
209 {
210 	gint width = 0;
211 	gint height = 0;
212 	GdkScreen* screen = gdk_display_get_screen (gdk_display_get_default (), 0);
213 	gint n_monitors = gdk_screen_get_n_monitors (screen);
214 
215 	if (!screen) {
216 		return;
217 	}
218 
219 	for (gint i = 0; i < n_monitors; ++i) {
220 		GdkRectangle rect;
221 		gdk_screen_get_monitor_geometry (screen, i, &rect);
222 		width = std::max (width, rect.width);
223 		height = std::max (height, rect.height);
224 	}
225 
226 	float wx = width  / 1920.f;
227 	float hx = height / 1080.f;
228 	float sx = std::min (wx, hx);
229 
230 	if (sx < 1.25) {
231 		ui_font_scale.set_active (0); // 100%
232 	} else if (sx < 1.6) {
233 		ui_font_scale.set_active (1); // 150%
234 	} else if (sx < 2.1) {
235 		ui_font_scale.set_active (2); // 200%
236 	} else {
237 		ui_font_scale.set_active (3); // 250%
238 	}
239 	rescale_ui ();
240 }
241 
242 void
default_dir_changed()243 NewUserWizard::default_dir_changed ()
244 {
245 	Config->set_default_session_parent_dir (default_dir_chooser->get_filename());
246 	// make new session folder chooser point to the new default
247 	new_folder_chooser.set_current_folder (Config->get_default_session_parent_dir());
248 	config_changed ();
249 }
250 
251 void
config_changed()252 NewUserWizard::config_changed ()
253 {
254 	config_modified = true;
255 }
256 
257 void
setup_first_time_config_page()258 NewUserWizard::setup_first_time_config_page ()
259 {
260 	default_dir_chooser = manage (new FileChooserButton (string_compose (_("Default folder for %1 sessions"), PROGRAM_NAME),
261 							     FILE_CHOOSER_ACTION_SELECT_FOLDER));
262 	Gtk::Label* txt = manage (new Label);
263 	HBox* hbox = manage (new HBox);
264 	VBox* vbox = manage (new VBox);
265 
266 	txt->set_markup (string_compose (_("\
267 Each project that you work on with %1 has its own folder.\n\
268 These can require a lot of disk space if you are recording audio.\n\
269 \n\
270 Where would you like new %1 sessions to be stored by default?\n\n\
271 <i>(You can put new sessions anywhere, this is just a default)</i>"), PROGRAM_NAME));
272 	txt->set_alignment (0.0, 0.0);
273 
274 	vbox->set_spacing (18);
275 	vbox->set_border_width (24);
276 
277 	hbox->pack_start (*default_dir_chooser, false, true, 8);
278 	vbox->pack_start (*txt, false, false);
279 	vbox->pack_start (*hbox, false, true);
280 
281 	cerr << "set default folder to " << poor_mans_glob (Config->get_default_session_parent_dir()) << endl;
282 	Gtkmm2ext::add_volume_shortcuts (*default_dir_chooser);
283 	default_dir_chooser->set_current_folder (poor_mans_glob (Config->get_default_session_parent_dir()));
284 	default_dir_chooser->signal_current_folder_changed().connect (sigc::mem_fun (*this, &NewUserWizard::default_dir_changed));
285 	default_dir_chooser->show ();
286 
287 	vbox->show_all ();
288 
289 	default_folder_page_index = append_page (*vbox);
290 	set_page_title (*vbox, _("Default folder for new sessions"));
291 	set_page_header_image (*vbox, icon_pixbuf);
292 	set_page_type (*vbox, ASSISTANT_PAGE_CONTENT);
293 
294 	/* user can just skip all these settings if they want to */
295 
296 	set_page_complete (*vbox, true);
297 }
298 
299 void
setup_monitoring_choice_page()300 NewUserWizard::setup_monitoring_choice_page ()
301 {
302 	mon_vbox.set_spacing (18);
303 	mon_vbox.set_border_width (24);
304 
305 	HBox* hbox = manage (new HBox);
306 	VBox* vbox = manage (new VBox);
307 	/* first button will be on by default */
308 	RadioButton::Group g (monitor_via_ardour_button.get_group());
309 	monitor_via_hardware_button.set_group (g);
310 
311 	monitor_label.set_markup(_("\
312 While recording instruments or vocals, you probably want to listen to the\n\
313 signal as well as record it. This is called \"monitoring\". There are\n\
314 different ways to do this depending on the equipment you have and the\n\
315 configuration of that equipment. The two most common are presented here.\n\
316 Please choose whichever one is right for your setup.\n\n\
317 <i>(You can change this preference at any time, via the Preferences dialog)</i>\n\n\
318 <i>If you do not understand what this is about, just accept the default.</i>"));
319 	monitor_label.set_alignment (0.0, 0.0);
320 
321 	vbox->set_spacing (6);
322 
323 	vbox->pack_start (monitor_via_hardware_button, false, true);
324 	vbox->pack_start (monitor_via_ardour_button, false, true);
325 	hbox->pack_start (*vbox, true, true, 8);
326 	mon_vbox.pack_start (monitor_label, false, false);
327 	mon_vbox.pack_start (*hbox, false, false);
328 
329 	mon_vbox.show_all ();
330 
331 	monitoring_page_index = append_page (mon_vbox);
332 	set_page_title (mon_vbox, _("Monitoring Choices"));
333 	set_page_header_image (mon_vbox, icon_pixbuf);
334 
335 	monitor_via_hardware_button.signal_toggled().connect (sigc::mem_fun (*this, &NewUserWizard::config_changed));
336 	monitor_via_ardour_button.signal_toggled().connect (sigc::mem_fun (*this, &NewUserWizard::config_changed));
337 
338 	/* user could just click on "Forward" if default
339 	 * choice is correct.
340 	 */
341 
342 	set_page_complete (mon_vbox, true);
343 }
344 
345 void
setup_monitor_section_choice_page()346 NewUserWizard::setup_monitor_section_choice_page ()
347 {
348 	mon_sec_vbox.set_spacing (18);
349 	mon_sec_vbox.set_border_width (24);
350 
351 	HBox* hbox = manage (new HBox);
352 	VBox* main_vbox = manage (new VBox);
353 	VBox* vbox;
354 	Label* l = manage (new Label);
355 
356 	main_vbox->set_spacing (32);
357 
358 	no_monitor_section_button.set_label (_("Use a Master bus directly"));
359 	l->set_alignment (0.0, 1.0);
360 	l->set_markup(_("Connect the Master bus directly to your hardware outputs. This is preferable for simple usage."));
361 
362 	vbox = manage (new VBox);
363 	vbox->set_spacing (6);
364 	vbox->pack_start (no_monitor_section_button, false, true);
365 	vbox->pack_start (*l, false, true);
366 
367 	main_vbox->pack_start (*vbox, false, false);
368 
369 	use_monitor_section_button.set_label (_("Use an additional Monitor bus"));
370 	l = manage (new Label);
371 	l->set_alignment (0.0, 1.0);
372 	l->set_text (_("Use a Monitor bus between Master bus and hardware outputs for \n\
373 greater control in monitoring without affecting the mix."));
374 
375 	vbox = manage (new VBox);
376 	vbox->set_spacing (6);
377 	vbox->pack_start (use_monitor_section_button, false, true);
378 	vbox->pack_start (*l, false, true);
379 
380 	main_vbox->pack_start (*vbox, false, false);
381 
382 	RadioButton::Group g (use_monitor_section_button.get_group());
383 	no_monitor_section_button.set_group (g);
384 
385 	if (Config->get_use_monitor_bus()) {
386 		use_monitor_section_button.set_active (true);
387 	} else {
388 		no_monitor_section_button.set_active (true);
389 	}
390 
391 	use_monitor_section_button.signal_toggled().connect (sigc::mem_fun (*this, &NewUserWizard::config_changed));
392 	no_monitor_section_button.signal_toggled().connect (sigc::mem_fun (*this, &NewUserWizard::config_changed));
393 
394 	monitor_section_label.set_markup(_("<i>You can change this preference at any time via the Preferences dialog.\nYou can also add or remove the monitor section to/from any session.</i>\n\n\
395 <i>If you do not understand what this is about, just accept the default.</i>"));
396 	monitor_section_label.set_alignment (0.0, 0.0);
397 
398 	hbox->pack_start (*main_vbox, true, true, 8);
399 	mon_sec_vbox.pack_start (*hbox, false, false);
400 	mon_sec_vbox.pack_start (monitor_section_label, false, false);
401 
402 	mon_sec_vbox.show_all ();
403 
404 	monitor_section_page_index = append_page (mon_sec_vbox);
405 	set_page_title (mon_sec_vbox, _("Monitor Section"));
406 	set_page_header_image (mon_sec_vbox, icon_pixbuf);
407 
408 	/* user could just click on "Forward" if default
409 	 * choice is correct.
410 	 */
411 
412 	set_page_complete (mon_sec_vbox, true);
413 }
414 
415 void
setup_final_page()416 NewUserWizard::setup_final_page ()
417 {
418 	string msg = string_compose (_("%1 is ready for use"), PROGRAM_NAME);
419 
420 	Gtk::Label* final_label = manage (new Label);
421 	final_label->set_markup (string_compose ("<span weight=\"bold\" size=\"large\">%1</span>", msg));
422 	final_label->show ();
423 
424 	VBox* vbox = manage (new VBox);
425 	vbox->pack_start (*final_label, true, true);
426 	vbox->show ();
427 
428 	final_page_index = append_page (*vbox);
429 	set_page_complete (*vbox, true);
430 	set_page_header_image (*vbox, icon_pixbuf);
431 	set_page_type (*vbox, ASSISTANT_PAGE_CONFIRM);
432 }
433 
434 void
on_cancel()435 NewUserWizard::on_cancel ()
436 {
437 	_signal_response (int (RESPONSE_CANCEL));
438 }
439 
440 bool
on_delete_event(GdkEventAny *)441 NewUserWizard::on_delete_event (GdkEventAny*)
442 {
443 	_signal_response (int (RESPONSE_CLOSE));
444 	return true;
445 }
446 
447 void
on_apply()448 NewUserWizard::on_apply ()
449 {
450 	/* file-chooser button does not emit 'current_folder_changed' signal
451 	 * when a folder from the dropdown or the sidebar is chosen.
452 	 * -> explicitly poll for the dir as suggested by the gtk documentation.
453 	 */
454 	if (default_dir_chooser && default_dir_chooser->get_filename() != Config->get_default_session_parent_dir ()) {
455 		config_modified = true;
456 	}
457 
458 	if (config_modified) {
459 
460 		if (default_dir_chooser) {
461 			Config->set_default_session_parent_dir (default_dir_chooser->get_filename());
462 		}
463 
464 		if (monitor_via_hardware_button.get_active()) {
465 			Config->set_monitoring_model (ExternalMonitoring);
466 		} else if (monitor_via_ardour_button.get_active()) {
467 			Config->set_monitoring_model (SoftwareMonitoring);
468 		}
469 
470 		Config->set_use_monitor_bus (use_monitor_section_button.get_active());
471 
472 		Config->save_state ();
473 
474 	}
475 
476 	{
477 		/* "touch" the been-here-before path now we've successfully
478 		   made it through the first time setup (at least)
479 		*/
480 		PBD::ScopedFileDescriptor fout (g_open (been_here_before_path ().c_str(), O_CREAT|O_TRUNC|O_RDWR, 0666));
481 
482 	}
483 
484 	if (ARDOUR::Profile->get_mixbus () && Config->get_copy_demo_sessions ()) {
485 		std::string dspd (Config->get_default_session_parent_dir());
486 		Searchpath ds (ARDOUR::ardour_data_search_path());
487 		ds.add_subdirectory_to_paths ("sessions");
488 		vector<string> demos;
489 		find_files_matching_pattern (demos, ds, ARDOUR::session_archive_suffix);
490 
491 		ARDOUR::RecentSessions rs;
492 		ARDOUR::read_recent_sessions (rs);
493 
494 		for (vector<string>::iterator i = demos.begin(); i != demos.end (); ++i) {
495 			/* "demo-session" must be inside "demo-session.<session_archive_suffix>" */
496 			std::string name = basename_nosuffix (basename_nosuffix (*i));
497 			std::string path = Glib::build_filename (dspd, name);
498 			/* skip if session-dir already exists */
499 			if (Glib::file_test(path.c_str(), Glib::FILE_TEST_IS_DIR)) {
500 				continue;
501 			}
502 			/* skip sessions that are already in 'recent'.
503 			 * eg. a new user changed <session-default-dir> shorly after installation
504 			 */
505 			for (ARDOUR::RecentSessions::iterator r = rs.begin(); r != rs.end(); ++r) {
506 				if ((*r).first == name) {
507 					continue;
508 				}
509 			}
510 			try {
511 				PBD::FileArchive ar (*i);
512 				if (0 == ar.inflate (dspd)) {
513 					store_recent_sessions (name, path);
514 					info << string_compose (_("Copied Demo Session %1."), name) << endmsg;
515 				}
516 			} catch (...) {}
517 		}
518 	}
519 
520 	_signal_response (int (RESPONSE_OK));
521 }
522 
523 
524 void
move_along_now()525 NewUserWizard::move_along_now ()
526 {
527 	on_apply ();
528 }
529