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