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