Make Dropdown menus at least as wide as the button
[ardour.git] / gtk2_ardour / option_editor.h
1 /*
2     Copyright (C) 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
20 #ifndef __gtk_ardour_option_editor_h__
21 #define __gtk_ardour_option_editor_h__
22
23 #include <gtkmm/notebook.h>
24 #include <gtkmm/checkbutton.h>
25 #include <gtkmm/comboboxtext.h>
26 #include <gtkmm/spinbutton.h>
27 #include <gtkmm/table.h>
28 #include <gtkmm/window.h>
29
30 #include "gtkmm2ext/slider_controller.h"
31
32 #include "ardour_window.h"
33 #include "audio_clock.h"
34 #include "ardour/types.h"
35
36 /** @file option_editor.h
37  *  @brief Base class for option editing dialog boxes.
38  *
39  *  Code to provided the basis for dialogs which allow the user to edit options
40  *  from an ARDOUR::Configuration class.
41  *
42  *  The idea is that we have an OptionEditor class which is the dialog box.
43  *  This is essentially a GTK Notebook.  OptionEditorComponent objects can
44  *  then be added to the OptionEditor, and these components are arranged on
45  *  the pages of the Notebook.  There is also an OptionEditorComponent hierarchy
46  *  here, providing things like boolean and combobox option components.
47  *
48  *  It is intended that OptionEditor be subclassed to implement a particular
49  *  options dialog.
50  */
51
52 namespace PBD {
53         class Configuration;
54 }
55
56 class OptionEditorPage;
57
58 /** Base class for components of an OptionEditor dialog */
59 class OptionEditorComponent
60 {
61 public:
62         virtual ~OptionEditorComponent() {}
63
64         /** Called when a configuration parameter's value has changed.
65          *  @param p parameter name
66          */
67         virtual void parameter_changed (std::string const & p) = 0;
68
69         /** Called to instruct the object to set its UI state from the configuration */
70         virtual void set_state_from_config () = 0;
71
72         /** Called to instruct the object to add itself to an OptionEditorPage */
73         virtual void add_to_page (OptionEditorPage *) = 0;
74
75         void add_widget_to_page (OptionEditorPage*, Gtk::Widget*);
76         void add_widgets_to_page (OptionEditorPage*, Gtk::Widget*, Gtk::Widget*);
77
78         void set_note (std::string const &);
79
80         virtual Gtk::Widget& tip_widget() = 0;
81
82 private:
83         void maybe_add_note (OptionEditorPage *, int);
84
85         std::string _note;
86 };
87
88 /** A component which provides a subheading within the dialog */
89 class OptionEditorHeading : public OptionEditorComponent
90 {
91 public:
92         OptionEditorHeading (std::string const &);
93
94         void parameter_changed (std::string const &) {}
95         void set_state_from_config () {}
96         void add_to_page (OptionEditorPage *);
97
98         Gtk::Widget& tip_widget() { return *_label; }
99
100 private:
101         Gtk::Label* _label; ///< the label used for the heading
102 };
103
104 /** A component which provides a box into which a subclass can put arbitrary widgets */
105 class OptionEditorBox : public OptionEditorComponent
106 {
107 public:
108
109         /** Construct an OpenEditorBox */
110         OptionEditorBox ()
111         {
112                 _box = Gtk::manage (new Gtk::VBox);
113                 _box->set_spacing (4);
114         }
115
116         void parameter_changed (std::string const &) = 0;
117         void set_state_from_config () = 0;
118         void add_to_page (OptionEditorPage *);
119
120         Gtk::Widget& tip_widget() { return *_box->children().front().get_widget(); }
121
122 protected:
123
124         Gtk::VBox* _box; ///< constituent box for subclasses to add widgets to
125 };
126
127 class RcConfigDisplay : public OptionEditorComponent
128 {
129 public:
130         RcConfigDisplay (std::string const &, std::string const &, sigc::slot<std::string>, char s = '\0');
131         void add_to_page (OptionEditorPage *);
132         void parameter_changed (std::string const & p);
133         void set_state_from_config ();
134         Gtk::Widget& tip_widget() { return *_info; }
135 protected:
136         sigc::slot<std::string> _get;
137         Gtk::Label* _label;
138         Gtk::Label* _info;
139         std::string _id;
140         char _sep;
141 };
142
143 class RcActionButton : public OptionEditorComponent
144 {
145 public:
146         RcActionButton (std::string const & t, const Glib::SignalProxy0< void >::SlotType & slot, std::string const & l = "");
147         void add_to_page (OptionEditorPage *);
148
149         void parameter_changed (std::string const & p) {}
150         void set_state_from_config () {}
151         Gtk::Widget& tip_widget() { return *_button; }
152
153 protected:
154         Gtk::Button* _button;
155         Gtk::Label* _label;
156         std::string _name;
157 };
158
159 /** Base class for components which provide UI to change an option */
160 class Option : public OptionEditorComponent
161 {
162 public:
163         /** Construct an Option.
164          *  @param i Option id (e.g. "plugins-stop-with-transport")
165          *  @param n User-visible name (e.g. "Stop plugins when the transport is stopped")
166          */
167         Option (std::string const & i,
168                 std::string const & n
169                 )
170                 : _id (i),
171                   _name (n)
172         {}
173
174         void parameter_changed (std::string const & p)
175         {
176                 if (p == _id) {
177                         set_state_from_config ();
178                 }
179         }
180
181         virtual void set_state_from_config () = 0;
182         virtual void add_to_page (OptionEditorPage*) = 0;
183
184         std::string id () const {
185                 return _id;
186         }
187
188 protected:
189
190         std::string _id;
191         std::string _name;
192 };
193
194 /** Component which provides the UI to handle a boolean option using a GTK CheckButton */
195 class BoolOption : public Option
196 {
197 public:
198
199         BoolOption (std::string const &, std::string const &, sigc::slot<bool>, sigc::slot<bool, bool>);
200         void set_state_from_config ();
201         void add_to_page (OptionEditorPage*);
202
203         void set_sensitive (bool yn) {
204                 _button->set_sensitive (yn);
205         }
206
207         Gtk::Widget& tip_widget() { return *_button; }
208
209 protected:
210
211         virtual void toggled ();
212
213         sigc::slot<bool>       _get; ///< slot to get the configuration variable's value
214         sigc::slot<bool, bool> _set;  ///< slot to set the configuration variable's value
215         Gtk::CheckButton*      _button; ///< UI button
216         Gtk::Label*            _label; ///< label for button, so we can use markup
217 };
218
219 class RouteDisplayBoolOption : public BoolOption
220 {
221 public:
222         RouteDisplayBoolOption (std::string const &, std::string const &, sigc::slot<bool>, sigc::slot<bool, bool>);
223 protected:
224         virtual void toggled ();
225 };
226
227 /** Component which allows to add any GTK Widget - intended for single buttons and custom stateless objects */
228 class FooOption : public OptionEditorComponent
229 {
230 public:
231         FooOption (Gtk::Widget *w) : _w (w) {}
232
233         void add_to_page (OptionEditorPage* p) {
234                 add_widget_to_page (p, _w);
235         }
236
237         Gtk::Widget& tip_widget() { return *_w; }
238         void set_state_from_config () {}
239         void parameter_changed (std::string const &) {}
240 private:
241         Gtk::Widget *_w;
242 };
243
244 /** Component which provides the UI to handle a string option using a GTK Entry */
245 class EntryOption : public Option
246 {
247 public:
248
249         EntryOption (std::string const &, std::string const &, sigc::slot<std::string>, sigc::slot<bool, std::string>);
250         void set_state_from_config ();
251         void add_to_page (OptionEditorPage*);
252         void set_sensitive (bool);
253         void set_invalid_chars (std::string i) { _invalid = i; }
254
255         Gtk::Widget& tip_widget() { return *_entry; }
256
257 private:
258
259         void activated ();
260         bool focus_out (GdkEventFocus*);
261         void filter_text (const Glib::ustring&, int*);
262
263         sigc::slot<std::string> _get; ///< slot to get the configuration variable's value
264         sigc::slot<bool, std::string> _set;  ///< slot to set the configuration variable's value
265         Gtk::Label* _label; ///< UI label
266         Gtk::Entry* _entry; ///< UI entry
267         std::string _invalid;
268 };
269
270
271 /** Component which provides the UI to handle an enumerated option using a GTK ComboBox.
272  *  The template parameter is the enumeration.
273  */
274 template <class T>
275 class ComboOption : public Option
276 {
277 public:
278
279         /** Construct an ComboOption.
280          *  @param i id
281          *  @param n User-visible name.
282          *  @param g Slot to get the variable's value.
283          *  @param s Slot to set the variable's value.
284          */
285         ComboOption (
286                 std::string const & i,
287                 std::string const & n,
288                 sigc::slot<T> g,
289                 sigc::slot<bool, T> s
290                 )
291                 : Option (i, n),
292                   _get (g),
293                   _set (s)
294         {
295                 _label = Gtk::manage (new Gtk::Label (n + ":"));
296                 _label->set_alignment (0, 0.5);
297                 _combo = Gtk::manage (new Gtk::ComboBoxText);
298                 _combo->signal_changed().connect (sigc::mem_fun (*this, &ComboOption::changed));
299         }
300
301         void set_state_from_config () {
302                 uint32_t r = 0;
303                 while (r < _options.size() && _get () != _options[r]) {
304                         ++r;
305                 }
306
307                 if (r < _options.size()) {
308                         _combo->set_active (r);
309                 }
310         }
311
312         void add_to_page (OptionEditorPage* p)
313         {
314                 add_widgets_to_page (p, _label, _combo);
315         }
316
317         /** Add an allowed value for this option.
318          *  @param e Enumeration.
319          *  @param o User-visible name for this value.
320          */
321         void add (T e, std::string const & o) {
322                 _options.push_back (e);
323                 _combo->append_text (o);
324         }
325
326         void clear () {
327                 _combo->clear_items();
328                 _options.clear ();
329         }
330
331         void changed () {
332                 uint32_t const r = _combo->get_active_row_number ();
333                 if (r < _options.size()) {
334                         _set (_options[r]);
335                 }
336         }
337
338         void set_sensitive (bool yn) {
339                 _combo->set_sensitive (yn);
340         }
341
342         Gtk::Widget& tip_widget() { return *_combo; }
343
344 private:
345
346         sigc::slot<T> _get;
347         sigc::slot<bool, T> _set;
348         Gtk::Label* _label;
349         Gtk::ComboBoxText* _combo;
350         std::vector<T> _options;
351 };
352
353
354 /** Component which provides the UI for a GTK HScale.
355  */
356 class HSliderOption : public Option
357 {
358 public:
359
360         /** Construct an ComboOption.
361          *  @param i id
362          *  @param n User-visible name.
363          *  @param g Slot to get the variable's value.
364          *  @param s Slot to set the variable's value.
365          */
366         HSliderOption (
367                 std::string const & i,
368                 std::string const & n,
369                 Gtk::Adjustment &adj
370                 )
371                 : Option (i, n)
372         {
373                 _label = Gtk::manage (new Gtk::Label (n + ":"));
374                 _label->set_alignment (0, 0.5);
375                 _hscale = Gtk::manage (new Gtk::HScale(adj));
376                 _adj = NULL;
377         }
378
379         HSliderOption (
380                 std::string const & i,
381                 std::string const & n,
382                 Gtk::Adjustment *adj,
383                 sigc::slot<float> g,
384                 sigc::slot<bool, float> s
385                 )
386                 : Option (i, n)
387                 , _get (g)
388                 , _set (s)
389                 , _adj (adj)
390         {
391                 _label = Gtk::manage (new Gtk::Label (n + ":"));
392                 _label->set_alignment (0, 0.5);
393                 _hscale = Gtk::manage (new Gtk::HScale(*_adj));
394                 _adj->signal_value_changed().connect (sigc::mem_fun (*this, &HSliderOption::changed));
395         }
396
397         void set_state_from_config () {
398                 if (_adj) _adj->set_value (_get());
399         }
400
401         void changed () {
402                 if (_adj) _set (_adj->get_value ());
403         }
404
405         void add_to_page (OptionEditorPage* p)
406         {
407                 add_widgets_to_page (p, _label, _hscale);
408         }
409
410         void set_sensitive (bool yn) {
411                 _hscale->set_sensitive (yn);
412         }
413
414         Gtk::Widget& tip_widget() { return *_hscale; }
415         Gtk::HScale& scale() { return *_hscale; }
416
417 private:
418         sigc::slot<float> _get;
419         sigc::slot<bool, float> _set;
420         Gtk::Label* _label;
421         Gtk::HScale* _hscale;
422         Gtk::Adjustment* _adj;
423 };
424
425 /** Component which provides the UI to handle an enumerated option using a GTK ComboBox.
426  *  The template parameter is the enumeration.
427  */
428 class ComboStringOption : public Option
429 {
430 public:
431
432         /** Construct an ComboOption.
433          *  @param i id
434          *  @param n User-visible name.
435          *  @param g Slot to get the variable's value.
436          *  @param s Slot to set the variable's value.
437          */
438         ComboStringOption (
439                 std::string const & i,
440                 std::string const & n,
441                 sigc::slot<std::string> g,
442                 sigc::slot<bool, std::string> s
443                 )
444                 : Option (i, n),
445                   _get (g),
446                   _set (s)
447         {
448                 _label = Gtk::manage (new Gtk::Label (n + ":"));
449                 _label->set_alignment (0, 0.5);
450                 _combo = Gtk::manage (new Gtk::ComboBoxText);
451                 _combo->signal_changed().connect (sigc::mem_fun (*this, &ComboStringOption::changed));
452         }
453
454         void set_state_from_config () {
455                 _combo->set_active_text (_get());
456         }
457
458         void add_to_page (OptionEditorPage* p)
459         {
460                 add_widgets_to_page (p, _label, _combo);
461         }
462
463         /** Set the allowed strings for this option
464          *  @param strings a vector of allowed strings
465          */
466         void set_popdown_strings (const std::vector<std::string>& strings) {
467                 _combo->clear_items ();
468                 for (std::vector<std::string>::const_iterator i = strings.begin(); i != strings.end(); ++i) {
469                         _combo->append_text (*i);
470                 }
471         }
472
473         void clear () {
474                 _combo->clear_items();
475         }
476
477         void changed () {
478                 _set (_combo->get_active_text ());
479         }
480
481         void set_sensitive (bool yn) {
482                 _combo->set_sensitive (yn);
483         }
484
485         Gtk::Widget& tip_widget() { return *_combo; }
486
487 private:
488         sigc::slot<std::string> _get;
489         sigc::slot<bool, std::string> _set;
490         Gtk::Label* _label;
491         Gtk::ComboBoxText* _combo;
492 };
493
494
495 /** Component which provides the UI to handle a boolean option which needs
496  *  to be represented as a ComboBox to be clear to the user.
497  */
498 class BoolComboOption : public Option
499 {
500 public:
501
502         BoolComboOption (
503                 std::string const &,
504                 std::string const &,
505                 std::string const &,
506                 std::string const &,
507                 sigc::slot<bool>,
508                 sigc::slot<bool, bool>
509                 );
510
511         void set_state_from_config ();
512         void add_to_page (OptionEditorPage *);
513         void changed ();
514         void set_sensitive (bool);
515
516         Gtk::Widget& tip_widget() { return *_combo; }
517
518 private:
519
520         sigc::slot<bool> _get;
521         sigc::slot<bool, bool> _set;
522         Gtk::Label* _label;
523         Gtk::ComboBoxText* _combo;
524 };
525
526
527
528 /** Component which provides the UI to handle an numeric option using a GTK SpinButton */
529 template <class T>
530 class SpinOption : public Option
531 {
532 public:
533         /** Construct an SpinOption.
534          *  @param i id
535          *  @param n User-visible name.
536          *  @param g Slot to get the variable's value.
537          *  @param s Slot to set the variable's value.
538          *  @param min Variable minimum value.
539          *  @param max Variable maximum value.
540          *  @param step Step for the spin button.
541          *  @param page Page step for the spin button.
542          *  @param unit Unit name.
543          *  @param scale Scaling factor (such that for a value x in the spinbutton, x * scale is written to the config)
544          *  @param digits Number of decimal digits to show.
545          */
546         SpinOption (
547                 std::string const & i,
548                 std::string const & n,
549                 sigc::slot<T> g,
550                 sigc::slot<bool, T> s,
551                 T min,
552                 T max,
553                 T step,
554                 T page,
555                 std::string const & unit = "",
556                 float scale = 1,
557                 unsigned digits = 0
558                 )
559                 : Option (i, n),
560                   _get (g),
561                   _set (s),
562                   _scale (scale)
563         {
564                 _label = Gtk::manage (new Gtk::Label (n + ":"));
565                 _label->set_alignment (0, 0.5);
566
567                 _spin = Gtk::manage (new Gtk::SpinButton);
568                 _spin->set_range (min, max);
569                 _spin->set_increments (step, page);
570                 _spin->set_digits(digits);
571
572                 _box = Gtk::manage (new Gtk::HBox);
573                 _box->pack_start (*_spin, true, true);
574                 _box->set_spacing (4);
575                 if (unit.length()) {
576                         _box->pack_start (*Gtk::manage (new Gtk::Label (unit)), false, false);
577                 }
578
579                 _spin->signal_value_changed().connect (sigc::mem_fun (*this, &SpinOption::changed));
580         }
581
582         void set_state_from_config ()
583         {
584                 _spin->set_value (_get () / _scale);
585         }
586
587         void add_to_page (OptionEditorPage* p)
588         {
589                 add_widgets_to_page (p, _label, _box);
590         }
591
592         void changed ()
593         {
594                 _set (static_cast<T> (_spin->get_value ()) * _scale);
595         }
596
597         Gtk::Widget& tip_widget() { return *_spin; }
598
599 private:
600         sigc::slot<T> _get;
601         sigc::slot<bool, T> _set;
602         float _scale;
603         Gtk::Label* _label;
604         Gtk::HBox* _box;
605         Gtk::SpinButton* _spin;
606 };
607
608 class FaderOption : public Option
609 {
610 public:
611
612         FaderOption (std::string const &, std::string const &, sigc::slot<ARDOUR::gain_t> g, sigc::slot<bool, ARDOUR::gain_t> s);
613         void set_state_from_config ();
614         void add_to_page (OptionEditorPage *);
615
616         Gtk::Widget& tip_widget() { return *_db_slider; }
617
618 private:
619         void db_changed ();
620         void on_activate ();
621         bool on_key_press (GdkEventKey* ev);
622
623         Gtk::Adjustment _db_adjustment;
624         Gtkmm2ext::HSliderController* _db_slider;
625         Gtk::Entry _db_display;
626         Gtk::Label _label;
627         Gtk::HBox _box;
628         Gtk::VBox _fader_centering_box;
629         sigc::slot<ARDOUR::gain_t> _get;
630         sigc::slot<bool, ARDOUR::gain_t> _set;
631 };
632
633 class ClockOption : public Option
634 {
635 public:
636         ClockOption (std::string const &, std::string const &, sigc::slot<std::string>, sigc::slot<bool, std::string>);
637         void set_state_from_config ();
638         void add_to_page (OptionEditorPage *);
639         void set_session (ARDOUR::Session *);
640
641         Gtk::Widget& tip_widget() { return _clock; }
642         AudioClock& clock() { return _clock; }
643
644 private:
645         void save_clock_time ();
646         Gtk::Label _label;
647         AudioClock _clock;
648         sigc::slot<std::string> _get;
649         sigc::slot<bool, std::string> _set;
650         ARDOUR::Session *_session;
651 };
652
653 class DirectoryOption : public Option
654 {
655 public:
656         DirectoryOption (std::string const &, std::string const &, sigc::slot<std::string>, sigc::slot<bool, std::string>);
657
658         void set_state_from_config ();
659         void add_to_page (OptionEditorPage *);
660
661         Gtk::Widget& tip_widget() { return _file_chooser; }
662
663 private:
664         void selection_changed ();
665
666         sigc::slot<std::string> _get; ///< slot to get the configuration variable's value
667         sigc::slot<bool, std::string> _set;  ///< slot to set the configuration variable's value
668         Gtk::FileChooserButton _file_chooser;
669 };
670
671 /** Class to represent a single page in an OptionEditor's notebook.
672  *  Pages are laid out using a 3-column table; the 1st column is used
673  *  to indent non-headings, and the 2nd and 3rd for actual content.
674  */
675 class OptionEditorPage
676 {
677 public:
678         OptionEditorPage (Gtk::Notebook&, std::string const &);
679
680         Gtk::VBox box;
681         Gtk::Table table;
682         std::list<OptionEditorComponent*> components;
683 };
684
685 /** The OptionEditor dialog base class */
686 class OptionEditor : virtual public sigc::trackable
687 {
688 public:
689         OptionEditor (PBD::Configuration *);
690         virtual ~OptionEditor ();
691
692         void add_option (std::string const &, OptionEditorComponent *);
693         void add_page (std::string const &, Gtk::Widget& page_widget);
694
695         void set_current_page (std::string const &);
696
697 protected:
698         virtual void parameter_changed (std::string const &);
699
700         PBD::Configuration* _config;
701         Gtk::Notebook& notebook() { return _notebook; }
702         Gtk::TreeView& treeview() { return option_treeview; }
703
704         class OptionColumns : public Gtk::TreeModel::ColumnRecord
705         {
706           public:
707                 Gtk::TreeModelColumn<std::string> name;
708                 Gtk::TreeModelColumn<Gtk::Widget*> widget;
709
710                 OptionColumns() {
711                         add (name);
712                         add (widget);
713                 }
714         };
715
716         OptionColumns option_columns;
717         Glib::RefPtr<Gtk::TreeStore> option_tree;
718
719 private:
720         PBD::ScopedConnection config_connection;
721         Gtk::Notebook _notebook;
722         Gtk::TreeView option_treeview;
723         std::map<std::string, OptionEditorPage*> _pages;
724
725         void add_path_to_treeview (std::string const &, Gtk::Widget&);
726         Gtk::TreeModel::iterator find_path_in_treemodel (std::string const & pn,
727                                                          bool create_missing = false);
728         void treeview_row_selected ();
729 };
730
731 /** The OptionEditor dialog-as-container base class */
732 class OptionEditorContainer : public OptionEditor, public Gtk::VBox
733 {
734 public:
735         OptionEditorContainer (PBD::Configuration *, std::string const &);
736         ~OptionEditorContainer() {}
737 private:
738         Gtk::HBox hpacker;
739 };
740
741 /** The OptionEditor dialog-as-container base class */
742 class OptionEditorWindow : public OptionEditor, public ArdourWindow
743 {
744 public:
745         OptionEditorWindow (PBD::Configuration *, std::string const &);
746         ~OptionEditorWindow() {}
747 private:
748         Gtk::VBox container;
749         Gtk::HBox hpacker;
750 };
751
752 #endif /* __gtk_ardour_option_editor_h__ */