GUI control over saved and in-memory history depth
[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         MIDI::PortRequest req (X_("ardour"),
716                                dialog.port_name.get_text(),
717                                smod,
718                                MIDI::PortFactory::default_port_type());
719
720         if (MIDI::Manager::instance()->add_port (req) != 0) {
721                 redisplay_midi_ports ();
722         }
723 }
724
725 bool
726 OptionEditor::port_removable (MIDI::Port *port)
727 {
728         if (!session) {
729                 return true;
730         }
731
732         if (port == session->mtc_port() ||
733             port == session->mmc_port() ||
734             port == session->midi_port()) {
735                 return false;
736         }
737         return true;
738 }
739
740 void
741 OptionEditor::mtc_port_chosen (MIDI::Port *port, Gtk::RadioButton* rb, Gtk::Button* bb) 
742 {
743         if (session) {
744                 if (rb->get_active()) {
745                         session->set_mtc_port (port->name());
746                         Config->set_mtc_port_name (port->name());
747                 } else {
748                         session->set_mtc_port ("");
749                 }
750                 bb->set_sensitive (port_removable (port));
751         }
752 }
753
754 void
755 OptionEditor::mmc_port_chosen (MIDI::Port* port, Gtk::RadioButton* rb, Gtk::Button* bb)
756 {
757         if (session) {
758                 if (rb->get_active()) {
759                         session->set_mmc_port (port->name());
760                         Config->set_mtc_port_name (port->name());
761                 } else {
762                         session->set_mmc_port ("");
763                 }
764                 bb->set_sensitive (port_removable (port));
765         }
766 }
767
768 void
769 OptionEditor::midi_port_chosen (MIDI::Port* port, Gtk::RadioButton* rb, Gtk::Button* bb)
770 {
771         if (session) {
772                 if (rb->get_active()) {
773                         session->set_midi_port (port->name());
774                         Config->set_midi_port_name (port->name());
775                 } else {
776                         session->set_midi_port ("");
777                 }
778                 bb->set_sensitive (port_removable (port));
779         }
780 }
781
782 void
783 OptionEditor::port_online_toggled (MIDI::Port* port, ToggleButton* tb)
784 {
785         bool wanted = tb->get_active();
786
787         if (port->input()) {
788                 if (wanted != port->input()->offline()) {
789                         port->input()->set_offline (wanted);
790                 } 
791         }
792 }
793
794 void
795 OptionEditor::map_port_online (MIDI::Port* port, ToggleButton* tb)
796 {
797         bool bstate = tb->get_active ();
798         
799         if (port->input()) {
800                 if (bstate != port->input()->offline()) {
801                         if (port->input()->offline()) {
802                                 tb->set_label (_("offline"));
803                                 tb->set_active (false);
804                         } else {
805                                 tb->set_label (_("online"));
806                                 tb->set_active (true);
807                         }
808                 }
809         }
810 }
811
812 void
813 OptionEditor::mmc_receive_device_id_adjusted ()
814 {
815         uint8_t id = (uint8_t) mmc_receive_device_id_spinner.get_value();
816         Config->set_mmc_receive_device_id (id);
817 }
818
819 void
820 OptionEditor::mmc_send_device_id_adjusted ()
821 {
822         uint8_t id = (uint8_t) mmc_send_device_id_spinner.get_value();
823         Config->set_mmc_send_device_id (id);
824 }
825
826 void
827 OptionEditor::port_trace_in_toggled (MIDI::Port* port, ToggleButton* tb)
828 {
829         bool trace = tb->get_active();
830
831         if (port->input()) {
832                 if (port->input()->tracing() != trace) {
833                         port->input()->trace (trace, &cerr, string (port->name()) + string (" input: "));
834                 }
835         }
836 }
837
838 void
839 OptionEditor::port_trace_out_toggled (MIDI::Port* port, ToggleButton* tb)
840 {
841         bool trace = tb->get_active();
842
843         if (port->output()) {
844                 if (port->output()->tracing() != trace) {
845                         port->output()->trace (trace, &cerr, string (port->name()) + string (" output: "));
846                 }
847         }
848 }
849
850 void
851 OptionEditor::save ()
852 {
853         /* XXX a bit odd that we save the entire session state here */
854
855         ui.save_state ("");
856 }
857
858 gint
859 OptionEditor::wm_close (GdkEventAny *ev)
860 {
861         save ();
862         hide ();
863         return TRUE;
864 }
865
866 void
867 OptionEditor::raid_path_changed ()
868 {
869         if (session) {
870                 Config->set_raid_path (session_raid_entry.get_text());
871         }
872 }
873
874 void
875 OptionEditor::click_browse_clicked ()
876 {
877         SoundFileChooser sfdb (*this, _("Choose Click"), session);
878         
879         sfdb.show_all ();
880         sfdb.present ();
881
882         int result = sfdb.run ();
883  
884         if (result == Gtk::RESPONSE_OK) {
885                 click_chosen(sfdb.get_filename());
886         }
887 }
888
889 void
890 OptionEditor::click_chosen (const string & path)
891 {
892         click_path_entry.set_text (path);
893         click_sound_changed ();
894 }
895
896 void
897 OptionEditor::click_emphasis_browse_clicked ()
898 {
899         SoundFileChooser sfdb (*this, _("Choose Click Emphasis"), session);
900
901         sfdb.show_all ();
902         sfdb.present ();
903
904         int result = sfdb.run ();
905
906         if (result == Gtk::RESPONSE_OK) {
907                 click_emphasis_chosen (sfdb.get_filename());
908         }
909 }
910
911 void
912 OptionEditor::click_emphasis_chosen (const string & path)
913 {       
914         click_emphasis_path_entry.set_text (path);
915         click_emphasis_sound_changed ();
916 }
917
918 void
919 OptionEditor::click_sound_changed ()
920 {
921         if (session) {
922                 string path = click_path_entry.get_text();
923
924                 if (path == Config->get_click_sound()) {
925                         return;
926                 }
927
928                 strip_whitespace_edges (path);
929
930                 if (path == _("internal")) {
931                         Config->set_click_sound ("");
932                 } else {
933                         Config->set_click_sound (path);
934                 }
935         }
936 }
937
938 void
939 OptionEditor::click_emphasis_sound_changed ()
940 {
941         if (session) {
942                 string path = click_emphasis_path_entry.get_text();
943
944                 if (path == Config->get_click_emphasis_sound()) {
945                         return;
946                 }
947
948                 strip_whitespace_edges (path);
949
950                 if (path == _("internal")) {
951                         Config->set_click_emphasis_sound ("");
952                 } else {
953                         Config->set_click_emphasis_sound (path);
954                 }
955         }
956 }
957
958 void
959 OptionEditor::clear_click_editor ()
960 {
961         if (click_io_selector) {
962                 click_packer.remove (*click_io_selector);
963                 click_packer.remove (*click_gpm);
964                 delete click_io_selector;
965                 delete click_gpm;
966                 click_io_selector = 0;
967                 click_gpm = 0;
968         }
969 }
970
971 void
972 OptionEditor::setup_click_editor ()
973 {
974         Label* label;
975         HBox* hpacker = manage (new HBox);
976
977         click_path_entry.set_sensitive (true);
978         click_emphasis_path_entry.set_sensitive (true);
979
980         click_path_entry.set_name ("OptionsEntry");
981         click_emphasis_path_entry.set_name ("OptionsEntry");
982         
983         click_path_entry.signal_activate().connect (mem_fun(*this, &OptionEditor::click_sound_changed));
984         click_emphasis_path_entry.signal_activate().connect (mem_fun(*this, &OptionEditor::click_emphasis_sound_changed));
985
986         click_path_entry.signal_focus_out_event().connect (bind (mem_fun(*this, &OptionEditor::focus_out_event_handler), &OptionEditor::click_sound_changed));
987         click_emphasis_path_entry.signal_focus_out_event().connect (bind (mem_fun(*this, &OptionEditor::focus_out_event_handler), &OptionEditor::click_emphasis_sound_changed));
988
989         click_browse_button.set_name ("EditorGTKButton");
990         click_emphasis_browse_button.set_name ("EditorGTKButton");
991         click_browse_button.signal_clicked().connect (mem_fun(*this, &OptionEditor::click_browse_clicked));
992         click_emphasis_browse_button.signal_clicked().connect (mem_fun(*this, &OptionEditor::click_emphasis_browse_clicked));
993
994         click_packer.set_border_width (12);
995         click_packer.set_spacing (5);
996
997         click_io_selector = new IOSelector (*session, session->click_io(), false);
998         click_gpm = new GainMeter (session->click_io(), *session);
999
1000         click_table.set_col_spacings (10);
1001         
1002         label = manage(new Label(_("Click audio file")));
1003         label->set_name ("OptionsLabel");
1004         click_table.attach (*label, 0, 1, 0, 1, FILL|EXPAND, FILL);
1005         click_table.attach (click_path_entry, 1, 2, 0, 1, Gtk::FILL|Gtk::EXPAND, FILL);
1006         click_table.attach (click_browse_button, 2, 3, 0, 1, FILL|EXPAND, FILL);
1007         
1008         label = manage(new Label(_("Click emphasis audiofile")));
1009         label->set_name ("OptionsLabel");
1010         click_table.attach (*label, 0, 1, 1, 2, FILL|EXPAND, FILL);
1011         click_table.attach (click_emphasis_path_entry, 1, 2, 1, 2, Gtk::FILL|Gtk::EXPAND, FILL);
1012         click_table.attach (click_emphasis_browse_button, 2, 3, 1, 2, FILL|EXPAND, FILL);
1013
1014         hpacker->set_spacing (10);
1015         hpacker->pack_start (*click_io_selector, false, false);
1016         hpacker->pack_start (*click_gpm, false, false);
1017
1018         click_packer.pack_start (click_table, false, false);
1019         click_packer.pack_start (*hpacker, false, false);
1020
1021         click_packer.show_all ();
1022 }
1023
1024 void
1025 OptionEditor::clear_auditioner_editor ()
1026 {
1027         if (auditioner_io_selector) {
1028                 audition_hpacker.remove (*auditioner_io_selector);
1029                 audition_hpacker.remove (*auditioner_gpm);
1030                 delete auditioner_io_selector;
1031                 delete auditioner_gpm;
1032                 auditioner_io_selector = 0;
1033                 auditioner_gpm = 0;
1034         }
1035 }
1036
1037 void
1038 OptionEditor::setup_auditioner_editor ()
1039 {
1040         audition_packer.set_border_width (12);
1041         audition_packer.set_spacing (5);
1042         audition_hpacker.set_spacing (10);
1043
1044         audition_label.set_name ("OptionEditorAuditionerLabel");
1045         audition_label.set_text (_("The auditioner is a dedicated mixer strip used\n"
1046                                    "for listening to specific regions outside the context\n"
1047                                    "of the overall mix. It can be connected just like any\n"
1048                                    "other mixer strip."));
1049         
1050         audition_packer.pack_start (audition_label, false, false, 10);
1051         audition_packer.pack_start (audition_hpacker, false, false);
1052 }
1053
1054 void
1055 OptionEditor::connect_audition_editor ()
1056 {
1057         auditioner_io_selector = new IOSelector (*session, session->the_auditioner(), false);
1058         auditioner_gpm = new GainMeter (session->the_auditioner(), *session);
1059
1060         audition_hpacker.pack_start (*auditioner_io_selector, false, false);
1061         audition_hpacker.pack_start (*auditioner_gpm, false, false);
1062
1063         auditioner_io_selector->show_all ();
1064         auditioner_gpm->show_all ();
1065 }
1066
1067 bool
1068 OptionEditor::focus_out_event_handler (GdkEventFocus* ev, void (OptionEditor::*pmf)()) 
1069 {
1070         (this->*pmf)();
1071         return false;
1072 }
1073
1074 static const struct {
1075     const char *name;
1076     guint   modifier;
1077 } modifiers[] = {
1078         { "Shift", GDK_SHIFT_MASK },
1079         { "Control", GDK_CONTROL_MASK },
1080         { "Alt (Mod1)", GDK_MOD1_MASK },
1081         { "Control-Shift", GDK_CONTROL_MASK|GDK_SHIFT_MASK },
1082         { "Control-Alt", GDK_CONTROL_MASK|GDK_MOD1_MASK },
1083         { "Shift-Alt", GDK_SHIFT_MASK|GDK_MOD1_MASK },
1084         { "Control-Shift-Alt", GDK_CONTROL_MASK|GDK_SHIFT_MASK|GDK_MOD1_MASK },
1085         { "Mod2", GDK_MOD2_MASK },
1086         { "Mod3", GDK_MOD3_MASK },
1087         { "Mod4", GDK_MOD4_MASK },
1088         { "Mod5", GDK_MOD5_MASK },
1089         { 0, 0 }
1090 };
1091
1092 void
1093 OptionEditor::setup_keyboard_options ()
1094 {
1095         vector<string> dumb;
1096         Label* label;
1097
1098         keyboard_mouse_table.set_border_width (12);
1099         keyboard_mouse_table.set_row_spacings (5);
1100         keyboard_mouse_table.set_col_spacings (5);
1101
1102         /* internationalize and prepare for use with combos */
1103
1104         for (int i = 0; modifiers[i].name; ++i) {
1105                 dumb.push_back (_(modifiers[i].name));
1106         }
1107
1108         set_popdown_strings (edit_modifier_combo, dumb);
1109         edit_modifier_combo.signal_changed().connect (mem_fun(*this, &OptionEditor::edit_modifier_chosen));
1110
1111         for (int x = 0; modifiers[x].name; ++x) {
1112                 if (modifiers[x].modifier == Keyboard::edit_modifier ()) {
1113                         edit_modifier_combo.set_active_text (_(modifiers[x].name));
1114                         break;
1115                 }
1116         }
1117
1118         label = manage (new Label (_("Edit using")));
1119         label->set_name ("OptionsLabel");
1120         label->set_alignment (1.0, 0.5);
1121                 
1122         keyboard_mouse_table.attach (*label, 0, 1, 0, 1, Gtk::FILL|Gtk::EXPAND, FILL);
1123         keyboard_mouse_table.attach (edit_modifier_combo, 1, 2, 0, 1, Gtk::FILL|Gtk::EXPAND, FILL);
1124
1125         label = manage (new Label (_("+ button")));
1126         label->set_name ("OptionsLabel");
1127         
1128         keyboard_mouse_table.attach (*label, 3, 4, 0, 1, Gtk::FILL|Gtk::EXPAND, FILL);
1129         keyboard_mouse_table.attach (edit_button_spin, 4, 5, 0, 1, Gtk::FILL|Gtk::EXPAND, FILL);
1130
1131         edit_button_spin.set_name ("OptionsEntry");
1132         edit_button_adjustment.set_value (Keyboard::edit_button());
1133         edit_button_adjustment.signal_value_changed().connect (mem_fun(*this, &OptionEditor::edit_button_changed));
1134
1135         set_popdown_strings (delete_modifier_combo, dumb);
1136         delete_modifier_combo.signal_changed().connect (mem_fun(*this, &OptionEditor::delete_modifier_chosen));
1137
1138         for (int x = 0; modifiers[x].name; ++x) {
1139                 if (modifiers[x].modifier == Keyboard::delete_modifier ()) {
1140                         delete_modifier_combo.set_active_text (_(modifiers[x].name));
1141                         break;
1142                 }
1143         }
1144
1145         label = manage (new Label (_("Delete using")));
1146         label->set_name ("OptionsLabel");
1147         label->set_alignment (1.0, 0.5);
1148                 
1149         keyboard_mouse_table.attach (*label, 0, 1, 1, 2, Gtk::FILL|Gtk::EXPAND, FILL);
1150         keyboard_mouse_table.attach (delete_modifier_combo, 1, 2, 1, 2, Gtk::FILL|Gtk::EXPAND, FILL);
1151
1152         label = manage (new Label (_("+ button")));
1153         label->set_name ("OptionsLabel");
1154
1155         keyboard_mouse_table.attach (*label, 3, 4, 1, 2, Gtk::FILL|Gtk::EXPAND, FILL);
1156         keyboard_mouse_table.attach (delete_button_spin, 4, 5, 1, 2, Gtk::FILL|Gtk::EXPAND, FILL);
1157
1158         delete_button_spin.set_name ("OptionsEntry");
1159         delete_button_adjustment.set_value (Keyboard::delete_button());
1160         delete_button_adjustment.signal_value_changed().connect (mem_fun(*this, &OptionEditor::delete_button_changed));
1161
1162         set_popdown_strings (snap_modifier_combo, dumb);
1163         snap_modifier_combo.signal_changed().connect (mem_fun(*this, &OptionEditor::snap_modifier_chosen));
1164         
1165         for (int x = 0; modifiers[x].name; ++x) {
1166                 if (modifiers[x].modifier == (guint) Keyboard::snap_modifier ()) {
1167                         snap_modifier_combo.set_active_text (_(modifiers[x].name));
1168                         break;
1169                 }
1170         }
1171
1172         label = manage (new Label (_("Ignore snap using")));
1173         label->set_name ("OptionsLabel");
1174         label->set_alignment (1.0, 0.5);
1175         
1176         keyboard_mouse_table.attach (*label, 0, 1, 2, 3, Gtk::FILL|Gtk::EXPAND, FILL);
1177         keyboard_mouse_table.attach (snap_modifier_combo, 1, 2, 2, 3, Gtk::FILL|Gtk::EXPAND, FILL);
1178 }
1179
1180 void
1181 OptionEditor::edit_modifier_chosen ()
1182 {
1183         string txt;
1184         
1185         txt = edit_modifier_combo.get_active_text();
1186
1187         for (int i = 0; modifiers[i].name; ++i) {
1188                 if (txt == _(modifiers[i].name)) {
1189                         Keyboard::set_edit_modifier (modifiers[i].modifier);
1190                         break;
1191                 }
1192         }
1193 }
1194
1195 void
1196 OptionEditor::delete_modifier_chosen ()
1197 {
1198         string txt;
1199         
1200         txt = delete_modifier_combo.get_active_text();
1201
1202         for (int i = 0; modifiers[i].name; ++i) {
1203                 if (txt == _(modifiers[i].name)) {
1204                         Keyboard::set_delete_modifier (modifiers[i].modifier);
1205                         break;
1206                 }
1207         }
1208 }
1209
1210 void
1211 OptionEditor::snap_modifier_chosen ()
1212 {
1213         string txt;
1214         
1215         txt = snap_modifier_combo.get_active_text();
1216
1217         for (int i = 0; modifiers[i].name; ++i) {
1218                 if (txt == _(modifiers[i].name)) {
1219                         Keyboard::set_snap_modifier (modifiers[i].modifier);
1220                         break;
1221                 }
1222         }
1223 }
1224
1225 void
1226 OptionEditor::delete_button_changed ()
1227 {
1228         Keyboard::set_delete_button ((guint) delete_button_adjustment.get_value());
1229 }
1230
1231 void
1232 OptionEditor::edit_button_changed ()
1233 {
1234         Keyboard::set_edit_button ((guint) edit_button_adjustment.get_value());
1235 }
1236
1237 void
1238 OptionEditor::fixup_combo_size (Gtk::ComboBoxText& combo, vector<string>& strings)
1239 {
1240         /* find the widest string */
1241
1242         string::size_type maxlen = 0;
1243         string maxstring;
1244
1245         for (vector<string>::iterator i = strings.begin(); i != strings.end(); ++i) {
1246                 string::size_type l;
1247
1248                 if ((l = (*i).length()) > maxlen) {
1249                         maxlen = l;
1250                         maxstring = *i;
1251                 }
1252         }
1253
1254         /* try to include ascenders and descenders */
1255
1256         if (maxstring.length() > 2) {
1257                 maxstring[0] = 'g';
1258                 maxstring[1] = 'l';
1259         }
1260
1261         const guint32 FUDGE = 10; // Combo's are stupid - they steal space from the entry for the button
1262
1263         set_size_request_to_display_given_text (combo, maxstring.c_str(), 10 + FUDGE, 10);
1264 }
1265
1266 void
1267 OptionEditor::parameter_changed (const char* parameter_name)
1268 {
1269         ENSURE_GUI_THREAD (bind (mem_fun (*this, &OptionEditor::parameter_changed), parameter_name));
1270
1271 #define PARAM_IS(x) (!strcmp (parameter_name, (x)))
1272         
1273         if (PARAM_IS ("timecode-source-is-synced")) {
1274                 synced_timecode_button.set_active (Config->get_timecode_source_is_synced());
1275         } else if (PARAM_IS ("history-depth")) {
1276                 int32_t depth = Config->get_history_depth();
1277                 
1278                 history_depth.set_value (depth);
1279                 history_depth_spinner.set_sensitive (depth != 0);
1280                 limit_history_button.set_active (depth != 0);
1281
1282         } else if (PARAM_IS ("saved-history-depth")) {
1283
1284                 saved_history_depth.set_value (Config->get_saved_history_depth());
1285
1286         } else if (PARAM_IS ("save-history")) {
1287
1288                 bool x = Config->get_save_history();
1289
1290                 save_history_button.set_active (x);
1291                 saved_history_depth_spinner.set_sensitive (x);
1292         }
1293 }