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