move ARDOUR::Configuration and ARDOUR::ConfigVariable into libpbd
[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 "pbd/configuration.h"
26
27 #include "ardour/rc_configuration.h"
28 #include "ardour/utils.h"
29 #include "ardour/dB.h"
30 #include "ardour/session.h"
31
32 #include "public_editor.h"
33 #include "option_editor.h"
34 #include "gui_thread.h"
35 #include "i18n.h"
36
37 using namespace std;
38 using namespace Gtk;
39 using namespace Gtkmm2ext;
40 using namespace ARDOUR;
41
42 void
43 OptionEditorComponent::add_widget_to_page (OptionEditorPage* p, Gtk::Widget* w)
44 {
45         int const n = p->table.property_n_rows();
46         int m = n + 1;
47         if (!_note.empty ()) {
48                 ++m;
49         }
50
51         p->table.resize (m, 3);
52         p->table.attach (*w, 1, 3, n, n + 1, FILL | EXPAND);
53
54         maybe_add_note (p, n + 1);
55 }
56
57 void
58 OptionEditorComponent::add_widgets_to_page (OptionEditorPage* p, Gtk::Widget* wa, Gtk::Widget* wb)
59 {
60         int const n = p->table.property_n_rows();
61         int m = n + 1;
62         if (!_note.empty ()) {
63                 ++m;
64         }
65         
66         p->table.resize (m, 3);
67         p->table.attach (*wa, 1, 2, n, n + 1, FILL);
68         p->table.attach (*wb, 2, 3, n, n + 1, FILL | EXPAND);
69         
70         maybe_add_note (p, n + 1);
71 }
72
73 void
74 OptionEditorComponent::maybe_add_note (OptionEditorPage* p, int n)
75 {
76         if (!_note.empty ()) {
77                 Gtk::Label* l = manage (new Gtk::Label (string_compose (X_("<i>%1</i>"), _note)));
78                 l->set_use_markup (true);
79                 p->table.attach (*l, 1, 3, n, n + 1, FILL | EXPAND);
80         }
81 }
82
83 void
84 OptionEditorComponent::set_note (string const & n)
85 {
86         _note = n;
87 }
88
89 OptionEditorHeading::OptionEditorHeading (string const & h)
90 {
91         std::stringstream s;
92         s << "<b>" << h << "</b>";
93         _label = manage (left_aligned_label (s.str()));
94         _label->set_use_markup (true);
95 }
96
97 void
98 OptionEditorHeading::add_to_page (OptionEditorPage* p)
99 {
100         int const n = p->table.property_n_rows();
101         p->table.resize (n + 2, 3);
102
103         p->table.attach (*manage (new Label ("")), 0, 3, n, n + 1, FILL | EXPAND);
104         p->table.attach (*_label, 0, 3, n + 1, n + 2, FILL | EXPAND);
105 }
106
107 void
108 OptionEditorBox::add_to_page (OptionEditorPage* p)
109 {
110         add_widget_to_page (p, _box);
111 }
112
113 BoolOption::BoolOption (string const & i, string const & n, sigc::slot<bool> g, sigc::slot<bool, bool> s)
114         : Option (i, n),
115           _get (g),
116           _set (s)
117 {
118         _button = manage (new CheckButton);
119         _label = manage (new Label);
120         _label->set_markup (n);
121         _button->add (*_label);
122         _button->set_active (_get ());
123         _button->signal_toggled().connect (sigc::mem_fun (*this, &BoolOption::toggled));
124 }
125
126 void
127 BoolOption::add_to_page (OptionEditorPage* p)
128 {
129         add_widget_to_page (p, _button);
130 }
131
132 void
133 BoolOption::set_state_from_config ()
134 {
135         _button->set_active (_get ());
136 }
137
138 void
139 BoolOption::toggled ()
140 {
141         _set (_button->get_active ());
142 }
143
144 RouteDisplayBoolOption::RouteDisplayBoolOption (string const & i, string const & n, sigc::slot<bool> g, sigc::slot<bool, bool> s)
145         : BoolOption (i, n, g, s)
146 {
147 }
148
149 void
150 RouteDisplayBoolOption::toggled ()
151 {
152         DisplaySuspender ds;
153         BoolOption::toggled ();
154 }
155
156 EntryOption::EntryOption (string const & i, string const & n, sigc::slot<string> g, sigc::slot<bool, string> s)
157         : Option (i, n),
158           _get (g),
159           _set (s)
160 {
161         _label = manage (left_aligned_label (n + ":"));
162         _entry = manage (new Entry);
163         _entry->signal_activate().connect (sigc::mem_fun (*this, &EntryOption::activated));
164         _entry->signal_focus_out_event().connect (sigc::mem_fun (*this, &EntryOption::focus_out));
165         _entry->signal_insert_text().connect (sigc::mem_fun (*this, &EntryOption::filter_text));
166 }
167
168 void
169 EntryOption::add_to_page (OptionEditorPage* p)
170 {
171         add_widgets_to_page (p, _label, _entry);
172 }
173
174 void
175 EntryOption::set_state_from_config ()
176 {
177         _entry->set_text (_get ());
178 }
179
180 void
181 EntryOption::set_sensitive (bool s)
182 {
183         _entry->set_sensitive (s);
184 }
185
186 void
187 EntryOption::filter_text (const Glib::ustring&, int*)
188 {
189         std::string text = _entry->get_text ();
190         for (size_t i = 0; i < _invalid.length(); ++i) {
191                 text.erase (std::remove(text.begin(), text.end(), _invalid.at(i)), text.end());
192         }
193         if (text != _entry->get_text ()) {
194                 _entry->set_text (text);
195         }
196 }
197
198 void
199 EntryOption::activated ()
200 {
201         _set (_entry->get_text ());
202 }
203
204 bool
205 EntryOption::focus_out (GdkEventFocus*)
206 {
207         _set (_entry->get_text ());
208         return true;
209 }
210
211 /** Construct a BoolComboOption.
212  *  @param i id
213  *  @param n User-visible name.
214  *  @param t Text to give for the variable being true.
215  *  @param f Text to give for the variable being false.
216  *  @param g Slot to get the variable's value.
217  *  @param s Slot to set the variable's value.
218  */
219 BoolComboOption::BoolComboOption (
220         string const & i, string const & n, string const & t, string const & f, 
221         sigc::slot<bool> g, sigc::slot<bool, bool> s
222         )
223         : Option (i, n)
224         , _get (g)
225         , _set (s)
226 {
227         _label = manage (new Label (n + ":"));
228         _label->set_alignment (0, 0.5);
229         _combo = manage (new ComboBoxText);
230
231         /* option 0 is the false option */
232         _combo->append_text (f);
233         /* and option 1 is the true */
234         _combo->append_text (t);
235         
236         _combo->signal_changed().connect (sigc::mem_fun (*this, &BoolComboOption::changed));
237 }
238
239 void
240 BoolComboOption::set_state_from_config ()
241 {
242         _combo->set_active (_get() ? 1 : 0);
243 }
244
245 void
246 BoolComboOption::add_to_page (OptionEditorPage* p)
247 {
248         add_widgets_to_page (p, _label, _combo);
249 }
250
251 void
252 BoolComboOption::changed ()
253 {
254         _set (_combo->get_active_row_number () == 0 ? false : true);
255 }
256
257 void
258 BoolComboOption::set_sensitive (bool yn)
259 {
260         _combo->set_sensitive (yn);
261 }
262         
263
264           
265 FaderOption::FaderOption (string const & i, string const & n, sigc::slot<gain_t> g, sigc::slot<bool, gain_t> s)
266         : Option (i, n)
267         , _db_adjustment (gain_to_slider_position_with_max (1.0, Config->get_max_gain()), 0, 1, 0.01, 0.1)
268         , _get (g)
269         , _set (s)
270 {
271         _db_slider = manage (new HSliderController (&_db_adjustment, boost::shared_ptr<PBD::Controllable>(), 115, 18));
272
273         _label.set_text (n + ":");
274         _label.set_alignment (0, 0.5);
275         _label.set_name (X_("OptionsLabel"));
276
277         _fader_centering_box.pack_start (*_db_slider, true, false);
278
279         _box.set_spacing (4);
280         _box.set_homogeneous (false);
281         _box.pack_start (_fader_centering_box, false, false);
282         _box.pack_start (_db_display, false, false);
283         _box.show_all ();
284
285         set_size_request_to_display_given_text (_db_display, "-99.00", 12, 12);
286
287         _db_adjustment.signal_value_changed().connect (sigc::mem_fun (*this, &FaderOption::db_changed));
288 }
289
290 void
291 FaderOption::set_state_from_config ()
292 {
293         gain_t const val = _get ();
294         _db_adjustment.set_value (gain_to_slider_position_with_max (val, Config->get_max_gain ()));
295
296         char buf[16];
297
298         if (val == 0.0) {
299                 snprintf (buf, sizeof (buf), "-inf");
300         } else {
301                 snprintf (buf, sizeof (buf), "%.2f", accurate_coefficient_to_dB (val));
302         }
303
304         _db_display.set_text (buf);
305 }
306
307 void
308 FaderOption::db_changed ()
309 {
310         _set (slider_position_to_gain_with_max (_db_adjustment.get_value (), Config->get_max_gain()));
311 }
312
313 void
314 FaderOption::add_to_page (OptionEditorPage* p)
315 {
316         add_widgets_to_page (p, &_label, &_box);
317 }
318
319 ClockOption::ClockOption (string const & i, string const & n, sigc::slot<std::string> g, sigc::slot<bool, std::string> s)
320         : Option (i, n)
321         , _clock (X_("timecode-offset"), true, X_(""), true, false, true, false)
322         , _get (g)
323         , _set (s)
324 {
325         _label.set_text (n + ":");
326         _label.set_alignment (0, 0.5);
327         _label.set_name (X_("OptionsLabel"));
328         _clock.ValueChanged.connect (sigc::mem_fun (*this, &ClockOption::save_clock_time));
329 }
330
331 void
332 ClockOption::set_state_from_config ()
333 {
334         Timecode::Time TC;
335         framepos_t when;
336         if (!Timecode::parse_timecode_format(_get(), TC)) {
337                 _clock.set (0, true);
338         }
339         TC.rate = _session->frames_per_timecode_frame();
340         TC.drop = _session->timecode_drop_frames();
341         _session->timecode_to_sample(TC, when, false, false);
342         if (TC.negative) { when=-when; }
343         _clock.set (when, true);
344 }
345
346 void
347 ClockOption::save_clock_time ()
348 {
349         Timecode::Time TC;
350         _session->sample_to_timecode(_clock.current_time(), TC, false, false);
351         _set (Timecode::timecode_format_time(TC));
352 }
353
354 void
355 ClockOption::add_to_page (OptionEditorPage* p)
356 {
357         add_widgets_to_page (p, &_label, &_clock);
358 }
359
360 void
361 ClockOption::set_session (Session* s)
362 {
363         _session = s;
364         _clock.set_session (s);
365 }
366
367 OptionEditorPage::OptionEditorPage (Gtk::Notebook& n, std::string const & t)
368         : table (1, 3)
369 {
370         table.set_spacings (4);
371         table.set_col_spacing (0, 32);
372         box.pack_start (table, false, false);
373         box.set_border_width (4);
374         n.append_page (box, t);
375 }
376
377 /** Construct an OptionEditor.
378  *  @param o Configuration to edit.
379  *  @param t Title for the dialog.
380  */
381 OptionEditor::OptionEditor (PBD::Configuration* c, std::string const & t)
382         : ArdourWindow (t), _config (c)
383 {
384         using namespace Notebook_Helpers;
385
386         set_default_size (300, 300);
387         // set_wmclass (X_("ardour_preferences"), PROGRAM_NAME);
388
389         set_name ("Preferences");
390         add_events (Gdk::KEY_PRESS_MASK | Gdk::KEY_RELEASE_MASK);
391
392         set_border_width (4);
393
394         add (_notebook);
395
396         _notebook.set_show_tabs (true);
397         _notebook.set_show_border (true);
398         _notebook.set_name ("OptionsNotebook");
399
400         show_all_children();
401
402         /* Watch out for changes to parameters */
403         _config->ParameterChanged.connect (config_connection, invalidator (*this), boost::bind (&OptionEditor::parameter_changed, this, _1), gui_context());
404 }
405
406 OptionEditor::~OptionEditor ()
407 {
408         for (std::map<std::string, OptionEditorPage*>::iterator i = _pages.begin(); i != _pages.end(); ++i) {
409                 for (std::list<OptionEditorComponent*>::iterator j = i->second->components.begin(); j != i->second->components.end(); ++j) {
410                         delete *j;
411                 }
412                 delete i->second;
413         }
414 }
415
416 /** Called when a configuration parameter has been changed.
417  *  @param p Parameter name.
418  */
419 void
420 OptionEditor::parameter_changed (std::string const & p)
421 {
422         ENSURE_GUI_THREAD (*this, &OptionEditor::parameter_changed, p)
423
424         for (std::map<std::string, OptionEditorPage*>::iterator i = _pages.begin(); i != _pages.end(); ++i) {
425                 for (std::list<OptionEditorComponent*>::iterator j = i->second->components.begin(); j != i->second->components.end(); ++j) {
426                         (*j)->parameter_changed (p);
427                 }
428         }
429 }
430
431 /** Add a component to a given page.
432  *  @param pn Page name (will be created if it doesn't already exist)
433  *  @param o Component.
434  */
435 void
436 OptionEditor::add_option (std::string const & pn, OptionEditorComponent* o)
437 {
438         if (_pages.find (pn) == _pages.end()) {
439                 _pages[pn] = new OptionEditorPage (_notebook, pn);
440         }
441
442         OptionEditorPage* p = _pages[pn];
443         p->components.push_back (o);
444
445         o->add_to_page (p);
446         o->set_state_from_config ();
447 }
448
449 /** Add a new page 
450  *  @param pn Page name (will be created if it doesn't already exist)
451  *  @param w widget that fills the page
452  */
453 void
454 OptionEditor::add_page (std::string const & pn, Gtk::Widget& w)
455 {
456         if (_pages.find (pn) == _pages.end()) {
457                 _pages[pn] = new OptionEditorPage (_notebook, pn);
458         }
459
460         OptionEditorPage* p = _pages[pn];
461         p->box.pack_start (w, true, true);
462 }
463
464 void
465 OptionEditor::set_current_page (string const & p)
466 {
467         int i = 0;
468         while (i < _notebook.get_n_pages ()) {
469                 if (_notebook.get_tab_label_text (*_notebook.get_nth_page (i)) == p) {
470                         _notebook.set_current_page (i);
471                         return;
472                 }
473
474                 ++i;
475         }
476 }
477
478
479 DirectoryOption::DirectoryOption (string const & i, string const & n, sigc::slot<string> g, sigc::slot<bool, string> s)
480         : Option (i, n)
481         , _get (g)
482         , _set (s)
483 {
484         _file_chooser.set_action (Gtk::FILE_CHOOSER_ACTION_SELECT_FOLDER);
485         _file_chooser.signal_file_set().connect (sigc::mem_fun (*this, &DirectoryOption::file_set));
486         _file_chooser.signal_current_folder_changed().connect (sigc::mem_fun (*this, &DirectoryOption::current_folder_set));
487 }
488
489
490 void
491 DirectoryOption::set_state_from_config ()
492 {
493         _file_chooser.set_current_folder (_get ());
494 }
495
496 void
497 DirectoryOption::add_to_page (OptionEditorPage* p)
498 {
499         Gtk::Label *label = manage (new Label (_name));
500         label->set_alignment (0, 0.5);
501         label->set_name (X_("OptionsLabel"));
502         add_widgets_to_page (p, label, &_file_chooser);
503 }
504
505 void
506 DirectoryOption::file_set ()
507 {
508         _set (_file_chooser.get_filename ());
509 }
510
511 void
512 DirectoryOption::current_folder_set ()
513 {
514         _set (_file_chooser.get_current_folder ());
515 }