merge from 2.0-ongoing by hand, minus key binding editor
[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         add_midi_port_button.set_label ("Add MIDI port");
476         midi_packer.pack_start (add_midi_port_button, false, false);
477
478         hbox = manage (new HBox);
479         hbox->set_border_width (6);
480         hbox->set_spacing (6);
481         label = (manage (new Label (_("Inbound MMC Device ID")))); 
482         hbox->pack_start (mmc_receive_device_id_spinner, false, false);
483         hbox->pack_start (*label, false, false);
484         midi_packer.pack_start (*hbox, false, false); 
485
486         mmc_receive_device_id_spinner.set_value(Config->get_mmc_receive_device_id ());
487
488         hbox = manage (new HBox);
489         hbox->set_border_width (6);
490         hbox->set_spacing (6);
491         label = (manage (new Label (_("Outbound MMC Device ID")))); 
492         hbox->pack_start (mmc_send_device_id_spinner, false, false);
493         hbox->pack_start (*label, false, false);
494         midi_packer.pack_start (*hbox, false, false);
495
496         mmc_send_device_id_spinner.set_value(Config->get_mmc_send_device_id ());
497
498         add_midi_port_button.signal_clicked().connect (mem_fun (*this, &OptionEditor::add_midi_port));
499 }
500
501 void
502 OptionEditor::redisplay_midi_ports ()
503 {
504         MIDI::Manager::PortMap::const_iterator i;
505         const MIDI::Manager::PortMap& ports = MIDI::Manager::instance()->get_midi_ports();
506         int n;
507
508         /* remove all existing widgets */
509
510         // XXX broken in gtkmm 2.10
511         // midi_port_table.clear ();
512
513         for (vector<Widget*>::iterator w = midi_port_table_widgets.begin(); w != midi_port_table_widgets.end(); ++w) {
514                 midi_port_table.remove (**w);
515         }
516
517         midi_port_table_widgets.clear ();
518
519         midi_port_table.resize (ports.size() + 4, 11);
520
521         Gtk::Label* label;
522
523         label = (manage (new Label (_("Port")))); 
524         label->show ();
525         midi_port_table_widgets.push_back (label);
526         midi_port_table.attach (*label, 0, 1, 0, 1);
527         label = (manage (new Label (_("Offline")))); 
528         label->show ();
529         midi_port_table_widgets.push_back (label);
530         midi_port_table.attach (*label, 1, 2, 0, 1);
531         label = (manage (new Label (_("Trace\nInput")))); 
532         label->show ();
533         midi_port_table_widgets.push_back (label);
534         midi_port_table.attach (*label, 2, 3, 0, 1);
535         label = (manage (new Label (_("Trace\nOutput")))); 
536         label->show ();
537         midi_port_table_widgets.push_back (label);
538         midi_port_table.attach (*label, 3, 4, 0, 1);
539         label = (manage (new Label (_("MTC")))); 
540         label->show ();
541         midi_port_table_widgets.push_back (label);
542         midi_port_table.attach (*label, 4, 5, 0, 1);
543         label = (manage (new Label (_("MMC")))); 
544         label->show ();
545         midi_port_table_widgets.push_back (label);
546         midi_port_table.attach (*label, 6, 7, 0, 1);
547         label = (manage (new Label (_("MIDI Parameter\nControl")))); 
548         label->show ();
549         midi_port_table_widgets.push_back (label);
550         midi_port_table.attach (*label, 8, 9, 0, 1);
551
552         Gtk::HSeparator* hsep = (manage (new HSeparator())); 
553         hsep->show ();
554         midi_port_table_widgets.push_back (hsep);
555         midi_port_table.attach (*hsep, 0, 9, 1, 2);
556         Gtk::VSeparator* vsep = (manage (new VSeparator())); 
557         vsep->show ();
558         midi_port_table_widgets.push_back (vsep);
559         midi_port_table.attach (*vsep, 5, 6, 0, 8);
560         vsep = (manage (new VSeparator())); 
561         vsep->show ();
562         midi_port_table_widgets.push_back (vsep);
563         midi_port_table.attach (*vsep, 7, 8, 0, 8);
564         
565         for (n = 0, i = ports.begin(); i != ports.end(); ++n, ++i) {
566
567                 ToggleButton* tb;
568                 RadioButton* rb;
569                 Button* bb;
570
571                 /* the remove button. create early so we can pass it to various callbacks */
572                 
573                 bb = manage (new Button (Stock::REMOVE));
574                 bb->set_name ("OptionEditorToggleButton");
575                 bb->show ();
576                 midi_port_table_widgets.push_back (bb);
577                 midi_port_table.attach (*bb, 9, 10, n+2, n+3, FILL|EXPAND, FILL);
578                 bb->signal_clicked().connect (bind (mem_fun(*this, &OptionEditor::remove_midi_port), i->second));
579                 bb->set_sensitive (port_removable (i->second));
580
581                 label = (manage (new Label (i->first))); 
582                 label->show ();
583                 midi_port_table_widgets.push_back (label);
584                 midi_port_table.attach (*label, 0, 1, n+2, n+3,FILL|EXPAND, FILL );
585                 
586                 tb = manage (new ToggleButton (_("online")));
587                 tb->set_name ("OptionEditorToggleButton");
588
589                 /* remember, we have to handle the i18n case where the relative
590                    lengths of the strings in language N is different than in english.
591                 */
592
593                 if (strlen (_("offline")) > strlen (_("online"))) {
594                         set_size_request_to_display_given_text (*tb, _("offline"), 15, 12);
595                 } else {
596                         set_size_request_to_display_given_text (*tb, _("online"), 15, 12);
597                 }
598
599                 if (i->second->input()) {
600                         tb->set_active (!i->second->input()->offline());
601                         tb->signal_toggled().connect (bind (mem_fun(*this, &OptionEditor::port_online_toggled), i->second, tb));
602                         i->second->input()->OfflineStatusChanged.connect (bind (mem_fun(*this, &OptionEditor::map_port_online), (*i).second, tb));
603                 }
604                 tb->show ();
605                 midi_port_table_widgets.push_back (tb);
606                 midi_port_table.attach (*tb, 1, 2, n+2, n+3, FILL|EXPAND, FILL);
607
608                 tb = manage (new ToggleButton ());
609                 tb->set_name ("OptionEditorToggleButton");
610                 tb->signal_toggled().connect (bind (mem_fun(*this, &OptionEditor::port_trace_in_toggled), (*i).second, tb));
611                 tb->set_size_request (10, 10);
612                 tb->show ();
613                 midi_port_table_widgets.push_back (tb);
614                 midi_port_table.attach (*tb, 2, 3, n+2, n+3, FILL|EXPAND, FILL);
615
616                 tb = manage (new ToggleButton ());
617                 tb->set_name ("OptionEditorToggleButton");
618                 tb->signal_toggled().connect (bind (mem_fun(*this, &OptionEditor::port_trace_out_toggled), (*i).second, tb));
619                 tb->set_size_request (10, 10);
620                 tb->show ();
621                 midi_port_table_widgets.push_back (tb);
622                 midi_port_table.attach (*tb, 3, 4, n+2, n+3, FILL|EXPAND, FILL);
623
624                 rb = manage (new RadioButton ());
625                 rb->set_name ("OptionEditorToggleButton");
626                 if (n == 0) {
627                         mtc_button_group = rb->get_group();
628                 } else {
629                         rb->set_group (mtc_button_group);
630
631                 }
632                 rb->show ();
633                 midi_port_table_widgets.push_back (rb);
634                 midi_port_table.attach (*rb, 4, 5, n+2, n+3, FILL|EXPAND, FILL);
635                 rb->signal_toggled().connect (bind (mem_fun(*this, &OptionEditor::mtc_port_chosen), (*i).second, rb, bb));
636
637                 if (session && i->second == session->mtc_port()) {
638                         rb->set_active (true);
639                 }
640                 
641                 rb = manage (new RadioButton ());
642                 rb->set_name ("OptionEditorToggleButton");
643                 if (n == 0) {
644                         mmc_button_group = rb->get_group();
645                 } else {
646                         rb->set_group (mmc_button_group);
647                 }
648                 rb->show ();
649                 midi_port_table_widgets.push_back (rb);
650                 midi_port_table.attach (*rb, 6, 7, n+2, n+3, FILL|EXPAND, FILL);
651                 rb->signal_toggled().connect (bind (mem_fun(*this, &OptionEditor::mmc_port_chosen), (*i).second, rb, bb));
652
653                 if (session && i->second == session->mmc_port()) {
654                         rb->set_active (true);
655                 }
656
657                 rb = manage (new RadioButton ());
658                 rb->set_name ("OptionEditorToggleButton");
659                 if (n == 0) {
660                         midi_button_group = rb->get_group();
661                 } else {
662                         rb->set_group (midi_button_group);
663                 }
664                 rb->show ();
665                 midi_port_table_widgets.push_back (rb);
666                 midi_port_table.attach (*rb, 8, 9, n+2, n+3, FILL|EXPAND, FILL);
667                 rb->signal_toggled().connect (bind (mem_fun(*this, &OptionEditor::midi_port_chosen), (*i).second, rb, bb));
668
669                 if (session && i->second == session->midi_port()) {
670                         rb->set_active (true);
671                 }
672
673         }
674
675         midi_port_table.show();
676 }
677
678 void
679 OptionEditor::remove_midi_port (MIDI::Port* port)
680 {
681         MIDI::Manager::instance()->remove_port (port);
682         redisplay_midi_ports ();
683 }
684
685 void
686 OptionEditor::add_midi_port ()
687 {
688         MidiPortDialog dialog;
689
690         dialog.set_position (WIN_POS_MOUSE);
691         dialog.set_transient_for (*this);
692
693         dialog.show ();
694
695         int ret = dialog.run ();
696
697         switch (ret) {
698         case RESPONSE_ACCEPT:
699                 break;
700         default:
701                 return;
702                 break;
703         }
704
705         Glib::ustring mode = dialog.port_mode_combo.get_active_text();
706         std::string smod;
707
708         if (mode == _("input")) {
709                 smod = X_("input");
710         } else if (mode == (_("output"))) {
711                 smod = X_("output");
712         } else {
713                 smod = "duplex";
714         }
715
716
717         XMLNode node (X_("MIDI-port"));
718
719         node.add_property ("tag", dialog.port_name.get_text());
720         node.add_property ("device", X_("ardour")); // XXX this can't be right for all types
721         node.add_property ("type", MIDI::PortFactory::default_port_type());
722         node.add_property ("mode", smod);
723
724         if (MIDI::Manager::instance()->add_port (node) != 0) {
725                 redisplay_midi_ports ();
726         }
727 }
728
729 bool
730 OptionEditor::port_removable (MIDI::Port *port)
731 {
732         if (!session) {
733                 return true;
734         }
735
736         if (port == session->mtc_port() ||
737             port == session->mmc_port() ||
738             port == session->midi_port()) {
739                 return false;
740         }
741         return true;
742 }
743
744 void
745 OptionEditor::mtc_port_chosen (MIDI::Port *port, Gtk::RadioButton* rb, Gtk::Button* bb) 
746 {
747         if (session) {
748                 if (rb->get_active()) {
749                         session->set_mtc_port (port->name());
750                         Config->set_mtc_port_name (port->name());
751                 } else {
752                         session->set_mtc_port ("");
753                 }
754                 bb->set_sensitive (port_removable (port));
755         }
756 }
757
758 void
759 OptionEditor::mmc_port_chosen (MIDI::Port* port, Gtk::RadioButton* rb, Gtk::Button* bb)
760 {
761         if (session) {
762                 if (rb->get_active()) {
763                         session->set_mmc_port (port->name());
764                         Config->set_mtc_port_name (port->name());
765                 } else {
766                         session->set_mmc_port ("");
767                 }
768                 bb->set_sensitive (port_removable (port));
769         }
770 }
771
772 void
773 OptionEditor::midi_port_chosen (MIDI::Port* port, Gtk::RadioButton* rb, Gtk::Button* bb)
774 {
775         if (session) {
776                 if (rb->get_active()) {
777                         session->set_midi_port (port->name());
778                         Config->set_midi_port_name (port->name());
779                 } else {
780                         session->set_midi_port ("");
781                 }
782                 bb->set_sensitive (port_removable (port));
783         }
784 }
785
786 void
787 OptionEditor::port_online_toggled (MIDI::Port* port, ToggleButton* tb)
788 {
789         bool wanted = tb->get_active();
790
791         if (port->input()) {
792                 if (wanted != port->input()->offline()) {
793                         port->input()->set_offline (wanted);
794                 } 
795         }
796 }
797
798 void
799 OptionEditor::map_port_online (MIDI::Port* port, ToggleButton* tb)
800 {
801         bool bstate = tb->get_active ();
802         
803         if (port->input()) {
804                 if (bstate != port->input()->offline()) {
805                         if (port->input()->offline()) {
806                                 tb->set_label (_("offline"));
807                                 tb->set_active (false);
808                         } else {
809                                 tb->set_label (_("online"));
810                                 tb->set_active (true);
811                         }
812                 }
813         }
814 }
815
816 void
817 OptionEditor::mmc_receive_device_id_adjusted ()
818 {
819         uint8_t id = (uint8_t) mmc_receive_device_id_spinner.get_value();
820         Config->set_mmc_receive_device_id (id);
821 }
822
823 void
824 OptionEditor::mmc_send_device_id_adjusted ()
825 {
826         uint8_t id = (uint8_t) mmc_send_device_id_spinner.get_value();
827         Config->set_mmc_send_device_id (id);
828 }
829
830 void
831 OptionEditor::port_trace_in_toggled (MIDI::Port* port, ToggleButton* tb)
832 {
833         bool trace = tb->get_active();
834
835         if (port->input()) {
836                 if (port->input()->tracing() != trace) {
837                         port->input()->trace (trace, &cerr, string (port->name()) + string (" input: "));
838                 }
839         }
840 }
841
842 void
843 OptionEditor::port_trace_out_toggled (MIDI::Port* port, ToggleButton* tb)
844 {
845         bool trace = tb->get_active();
846
847         if (port->output()) {
848                 if (port->output()->tracing() != trace) {
849                         port->output()->trace (trace, &cerr, string (port->name()) + string (" output: "));
850                 }
851         }
852 }
853
854 void
855 OptionEditor::save ()
856 {
857         /* XXX a bit odd that we save the entire session state here */
858
859         ui.save_state ("");
860 }
861
862 gint
863 OptionEditor::wm_close (GdkEventAny *ev)
864 {
865         save ();
866         hide ();
867         return TRUE;
868 }
869
870 void
871 OptionEditor::raid_path_changed ()
872 {
873         if (session) {
874                 Config->set_raid_path (session_raid_entry.get_text());
875         }
876 }
877
878 void
879 OptionEditor::click_browse_clicked ()
880 {
881         SoundFileChooser sfdb (*this, _("Choose Click"), session);
882         
883         sfdb.show_all ();
884         sfdb.present ();
885
886         int result = sfdb.run ();
887  
888         if (result == Gtk::RESPONSE_OK) {
889                 click_chosen(sfdb.get_filename());
890         }
891 }
892
893 void
894 OptionEditor::click_chosen (const string & path)
895 {
896         click_path_entry.set_text (path);
897         click_sound_changed ();
898 }
899
900 void
901 OptionEditor::click_emphasis_browse_clicked ()
902 {
903         SoundFileChooser sfdb (*this, _("Choose Click Emphasis"), session);
904
905         sfdb.show_all ();
906         sfdb.present ();
907
908         int result = sfdb.run ();
909
910         if (result == Gtk::RESPONSE_OK) {
911                 click_emphasis_chosen (sfdb.get_filename());
912         }
913 }
914
915 void
916 OptionEditor::click_emphasis_chosen (const string & path)
917 {       
918         click_emphasis_path_entry.set_text (path);
919         click_emphasis_sound_changed ();
920 }
921
922 void
923 OptionEditor::click_sound_changed ()
924 {
925         if (session) {
926                 string path = click_path_entry.get_text();
927
928                 if (path == Config->get_click_sound()) {
929                         return;
930                 }
931
932                 strip_whitespace_edges (path);
933
934                 if (path == _("internal")) {
935                         Config->set_click_sound ("");
936                 } else {
937                         Config->set_click_sound (path);
938                 }
939         }
940 }
941
942 void
943 OptionEditor::click_emphasis_sound_changed ()
944 {
945         if (session) {
946                 string path = click_emphasis_path_entry.get_text();
947
948                 if (path == Config->get_click_emphasis_sound()) {
949                         return;
950                 }
951
952                 strip_whitespace_edges (path);
953
954                 if (path == _("internal")) {
955                         Config->set_click_emphasis_sound ("");
956                 } else {
957                         Config->set_click_emphasis_sound (path);
958                 }
959         }
960 }
961
962 void
963 OptionEditor::clear_click_editor ()
964 {
965         if (click_io_selector) {
966                 click_packer.remove (*click_io_selector);
967                 click_packer.remove (*click_gpm);
968                 delete click_io_selector;
969                 delete click_gpm;
970                 click_io_selector = 0;
971                 click_gpm = 0;
972         }
973 }
974
975 void
976 OptionEditor::setup_click_editor ()
977 {
978         Label* label;
979         HBox* hpacker = manage (new HBox);
980
981         click_path_entry.set_sensitive (true);
982         click_emphasis_path_entry.set_sensitive (true);
983
984         click_path_entry.set_name ("OptionsEntry");
985         click_emphasis_path_entry.set_name ("OptionsEntry");
986         
987         click_path_entry.signal_activate().connect (mem_fun(*this, &OptionEditor::click_sound_changed));
988         click_emphasis_path_entry.signal_activate().connect (mem_fun(*this, &OptionEditor::click_emphasis_sound_changed));
989
990         click_path_entry.signal_focus_out_event().connect (bind (mem_fun(*this, &OptionEditor::focus_out_event_handler), &OptionEditor::click_sound_changed));
991         click_emphasis_path_entry.signal_focus_out_event().connect (bind (mem_fun(*this, &OptionEditor::focus_out_event_handler), &OptionEditor::click_emphasis_sound_changed));
992
993         click_browse_button.set_name ("EditorGTKButton");
994         click_emphasis_browse_button.set_name ("EditorGTKButton");
995         click_browse_button.signal_clicked().connect (mem_fun(*this, &OptionEditor::click_browse_clicked));
996         click_emphasis_browse_button.signal_clicked().connect (mem_fun(*this, &OptionEditor::click_emphasis_browse_clicked));
997
998         click_packer.set_border_width (12);
999         click_packer.set_spacing (5);
1000
1001         click_io_selector = new IOSelector (*session, session->click_io(), false);
1002         click_gpm = new GainMeter (session->click_io(), *session);
1003
1004         click_table.set_col_spacings (10);
1005         
1006         label = manage(new Label(_("Click audio file")));
1007         label->set_name ("OptionsLabel");
1008         click_table.attach (*label, 0, 1, 0, 1, FILL|EXPAND, FILL);
1009         click_table.attach (click_path_entry, 1, 2, 0, 1, Gtk::FILL|Gtk::EXPAND, FILL);
1010         click_table.attach (click_browse_button, 2, 3, 0, 1, FILL|EXPAND, FILL);
1011         
1012         label = manage(new Label(_("Click emphasis audiofile")));
1013         label->set_name ("OptionsLabel");
1014         click_table.attach (*label, 0, 1, 1, 2, FILL|EXPAND, FILL);
1015         click_table.attach (click_emphasis_path_entry, 1, 2, 1, 2, Gtk::FILL|Gtk::EXPAND, FILL);
1016         click_table.attach (click_emphasis_browse_button, 2, 3, 1, 2, FILL|EXPAND, FILL);
1017
1018         hpacker->set_spacing (10);
1019         hpacker->pack_start (*click_io_selector, false, false);
1020         hpacker->pack_start (*click_gpm, false, false);
1021
1022         click_packer.pack_start (click_table, false, false);
1023         click_packer.pack_start (*hpacker, false, false);
1024
1025         click_packer.show_all ();
1026 }
1027
1028 void
1029 OptionEditor::clear_auditioner_editor ()
1030 {
1031         if (auditioner_io_selector) {
1032                 audition_hpacker.remove (*auditioner_io_selector);
1033                 audition_hpacker.remove (*auditioner_gpm);
1034                 delete auditioner_io_selector;
1035                 delete auditioner_gpm;
1036                 auditioner_io_selector = 0;
1037                 auditioner_gpm = 0;
1038         }
1039 }
1040
1041 void
1042 OptionEditor::setup_auditioner_editor ()
1043 {
1044         audition_packer.set_border_width (12);
1045         audition_packer.set_spacing (5);
1046         audition_hpacker.set_spacing (10);
1047
1048         audition_label.set_name ("OptionEditorAuditionerLabel");
1049         audition_label.set_text (_("The auditioner is a dedicated mixer strip used\n"
1050                                    "for listening to specific regions outside the context\n"
1051                                    "of the overall mix. It can be connected just like any\n"
1052                                    "other mixer strip."));
1053         
1054         audition_packer.pack_start (audition_label, false, false, 10);
1055         audition_packer.pack_start (audition_hpacker, false, false);
1056 }
1057
1058 void
1059 OptionEditor::connect_audition_editor ()
1060 {
1061         auditioner_io_selector = new IOSelector (*session, session->the_auditioner(), false);
1062         auditioner_gpm = new GainMeter (session->the_auditioner(), *session);
1063
1064         audition_hpacker.pack_start (*auditioner_io_selector, false, false);
1065         audition_hpacker.pack_start (*auditioner_gpm, false, false);
1066
1067         auditioner_io_selector->show_all ();
1068         auditioner_gpm->show_all ();
1069 }
1070
1071 bool
1072 OptionEditor::focus_out_event_handler (GdkEventFocus* ev, void (OptionEditor::*pmf)()) 
1073 {
1074         (this->*pmf)();
1075         return false;
1076 }
1077
1078 static const struct {
1079     const char *name;
1080     guint   modifier;
1081 } modifiers[] = {
1082         { "Shift", GDK_SHIFT_MASK },
1083         { "Control", GDK_CONTROL_MASK },
1084         { "Alt (Mod1)", GDK_MOD1_MASK },
1085         { "Control-Shift", GDK_CONTROL_MASK|GDK_SHIFT_MASK },
1086         { "Control-Alt", GDK_CONTROL_MASK|GDK_MOD1_MASK },
1087         { "Shift-Alt", GDK_SHIFT_MASK|GDK_MOD1_MASK },
1088         { "Control-Shift-Alt", GDK_CONTROL_MASK|GDK_SHIFT_MASK|GDK_MOD1_MASK },
1089         { "Mod2", GDK_MOD2_MASK },
1090         { "Mod3", GDK_MOD3_MASK },
1091         { "Mod4", GDK_MOD4_MASK },
1092         { "Mod5", GDK_MOD5_MASK },
1093         { 0, 0 }
1094 };
1095
1096 void
1097 OptionEditor::setup_keyboard_options ()
1098 {
1099         vector<string> dumb;
1100         Label* label;
1101
1102         keyboard_mouse_table.set_border_width (12);
1103         keyboard_mouse_table.set_row_spacings (5);
1104         keyboard_mouse_table.set_col_spacings (5);
1105
1106         /* internationalize and prepare for use with combos */
1107
1108         for (int i = 0; modifiers[i].name; ++i) {
1109                 dumb.push_back (_(modifiers[i].name));
1110         }
1111
1112         set_popdown_strings (edit_modifier_combo, dumb);
1113         edit_modifier_combo.signal_changed().connect (mem_fun(*this, &OptionEditor::edit_modifier_chosen));
1114
1115         for (int x = 0; modifiers[x].name; ++x) {
1116                 if (modifiers[x].modifier == Keyboard::edit_modifier ()) {
1117                         edit_modifier_combo.set_active_text (_(modifiers[x].name));
1118                         break;
1119                 }
1120         }
1121
1122         label = manage (new Label (_("Edit using")));
1123         label->set_name ("OptionsLabel");
1124         label->set_alignment (1.0, 0.5);
1125                 
1126         keyboard_mouse_table.attach (*label, 0, 1, 0, 1, Gtk::FILL|Gtk::EXPAND, FILL);
1127         keyboard_mouse_table.attach (edit_modifier_combo, 1, 2, 0, 1, Gtk::FILL|Gtk::EXPAND, FILL);
1128
1129         label = manage (new Label (_("+ button")));
1130         label->set_name ("OptionsLabel");
1131         
1132         keyboard_mouse_table.attach (*label, 3, 4, 0, 1, Gtk::FILL|Gtk::EXPAND, FILL);
1133         keyboard_mouse_table.attach (edit_button_spin, 4, 5, 0, 1, Gtk::FILL|Gtk::EXPAND, FILL);
1134
1135         edit_button_spin.set_name ("OptionsEntry");
1136         edit_button_adjustment.set_value (Keyboard::edit_button());
1137         edit_button_adjustment.signal_value_changed().connect (mem_fun(*this, &OptionEditor::edit_button_changed));
1138
1139         set_popdown_strings (delete_modifier_combo, dumb);
1140         delete_modifier_combo.signal_changed().connect (mem_fun(*this, &OptionEditor::delete_modifier_chosen));
1141
1142         for (int x = 0; modifiers[x].name; ++x) {
1143                 if (modifiers[x].modifier == Keyboard::delete_modifier ()) {
1144                         delete_modifier_combo.set_active_text (_(modifiers[x].name));
1145                         break;
1146                 }
1147         }
1148
1149         label = manage (new Label (_("Delete using")));
1150         label->set_name ("OptionsLabel");
1151         label->set_alignment (1.0, 0.5);
1152                 
1153         keyboard_mouse_table.attach (*label, 0, 1, 1, 2, Gtk::FILL|Gtk::EXPAND, FILL);
1154         keyboard_mouse_table.attach (delete_modifier_combo, 1, 2, 1, 2, Gtk::FILL|Gtk::EXPAND, FILL);
1155
1156         label = manage (new Label (_("+ button")));
1157         label->set_name ("OptionsLabel");
1158
1159         keyboard_mouse_table.attach (*label, 3, 4, 1, 2, Gtk::FILL|Gtk::EXPAND, FILL);
1160         keyboard_mouse_table.attach (delete_button_spin, 4, 5, 1, 2, Gtk::FILL|Gtk::EXPAND, FILL);
1161
1162         delete_button_spin.set_name ("OptionsEntry");
1163         delete_button_adjustment.set_value (Keyboard::delete_button());
1164         delete_button_adjustment.signal_value_changed().connect (mem_fun(*this, &OptionEditor::delete_button_changed));
1165
1166         set_popdown_strings (snap_modifier_combo, dumb);
1167         snap_modifier_combo.signal_changed().connect (mem_fun(*this, &OptionEditor::snap_modifier_chosen));
1168         
1169         for (int x = 0; modifiers[x].name; ++x) {
1170                 if (modifiers[x].modifier == (guint) Keyboard::snap_modifier ()) {
1171                         snap_modifier_combo.set_active_text (_(modifiers[x].name));
1172                         break;
1173                 }
1174         }
1175
1176         label = manage (new Label (_("Ignore snap using")));
1177         label->set_name ("OptionsLabel");
1178         label->set_alignment (1.0, 0.5);
1179         
1180         keyboard_mouse_table.attach (*label, 0, 1, 2, 3, Gtk::FILL|Gtk::EXPAND, FILL);
1181         keyboard_mouse_table.attach (snap_modifier_combo, 1, 2, 2, 3, Gtk::FILL|Gtk::EXPAND, FILL);
1182 }
1183
1184 void
1185 OptionEditor::edit_modifier_chosen ()
1186 {
1187         string txt;
1188         
1189         txt = edit_modifier_combo.get_active_text();
1190
1191         for (int i = 0; modifiers[i].name; ++i) {
1192                 if (txt == _(modifiers[i].name)) {
1193                         Keyboard::set_edit_modifier (modifiers[i].modifier);
1194                         break;
1195                 }
1196         }
1197 }
1198
1199 void
1200 OptionEditor::delete_modifier_chosen ()
1201 {
1202         string txt;
1203         
1204         txt = delete_modifier_combo.get_active_text();
1205
1206         for (int i = 0; modifiers[i].name; ++i) {
1207                 if (txt == _(modifiers[i].name)) {
1208                         Keyboard::set_delete_modifier (modifiers[i].modifier);
1209                         break;
1210                 }
1211         }
1212 }
1213
1214 void
1215 OptionEditor::snap_modifier_chosen ()
1216 {
1217         string txt;
1218         
1219         txt = snap_modifier_combo.get_active_text();
1220
1221         for (int i = 0; modifiers[i].name; ++i) {
1222                 if (txt == _(modifiers[i].name)) {
1223                         Keyboard::set_snap_modifier (modifiers[i].modifier);
1224                         break;
1225                 }
1226         }
1227 }
1228
1229 void
1230 OptionEditor::delete_button_changed ()
1231 {
1232         Keyboard::set_delete_button ((guint) delete_button_adjustment.get_value());
1233 }
1234
1235 void
1236 OptionEditor::edit_button_changed ()
1237 {
1238         Keyboard::set_edit_button ((guint) edit_button_adjustment.get_value());
1239 }
1240
1241 void
1242 OptionEditor::fixup_combo_size (Gtk::ComboBoxText& combo, vector<string>& strings)
1243 {
1244         /* find the widest string */
1245
1246         string::size_type maxlen = 0;
1247         string maxstring;
1248
1249         for (vector<string>::iterator i = strings.begin(); i != strings.end(); ++i) {
1250                 string::size_type l;
1251
1252                 if ((l = (*i).length()) > maxlen) {
1253                         maxlen = l;
1254                         maxstring = *i;
1255                 }
1256         }
1257
1258         /* try to include ascenders and descenders */
1259
1260         if (maxstring.length() > 2) {
1261                 maxstring[0] = 'g';
1262                 maxstring[1] = 'l';
1263         }
1264
1265         const guint32 FUDGE = 10; // Combo's are stupid - they steal space from the entry for the button
1266
1267         set_size_request_to_display_given_text (combo, maxstring.c_str(), 10 + FUDGE, 10);
1268 }
1269
1270 void
1271 OptionEditor::parameter_changed (const char* parameter_name)
1272 {
1273         ENSURE_GUI_THREAD (bind (mem_fun (*this, &OptionEditor::parameter_changed), parameter_name));
1274
1275 #define PARAM_IS(x) (!strcmp (parameter_name, (x)))
1276         
1277         if (PARAM_IS ("timecode-source-is-synced")) {
1278                 synced_timecode_button.set_active (Config->get_timecode_source_is_synced());
1279         } else if (PARAM_IS ("history-depth")) {
1280                 int32_t depth = Config->get_history_depth();
1281                 
1282                 history_depth.set_value (depth);
1283                 history_depth_spinner.set_sensitive (depth != 0);
1284                 limit_history_button.set_active (depth != 0);
1285
1286         } else if (PARAM_IS ("saved-history-depth")) {
1287
1288                 saved_history_depth.set_value (Config->get_saved_history_depth());
1289
1290         } else if (PARAM_IS ("save-history")) {
1291
1292                 bool x = Config->get_save_history();
1293
1294                 save_history_button.set_active (x);
1295                 saved_history_depth_spinner.set_sensitive (x);
1296         }
1297 }