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