fix for vamp-sdk botch in SConstruct; new audio clock focus command; make insert...
[ardour.git] / gtk2_ardour / option_editor.cc
1 /*
2     Copyright (C) 2001-2006 Paul Davis 
3
4     This program is free software; you can redistribute it and/or modify
5     it under the terms of the GNU General Public License as published by
6     the Free Software Foundation; either version 2 of the License, or
7     (at your option) any later version.
8
9     This program is distributed in the hope that it will be useful,
10     but WITHOUT ANY WARRANTY; without even the implied warranty of
11     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12     GNU General Public License for more details.
13
14     You should have received a copy of the GNU General Public License
15     along with this program; if not, write to the Free Software
16     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17
18 */
19
20 #include <pbd/whitespace.h>
21
22 #include <ardour/session.h>
23 #include <ardour/audioengine.h>
24 #include <ardour/configuration.h>
25 #include <ardour/auditioner.h>
26 #include <ardour/sndfilesource.h>
27 #include <ardour/crossfade.h>
28 #include <midi++/manager.h>
29 #include <midi++/factory.h>
30 #include <gtkmm2ext/stop_signal.h>
31 #include <gtkmm2ext/utils.h>
32 #include <gtkmm2ext/window_title.h>
33
34 #include "public_editor.h"
35 #include "keyboard.h"
36 #include "mixer_ui.h"
37 #include "ardour_ui.h"
38 #include "io_selector.h"
39 #include "gain_meter.h"
40 #include "sfdb_ui.h"
41 #include "utils.h"
42 #include "editing.h"
43 #include "option_editor.h"
44 #include "midi_port_dialog.h"
45 #include "gui_thread.h"
46
47 #include "i18n.h"
48
49 using namespace ARDOUR;
50 using namespace PBD;
51 using namespace Gtk;
52 using namespace Editing;
53 using namespace Gtkmm2ext;
54 using namespace std;
55
56 static vector<string> positional_sync_strings;
57
58 OptionEditor::OptionEditor (ARDOUR_UI& uip, PublicEditor& ed, Mixer_UI& mixui)
59         : ArdourDialog ("options editor", false),
60           ui (uip),
61           editor (ed),
62           mixer (mixui),
63
64           /* Paths */
65           path_table (11, 2),
66
67           /* misc */
68
69           short_xfade_adjustment (0, 1.0, 500.0, 5.0, 100.0),
70           short_xfade_slider (short_xfade_adjustment),
71           destructo_xfade_adjustment (1.0, 1.0, 500.0, 1.0, 100.0),
72           destructo_xfade_slider (destructo_xfade_adjustment),
73           history_depth (20, -1, 100, 1.0, 10.0),
74           saved_history_depth (20, 0, 100, 1.0, 10.0),
75           history_depth_spinner (history_depth),
76           saved_history_depth_spinner (saved_history_depth),
77           limit_history_button (_("Limit undo history")),
78           save_history_button (_("Save undo history")),
79
80           /* Sync */
81
82           smpte_offset_clock (X_("smpteoffset"), false, X_("SMPTEOffsetClock"), true, true),
83           smpte_offset_negative_button (_("SMPTE offset is negative")),
84           synced_timecode_button (_("Timecode source is sample-clock synced")),
85
86           /* MIDI */
87
88           midi_port_table (4, 11),
89           mmc_receive_device_id_adjustment (0.0, 0.0, (double) 0x7f, 1.0, 16.0),
90           mmc_receive_device_id_spinner (mmc_receive_device_id_adjustment),
91           mmc_send_device_id_adjustment (0.0, 0.0, (double) 0x7f, 1.0, 16.0),
92           mmc_send_device_id_spinner (mmc_send_device_id_adjustment),
93           add_midi_port_button (_("Add new MIDI port")),
94
95           /* Click */
96
97           click_table (2, 3),
98           click_browse_button (_("Browse")),
99           click_emphasis_browse_button (_("Browse")),
100
101           /* kbd/mouse */
102
103           keyboard_mouse_table (3, 4),
104           delete_button_adjustment (3, 1, 5),
105           delete_button_spin (delete_button_adjustment),
106           edit_button_adjustment (3, 1, 5),
107           edit_button_spin (edit_button_adjustment)
108           
109 {
110         using namespace Notebook_Helpers;
111
112         click_io_selector = 0;
113         auditioner_io_selector = 0;
114         session = 0;
115         
116         WindowTitle title(Glib::get_application_name());
117         title += _("Preferences");
118         set_title(title.get_string());
119
120         set_default_size (300, 300);
121         set_wmclass (X_("ardour_preferences"), "Ardour");
122
123         set_name ("Preferences");
124         add_events (Gdk::KEY_PRESS_MASK|Gdk::KEY_RELEASE_MASK);
125         
126         VBox *vbox = get_vbox();
127         set_border_width (3);
128
129         vbox->set_spacing (4);
130         vbox->pack_start(notebook);
131
132         signal_delete_event().connect (mem_fun(*this, &OptionEditor::wm_close));
133
134         notebook.set_show_tabs (true);
135         notebook.set_show_border (true);
136         notebook.set_name ("OptionsNotebook");
137
138         setup_sync_options();
139         setup_path_options();
140         setup_misc_options ();
141         setup_keyboard_options ();
142         setup_auditioner_editor ();
143
144         notebook.pages().push_back (TabElem (sync_packer, _("Sync")));
145         notebook.pages().push_back (TabElem (path_table, _("Paths/Files")));
146         notebook.pages().push_back (TabElem (keyboard_mouse_table, _("Kbd/Mouse")));
147         notebook.pages().push_back (TabElem (click_packer, _("Click")));
148         notebook.pages().push_back (TabElem (audition_packer, _("Audition")));
149         notebook.pages().push_back (TabElem (misc_packer, _("Misc")));
150
151         setup_midi_options ();
152         notebook.pages().push_back (TabElem (midi_packer, _("MIDI")));
153
154         set_session (0);
155         show_all_children();
156
157         Config->map_parameters (mem_fun (*this, &OptionEditor::parameter_changed));
158         Config->ParameterChanged.connect (mem_fun (*this, &OptionEditor::parameter_changed));
159 }
160
161 void
162 OptionEditor::set_session (Session *s)
163 {
164         clear_click_editor ();
165         clear_auditioner_editor ();
166
167         click_path_entry.set_text ("");
168         click_emphasis_path_entry.set_text ("");
169         session_raid_entry.set_text ("");
170
171         click_path_entry.set_sensitive (false);
172         click_emphasis_path_entry.set_sensitive (false);
173         session_raid_entry.set_sensitive (false);
174
175         short_xfade_slider.set_sensitive (false);
176         smpte_offset_negative_button.set_sensitive (false);
177
178         smpte_offset_clock.set_session (s);
179
180         if ((session = s) == 0) {
181                 return;
182         }
183
184         click_path_entry.set_sensitive (true);
185         click_emphasis_path_entry.set_sensitive (true);
186         session_raid_entry.set_sensitive (true);
187         short_xfade_slider.set_sensitive (true);
188         smpte_offset_negative_button.set_sensitive (true);
189
190         smpte_offset_clock.set_session (s);
191         smpte_offset_clock.set (s->smpte_offset (), true);
192
193         smpte_offset_negative_button.set_active (session->smpte_offset_negative());
194
195         redisplay_midi_ports ();
196
197         setup_click_editor ();
198         connect_audition_editor ();
199
200         short_xfade_adjustment.set_value ((Crossfade::short_xfade_length() / (float) session->frame_rate()) * 1000.0);
201
202         add_session_paths ();
203 }
204
205 OptionEditor::~OptionEditor ()
206 {
207 }
208
209 void
210 OptionEditor::setup_path_options()
211 {
212         Gtk::Label* label;
213
214         path_table.set_homogeneous (false);
215         path_table.set_border_width (12);
216         path_table.set_row_spacings (5);
217
218         session_raid_entry.set_name ("OptionsEntry");
219
220         session_raid_entry.signal_activate().connect (mem_fun(*this, &OptionEditor::raid_path_changed));
221
222         label = manage(new Label(_("session RAID path")));
223         label->set_name ("OptionsLabel");
224         path_table.attach (*label, 0, 1, 0, 1, FILL|EXPAND, FILL);
225         path_table.attach (session_raid_entry, 1, 3, 0, 1, Gtk::FILL|Gtk::EXPAND, FILL);
226
227         path_table.show_all();
228 }
229
230 void
231 OptionEditor::add_session_paths ()
232 {
233         click_path_entry.set_sensitive (true);
234         click_emphasis_path_entry.set_sensitive (true);
235         session_raid_entry.set_sensitive (true);
236
237         if (Config->get_click_sound().empty()) {
238                 click_path_entry.set_text (_("internal"));
239         } else {
240                 click_path_entry.set_text (Config->get_click_sound());
241         }
242
243         if (Config->get_click_emphasis_sound().empty()) {
244                 click_emphasis_path_entry.set_text (_("internal"));
245         } else {
246                 click_emphasis_path_entry.set_text (Config->get_click_emphasis_sound());
247         }
248
249         session_raid_entry.set_text(session->raid_path());
250 }
251
252 void
253 OptionEditor::setup_misc_options ()
254 {
255         Gtk::HBox* hbox;
256         
257         Label* label = manage (new Label (_("Short crossfade length (msecs)")));
258         label->set_name ("OptionsLabel");
259         
260         hbox = manage (new HBox);
261         hbox->set_border_width (5);
262         hbox->set_spacing (10);
263         hbox->pack_start (*label, false, false);
264         hbox->pack_start (short_xfade_slider, true, true);
265         misc_packer.pack_start (*hbox, false, false);
266
267         short_xfade_adjustment.signal_value_changed().connect (mem_fun(*this, &OptionEditor::short_xfade_adjustment_changed));
268
269         label = manage (new Label (_("Destructive crossfade length (msecs)")));
270         label->set_name ("OptionsLabel");
271         
272         hbox = manage (new HBox);
273         hbox->set_border_width (5);
274         hbox->set_spacing (10);
275         hbox->pack_start (*label, false, false);
276         hbox->pack_start (destructo_xfade_slider, true, true);
277         misc_packer.pack_start (*hbox, false, false);
278         
279
280         destructo_xfade_adjustment.signal_value_changed().connect (mem_fun(*this, &OptionEditor::destructo_xfade_adjustment_changed));
281
282         hbox = manage (new HBox);
283         hbox->set_border_width (5);
284         hbox->set_spacing (10);
285         hbox->pack_start (limit_history_button, false, false);
286         misc_packer.pack_start (*hbox, false, false);
287
288         label = manage (new Label (_("History depth (commands)")));
289         label->set_name ("OptionsLabel");
290
291         hbox = manage (new HBox);
292         hbox->set_border_width (5);
293         hbox->set_spacing (10);
294         hbox->pack_start (*label, false, false);
295         hbox->pack_start (history_depth_spinner, false, false);
296         misc_packer.pack_start (*hbox, false, false);
297
298         history_depth.signal_value_changed().connect (mem_fun (*this, &OptionEditor::history_depth_changed));
299         saved_history_depth.signal_value_changed().connect (mem_fun (*this, &OptionEditor::saved_history_depth_changed));
300         save_history_button.signal_toggled().connect (mem_fun (*this, &OptionEditor::save_history_toggled));
301         limit_history_button.signal_toggled().connect (mem_fun (*this, &OptionEditor::limit_history_toggled));
302
303         hbox = manage (new HBox);
304         hbox->set_border_width (5);
305         hbox->set_spacing (10);
306         hbox->pack_start (save_history_button, false, false);
307         misc_packer.pack_start (*hbox, false, false);
308
309         label = manage (new Label (_("Saved history depth (commands)")));
310         label->set_name ("OptionsLabel");
311
312         hbox = manage (new HBox);
313         hbox->set_border_width (5);
314         hbox->set_spacing (10);
315         hbox->pack_start (*label, false, false);
316         hbox->pack_start (saved_history_depth_spinner, false, false);
317         misc_packer.pack_start (*hbox, false, false);
318         
319         short_xfade_slider.set_update_policy (UPDATE_DISCONTINUOUS);
320         destructo_xfade_slider.set_update_policy (UPDATE_DISCONTINUOUS);
321
322         destructo_xfade_adjustment.set_value (Config->get_destructive_xfade_msecs());
323
324         misc_packer.show_all ();
325 }
326
327 void
328 OptionEditor::limit_history_toggled ()
329 {
330         bool x = limit_history_button.get_active();
331         
332         if (!x) {
333                 Config->set_history_depth (0);
334                 history_depth_spinner.set_sensitive (false);
335         } else {
336                 if (Config->get_history_depth() == 0) {
337                         /* get back to a sane default */
338                         Config->set_history_depth (20);
339                 }
340                 history_depth_spinner.set_sensitive (true);
341         }
342 }
343
344 void
345 OptionEditor::save_history_toggled ()
346 {
347         bool x = save_history_button.get_active();
348
349         if (x != Config->get_save_history()) {
350                 Config->set_save_history (x);
351                 saved_history_depth_spinner.set_sensitive (x);
352         }
353 }
354
355 void
356 OptionEditor::history_depth_changed()
357 {
358         Config->set_history_depth ((int32_t) floor (history_depth.get_value()));
359 }
360
361 void
362 OptionEditor::saved_history_depth_changed()
363 {
364         Config->set_saved_history_depth ((int32_t) floor (saved_history_depth.get_value()));
365 }
366
367 void
368 OptionEditor::short_xfade_adjustment_changed ()
369 {
370         if (session) {
371                 float val = short_xfade_adjustment.get_value();
372                 
373                 /* val is in msecs */
374                 
375                 Crossfade::set_short_xfade_length ((nframes_t) floor (session->frame_rate() * (val / 1000.0)));
376         }
377 }
378
379 void
380 OptionEditor::destructo_xfade_adjustment_changed ()
381 {
382         float val = destructo_xfade_adjustment.get_value();
383
384         /* val is in msecs */
385
386         
387         Config->set_destructive_xfade_msecs ((uint32_t) floor (val));
388
389         if (session) {
390                 SndFileSource::setup_standard_crossfades (session->frame_rate());
391         } 
392 }
393
394 void
395 OptionEditor::setup_sync_options ()
396 {
397         HBox* hbox;
398         vector<string> dumb;
399
400         smpte_offset_clock.set_mode (AudioClock::SMPTE);
401         smpte_offset_clock.ValueChanged.connect (mem_fun(*this, &OptionEditor::smpte_offset_chosen));
402         
403         smpte_offset_negative_button.set_name ("OptionEditorToggleButton");
404
405         smpte_offset_negative_button.unset_flags (Gtk::CAN_FOCUS);
406
407         Label *smpte_offset_label = manage (new Label (_("SMPTE Offset")));
408         smpte_offset_label->set_name("OptionsLabel");
409         
410         hbox = manage (new HBox);
411         hbox->set_border_width (5);
412         hbox->set_spacing (10);
413         hbox->pack_start (*smpte_offset_label, false, false);
414         hbox->pack_start (smpte_offset_clock, false, false);
415         hbox->pack_start (smpte_offset_negative_button, false, false);
416
417         sync_packer.pack_start (*hbox, false, false);
418         sync_packer.pack_start (synced_timecode_button, false, false);
419
420         smpte_offset_negative_button.signal_clicked().connect (mem_fun(*this, &OptionEditor::smpte_offset_negative_clicked));
421         synced_timecode_button.signal_toggled().connect (mem_fun(*this, &OptionEditor::synced_timecode_toggled));
422 }
423
424 void
425 OptionEditor::smpte_offset_negative_clicked ()
426 {
427         if (session) {
428                 session->set_smpte_offset_negative (smpte_offset_negative_button.get_active());
429         }
430 }
431
432 void
433 OptionEditor::synced_timecode_toggled ()
434 {
435         bool x;
436
437         if ((x = synced_timecode_button.get_active()) != Config->get_timecode_source_is_synced()) {
438                 Config->set_timecode_source_is_synced (x);
439                 Config->save_state();
440         }
441 }
442
443 void
444 OptionEditor::smpte_offset_chosen()
445 {
446         if (session) {
447                 nframes_t frames = smpte_offset_clock.current_duration();
448                 session->set_smpte_offset (frames);
449         }
450 }
451
452
453 void
454 OptionEditor::setup_midi_options ()
455 {
456         HBox* hbox;
457         Label* label;
458
459         midi_port_table.set_row_spacings (6);
460         midi_port_table.set_col_spacings (10);
461
462         redisplay_midi_ports ();
463
464         mmc_receive_device_id_adjustment.set_value (Config->get_mmc_receive_device_id());
465         mmc_send_device_id_adjustment.set_value (Config->get_mmc_send_device_id());
466
467         mmc_receive_device_id_adjustment.signal_value_changed().connect (mem_fun (*this, &OptionEditor::mmc_receive_device_id_adjusted));
468         mmc_send_device_id_adjustment.signal_value_changed().connect (mem_fun (*this, &OptionEditor::mmc_send_device_id_adjusted));
469
470         hbox = manage (new HBox);
471         hbox->set_border_width (6);
472         hbox->pack_start (midi_port_table, true, false);
473
474         midi_packer.pack_start (*hbox, false, false);
475         midi_packer.pack_start (add_midi_port_button, false, false);
476
477         hbox = manage (new HBox);
478         hbox->set_border_width (6);
479         hbox->set_spacing (6);
480         label = (manage (new Label (_("Inbound MMC Device ID")))); 
481         hbox->pack_start (mmc_receive_device_id_spinner, false, false);
482         hbox->pack_start (*label, false, false);
483         midi_packer.pack_start (*hbox, false, false); 
484
485         mmc_receive_device_id_spinner.set_value(Config->get_mmc_receive_device_id ());
486
487         hbox = manage (new HBox);
488         hbox->set_border_width (6);
489         hbox->set_spacing (6);
490         label = (manage (new Label (_("Outbound MMC Device ID")))); 
491         hbox->pack_start (mmc_send_device_id_spinner, false, false);
492         hbox->pack_start (*label, false, false);
493         midi_packer.pack_start (*hbox, false, false);
494
495         mmc_send_device_id_spinner.set_value(Config->get_mmc_send_device_id ());
496
497         add_midi_port_button.signal_clicked().connect (mem_fun (*this, &OptionEditor::add_midi_port));
498 }
499
500 void
501 OptionEditor::redisplay_midi_ports ()
502 {
503         MIDI::Manager::PortMap::const_iterator i;
504         const MIDI::Manager::PortMap& ports = MIDI::Manager::instance()->get_midi_ports();
505         int n;
506
507         /* remove all existing widgets */
508
509         // XXX broken in gtkmm 2.10
510         // midi_port_table.clear ();
511
512         for (vector<Widget*>::iterator w = midi_port_table_widgets.begin(); w != midi_port_table_widgets.end(); ++w) {
513                 midi_port_table.remove (**w);
514         }
515
516         midi_port_table_widgets.clear ();
517
518         midi_port_table.resize (ports.size() + 4, 11);
519
520         Gtk::Label* label;
521
522         label = (manage (new Label (_("Port")))); 
523         label->show ();
524         midi_port_table_widgets.push_back (label);
525         midi_port_table.attach (*label, 0, 1, 0, 1);
526         label = (manage (new Label (_("Offline")))); 
527         label->show ();
528         midi_port_table_widgets.push_back (label);
529         midi_port_table.attach (*label, 1, 2, 0, 1);
530         label = (manage (new Label (_("Trace\nInput")))); 
531         label->show ();
532         midi_port_table_widgets.push_back (label);
533         midi_port_table.attach (*label, 2, 3, 0, 1);
534         label = (manage (new Label (_("Trace\nOutput")))); 
535         label->show ();
536         midi_port_table_widgets.push_back (label);
537         midi_port_table.attach (*label, 3, 4, 0, 1);
538         label = (manage (new Label (_("MTC")))); 
539         label->show ();
540         midi_port_table_widgets.push_back (label);
541         midi_port_table.attach (*label, 4, 5, 0, 1);
542         label = (manage (new Label (_("MMC")))); 
543         label->show ();
544         midi_port_table_widgets.push_back (label);
545         midi_port_table.attach (*label, 6, 7, 0, 1);
546         label = (manage (new Label (_("MIDI Parameter\nControl")))); 
547         label->show ();
548         midi_port_table_widgets.push_back (label);
549         midi_port_table.attach (*label, 8, 9, 0, 1);
550
551         Gtk::HSeparator* hsep = (manage (new HSeparator())); 
552         hsep->show ();
553         midi_port_table_widgets.push_back (hsep);
554         midi_port_table.attach (*hsep, 0, 9, 1, 2);
555         Gtk::VSeparator* vsep = (manage (new VSeparator())); 
556         vsep->show ();
557         midi_port_table_widgets.push_back (vsep);
558         midi_port_table.attach (*vsep, 5, 6, 0, 8);
559         vsep = (manage (new VSeparator())); 
560         vsep->show ();
561         midi_port_table_widgets.push_back (vsep);
562         midi_port_table.attach (*vsep, 7, 8, 0, 8);
563         
564         for (n = 0, i = ports.begin(); i != ports.end(); ++n, ++i) {
565
566                 ToggleButton* tb;
567                 RadioButton* rb;
568                 Button* bb;
569
570                 /* the remove button. create early so we can pass it to various callbacks */
571                 
572                 bb = manage (new Button (Stock::REMOVE));
573                 bb->set_name ("OptionEditorToggleButton");
574                 bb->show ();
575                 midi_port_table_widgets.push_back (bb);
576                 midi_port_table.attach (*bb, 9, 10, n+2, n+3, FILL|EXPAND, FILL);
577                 bb->signal_clicked().connect (bind (mem_fun(*this, &OptionEditor::remove_midi_port), i->second));
578                 bb->set_sensitive (port_removable (i->second));
579
580                 label = (manage (new Label (i->first))); 
581                 label->show ();
582                 midi_port_table_widgets.push_back (label);
583                 midi_port_table.attach (*label, 0, 1, n+2, n+3,FILL|EXPAND, FILL );
584                 
585                 tb = manage (new ToggleButton (_("online")));
586                 tb->set_name ("OptionEditorToggleButton");
587
588                 /* remember, we have to handle the i18n case where the relative
589                    lengths of the strings in language N is different than in english.
590                 */
591
592                 if (strlen (_("offline")) > strlen (_("online"))) {
593                         set_size_request_to_display_given_text (*tb, _("offline"), 15, 12);
594                 } else {
595                         set_size_request_to_display_given_text (*tb, _("online"), 15, 12);
596                 }
597
598                 if (i->second->input()) {
599                         tb->set_active (!i->second->input()->offline());
600                         tb->signal_toggled().connect (bind (mem_fun(*this, &OptionEditor::port_online_toggled), i->second, tb));
601                         i->second->input()->OfflineStatusChanged.connect (bind (mem_fun(*this, &OptionEditor::map_port_online), (*i).second, tb));
602                 }
603                 tb->show ();
604                 midi_port_table_widgets.push_back (tb);
605                 midi_port_table.attach (*tb, 1, 2, n+2, n+3, FILL|EXPAND, FILL);
606
607                 tb = manage (new ToggleButton ());
608                 tb->set_name ("OptionEditorToggleButton");
609                 tb->signal_toggled().connect (bind (mem_fun(*this, &OptionEditor::port_trace_in_toggled), (*i).second, tb));
610                 tb->set_size_request (10, 10);
611                 tb->show ();
612                 midi_port_table_widgets.push_back (tb);
613                 midi_port_table.attach (*tb, 2, 3, n+2, n+3, FILL|EXPAND, FILL);
614
615                 tb = manage (new ToggleButton ());
616                 tb->set_name ("OptionEditorToggleButton");
617                 tb->signal_toggled().connect (bind (mem_fun(*this, &OptionEditor::port_trace_out_toggled), (*i).second, tb));
618                 tb->set_size_request (10, 10);
619                 tb->show ();
620                 midi_port_table_widgets.push_back (tb);
621                 midi_port_table.attach (*tb, 3, 4, n+2, n+3, FILL|EXPAND, FILL);
622
623                 rb = manage (new RadioButton ());
624                 rb->set_name ("OptionEditorToggleButton");
625                 if (n == 0) {
626                         mtc_button_group = rb->get_group();
627                 } else {
628                         rb->set_group (mtc_button_group);
629
630                 }
631                 rb->show ();
632                 midi_port_table_widgets.push_back (rb);
633                 midi_port_table.attach (*rb, 4, 5, n+2, n+3, FILL|EXPAND, FILL);
634                 rb->signal_toggled().connect (bind (mem_fun(*this, &OptionEditor::mtc_port_chosen), (*i).second, rb, bb));
635
636                 if (session && i->second == session->mtc_port()) {
637                         rb->set_active (true);
638                 }
639                 
640                 rb = manage (new RadioButton ());
641                 rb->set_name ("OptionEditorToggleButton");
642                 if (n == 0) {
643                         mmc_button_group = rb->get_group();
644                 } else {
645                         rb->set_group (mmc_button_group);
646                 }
647                 rb->show ();
648                 midi_port_table_widgets.push_back (rb);
649                 midi_port_table.attach (*rb, 6, 7, n+2, n+3, FILL|EXPAND, FILL);
650                 rb->signal_toggled().connect (bind (mem_fun(*this, &OptionEditor::mmc_port_chosen), (*i).second, rb, bb));
651
652                 if (session && i->second == session->mmc_port()) {
653                         rb->set_active (true);
654                 }
655
656                 rb = manage (new RadioButton ());
657                 rb->set_name ("OptionEditorToggleButton");
658                 if (n == 0) {
659                         midi_button_group = rb->get_group();
660                 } else {
661                         rb->set_group (midi_button_group);
662                 }
663                 rb->show ();
664                 midi_port_table_widgets.push_back (rb);
665                 midi_port_table.attach (*rb, 8, 9, n+2, n+3, FILL|EXPAND, FILL);
666                 rb->signal_toggled().connect (bind (mem_fun(*this, &OptionEditor::midi_port_chosen), (*i).second, rb, bb));
667
668                 if (session && i->second == session->midi_port()) {
669                         rb->set_active (true);
670                 }
671
672         }
673
674         midi_port_table.show();
675 }
676
677 void
678 OptionEditor::remove_midi_port (MIDI::Port* port)
679 {
680         MIDI::Manager::instance()->remove_port (port);
681         redisplay_midi_ports ();
682 }
683
684 void
685 OptionEditor::add_midi_port ()
686 {
687         MidiPortDialog dialog;
688
689         dialog.set_position (WIN_POS_MOUSE);
690         dialog.set_transient_for (*this);
691
692         dialog.show ();
693
694         int ret = dialog.run ();
695
696         switch (ret) {
697         case RESPONSE_ACCEPT:
698                 break;
699         default:
700                 return;
701                 break;
702         }
703
704         Glib::ustring mode = dialog.port_mode_combo.get_active_text();
705         std::string smod;
706
707         if (mode == _("input")) {
708                 smod = X_("input");
709         } else if (mode == (_("output"))) {
710                 smod = X_("output");
711         } else {
712                 smod = "duplex";
713         }
714
715
716         XMLNode node (X_("MIDI-port"));
717
718         node.add_property ("tag", dialog.port_name.get_text());
719         node.add_property ("device", X_("ardour")); // XXX this can't be right for all types
720         node.add_property ("type", MIDI::PortFactory::default_port_type());
721         node.add_property ("mode", smod);
722
723         if (MIDI::Manager::instance()->add_port (node) != 0) {
724                 redisplay_midi_ports ();
725         }
726 }
727
728 bool
729 OptionEditor::port_removable (MIDI::Port *port)
730 {
731         if (!session) {
732                 return true;
733         }
734
735         if (port == session->mtc_port() ||
736             port == session->mmc_port() ||
737             port == session->midi_port()) {
738                 return false;
739         }
740         return true;
741 }
742
743 void
744 OptionEditor::mtc_port_chosen (MIDI::Port *port, Gtk::RadioButton* rb, Gtk::Button* bb) 
745 {
746         if (session) {
747                 if (rb->get_active()) {
748                         session->set_mtc_port (port->name());
749                         Config->set_mtc_port_name (port->name());
750                 } else {
751                         session->set_mtc_port ("");
752                 }
753                 bb->set_sensitive (port_removable (port));
754         }
755 }
756
757 void
758 OptionEditor::mmc_port_chosen (MIDI::Port* port, Gtk::RadioButton* rb, Gtk::Button* bb)
759 {
760         if (session) {
761                 if (rb->get_active()) {
762                         session->set_mmc_port (port->name());
763                         Config->set_mtc_port_name (port->name());
764                 } else {
765                         session->set_mmc_port ("");
766                 }
767                 bb->set_sensitive (port_removable (port));
768         }
769 }
770
771 void
772 OptionEditor::midi_port_chosen (MIDI::Port* port, Gtk::RadioButton* rb, Gtk::Button* bb)
773 {
774         if (session) {
775                 if (rb->get_active()) {
776                         session->set_midi_port (port->name());
777                         Config->set_midi_port_name (port->name());
778                 } else {
779                         session->set_midi_port ("");
780                 }
781                 bb->set_sensitive (port_removable (port));
782         }
783 }
784
785 void
786 OptionEditor::port_online_toggled (MIDI::Port* port, ToggleButton* tb)
787 {
788         bool wanted = tb->get_active();
789
790         if (port->input()) {
791                 if (wanted != port->input()->offline()) {
792                         port->input()->set_offline (wanted);
793                 } 
794         }
795 }
796
797 void
798 OptionEditor::map_port_online (MIDI::Port* port, ToggleButton* tb)
799 {
800         bool bstate = tb->get_active ();
801         
802         if (port->input()) {
803                 if (bstate != port->input()->offline()) {
804                         if (port->input()->offline()) {
805                                 tb->set_label (_("offline"));
806                                 tb->set_active (false);
807                         } else {
808                                 tb->set_label (_("online"));
809                                 tb->set_active (true);
810                         }
811                 }
812         }
813 }
814
815 void
816 OptionEditor::mmc_receive_device_id_adjusted ()
817 {
818         uint8_t id = (uint8_t) mmc_receive_device_id_spinner.get_value();
819         Config->set_mmc_receive_device_id (id);
820 }
821
822 void
823 OptionEditor::mmc_send_device_id_adjusted ()
824 {
825         uint8_t id = (uint8_t) mmc_send_device_id_spinner.get_value();
826         Config->set_mmc_send_device_id (id);
827 }
828
829 void
830 OptionEditor::port_trace_in_toggled (MIDI::Port* port, ToggleButton* tb)
831 {
832         bool trace = tb->get_active();
833
834         if (port->input()) {
835                 if (port->input()->tracing() != trace) {
836                         port->input()->trace (trace, &cerr, string (port->name()) + string (" input: "));
837                 }
838         }
839 }
840
841 void
842 OptionEditor::port_trace_out_toggled (MIDI::Port* port, ToggleButton* tb)
843 {
844         bool trace = tb->get_active();
845
846         if (port->output()) {
847                 if (port->output()->tracing() != trace) {
848                         port->output()->trace (trace, &cerr, string (port->name()) + string (" output: "));
849                 }
850         }
851 }
852
853 void
854 OptionEditor::save ()
855 {
856         /* XXX a bit odd that we save the entire session state here */
857
858         ui.save_state ("");
859 }
860
861 gint
862 OptionEditor::wm_close (GdkEventAny *ev)
863 {
864         save ();
865         hide ();
866         return TRUE;
867 }
868
869 void
870 OptionEditor::raid_path_changed ()
871 {
872         if (session) {
873                 Config->set_raid_path (session_raid_entry.get_text());
874         }
875 }
876
877 void
878 OptionEditor::click_browse_clicked ()
879 {
880         SoundFileChooser sfdb (*this, _("Choose Click"), session);
881         
882         sfdb.show_all ();
883         sfdb.present ();
884
885         int result = sfdb.run ();
886  
887         if (result == Gtk::RESPONSE_OK) {
888                 click_chosen(sfdb.get_filename());
889         }
890 }
891
892 void
893 OptionEditor::click_chosen (const string & path)
894 {
895         click_path_entry.set_text (path);
896         click_sound_changed ();
897 }
898
899 void
900 OptionEditor::click_emphasis_browse_clicked ()
901 {
902         SoundFileChooser sfdb (*this, _("Choose Click Emphasis"), session);
903
904         sfdb.show_all ();
905         sfdb.present ();
906
907         int result = sfdb.run ();
908
909         if (result == Gtk::RESPONSE_OK) {
910                 click_emphasis_chosen (sfdb.get_filename());
911         }
912 }
913
914 void
915 OptionEditor::click_emphasis_chosen (const string & path)
916 {       
917         click_emphasis_path_entry.set_text (path);
918         click_emphasis_sound_changed ();
919 }
920
921 void
922 OptionEditor::click_sound_changed ()
923 {
924         if (session) {
925                 string path = click_path_entry.get_text();
926
927                 if (path == Config->get_click_sound()) {
928                         return;
929                 }
930
931                 strip_whitespace_edges (path);
932
933                 if (path == _("internal")) {
934                         Config->set_click_sound ("");
935                 } else {
936                         Config->set_click_sound (path);
937                 }
938         }
939 }
940
941 void
942 OptionEditor::click_emphasis_sound_changed ()
943 {
944         if (session) {
945                 string path = click_emphasis_path_entry.get_text();
946
947                 if (path == Config->get_click_emphasis_sound()) {
948                         return;
949                 }
950
951                 strip_whitespace_edges (path);
952
953                 if (path == _("internal")) {
954                         Config->set_click_emphasis_sound ("");
955                 } else {
956                         Config->set_click_emphasis_sound (path);
957                 }
958         }
959 }
960
961 void
962 OptionEditor::clear_click_editor ()
963 {
964         if (click_io_selector) {
965                 click_packer.remove (*click_io_selector);
966                 click_packer.remove (*click_gpm);
967                 delete click_io_selector;
968                 delete click_gpm;
969                 click_io_selector = 0;
970                 click_gpm = 0;
971         }
972 }
973
974 void
975 OptionEditor::setup_click_editor ()
976 {
977         Label* label;
978         HBox* hpacker = manage (new HBox);
979
980         click_path_entry.set_sensitive (true);
981         click_emphasis_path_entry.set_sensitive (true);
982
983         click_path_entry.set_name ("OptionsEntry");
984         click_emphasis_path_entry.set_name ("OptionsEntry");
985         
986         click_path_entry.signal_activate().connect (mem_fun(*this, &OptionEditor::click_sound_changed));
987         click_emphasis_path_entry.signal_activate().connect (mem_fun(*this, &OptionEditor::click_emphasis_sound_changed));
988
989         click_path_entry.signal_focus_out_event().connect (bind (mem_fun(*this, &OptionEditor::focus_out_event_handler), &OptionEditor::click_sound_changed));
990         click_emphasis_path_entry.signal_focus_out_event().connect (bind (mem_fun(*this, &OptionEditor::focus_out_event_handler), &OptionEditor::click_emphasis_sound_changed));
991
992         click_browse_button.set_name ("EditorGTKButton");
993         click_emphasis_browse_button.set_name ("EditorGTKButton");
994         click_browse_button.signal_clicked().connect (mem_fun(*this, &OptionEditor::click_browse_clicked));
995         click_emphasis_browse_button.signal_clicked().connect (mem_fun(*this, &OptionEditor::click_emphasis_browse_clicked));
996
997         click_packer.set_border_width (12);
998         click_packer.set_spacing (5);
999
1000         click_io_selector = new IOSelector (*session, session->click_io(), false);
1001         click_gpm = new GainMeter (session->click_io(), *session);
1002
1003         click_table.set_col_spacings (10);
1004         
1005         label = manage(new Label(_("Click audio file")));
1006         label->set_name ("OptionsLabel");
1007         click_table.attach (*label, 0, 1, 0, 1, FILL|EXPAND, FILL);
1008         click_table.attach (click_path_entry, 1, 2, 0, 1, Gtk::FILL|Gtk::EXPAND, FILL);
1009         click_table.attach (click_browse_button, 2, 3, 0, 1, FILL|EXPAND, FILL);
1010         
1011         label = manage(new Label(_("Click emphasis audiofile")));
1012         label->set_name ("OptionsLabel");
1013         click_table.attach (*label, 0, 1, 1, 2, FILL|EXPAND, FILL);
1014         click_table.attach (click_emphasis_path_entry, 1, 2, 1, 2, Gtk::FILL|Gtk::EXPAND, FILL);
1015         click_table.attach (click_emphasis_browse_button, 2, 3, 1, 2, FILL|EXPAND, FILL);
1016
1017         hpacker->set_spacing (10);
1018         hpacker->pack_start (*click_io_selector, false, false);
1019         hpacker->pack_start (*click_gpm, false, false);
1020
1021         click_packer.pack_start (click_table, false, false);
1022         click_packer.pack_start (*hpacker, false, false);
1023
1024         click_packer.show_all ();
1025 }
1026
1027 void
1028 OptionEditor::clear_auditioner_editor ()
1029 {
1030         if (auditioner_io_selector) {
1031                 audition_hpacker.remove (*auditioner_io_selector);
1032                 audition_hpacker.remove (*auditioner_gpm);
1033                 delete auditioner_io_selector;
1034                 delete auditioner_gpm;
1035                 auditioner_io_selector = 0;
1036                 auditioner_gpm = 0;
1037         }
1038 }
1039
1040 void
1041 OptionEditor::setup_auditioner_editor ()
1042 {
1043         audition_packer.set_border_width (12);
1044         audition_packer.set_spacing (5);
1045         audition_hpacker.set_spacing (10);
1046
1047         audition_label.set_name ("OptionEditorAuditionerLabel");
1048         audition_label.set_text (_("The auditioner is a dedicated mixer strip used\n"
1049                                    "for listening to specific regions outside the context\n"
1050                                    "of the overall mix. It can be connected just like any\n"
1051                                    "other mixer strip."));
1052         
1053         audition_packer.pack_start (audition_label, false, false, 10);
1054         audition_packer.pack_start (audition_hpacker, false, false);
1055 }
1056
1057 void
1058 OptionEditor::connect_audition_editor ()
1059 {
1060         auditioner_io_selector = new IOSelector (*session, session->the_auditioner(), false);
1061         auditioner_gpm = new GainMeter (session->the_auditioner(), *session);
1062
1063         audition_hpacker.pack_start (*auditioner_io_selector, false, false);
1064         audition_hpacker.pack_start (*auditioner_gpm, false, false);
1065
1066         auditioner_io_selector->show_all ();
1067         auditioner_gpm->show_all ();
1068 }
1069
1070 bool
1071 OptionEditor::focus_out_event_handler (GdkEventFocus* ev, void (OptionEditor::*pmf)()) 
1072 {
1073         (this->*pmf)();
1074         return false;
1075 }
1076
1077 static const struct {
1078     const char *name;
1079     guint   modifier;
1080 } modifiers[] = {
1081
1082 #ifdef GTKOSX 
1083
1084         /* Command = Mod1
1085            Option/Alt = Mod5
1086         */
1087
1088         { "Shift", GDK_SHIFT_MASK },
1089         { "Command", GDK_MOD1_MASK },
1090         { "Control", GDK_CONTROL_MASK },
1091         { "Option", GDK_MOD5_MASK },
1092         { "Command-Shift", GDK_MOD1_MASK|GDK_SHIFT_MASK },
1093         { "Command-Option", GDK_MOD1_MASK|GDK_MOD5_MASK },
1094         { "Shift-Option", GDK_SHIFT_MASK|GDK_MOD5_MASK },
1095         { "Shift-Command-Option", GDK_MOD5_MASK|GDK_SHIFT_MASK|GDK_MOD1_MASK },
1096
1097 #else
1098         { "Shift", GDK_SHIFT_MASK },
1099         { "Control", GDK_CONTROL_MASK },
1100         { "Alt (Mod1)", GDK_MOD1_MASK },
1101         { "Control-Shift", GDK_CONTROL_MASK|GDK_SHIFT_MASK },
1102         { "Control-Alt", GDK_CONTROL_MASK|GDK_MOD1_MASK },
1103         { "Shift-Alt", GDK_SHIFT_MASK|GDK_MOD1_MASK },
1104         { "Control-Shift-Alt", GDK_CONTROL_MASK|GDK_SHIFT_MASK|GDK_MOD1_MASK },
1105         { "Mod2", GDK_MOD2_MASK },
1106         { "Mod3", GDK_MOD3_MASK },
1107         { "Mod4", GDK_MOD4_MASK },
1108         { "Mod5", GDK_MOD5_MASK },
1109 #endif
1110         { 0, 0 }
1111 };
1112
1113 void
1114 OptionEditor::setup_keyboard_options ()
1115 {
1116         vector<string> dumb;
1117         Label* label;
1118
1119         keyboard_mouse_table.set_border_width (12);
1120         keyboard_mouse_table.set_row_spacings (5);
1121         keyboard_mouse_table.set_col_spacings (5);
1122
1123         /* internationalize and prepare for use with combos */
1124
1125         for (int i = 0; modifiers[i].name; ++i) {
1126                 dumb.push_back (_(modifiers[i].name));
1127         }
1128
1129         set_popdown_strings (edit_modifier_combo, dumb);
1130         edit_modifier_combo.signal_changed().connect (mem_fun(*this, &OptionEditor::edit_modifier_chosen));
1131
1132         for (int x = 0; modifiers[x].name; ++x) {
1133                 if (modifiers[x].modifier == Keyboard::edit_modifier ()) {
1134                         edit_modifier_combo.set_active_text (_(modifiers[x].name));
1135                         break;
1136                 }
1137         }
1138
1139         label = manage (new Label (_("Edit using")));
1140         label->set_name ("OptionsLabel");
1141         label->set_alignment (1.0, 0.5);
1142                 
1143         keyboard_mouse_table.attach (*label, 0, 1, 0, 1, Gtk::FILL|Gtk::EXPAND, FILL);
1144         keyboard_mouse_table.attach (edit_modifier_combo, 1, 2, 0, 1, Gtk::FILL|Gtk::EXPAND, FILL);
1145
1146         label = manage (new Label (_("+ button")));
1147         label->set_name ("OptionsLabel");
1148         
1149         keyboard_mouse_table.attach (*label, 3, 4, 0, 1, Gtk::FILL|Gtk::EXPAND, FILL);
1150         keyboard_mouse_table.attach (edit_button_spin, 4, 5, 0, 1, Gtk::FILL|Gtk::EXPAND, FILL);
1151
1152         edit_button_spin.set_name ("OptionsEntry");
1153         edit_button_adjustment.set_value (Keyboard::edit_button());
1154         edit_button_adjustment.signal_value_changed().connect (mem_fun(*this, &OptionEditor::edit_button_changed));
1155
1156         set_popdown_strings (delete_modifier_combo, dumb);
1157         delete_modifier_combo.signal_changed().connect (mem_fun(*this, &OptionEditor::delete_modifier_chosen));
1158
1159         for (int x = 0; modifiers[x].name; ++x) {
1160                 if (modifiers[x].modifier == Keyboard::delete_modifier ()) {
1161                         delete_modifier_combo.set_active_text (_(modifiers[x].name));
1162                         break;
1163                 }
1164         }
1165
1166         label = manage (new Label (_("Delete using")));
1167         label->set_name ("OptionsLabel");
1168         label->set_alignment (1.0, 0.5);
1169                 
1170         keyboard_mouse_table.attach (*label, 0, 1, 1, 2, Gtk::FILL|Gtk::EXPAND, FILL);
1171         keyboard_mouse_table.attach (delete_modifier_combo, 1, 2, 1, 2, Gtk::FILL|Gtk::EXPAND, FILL);
1172
1173         label = manage (new Label (_("+ button")));
1174         label->set_name ("OptionsLabel");
1175
1176         keyboard_mouse_table.attach (*label, 3, 4, 1, 2, Gtk::FILL|Gtk::EXPAND, FILL);
1177         keyboard_mouse_table.attach (delete_button_spin, 4, 5, 1, 2, Gtk::FILL|Gtk::EXPAND, FILL);
1178
1179         delete_button_spin.set_name ("OptionsEntry");
1180         delete_button_adjustment.set_value (Keyboard::delete_button());
1181         delete_button_adjustment.signal_value_changed().connect (mem_fun(*this, &OptionEditor::delete_button_changed));
1182
1183         set_popdown_strings (snap_modifier_combo, dumb);
1184         snap_modifier_combo.signal_changed().connect (mem_fun(*this, &OptionEditor::snap_modifier_chosen));
1185         
1186         for (int x = 0; modifiers[x].name; ++x) {
1187                 if (modifiers[x].modifier == (guint) Keyboard::snap_modifier ()) {
1188                         snap_modifier_combo.set_active_text (_(modifiers[x].name));
1189                         break;
1190                 }
1191         }
1192
1193         label = manage (new Label (_("Ignore snap using")));
1194         label->set_name ("OptionsLabel");
1195         label->set_alignment (1.0, 0.5);
1196         
1197         keyboard_mouse_table.attach (*label, 0, 1, 2, 3, Gtk::FILL|Gtk::EXPAND, FILL);
1198         keyboard_mouse_table.attach (snap_modifier_combo, 1, 2, 2, 3, Gtk::FILL|Gtk::EXPAND, FILL);
1199 }
1200
1201 void
1202 OptionEditor::edit_modifier_chosen ()
1203 {
1204         string txt;
1205         
1206         txt = edit_modifier_combo.get_active_text();
1207
1208         for (int i = 0; modifiers[i].name; ++i) {
1209                 if (txt == _(modifiers[i].name)) {
1210                         Keyboard::set_edit_modifier (modifiers[i].modifier);
1211                         break;
1212                 }
1213         }
1214 }
1215
1216 void
1217 OptionEditor::delete_modifier_chosen ()
1218 {
1219         string txt;
1220         
1221         txt = delete_modifier_combo.get_active_text();
1222
1223         for (int i = 0; modifiers[i].name; ++i) {
1224                 if (txt == _(modifiers[i].name)) {
1225                         Keyboard::set_delete_modifier (modifiers[i].modifier);
1226                         break;
1227                 }
1228         }
1229 }
1230
1231 void
1232 OptionEditor::snap_modifier_chosen ()
1233 {
1234         string txt;
1235         
1236         txt = snap_modifier_combo.get_active_text();
1237
1238         for (int i = 0; modifiers[i].name; ++i) {
1239                 if (txt == _(modifiers[i].name)) {
1240                         Keyboard::set_snap_modifier (modifiers[i].modifier);
1241                         break;
1242                 }
1243         }
1244 }
1245
1246 void
1247 OptionEditor::delete_button_changed ()
1248 {
1249         Keyboard::set_delete_button ((guint) delete_button_adjustment.get_value());
1250 }
1251
1252 void
1253 OptionEditor::edit_button_changed ()
1254 {
1255         Keyboard::set_edit_button ((guint) edit_button_adjustment.get_value());
1256 }
1257
1258 void
1259 OptionEditor::fixup_combo_size (Gtk::ComboBoxText& combo, vector<string>& strings)
1260 {
1261         /* find the widest string */
1262
1263         string::size_type maxlen = 0;
1264         string maxstring;
1265
1266         for (vector<string>::iterator i = strings.begin(); i != strings.end(); ++i) {
1267                 string::size_type l;
1268
1269                 if ((l = (*i).length()) > maxlen) {
1270                         maxlen = l;
1271                         maxstring = *i;
1272                 }
1273         }
1274
1275         /* try to include ascenders and descenders */
1276
1277         if (maxstring.length() > 2) {
1278                 maxstring[0] = 'g';
1279                 maxstring[1] = 'l';
1280         }
1281
1282         const guint32 FUDGE = 10; // Combo's are stupid - they steal space from the entry for the button
1283
1284         set_size_request_to_display_given_text (combo, maxstring.c_str(), 10 + FUDGE, 10);
1285 }
1286
1287 void
1288 OptionEditor::parameter_changed (const char* parameter_name)
1289 {
1290         ENSURE_GUI_THREAD (bind (mem_fun (*this, &OptionEditor::parameter_changed), parameter_name));
1291
1292 #define PARAM_IS(x) (!strcmp (parameter_name, (x)))
1293         
1294         if (PARAM_IS ("timecode-source-is-synced")) {
1295                 synced_timecode_button.set_active (Config->get_timecode_source_is_synced());
1296         } else if (PARAM_IS ("history-depth")) {
1297                 int32_t depth = Config->get_history_depth();
1298                 
1299                 history_depth.set_value (depth);
1300                 history_depth_spinner.set_sensitive (depth != 0);
1301                 limit_history_button.set_active (depth != 0);
1302
1303         } else if (PARAM_IS ("saved-history-depth")) {
1304
1305                 saved_history_depth.set_value (Config->get_saved_history_depth());
1306
1307         } else if (PARAM_IS ("save-history")) {
1308
1309                 bool x = Config->get_save_history();
1310
1311                 save_history_button.set_active (x);
1312                 saved_history_depth_spinner.set_sensitive (x);
1313         }
1314 }