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