Initialize an uninitialized variable
[ardour.git] / gtk2_ardour / luadialog.cc
1 /*
2  * Copyright (C) 2017 Robin Gareus <robin@gareus.org>
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public License
6  * as published by the Free Software Foundation; either version 2
7  * of the License, or (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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
17  */
18
19 #include <gtkmm.h>
20
21 #include "ardour/dB.h"
22 #include "ardour/rc_configuration.h"
23
24 #include "gtkmm2ext/utils.h"
25
26 #include "widgets/ardour_dropdown.h"
27 #include "widgets/slider_controller.h"
28
29 #include "ardour_dialog.h"
30 #include "luadialog.h"
31 #include "splash.h"
32 #include "utils.h"
33
34 using namespace LuaDialog;
35
36 /*******************************************************************************
37  * Simple Message Dialog
38  */
39 Message::Message (std::string const& title, std::string const& msg, Message::MessageType mt, Message::ButtonType bt)
40         : _message_dialog (msg, false, to_gtk_mt (mt), to_gtk_bt (bt), true)
41 {
42         _message_dialog.set_title (title);
43 }
44
45 int
46 Message::run ()
47 {
48
49         bool splash_pushed = false
50         Splash* spl = Splash::instance();
51         if (spl && spl->is_visible()) {
52                 spl->pop_back_for (_message_dialog);
53                 splash_pushed = true;
54         }
55
56         int rv = _message_dialog.run ();
57         _message_dialog.hide ();
58
59         if (splash_pushed) {
60                 spl = Splash::instance();
61                 if (spl) {
62                         spl->pop_front();
63                 }
64         }
65
66         switch (rv) {
67                 case Gtk::RESPONSE_OK:
68                         return 0;
69                 case Gtk::RESPONSE_CANCEL:
70                         return 1;
71                 case Gtk::RESPONSE_CLOSE:
72                         return 2;
73                 case Gtk::RESPONSE_YES:
74                         return 3;
75                 case Gtk::RESPONSE_NO:
76                         return 4;
77                 default:
78                         break;
79         }
80         return -1;
81 }
82
83 Gtk::ButtonsType
84 Message::to_gtk_bt (ButtonType bt)
85 {
86         switch (bt) {
87                 case OK:
88                         return Gtk::BUTTONS_OK;
89                 case Close:
90                         return Gtk::BUTTONS_CLOSE;
91                 case Cancel:
92                         return Gtk::BUTTONS_CANCEL;
93                 case Yes_No:
94                         return Gtk::BUTTONS_YES_NO;
95                 case OK_Cancel:
96                         return Gtk::BUTTONS_OK_CANCEL;
97         }
98         assert (0);
99         return Gtk::BUTTONS_OK;
100 }
101
102 Gtk::MessageType
103 Message::to_gtk_mt (MessageType mt)
104 {
105         switch (mt) {
106                 case Info:
107                         return Gtk::MESSAGE_INFO;
108                 case Warning:
109                         return Gtk::MESSAGE_WARNING;
110                 case Question:
111                         return Gtk::MESSAGE_QUESTION;
112                 case Error:
113                         return Gtk::MESSAGE_ERROR;
114         }
115         assert (0);
116         return Gtk::MESSAGE_INFO;
117 }
118
119
120 /* *****************************************************************************
121  * Lua Dialog Widgets
122  */
123
124 class LuaDialogLabel : public LuaDialogWidget
125 {
126 public:
127         LuaDialogLabel (std::string const& title, Gtk::AlignmentEnum xalign)
128                 : LuaDialogWidget ("", "")
129                 , _lbl ("<b>" + title + "</b>", xalign, Gtk::ALIGN_CENTER, false)
130         {
131                 _lbl.set_use_markup ();
132         }
133
134         Gtk::Widget* widget ()
135         {
136                 return &_lbl;
137         }
138
139         void assign (luabridge::LuaRef* rv) const { }
140 protected:
141         Gtk::Label _lbl;
142 };
143
144 class LuaDialogCheckbox : public LuaDialogWidget
145 {
146 public:
147         LuaDialogCheckbox (std::string const& key, std::string const& title, bool on)
148                 : LuaDialogWidget (key, "")
149                 , _cb (title)
150         {
151                 _cb.set_active (on);
152         }
153
154         Gtk::Widget* widget ()
155         {
156                 return &_cb;
157         }
158
159         void assign (luabridge::LuaRef* rv) const
160         {
161                 (*rv)[_key] = _cb.get_active ();
162         }
163
164 protected:
165         Gtk::CheckButton _cb;
166 };
167
168 class LuaDialogEntry : public LuaDialogWidget
169 {
170 public:
171         LuaDialogEntry (std::string const& key, std::string const& title, std::string const& dflt)
172                 : LuaDialogWidget (key, title)
173         {
174                 _entry.set_text (dflt);
175         }
176
177         Gtk::Widget* widget ()
178         {
179                 return &_entry;
180         }
181
182         void assign (luabridge::LuaRef* rv) const
183         {
184                 (*rv)[_key] = std::string (_entry.get_text ());
185         }
186
187 protected:
188         Gtk::Entry _entry;
189 };
190
191 class LuaDialogFader : public LuaDialogWidget
192 {
193 public:
194         LuaDialogFader (std::string const& key, std::string const& title, double dflt)
195                 : LuaDialogWidget (key, title)
196                 , _db_adjustment (ARDOUR::gain_to_slider_position_with_max (1.0, ARDOUR::Config->get_max_gain ()), 0, 1, 0.01, 0.1)
197         {
198                 _db_slider = Gtk::manage (new ArdourWidgets::HSliderController (&_db_adjustment, boost::shared_ptr<PBD::Controllable> (), 220, 18));
199
200                 _fader_centering_box.pack_start (*_db_slider, true, false);
201
202                 _box.set_spacing (4);
203                 _box.set_homogeneous (false);
204                 _box.pack_start (_fader_centering_box, false, false);
205                 _box.pack_start (_db_display, false, false);
206                 _box.pack_start (*Gtk::manage (new Gtk::Label ("dB")), false, false);
207
208                 Gtkmm2ext::set_size_request_to_display_given_text (_db_display, "-99.00", 12, 0);
209
210                 _db_adjustment.signal_value_changed ().connect (sigc::mem_fun (*this, &LuaDialogFader::db_changed));
211                 _db_display.signal_activate ().connect (sigc::mem_fun (*this, &LuaDialogFader::on_activate));
212                 _db_display.signal_key_press_event ().connect (sigc::mem_fun (*this, &LuaDialogFader::on_key_press), false);
213
214                 double coeff_val = dB_to_coefficient (dflt);
215                 _db_adjustment.set_value (ARDOUR::gain_to_slider_position_with_max (coeff_val, ARDOUR::Config->get_max_gain ()));
216                 db_changed ();
217         }
218
219         Gtk::Widget* widget ()
220         {
221                 return &_box;
222         }
223
224         void assign (luabridge::LuaRef* rv) const
225         {
226                 double const val = ARDOUR::slider_position_to_gain_with_max (_db_adjustment.get_value (), ARDOUR::Config->get_max_gain ());
227                 (*rv)[_key] = accurate_coefficient_to_dB (val);
228         }
229
230 protected:
231         void db_changed ()
232         {
233                 double const val = ARDOUR::slider_position_to_gain_with_max (_db_adjustment.get_value (), ARDOUR::Config->get_max_gain ());
234                 char buf[16];
235                 if (val == 0.0) {
236                         snprintf (buf, sizeof (buf), "-inf");
237                 } else {
238                         snprintf (buf, sizeof (buf), "%.2f", accurate_coefficient_to_dB (val));
239                 }
240                 _db_display.set_text (buf);
241         }
242
243         void on_activate ()
244         {
245                 float db_val = atof (_db_display.get_text ().c_str ());
246                 double coeff_val = dB_to_coefficient (db_val);
247                 _db_adjustment.set_value (ARDOUR::gain_to_slider_position_with_max (coeff_val, ARDOUR::Config->get_max_gain ()));
248         }
249
250         bool on_key_press (GdkEventKey* ev)
251         {
252                 if (ARDOUR_UI_UTILS::key_is_legal_for_numeric_entry (ev->keyval)) {
253                         return false;
254                 }
255                 return true;
256         }
257
258         Gtk::Adjustment _db_adjustment;
259         ArdourWidgets::HSliderController* _db_slider;
260         Gtk::Entry _db_display;
261         Gtk::HBox _box;
262         Gtk::VBox _fader_centering_box;
263 };
264
265 class LuaDialogSlider : public LuaDialogWidget
266 {
267 public:
268         LuaDialogSlider (std::string const& key, std::string const& title, double lower, double upper, double dflt, int digits, luabridge::LuaRef scalepoints)
269                 : LuaDialogWidget (key, title)
270                 , _adj (dflt, lower, upper, 1, (upper - lower) / 20, 0)
271                 , _hscale (_adj)
272         {
273                 _hscale.set_digits (digits);
274                 _hscale.set_draw_value (true);
275                 _hscale.set_value_pos (Gtk::POS_TOP);
276
277                 if (!scalepoints.isTable ()) {
278                         return;
279                 }
280
281                 for (luabridge::Iterator i (scalepoints); !i.isNil (); ++i) {
282                         if (!i.key ().isNumber ())  { continue; }
283                         if (!i.value ().isString ())  { continue; }
284                         _hscale.add_mark (i.key ().cast<double> (), Gtk::POS_BOTTOM, i.value ().cast<std::string> ());
285                 }
286         }
287
288         Gtk::Widget* widget ()
289         {
290                 return &_hscale;
291         }
292
293         void assign (luabridge::LuaRef* rv) const
294         {
295                 (*rv)[_key] = _adj.get_value ();
296         }
297
298 protected:
299         Gtk::Adjustment _adj;
300         Gtk::HScale _hscale;
301 };
302
303 class LuaDialogSpinBox : public LuaDialogWidget
304 {
305 public:
306         LuaDialogSpinBox (std::string const& key, std::string const& title, double lower, double upper, double dflt, double step, int digits)
307                 : LuaDialogWidget (key, title)
308                 , _adj (dflt, lower, upper, step, step, 0)
309                 , _spin (_adj)
310         {
311                 _spin.set_digits (digits);
312         }
313
314         Gtk::Widget* widget ()
315         {
316                 return &_spin;
317         }
318
319         void assign (luabridge::LuaRef* rv) const
320         {
321                 (*rv)[_key] = _adj.get_value ();
322         }
323
324 protected:
325         Gtk::Adjustment _adj;
326         Gtk::SpinButton _spin;
327 };
328
329 class LuaDialogRadio : public LuaDialogWidget
330 {
331 public:
332         LuaDialogRadio (std::string const& key, std::string const& title, luabridge::LuaRef values, std::string const& dflt)
333                 : LuaDialogWidget (key, title)
334                 , _rv (0)
335         {
336                 for (luabridge::Iterator i (values); !i.isNil (); ++i) {
337                         if (!i.key ().isString ())  { continue; }
338                         std::string key = i.key ().cast<std::string> ();
339                         Gtk::RadioButton* rb = Gtk::manage (new Gtk::RadioButton (_group, key));
340                         _hbox.pack_start (*rb);
341                         luabridge::LuaRef* ref = new luabridge::LuaRef (i.value ());
342                         _refs.push_back (ref);
343                         if (!_rv) { _rv = ref; }
344                         rb->signal_toggled ().connect (sigc::bind (
345                                                 sigc::mem_fun (*this, &LuaDialogRadio::rb_toggled), rb, ref
346                                                 ) , false);
347
348                         if (key == dflt) {
349                                 rb->set_active ();
350                         }
351                 }
352         }
353
354         ~LuaDialogRadio ()
355         {
356                 for (std::vector<luabridge::LuaRef*>::const_iterator i = _refs.begin (); i != _refs.end (); ++i) {
357                         delete *i;
358                 }
359                 _refs.clear ();
360         }
361
362         Gtk::Widget* widget ()
363         {
364                 return &_hbox;
365         }
366
367         void assign (luabridge::LuaRef* rv) const
368         {
369                 if (_rv) {
370                         (*rv)[_key] = *_rv;
371                 } else {
372                         (*rv)[_key] = luabridge::Nil ();
373                 }
374         }
375
376 protected:
377         LuaDialogRadio (LuaDialogRadio const&); // prevent cc
378         void rb_toggled (Gtk::RadioButton* b, luabridge::LuaRef* rv) {
379                 if (b->get_active ()) {
380                         _rv = rv;
381                 }
382         }
383
384         Gtk::HBox _hbox;
385         Gtk::RadioButtonGroup _group;
386         std::vector<luabridge::LuaRef*> _refs;
387         luabridge::LuaRef* _rv;
388 };
389
390 class LuaDialogDropDown : public LuaDialogWidget
391 {
392 public:
393         LuaDialogDropDown (std::string const& key, std::string const& title, luabridge::LuaRef values, std::string const& dflt)
394                 : LuaDialogWidget (key, title)
395                 , _rv (0)
396         {
397                 populate (_dd.items (), values, dflt);
398         }
399
400         ~LuaDialogDropDown ()
401         {
402                 for (std::vector<luabridge::LuaRef*>::const_iterator i = _refs.begin (); i != _refs.end (); ++i) {
403                         delete *i;
404                 }
405                 _refs.clear ();
406         }
407
408         Gtk::Widget* widget ()
409         {
410                 return &_dd;
411         }
412
413         void assign (luabridge::LuaRef* rv) const
414         {
415                 if (_rv) {
416                         (*rv)[_key] = *_rv;
417                 } else {
418                         (*rv)[_key] = luabridge::Nil ();
419                 }
420         }
421
422 protected:
423         void populate (Gtk::Menu_Helpers::MenuList& items, luabridge::LuaRef values, std::string const& dflt)
424         {
425                 using namespace Gtk::Menu_Helpers;
426                 for (luabridge::Iterator i (values); !i.isNil (); ++i) {
427                         if (!i.key ().isString ())  { continue; }
428                         std::string key = i.key ().cast<std::string> ();
429                         if (i.value ().isTable ())  {
430                                 Gtk::Menu* menu  = Gtk::manage (new Gtk::Menu);
431                                 items.push_back (MenuElem (key, *menu));
432                                 populate (menu->items (), i.value (), dflt);
433                                 continue;
434                         }
435                         luabridge::LuaRef* ref = new luabridge::LuaRef (i.value ());
436                         _refs.push_back (ref);
437                         items.push_back (MenuElem (i.key ().cast<std::string> (),
438                                                 sigc::bind (sigc::mem_fun (*this, &LuaDialogDropDown::dd_select), key, ref)));
439
440                         if (!_rv || key == dflt) {
441                                 _rv = ref;
442                                 _dd.set_text (key);
443                         }
444                 }
445         }
446
447         void dd_select (std::string const& key, luabridge::LuaRef* rv) {
448                 _dd.set_text (key);
449                 _rv = rv;
450         }
451
452         ArdourWidgets::ArdourDropdown _dd;
453         std::vector<luabridge::LuaRef*> _refs;
454         luabridge::LuaRef* _rv;
455 };
456
457 class LuaFileChooser : public LuaDialogWidget
458 {
459 public:
460         LuaFileChooser (std::string const& key, std::string const& title, Gtk::FileChooserAction a, const std::string& path)
461                 : LuaDialogWidget (key, title)
462                 , _fc (a)
463         {
464                 if (!path.empty ()) {
465                         switch (a) {
466                                 case Gtk::FILE_CHOOSER_ACTION_OPEN:
467                                 case Gtk::FILE_CHOOSER_ACTION_SAVE:
468                                         if (Glib::file_test (path, Glib::FILE_TEST_IS_REGULAR|Glib::FILE_TEST_EXISTS)) {
469                                                 _fc.set_filename (path);
470                                         }
471                                         break;
472                                 case Gtk::FILE_CHOOSER_ACTION_SELECT_FOLDER:
473                                         if (Glib::file_test (path, Glib::FILE_TEST_IS_DIR|Glib::FILE_TEST_EXISTS)) {
474                                                 _fc.set_filename (path);
475                                         }
476                                         break;
477                                 case Gtk::FILE_CHOOSER_ACTION_CREATE_FOLDER:
478                                         break;
479                         }
480                 }
481         }
482
483         Gtk::Widget* widget ()
484         {
485                 return &_fc;
486         }
487
488         void assign (luabridge::LuaRef* rv) const
489         {
490                 (*rv)[_key] = std::string (_fc.get_filename ());
491         }
492
493 protected:
494         Gtk::FileChooserButton _fc;
495 };
496
497
498
499 /*******************************************************************************
500  * Lua Parameter Dialog
501  */
502 Dialog::Dialog (std::string const& title, luabridge::LuaRef lr)
503         :_ad (title, true, false)
504         , _title (title)
505 {
506         if (!lr.isTable ()) {
507                 return;
508         }
509         for (luabridge::Iterator i (lr); !i.isNil (); ++i) {
510                 if (!i.key ().isNumber ())  { continue; }
511                 if (!i.value ().isTable ()) { continue; }
512                 if (!i.value ()["title"].isString ()) { continue; }
513                 if (!i.value ()["type"].isString ()) { continue; }
514
515                 std::string title = i.value ()["title"].cast<std::string> ();
516                 std::string type = i.value ()["type"].cast<std::string> ();
517
518                 if (type == "heading") {
519                         Gtk::AlignmentEnum xalign = Gtk::ALIGN_CENTER;
520                         if (i.value ()["align"].isString ()) {
521                                 std::string align = i.value ()["align"].cast <std::string> ();
522                                 if (align == "left") {
523                                         xalign = Gtk::ALIGN_LEFT;
524                                 } else if (align == "right") {
525                                         xalign = Gtk::ALIGN_RIGHT;
526                                 }
527                         }
528                         _widgets.push_back (new LuaDialogLabel (title, xalign));
529                         continue;
530                 }
531
532                 if (!i.value ()["key"].isString ()) { continue; }
533                 std::string key = i.value ()["key"].cast<std::string> ();
534
535                 if (type == "checkbox") {
536                         bool dflt = false;
537                         if (i.value ()["default"].isBoolean ()) {
538                                 dflt = i.value ()["default"].cast<bool> ();
539                         }
540                         _widgets.push_back (new LuaDialogCheckbox (key, title, dflt));
541                 } else if (type == "entry") {
542                         std::string dflt;
543                         if (i.value ()["default"].isString ()) {
544                                 dflt = i.value ()["default"].cast<std::string> ();
545                         }
546                         _widgets.push_back (new LuaDialogEntry (key, title, dflt));
547                 } else if (type == "radio") {
548                         std::string dflt;
549                         if (!i.value ()["values"].isTable ()) {
550                                 continue;
551                         }
552                         if (i.value ()["default"].isString ()) {
553                                 dflt = i.value ()["default"].cast<std::string> ();
554                         }
555                         _widgets.push_back (new LuaDialogRadio (key, title, i.value ()["values"], dflt));
556                 } else if (type == "fader") {
557                         double dflt = 0;
558                         if (i.value ()["default"].isNumber ()) {
559                                 dflt = i.value ()["default"].cast<double> ();
560                         }
561                         _widgets.push_back (new LuaDialogFader (key, title, dflt));
562                 } else if (type == "slider") {
563                         double lower, upper, dflt;
564                         int digits = 0;
565                         if (!i.value ()["min"].isNumber ()) { continue; }
566                         if (!i.value ()["max"].isNumber ()) { continue; }
567                         lower = i.value ()["min"].cast<double> ();
568                         upper = i.value ()["max"].cast<double> ();
569                         if (i.value ()["default"].isNumber ()) {
570                                 dflt = i.value ()["default"].cast<double> ();
571                         } else {
572                                 dflt = lower;
573                         }
574                         if (i.value ()["digits"].isNumber ()) {
575                                 digits = i.value ()["digits"].cast<int> ();
576                         }
577                         _widgets.push_back (new LuaDialogSlider (key, title, lower, upper, dflt, digits, i.value ()["scalepoints"]));
578                 } else if (type == "number") {
579                         double lower, upper, dflt, step;
580                         int digits = 0;
581                         if (!i.value ()["min"].isNumber ()) { continue; }
582                         if (!i.value ()["max"].isNumber ()) { continue; }
583                         lower = i.value ()["min"].cast<double> ();
584                         upper = i.value ()["max"].cast<double> ();
585                         if (i.value ()["default"].isNumber ()) {
586                                 dflt = i.value ()["default"].cast<double> ();
587                         } else {
588                                 dflt = lower;
589                         }
590                         if (i.value ()["step"].isNumber ()) {
591                                 step = i.value ()["step"].cast<double> ();
592                         } else {
593                                 step = 1.0;
594                         }
595                         if (i.value ()["digits"].isNumber ()) {
596                                 digits = i.value ()["digits"].cast<int> ();
597                         }
598                         _widgets.push_back (new LuaDialogSpinBox (key, title, lower, upper, dflt, step, digits));
599                 } else if (type == "dropdown") {
600                         std::string dflt;
601                         if (!i.value ()["values"].isTable ()) {
602                                 continue;
603                         }
604                         if (i.value ()["default"].isString ()) {
605                                 dflt = i.value ()["default"].cast<std::string> ();
606                         }
607                         _widgets.push_back (new LuaDialogDropDown (key, title, i.value ()["values"], dflt));
608                 } else if (type == "file") {
609                         std::string path;
610                         if (i.value ()["path"].isString ()) {
611                                 path = i.value ()["path"].cast<std::string> ();
612                         }
613                         _widgets.push_back (new LuaFileChooser (key, title, Gtk::FILE_CHOOSER_ACTION_OPEN, path));
614                 } else if (type == "folder") {
615                         std::string path;
616                         if (i.value ()["path"].isString ()) {
617                                 path = i.value ()["path"].cast<std::string> ();
618                         }
619                         _widgets.push_back (new LuaFileChooser (key, title, Gtk::FILE_CHOOSER_ACTION_SELECT_FOLDER, path));
620                 }
621         }
622
623         _ad.add_button (Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL);
624         _ad.add_button (Gtk::Stock::OK, Gtk::RESPONSE_ACCEPT);
625
626         Gtk::Table* table = Gtk::manage (new Gtk::Table ());
627         table->set_col_spacings (4);
628         table->set_row_spacings (8);
629         _ad.get_vbox ()->pack_start (*table);
630         int row = 0;
631
632         for (DialogWidgets::const_iterator i = _widgets.begin (); i != _widgets.end (); ++i) {
633                 std::string const& label = (*i)->label ();
634                 if (!label.empty ()) {
635                         Gtk::Label* lbl = Gtk::manage (new Gtk::Label (label + ":", Gtk::ALIGN_END, Gtk::ALIGN_CENTER, false));
636                         table->attach (*lbl, 0, 1, row, row + 1, Gtk::FILL | Gtk::EXPAND, Gtk::SHRINK);
637                         table->attach (*((*i)->widget ()), 1, 2, row, row + 1, Gtk::FILL | Gtk::EXPAND, Gtk::SHRINK);
638                 } else if ((*i)->key ().empty ()) {
639                         table->attach (*((*i)->widget ()), 0, 2, row, row + 1, Gtk::FILL | Gtk::EXPAND, Gtk::SHRINK);
640                 } else {
641                         table->attach (*((*i)->widget ()), 1, 2, row, row + 1, Gtk::FILL | Gtk::EXPAND, Gtk::SHRINK);
642                 }
643                 ++row;
644         }
645 }
646
647 Dialog::~Dialog ()
648 {
649         for (DialogWidgets::const_iterator i = _widgets.begin (); i != _widgets.end () ; ++i) {
650                 delete *i;
651         }
652         _widgets.clear ();
653 }
654
655 int
656 Dialog::run (lua_State *L)
657 {
658         _ad.get_vbox ()->show_all ();
659         switch (_ad.run ()) {
660                 case Gtk::RESPONSE_ACCEPT:
661                         break;
662                 default:
663                         lua_pushnil (L);
664                         return 1;
665         }
666
667         luabridge::LuaRef rv (luabridge::newTable (L));
668         for (DialogWidgets::const_iterator i = _widgets.begin (); i != _widgets.end () ; ++i) {
669                 (*i)->assign (&rv);
670         }
671         luabridge::push (L, rv);
672         return 1;
673 }