Consolidate Preferences/OptionEditor
[ardour.git] / gtk2_ardour / option_editor.cc
1 /*
2   Copyright (C) 2001-2009 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 #include <algorithm>
20
21 #include <gtkmm/box.h>
22 #include <gtkmm/alignment.h>
23 #include "gtkmm2ext/utils.h"
24
25 #include "ardour/dB.h"
26 #include "ardour/rc_configuration.h"
27 #include "ardour/session.h"
28 #include "ardour/types.h"
29 #include "ardour/utils.h"
30
31 #include "pbd/configuration.h"
32 #include "pbd/replace_all.h"
33 #include "pbd/strsplit.h"
34
35 #include "gui_thread.h"
36 #include "option_editor.h"
37 #include "public_editor.h"
38 #include "utils.h"
39 #include "pbd/i18n.h"
40
41 using namespace std;
42 using namespace Gtk;
43 using namespace Gtkmm2ext;
44 using namespace ARDOUR;
45
46 void
47 OptionEditorComponent::add_widget_to_page (OptionEditorPage* p, Gtk::Widget* w)
48 {
49         int const n = p->table.property_n_rows();
50         int m = n + 1;
51         if (!_note.empty ()) {
52                 ++m;
53         }
54
55         p->table.resize (m, 3);
56         p->table.attach (*w, 1, 3, n, n + 1, FILL | EXPAND);
57
58         maybe_add_note (p, n + 1);
59 }
60
61 void
62 OptionEditorComponent::add_widgets_to_page (OptionEditorPage* p, Gtk::Widget* wa, Gtk::Widget* wb)
63 {
64         int const n = p->table.property_n_rows();
65         int m = n + 1;
66         if (!_note.empty ()) {
67                 ++m;
68         }
69
70         p->table.resize (m, 3);
71         p->table.attach (*wa, 1, 2, n, n + 1, FILL);
72         p->table.attach (*wb, 2, 3, n, n + 1, FILL | EXPAND);
73
74         maybe_add_note (p, n + 1);
75 }
76
77 void
78 OptionEditorComponent::maybe_add_note (OptionEditorPage* p, int n)
79 {
80         if (!_note.empty ()) {
81                 Gtk::Label* l = manage (new Gtk::Label (string_compose (X_("<i>%1</i>"), _note)));
82                 l->set_use_markup (true);
83                 l->set_line_wrap (true);
84                 p->table.attach (*l, 1, 3, n, n + 1, FILL | EXPAND);
85         }
86 }
87
88 void
89 OptionEditorComponent::set_note (string const & n)
90 {
91         _note = n;
92 }
93
94 /*--------------------------*/
95
96 OptionEditorHeading::OptionEditorHeading (string const & h)
97 {
98         std::stringstream s;
99         s << "<b>" << h << "</b>";
100         _label = manage (left_aligned_label (s.str()));
101         _label->set_use_markup (true);
102 }
103
104 /*--------------------------*/
105
106 void
107 OptionEditorHeading::add_to_page (OptionEditorPage* p)
108 {
109         int const n = p->table.property_n_rows();
110         if (!_note.empty ()) {
111                 p->table.resize (n + 3, 3);
112         } else {
113                 p->table.resize (n + 2, 3);
114         }
115
116         p->table.attach (*manage (new Label ("")), 0, 3, n, n + 1, FILL | EXPAND);
117         p->table.attach (*_label, 0, 3, n + 1, n + 2, FILL | EXPAND);
118         maybe_add_note (p, n + 2);
119 }
120
121 /*--------------------------*/
122
123 void
124 OptionEditorBlank::add_to_page (OptionEditorPage* p)
125 {
126         int const n = p->table.property_n_rows();
127         p->table.resize (n + 1, 3);
128         p->table.attach (_dummy, 2, 3, n, n + 1, FILL | EXPAND, SHRINK, 0, 0);
129         _dummy.set_size_request (-1, 1);
130         _dummy.show ();
131 }
132
133 /*--------------------------*/
134
135 RcConfigDisplay::RcConfigDisplay (string const & i, string const & n, sigc::slot<string> g, char s)
136         : _get (g)
137         , _id (i)
138         , _sep (s)
139 {
140         _label = manage (right_aligned_label (n));
141         _info = manage (new Label);
142         _info-> set_line_wrap (true);
143         set_state_from_config ();
144 }
145
146 void
147 RcConfigDisplay::set_state_from_config ()
148 {
149         string p = _get();
150         if (_sep) {
151                 std::replace (p.begin(), p.end(), _sep, '\n');
152         }
153         _info->set_text (p);
154 }
155
156 void
157 RcConfigDisplay::parameter_changed (std::string const & p)
158 {
159         if (p == _id) {
160                 set_state_from_config ();
161         }
162 }
163
164 void
165 RcConfigDisplay::add_to_page (OptionEditorPage *p)
166 {
167         int const n = p->table.property_n_rows();
168         int m = n + 1;
169         p->table.resize (m, 3);
170         p->table.attach (*_label, 1, 2, n, n + 1, FILL | EXPAND);
171         p->table.attach (*_info,  2, 3, n, n + 1, FILL | EXPAND);
172 }
173
174 /*--------------------------*/
175
176 RcActionButton::RcActionButton (std::string const & t, const Glib::SignalProxy0< void >::SlotType & slot, std::string const & l)
177         : _label (NULL)
178 {
179         _button = manage (new Button (t));
180         _button->signal_clicked().connect (slot);
181         if (!l.empty ()) {
182                 _label = manage (right_aligned_label (l));
183         }
184 }
185
186 void
187 RcActionButton::add_to_page (OptionEditorPage *p)
188 {
189         int const n = p->table.property_n_rows();
190         int m = n + 1;
191         p->table.resize (m, 3);
192         if (_label) {
193                 p->table.attach (*_label,  1, 2, n, n + 1, FILL | EXPAND);
194                 p->table.attach (*_button, 2, 3, n, n + 1, FILL | EXPAND);
195         } else {
196                 p->table.attach (*_button, 1, 3, n, n + 1, FILL | EXPAND);
197         }
198 }
199
200 /*--------------------------*/
201
202 BoolOption::BoolOption (string const & i, string const & n, sigc::slot<bool> g, sigc::slot<bool, bool> s)
203         : Option (i, n),
204           _get (g),
205           _set (s)
206 {
207         _button = manage (new CheckButton);
208         _label = manage (new Label);
209         _label->set_markup (n);
210         _button->add (*_label);
211         _button->set_active (_get ());
212         _button->signal_toggled().connect (sigc::mem_fun (*this, &BoolOption::toggled));
213 }
214
215 void
216 BoolOption::add_to_page (OptionEditorPage* p)
217 {
218         add_widget_to_page (p, _button);
219 }
220
221 void
222 BoolOption::set_state_from_config ()
223 {
224         _button->set_active (_get ());
225 }
226
227 void
228 BoolOption::toggled ()
229 {
230         if (!_set (_button->get_active ())) {
231                 _button->set_active (_get ());
232         }
233 }
234
235 /*--------------------------*/
236
237 RouteDisplayBoolOption::RouteDisplayBoolOption (string const & i, string const & n, sigc::slot<bool> g, sigc::slot<bool, bool> s)
238         : BoolOption (i, n, g, s)
239 {
240 }
241
242 void
243 RouteDisplayBoolOption::toggled ()
244 {
245         DisplaySuspender ds;
246         BoolOption::toggled ();
247 }
248
249 /*--------------------------*/
250
251 EntryOption::EntryOption (string const & i, string const & n, sigc::slot<string> g, sigc::slot<bool, string> s)
252         : Option (i, n),
253           _get (g),
254           _set (s)
255 {
256         _label = manage (left_aligned_label (n + ":"));
257         _entry = manage (new Entry);
258         _entry->signal_activate().connect (sigc::mem_fun (*this, &EntryOption::activated));
259         _entry->signal_focus_out_event().connect (sigc::mem_fun (*this, &EntryOption::focus_out));
260         _entry->signal_insert_text().connect (sigc::mem_fun (*this, &EntryOption::filter_text));
261 }
262
263 void
264 EntryOption::add_to_page (OptionEditorPage* p)
265 {
266         add_widgets_to_page (p, _label, _entry);
267 }
268
269 void
270 EntryOption::set_state_from_config ()
271 {
272         _entry->set_text (_get ());
273 }
274
275 void
276 EntryOption::set_sensitive (bool s)
277 {
278         _entry->set_sensitive (s);
279 }
280
281 void
282 EntryOption::filter_text (const Glib::ustring&, int*)
283 {
284         std::string text = _entry->get_text ();
285         for (size_t i = 0; i < _invalid.length(); ++i) {
286                 text.erase (std::remove(text.begin(), text.end(), _invalid.at(i)), text.end());
287         }
288         if (text != _entry->get_text ()) {
289                 _entry->set_text (text);
290         }
291 }
292
293 void
294 EntryOption::activated ()
295 {
296         _set (_entry->get_text ());
297 }
298
299 bool
300 EntryOption::focus_out (GdkEventFocus*)
301 {
302         _set (_entry->get_text ());
303         return true;
304 }
305
306 /*--------------------------*/
307 HSliderOption::HSliderOption (
308                 std::string const& i,
309                 std::string const& n,
310                 sigc::slot<float> g,
311                 sigc::slot<bool, float> s,
312                 double lower, double upper,
313                 double step_increment,
314                 double page_increment,
315                 double mult,
316                 bool logarithmic
317                 )
318         : Option (i, n)
319         , _get (g)
320         , _set (s)
321         , _adj (lower, lower, upper, step_increment, page_increment, 0)
322         , _hscale (_adj)
323         , _label (n + ":")
324         , _mult (mult)
325         , _log (logarithmic)
326 {
327         _label.set_alignment (0, 0.5);
328         _label.set_name ("OptionsLabel");
329         _adj.set_value (_get());
330         _adj.signal_value_changed().connect (sigc::mem_fun (*this, &HSliderOption::changed));
331         _hscale.set_update_policy (Gtk::UPDATE_DISCONTINUOUS);
332 }
333
334 void
335 HSliderOption::set_state_from_config ()
336 {
337         if (_log) {
338                 _adj.set_value (log10(_get()) / _mult);
339         } else {
340                 _adj.set_value (_get() / _mult);
341         }
342 }
343
344 void
345 HSliderOption::changed ()
346 {
347         if (_log) {
348                 _set (pow (10, _adj.get_value () * _mult));
349         } else {
350                 _set (_adj.get_value () * _mult);
351         }
352 }
353
354 void
355 HSliderOption::add_to_page (OptionEditorPage* p)
356 {
357         add_widgets_to_page (p, &_label, &_hscale);
358 }
359
360 void
361 HSliderOption::set_sensitive (bool yn)
362 {
363         _hscale.set_sensitive (yn);
364 }
365
366 /*--------------------------*/
367
368 ComboStringOption::ComboStringOption (
369                 std::string const & i,
370                 std::string const & n,
371                 sigc::slot<std::string> g,
372                 sigc::slot<bool, std::string> s
373                 )
374         : Option (i, n)
375         , _get (g)
376         , _set (s)
377 {
378         _label = Gtk::manage (new Gtk::Label (n + ":"));
379         _label->set_alignment (0, 0.5);
380         _combo = Gtk::manage (new Gtk::ComboBoxText);
381         _combo->signal_changed().connect (sigc::mem_fun (*this, &ComboStringOption::changed));
382 }
383
384 void
385 ComboStringOption::set_state_from_config () {
386         _combo->set_active_text (_get());
387 }
388
389 void
390 ComboStringOption::add_to_page (OptionEditorPage* p)
391 {
392         add_widgets_to_page (p, _label, _combo);
393 }
394
395 /** Set the allowed strings for this option
396  *  @param strings a vector of allowed strings
397  */
398 void
399 ComboStringOption::set_popdown_strings (const std::vector<std::string>& strings) {
400         _combo->clear_items ();
401         for (std::vector<std::string>::const_iterator i = strings.begin(); i != strings.end(); ++i) {
402                 _combo->append_text (*i);
403         }
404 }
405
406 void
407 ComboStringOption::clear () {
408         _combo->clear_items();
409 }
410
411 void
412 ComboStringOption::changed () {
413         _set (_combo->get_active_text ());
414 }
415
416 void
417 ComboStringOption::set_sensitive (bool yn) {
418         _combo->set_sensitive (yn);
419 }
420
421 /*--------------------------*/
422
423 /** Construct a BoolComboOption.
424  *  @param i id
425  *  @param n User-visible name.
426  *  @param t Text to give for the variable being true.
427  *  @param f Text to give for the variable being false.
428  *  @param g Slot to get the variable's value.
429  *  @param s Slot to set the variable's value.
430  */
431 BoolComboOption::BoolComboOption (
432         string const & i, string const & n, string const & t, string const & f,
433         sigc::slot<bool> g, sigc::slot<bool, bool> s
434         )
435         : Option (i, n)
436         , _get (g)
437         , _set (s)
438 {
439         _label = manage (new Label (n + ":"));
440         _label->set_alignment (0, 0.5);
441         _combo = manage (new ComboBoxText);
442
443         /* option 0 is the false option */
444         _combo->append_text (f);
445         /* and option 1 is the true */
446         _combo->append_text (t);
447
448         _combo->signal_changed().connect (sigc::mem_fun (*this, &BoolComboOption::changed));
449 }
450
451 void
452 BoolComboOption::set_state_from_config ()
453 {
454         _combo->set_active (_get() ? 1 : 0);
455 }
456
457 void
458 BoolComboOption::add_to_page (OptionEditorPage* p)
459 {
460         add_widgets_to_page (p, _label, _combo);
461 }
462
463 void
464 BoolComboOption::changed ()
465 {
466         _set (_combo->get_active_row_number () == 0 ? false : true);
467 }
468
469 void
470 BoolComboOption::set_sensitive (bool yn)
471 {
472         _combo->set_sensitive (yn);
473 }
474
475 /*--------------------------*/
476
477 FaderOption::FaderOption (string const & i, string const & n, sigc::slot<gain_t> g, sigc::slot<bool, gain_t> s)
478         : Option (i, n)
479         , _db_adjustment (gain_to_slider_position_with_max (1.0, Config->get_max_gain()), 0, 1, 0.01, 0.1)
480         , _get (g)
481         , _set (s)
482 {
483         _db_slider = manage (new HSliderController (&_db_adjustment, boost::shared_ptr<PBD::Controllable>(), 220, 18));
484
485         _label.set_text (n + ":");
486         _label.set_alignment (0, 0.5);
487         _label.set_name (X_("OptionsLabel"));
488
489         _fader_centering_box.pack_start (*_db_slider, true, false);
490
491         _box.set_spacing (4);
492         _box.set_homogeneous (false);
493         _box.pack_start (_fader_centering_box, false, false);
494         _box.pack_start (_db_display, false, false);
495         _box.pack_start (*manage (new Label ("dB")), false, false);
496         _box.show_all ();
497
498         set_size_request_to_display_given_text (_db_display, "-99.00", 12, 0);
499
500         _db_adjustment.signal_value_changed().connect (sigc::mem_fun (*this, &FaderOption::db_changed));
501         _db_display.signal_activate().connect (sigc::mem_fun (*this, &FaderOption::on_activate));
502         _db_display.signal_key_press_event().connect (sigc::mem_fun (*this, &FaderOption::on_key_press), false);
503 }
504
505 void
506 FaderOption::set_state_from_config ()
507 {
508         gain_t const val = _get ();
509         _db_adjustment.set_value (gain_to_slider_position_with_max (val, Config->get_max_gain ()));
510
511         char buf[16];
512
513         if (val == 0.0) {
514                 snprintf (buf, sizeof (buf), "-inf");
515         } else {
516                 snprintf (buf, sizeof (buf), "%.2f", accurate_coefficient_to_dB (val));
517         }
518
519         _db_display.set_text (buf);
520 }
521
522 void
523 FaderOption::db_changed ()
524 {
525         _set (slider_position_to_gain_with_max (_db_adjustment.get_value (), Config->get_max_gain()));
526 }
527
528 void
529 FaderOption::on_activate ()
530 {
531         float db_val = atof (_db_display.get_text ().c_str ());
532         gain_t coeff_val = dB_to_coefficient (db_val);
533
534         _db_adjustment.set_value (gain_to_slider_position_with_max (coeff_val, Config->get_max_gain ()));
535 }
536
537 bool
538 FaderOption::on_key_press (GdkEventKey* ev)
539 {
540         if (ARDOUR_UI_UTILS::key_is_legal_for_numeric_entry (ev->keyval)) {
541                 /* drop through to normal handling */
542                 return false;
543         }
544         /* illegal key for gain entry */
545         return true;
546 }
547
548 void
549 FaderOption::add_to_page (OptionEditorPage* p)
550 {
551         add_widgets_to_page (p, &_label, &_box);
552 }
553
554 /*--------------------------*/
555
556 ClockOption::ClockOption (string const & i, string const & n, sigc::slot<std::string> g, sigc::slot<bool, std::string> s)
557         : Option (i, n)
558         , _clock (X_("timecode-offset"), true, X_(""), true, false, true, false)
559         , _get (g)
560         , _set (s)
561 {
562         _label.set_text (n + ":");
563         _label.set_alignment (0, 0.5);
564         _label.set_name (X_("OptionsLabel"));
565         _clock.ValueChanged.connect (sigc::mem_fun (*this, &ClockOption::save_clock_time));
566 }
567
568 void
569 ClockOption::set_state_from_config ()
570 {
571         Timecode::Time TC;
572         framepos_t when;
573         if (!Timecode::parse_timecode_format(_get(), TC)) {
574                 _clock.set (0, true);
575         }
576         TC.rate = _session->samples_per_timecode_frame();
577         TC.drop = _session->timecode_drop_frames();
578         _session->timecode_to_sample(TC, when, false, false);
579         if (TC.negative) { when=-when; }
580         _clock.set (when, true);
581 }
582
583 void
584 ClockOption::save_clock_time ()
585 {
586         Timecode::Time TC;
587         _session->sample_to_timecode(_clock.current_time(), TC, false, false);
588         _set (Timecode::timecode_format_time(TC));
589 }
590
591 void
592 ClockOption::add_to_page (OptionEditorPage* p)
593 {
594         add_widgets_to_page (p, &_label, &_clock);
595 }
596
597 void
598 ClockOption::set_session (Session* s)
599 {
600         _session = s;
601         _clock.set_session (s);
602 }
603
604 /*--------------------------*/
605
606 OptionEditorPage::OptionEditorPage ()
607         : table (1, 3)
608 {
609         init ();
610 }
611
612 OptionEditorPage::OptionEditorPage (Gtk::Notebook& n, std::string const & t)
613         : table (1, 3)
614 {
615         init ();
616         box.pack_start (table, false, false);
617         box.set_border_width (4);
618         n.append_page (box, t);
619 }
620
621 void
622 OptionEditorPage::init ()
623 {
624         table.set_spacings (4);
625         table.set_col_spacing (0, 32);
626 }
627
628 /*--------------------------*/
629
630 void
631 OptionEditorMiniPage::add_to_page (OptionEditorPage* p)
632 {
633         int const n = p->table.property_n_rows();
634         int m = n + 1;
635         if (!_note.empty ()) {
636                 ++m;
637         }
638         p->table.resize (m, 3);
639         p->table.attach (box, 0, 3, n, n + 1, FILL | EXPAND, SHRINK, 0, 0);
640         maybe_add_note (p, n + 1);
641 }
642
643 /*--------------------------*/
644
645 /** Construct an OptionEditor.
646  *  @param o Configuration to edit.
647  *  @param t Title for the dialog.
648  */
649 OptionEditor::OptionEditor (PBD::Configuration* c)
650         : _config (c)
651         , option_tree (TreeStore::create (option_columns))
652         , option_treeview (option_tree)
653 {
654         using namespace Notebook_Helpers;
655
656         _notebook.set_show_tabs (false);
657         _notebook.set_show_border (true);
658         _notebook.set_name ("OptionsNotebook");
659
660         option_treeview.append_column ("", option_columns.name);
661         option_treeview.set_enable_search(true);
662         option_treeview.set_search_column(0);
663         option_treeview.set_name ("OptionsTreeView");
664         option_treeview.set_headers_visible (false);
665
666         option_treeview.get_selection()->set_mode (Gtk::SELECTION_SINGLE);
667         option_treeview.get_selection()->signal_changed().connect (sigc::mem_fun (*this, &OptionEditor::treeview_row_selected));
668
669         /* Watch out for changes to parameters */
670         _config->ParameterChanged.connect (config_connection, invalidator (*this), boost::bind (&OptionEditor::parameter_changed, this, _1), gui_context());
671 }
672
673 OptionEditor::~OptionEditor ()
674 {
675         for (std::map<std::string, OptionEditorPage*>::iterator i = _pages.begin(); i != _pages.end(); ++i) {
676                 for (std::list<OptionEditorComponent*>::iterator j = i->second->components.begin(); j != i->second->components.end(); ++j) {
677                         delete *j;
678                 }
679                 delete i->second;
680         }
681 }
682
683 /** Called when a configuration parameter has been changed.
684  *  @param p Parameter name.
685  */
686 void
687 OptionEditor::parameter_changed (std::string const & p)
688 {
689         ENSURE_GUI_THREAD (*this, &OptionEditor::parameter_changed, p)
690
691         for (std::map<std::string, OptionEditorPage*>::iterator i = _pages.begin(); i != _pages.end(); ++i) {
692                 for (std::list<OptionEditorComponent*>::iterator j = i->second->components.begin(); j != i->second->components.end(); ++j) {
693                         (*j)->parameter_changed (p);
694                 }
695         }
696 }
697
698 void
699 OptionEditor::treeview_row_selected ()
700 {
701         Glib::RefPtr<Gtk::TreeSelection> selection = option_treeview.get_selection();
702         TreeModel::iterator iter = selection->get_selected();
703
704         if (iter) {
705                 TreeModel::Row row = *iter;
706                 Gtk::Widget* w = row[option_columns.widget];
707                 if (w) {
708                         _notebook.set_current_page (_notebook.page_num (*w));
709                 }
710         }
711 }
712
713 TreeModel::iterator
714 OptionEditor::find_path_in_treemodel (std::string const & pn, bool create_missing)
715 {
716         /* split page name, which is actually a path, into each component */
717
718         std::vector<std::string> components;
719         split (pn, components, '/');
720
721         /* start with top level children */
722
723         TreeModel::Children children = option_tree->children();
724         TreeModel::iterator iter;
725
726         /* foreach path component ... */
727
728         for (std::vector<std::string>::const_iterator s = components.begin(); s != components.end(); ++s) {
729
730                 for (iter = children.begin(); iter != children.end(); ++iter) {
731                         TreeModel::Row row = *iter;
732                         const std::string row_name = row[option_columns.name];
733                         if (row_name == (*s)) {
734                                 break;
735                         }
736                 }
737
738                 if (iter == children.end()) {
739                         /* the current component is missing; bail out or create it */
740                         if (!create_missing) {
741                                 return option_tree->get_iter(TreeModel::Path(""));
742                         } else {
743                                 iter = option_tree->append (children);
744                                 TreeModel::Row row = *iter;
745                                 row[option_columns.name] = *s;
746                                 row[option_columns.widget] = 0;
747                         }
748                 }
749
750                 /* from now on, iter points to a valid row, either the one we found or a new one */
751                 /* set children to the row's children to continue searching */
752                 children = (*iter)->children ();
753
754         }
755
756         return iter;
757 }
758
759 void
760 OptionEditor::add_path_to_treeview (std::string const & pn, Gtk::Widget& widget)
761 {
762         option_treeview.set_model (Glib::RefPtr<TreeStore>());
763
764         TreeModel::iterator row_iter = find_path_in_treemodel(pn, true);
765
766         assert(row_iter);
767
768         TreeModel::Row row = *row_iter;
769         row[option_columns.widget] = &widget;
770
771         option_treeview.set_model (option_tree);
772         option_treeview.expand_all ();
773 }
774
775 /** Add a component to a given page.
776  *  @param pn Page name (will be created if it doesn't already exist)
777  *  @param o Component.
778  */
779 void
780 OptionEditor::add_option (std::string const & pn, OptionEditorComponent* o)
781 {
782         if (_pages.find (pn) == _pages.end()) {
783                 OptionEditorPage* oep = new OptionEditorPage (_notebook, pn);
784                 _pages[pn] = oep;
785
786                 add_path_to_treeview (pn, oep->box);
787         }
788
789         OptionEditorPage* p = _pages[pn];
790         p->components.push_back (o);
791
792         o->add_to_page (p);
793         o->set_state_from_config ();
794 }
795
796 /** Add a new page
797  *  @param pn Page name (will be created if it doesn't already exist)
798  *  @param w widget that fills the page
799  */
800 void
801 OptionEditor::add_page (std::string const & pn, Gtk::Widget& w)
802 {
803         if (_pages.find (pn) == _pages.end()) {
804                 OptionEditorPage* oep = new OptionEditorPage (_notebook, pn);
805                 _pages[pn] = oep;
806                 add_path_to_treeview (pn, oep->box);
807         }
808
809         OptionEditorPage* p = _pages[pn];
810         p->box.pack_start (w, true, true);
811 }
812
813 void
814 OptionEditor::set_current_page (string const & p)
815 {
816         TreeModel::iterator row_iter = find_path_in_treemodel(p);
817
818         if (row_iter) {
819                 option_treeview.get_selection()->select(row_iter);
820         }
821
822 }
823
824 /*--------------------------*/
825
826 DirectoryOption::DirectoryOption (string const & i, string const & n, sigc::slot<string> g, sigc::slot<bool, string> s)
827         : Option (i, n)
828         , _get (g)
829         , _set (s)
830 {
831         _file_chooser.set_action (Gtk::FILE_CHOOSER_ACTION_SELECT_FOLDER);
832         _file_chooser.signal_selection_changed().connect (sigc::mem_fun (*this, &DirectoryOption::selection_changed));
833 }
834
835 void
836 DirectoryOption::set_state_from_config ()
837 {
838         _file_chooser.set_current_folder (poor_mans_glob(_get ()));
839 }
840
841 void
842 DirectoryOption::add_to_page (OptionEditorPage* p)
843 {
844         Gtk::Label *label = manage (new Label (_name));
845         label->set_alignment (0, 0.5);
846         label->set_name (X_("OptionsLabel"));
847         add_widgets_to_page (p, label, &_file_chooser);
848 }
849
850 void
851 DirectoryOption::selection_changed ()
852 {
853         _set (poor_mans_glob(_file_chooser.get_filename ()));
854 }
855
856 /*--------------------------*/
857
858 OptionEditorContainer::OptionEditorContainer (PBD::Configuration* c, string const& str)
859         : OptionEditor (c)
860 {
861         set_border_width (4);
862         hpacker.pack_start (treeview(), false, false);
863         hpacker.pack_start (notebook(), false, false);
864         pack_start (hpacker, true, true);
865
866         show_all ();
867 }
868
869 OptionEditorWindow::OptionEditorWindow (PBD::Configuration* c, string const& str)
870         : OptionEditor (c)
871         , ArdourWindow (str)
872 {
873         container.set_border_width (4);
874         hpacker.pack_start (treeview(), false, false);
875         hpacker.pack_start (notebook(), true, true);
876
877         container.pack_start (hpacker, true, true);
878
879         hpacker.show_all ();
880         container.show ();
881
882         add (container);
883 }