2 * Copyright (C) 2017-2019 Robin Gareus <robin@gareus.org>
3 * Copyright (C) 2018 Nikolaus Gullotta <nikolaus.gullotta@gmail.com>
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
24 #include "ardour/dB.h"
25 #include "ardour/rc_configuration.h"
27 #include "gtkmm2ext/utils.h"
29 #include "widgets/ardour_dropdown.h"
30 #include "widgets/slider_controller.h"
32 #include "stripable_colorpicker.h"
33 #include "ardour_dialog.h"
34 #include "luadialog.h"
38 using namespace LuaDialog;
40 /*******************************************************************************
41 * Simple Message Dialog
43 Message::Message (std::string const& title, std::string const& msg, Message::MessageType mt, Message::ButtonType bt)
44 : _message_dialog (msg, true, to_gtk_mt (mt), to_gtk_bt (bt), true)
46 _message_dialog.set_title (title);
53 bool splash_pushed = false;
54 Splash* spl = Splash::instance();
55 if (spl && spl->is_visible()) {
56 spl->pop_back_for (_message_dialog);
60 int rv = _message_dialog.run ();
61 _message_dialog.hide ();
64 spl = Splash::instance();
71 case Gtk::RESPONSE_OK:
73 case Gtk::RESPONSE_CANCEL:
75 case Gtk::RESPONSE_CLOSE:
77 case Gtk::RESPONSE_YES:
79 case Gtk::RESPONSE_NO:
88 Message::to_gtk_bt (ButtonType bt)
92 return Gtk::BUTTONS_OK;
94 return Gtk::BUTTONS_CLOSE;
96 return Gtk::BUTTONS_CANCEL;
98 return Gtk::BUTTONS_YES_NO;
100 return Gtk::BUTTONS_OK_CANCEL;
103 return Gtk::BUTTONS_OK;
107 Message::to_gtk_mt (MessageType mt)
111 return Gtk::MESSAGE_INFO;
113 return Gtk::MESSAGE_WARNING;
115 return Gtk::MESSAGE_QUESTION;
117 return Gtk::MESSAGE_ERROR;
120 return Gtk::MESSAGE_INFO;
124 /* *****************************************************************************
128 class LuaDialogLabel : public LuaDialogWidget
131 LuaDialogLabel (std::string const& title, Gtk::AlignmentEnum xalign)
132 : LuaDialogWidget ("", "", 0, 2)
133 , _lbl (title, xalign, Gtk::ALIGN_CENTER, false)
136 Gtk::Widget* widget ()
141 void assign (luabridge::LuaRef* rv) const { }
147 class LuaDialogHeading : public LuaDialogLabel
150 LuaDialogHeading (std::string const& title, Gtk::AlignmentEnum xalign)
151 : LuaDialogLabel ("<b>" + title + "</b>", xalign)
153 _lbl.set_use_markup ();
157 class LuaHSeparator : public LuaDialogWidget
161 : LuaDialogWidget ("", "", 0, 2)
164 Gtk::Widget* widget ()
169 void assign (luabridge::LuaRef* rv) const { }
171 Gtk::HSeparator _sep;
174 class LuaColorPicker : public LuaDialogWidget
177 LuaColorPicker (std::string const& key)
178 : LuaDialogWidget (key, "", 0, 1)
181 Gtk::Widget* widget ()
185 void assign (luabridge::LuaRef* rv) const {
186 uint32_t rgba = ARDOUR_UI_UTILS::gdk_color_to_rgba(_cs.get_color());
190 Gtk::ColorButton _cs;
193 class LuaDialogCheckbox : public LuaDialogWidget
196 LuaDialogCheckbox (std::string const& key, std::string const& title, bool on)
197 : LuaDialogWidget (key, "", 1, 1)
199 if (!title.empty ()) {
200 _cb.add_label (title, false, 0);
205 Gtk::Widget* widget ()
210 void assign (luabridge::LuaRef* rv) const
212 (*rv)[_key] = _cb.get_active ();
216 Gtk::CheckButton _cb;
219 class LuaDialogEntry : public LuaDialogWidget
222 LuaDialogEntry (std::string const& key, std::string const& title, std::string const& dflt)
223 : LuaDialogWidget (key, title)
225 _entry.set_text (dflt);
228 Gtk::Widget* widget ()
233 void assign (luabridge::LuaRef* rv) const
235 (*rv)[_key] = std::string (_entry.get_text ());
242 class LuaDialogFader : public LuaDialogWidget
245 LuaDialogFader (std::string const& key, std::string const& title, double dflt)
246 : LuaDialogWidget (key, title)
247 , _db_adjustment (ARDOUR::gain_to_slider_position_with_max (1.0, ARDOUR::Config->get_max_gain ()), 0, 1, 0.01, 0.1)
249 _db_slider = Gtk::manage (new ArdourWidgets::HSliderController (&_db_adjustment, boost::shared_ptr<PBD::Controllable> (), 220, 18));
251 _fader_centering_box.pack_start (*_db_slider, true, false);
253 _box.set_spacing (4);
254 _box.set_homogeneous (false);
255 _box.pack_start (_fader_centering_box, false, false);
256 _box.pack_start (_db_display, false, false);
257 _box.pack_start (*Gtk::manage (new Gtk::Label ("dB")), false, false);
259 Gtkmm2ext::set_size_request_to_display_given_text (_db_display, "-99.00", 12, 0);
261 _db_adjustment.signal_value_changed ().connect (sigc::mem_fun (*this, &LuaDialogFader::db_changed));
262 _db_display.signal_activate ().connect (sigc::mem_fun (*this, &LuaDialogFader::on_activate));
263 _db_display.signal_key_press_event ().connect (sigc::mem_fun (*this, &LuaDialogFader::on_key_press), false);
265 double coeff_val = dB_to_coefficient (dflt);
266 _db_adjustment.set_value (ARDOUR::gain_to_slider_position_with_max (coeff_val, ARDOUR::Config->get_max_gain ()));
270 Gtk::Widget* widget ()
275 void assign (luabridge::LuaRef* rv) const
277 double const val = ARDOUR::slider_position_to_gain_with_max (_db_adjustment.get_value (), ARDOUR::Config->get_max_gain ());
278 (*rv)[_key] = accurate_coefficient_to_dB (val);
284 double const val = ARDOUR::slider_position_to_gain_with_max (_db_adjustment.get_value (), ARDOUR::Config->get_max_gain ());
287 snprintf (buf, sizeof (buf), "-inf");
289 snprintf (buf, sizeof (buf), "%.2f", accurate_coefficient_to_dB (val));
291 _db_display.set_text (buf);
296 float db_val = atof (_db_display.get_text ().c_str ());
297 double coeff_val = dB_to_coefficient (db_val);
298 _db_adjustment.set_value (ARDOUR::gain_to_slider_position_with_max (coeff_val, ARDOUR::Config->get_max_gain ()));
301 bool on_key_press (GdkEventKey* ev)
303 if (ARDOUR_UI_UTILS::key_is_legal_for_numeric_entry (ev->keyval)) {
309 Gtk::Adjustment _db_adjustment;
310 ArdourWidgets::HSliderController* _db_slider;
311 Gtk::Entry _db_display;
313 Gtk::VBox _fader_centering_box;
316 class LuaDialogSlider : public LuaDialogWidget
319 LuaDialogSlider (std::string const& key, std::string const& title, double lower, double upper, double dflt, int digits, luabridge::LuaRef scalepoints)
320 : LuaDialogWidget (key, title)
321 , _adj (dflt, lower, upper, 1, (upper - lower) / 20, 0)
324 _hscale.set_digits (digits);
325 _hscale.set_draw_value (true);
326 _hscale.set_value_pos (Gtk::POS_TOP);
328 if (!scalepoints.isTable ()) {
332 for (luabridge::Iterator i (scalepoints); !i.isNil (); ++i) {
333 if (!i.key ().isNumber ()) { continue; }
334 if (!i.value ().isString ()) { continue; }
335 _hscale.add_mark (i.key ().cast<double> (), Gtk::POS_BOTTOM, i.value ().cast<std::string> ());
339 Gtk::Widget* widget ()
344 void assign (luabridge::LuaRef* rv) const
346 (*rv)[_key] = _adj.get_value ();
350 Gtk::Adjustment _adj;
354 class LuaDialogSpinBox : public LuaDialogWidget
357 LuaDialogSpinBox (std::string const& key, std::string const& title, double lower, double upper, double dflt, double step, int digits)
358 : LuaDialogWidget (key, title)
359 , _adj (dflt, lower, upper, step, step, 0)
362 _spin.set_digits (digits);
365 Gtk::Widget* widget ()
370 void assign (luabridge::LuaRef* rv) const
372 (*rv)[_key] = _adj.get_value ();
376 Gtk::Adjustment _adj;
377 Gtk::SpinButton _spin;
380 class LuaDialogRadio : public LuaDialogWidget
383 LuaDialogRadio (std::string const& key, std::string const& title, luabridge::LuaRef values, std::string const& dflt)
384 : LuaDialogWidget (key, title)
387 for (luabridge::Iterator i (values); !i.isNil (); ++i) {
388 if (!i.key ().isString ()) { continue; }
389 std::string key = i.key ().cast<std::string> ();
390 Gtk::RadioButton* rb = Gtk::manage (new Gtk::RadioButton (_group, key));
391 _hbox.pack_start (*rb);
392 luabridge::LuaRef* ref = new luabridge::LuaRef (i.value ());
393 _refs.push_back (ref);
394 if (!_rv) { _rv = ref; }
395 rb->signal_toggled ().connect (sigc::bind (
396 sigc::mem_fun (*this, &LuaDialogRadio::rb_toggled), rb, ref
407 for (std::vector<luabridge::LuaRef*>::const_iterator i = _refs.begin (); i != _refs.end (); ++i) {
413 Gtk::Widget* widget ()
418 void assign (luabridge::LuaRef* rv) const
423 (*rv)[_key] = luabridge::Nil ();
428 LuaDialogRadio (LuaDialogRadio const&); // prevent cc
429 void rb_toggled (Gtk::RadioButton* b, luabridge::LuaRef* rv) {
430 if (b->get_active ()) {
436 Gtk::RadioButtonGroup _group;
437 std::vector<luabridge::LuaRef*> _refs;
438 luabridge::LuaRef* _rv;
441 class LuaDialogDropDown : public LuaDialogWidget
444 LuaDialogDropDown (std::string const& key, std::string const& title, luabridge::LuaRef values, std::string const& dflt)
445 : LuaDialogWidget (key, title)
448 populate (_dd.items (), values, dflt);
451 ~LuaDialogDropDown ()
453 for (std::vector<luabridge::LuaRef*>::const_iterator i = _refs.begin (); i != _refs.end (); ++i) {
459 Gtk::Widget* widget ()
464 void assign (luabridge::LuaRef* rv) const
469 (*rv)[_key] = luabridge::Nil ();
474 void populate (Gtk::Menu_Helpers::MenuList& items, luabridge::LuaRef values, std::string const& dflt)
476 using namespace Gtk::Menu_Helpers;
477 std::vector<std::string> keys;
479 for (luabridge::Iterator i (values); !i.isNil (); ++i) {
480 if (!i.key ().isString ()) { continue; }
481 keys.push_back (i.key ().cast<std::string> ());
484 std::sort (keys.begin(), keys.end());
486 for (std::vector<std::string>::const_iterator i = keys.begin (); i != keys.end(); ++i) {
487 std::string key = *i;
489 if (values[key].isTable ()) {
490 Gtk::Menu* menu = Gtk::manage (new Gtk::Menu);
491 items.push_back (MenuElem (key, *menu));
492 populate (menu->items (), values[key], dflt);
495 luabridge::LuaRef* ref = new luabridge::LuaRef (values[key]);
496 _refs.push_back (ref);
497 items.push_back (MenuElem (key,
498 sigc::bind (sigc::mem_fun (*this, &LuaDialogDropDown::dd_select), key, ref)));
500 if (!_rv || key == dflt) {
507 void dd_select (std::string const& key, luabridge::LuaRef* rv) {
512 ArdourWidgets::ArdourDropdown _dd;
513 std::vector<luabridge::LuaRef*> _refs;
514 luabridge::LuaRef* _rv;
517 class LuaFileChooser : public LuaDialogWidget
520 LuaFileChooser (std::string const& key, std::string const& title, Gtk::FileChooserAction a, const std::string& path)
521 : LuaDialogWidget (key, title)
524 Gtkmm2ext::add_volume_shortcuts (_fc);
525 if (!path.empty ()) {
527 case Gtk::FILE_CHOOSER_ACTION_OPEN:
528 case Gtk::FILE_CHOOSER_ACTION_SELECT_FOLDER:
529 _fc.set_filename (path);
531 case Gtk::FILE_CHOOSER_ACTION_SAVE:
532 case Gtk::FILE_CHOOSER_ACTION_CREATE_FOLDER:
533 /* not supported by Gtk::FileChooserButton */
539 Gtk::Widget* widget ()
544 void assign (luabridge::LuaRef* rv) const
546 (*rv)[_key] = std::string (_fc.get_filename ());
550 Gtk::FileChooserButton _fc;
554 class LuaFileChooserWidget : public LuaDialogWidget
557 LuaFileChooserWidget (std::string const& key, std::string const& title, Gtk::FileChooserAction a, const std::string& path)
558 : LuaDialogWidget (key, title)
561 Gtkmm2ext::add_volume_shortcuts (_fc);
562 if (!path.empty ()) {
564 case Gtk::FILE_CHOOSER_ACTION_OPEN:
565 case Gtk::FILE_CHOOSER_ACTION_SELECT_FOLDER:
566 _fc.set_filename (path);
568 case Gtk::FILE_CHOOSER_ACTION_SAVE:
569 case Gtk::FILE_CHOOSER_ACTION_CREATE_FOLDER:
570 _fc.set_filename (path);
571 _fc.set_current_name (Glib::path_get_basename (path));
578 Gtk::Widget* widget ()
583 void assign (luabridge::LuaRef* rv) const
585 (*rv)[_key] = std::string (_fc.get_filename ());
589 Gtk::FileChooserWidget _fc;
592 /*******************************************************************************
593 * Lua Parameter Dialog
595 Dialog::Dialog (std::string const& title, luabridge::LuaRef lr)
596 :_ad (title, true, false)
599 if (!lr.isTable ()) {
602 for (luabridge::Iterator i (lr); !i.isNil (); ++i) {
603 if (!i.key ().isNumber ()) { continue; }
604 if (!i.value ().isTable ()) { continue; }
605 if (!i.value ()["title"].isString ()) { continue; }
606 if (!i.value ()["type"].isString ()) { continue; }
608 std::string title = i.value ()["title"].cast<std::string> ();
609 std::string type = i.value ()["type"].cast<std::string> ();
612 if (i.value ()["key"].isString ()) {
613 key = i.value ()["key"].cast<std::string> ();
616 LuaDialogWidget* w = NULL;
618 if (type == "heading") {
619 Gtk::AlignmentEnum xalign = Gtk::ALIGN_CENTER;
620 if (i.value ()["align"].isString ()) {
621 std::string align = i.value ()["align"].cast <std::string> ();
622 if (align == "left") {
623 xalign = Gtk::ALIGN_LEFT;
624 } else if (align == "right") {
625 xalign = Gtk::ALIGN_RIGHT;
628 w = new LuaDialogHeading (title, xalign);
629 } else if (type == "label") {
630 Gtk::AlignmentEnum xalign = Gtk::ALIGN_CENTER;
631 if (i.value ()["align"].isString ()) {
632 std::string align = i.value ()["align"].cast <std::string> ();
633 if (align == "left") {
634 xalign = Gtk::ALIGN_LEFT;
635 } else if (align == "right") {
636 xalign = Gtk::ALIGN_RIGHT;
639 w = new LuaDialogLabel (title, xalign);
640 } else if (type == "hseparator") {
641 w = new LuaHSeparator ();
643 /* the following widgets do require a key */
644 else if (key.empty ()) {
647 else if (type == "checkbox") {
649 if (i.value ()["default"].isBoolean ()) {
650 dflt = i.value ()["default"].cast<bool> ();
652 w = new LuaDialogCheckbox (key, title, dflt);
653 } else if (type == "entry") {
655 if (i.value ()["default"].isString ()) {
656 dflt = i.value ()["default"].cast<std::string> ();
658 w = new LuaDialogEntry (key, title, dflt);
659 } else if (type == "radio") {
661 if (!i.value ()["values"].isTable ()) {
664 if (i.value ()["default"].isString ()) {
665 dflt = i.value ()["default"].cast<std::string> ();
667 w = new LuaDialogRadio (key, title, i.value ()["values"], dflt);
668 } else if (type == "fader") {
670 if (i.value ()["default"].isNumber ()) {
671 dflt = i.value ()["default"].cast<double> ();
673 w = new LuaDialogFader (key, title, dflt);
674 } else if (type == "slider") {
675 double lower, upper, dflt;
677 if (!i.value ()["min"].isNumber ()) { continue; }
678 if (!i.value ()["max"].isNumber ()) { continue; }
679 lower = i.value ()["min"].cast<double> ();
680 upper = i.value ()["max"].cast<double> ();
681 if (i.value ()["default"].isNumber ()) {
682 dflt = i.value ()["default"].cast<double> ();
686 if (i.value ()["digits"].isNumber ()) {
687 digits = i.value ()["digits"].cast<int> ();
689 w = new LuaDialogSlider (key, title, lower, upper, dflt, digits, i.value ()["scalepoints"]);
690 } else if (type == "number") {
691 double lower, upper, dflt, step;
693 if (!i.value ()["min"].isNumber ()) { continue; }
694 if (!i.value ()["max"].isNumber ()) { continue; }
695 lower = i.value ()["min"].cast<double> ();
696 upper = i.value ()["max"].cast<double> ();
697 if (i.value ()["default"].isNumber ()) {
698 dflt = i.value ()["default"].cast<double> ();
702 if (i.value ()["step"].isNumber ()) {
703 step = i.value ()["step"].cast<double> ();
707 if (i.value ()["digits"].isNumber ()) {
708 digits = i.value ()["digits"].cast<int> ();
710 w = new LuaDialogSpinBox (key, title, lower, upper, dflt, step, digits);
711 } else if (type == "dropdown") {
713 if (!i.value ()["values"].isTable ()) {
716 if (i.value ()["default"].isString ()) {
717 dflt = i.value ()["default"].cast<std::string> ();
719 w = new LuaDialogDropDown (key, title, i.value ()["values"], dflt);
720 } else if (type == "file") {
722 if (i.value ()["path"].isString ()) {
723 path = i.value ()["path"].cast<std::string> ();
725 w = new LuaFileChooser (key, title, Gtk::FILE_CHOOSER_ACTION_OPEN, path);
726 } else if (type == "folder") {
728 if (i.value ()["path"].isString ()) {
729 path = i.value ()["path"].cast<std::string> ();
731 w = new LuaFileChooser (key, title, Gtk::FILE_CHOOSER_ACTION_SELECT_FOLDER, path);
732 } else if (type == "createfile") {
734 if (i.value ()["path"].isString ()) {
735 path = i.value ()["path"].cast<std::string> ();
737 w = new LuaFileChooserWidget (key, title, Gtk::FILE_CHOOSER_ACTION_SAVE, path);
738 } else if (type == "createdir") {
740 if (i.value ()["path"].isString ()) {
741 path = i.value ()["path"].cast<std::string> ();
743 w = new LuaFileChooserWidget (key, title, Gtk::FILE_CHOOSER_ACTION_CREATE_FOLDER, path);
744 } else if (type == "color") {
745 w = new LuaColorPicker (key);
749 if (i.value ()["col"].isNumber ()) {
750 w->set_col (i.value ()["col"].cast<int> ());
752 if (i.value ()["colspan"].isNumber ()) {
753 w->set_span (i.value ()["colspan"].cast<int> ());
755 _widgets.push_back(w);
759 _ad.add_button (Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL);
760 _ad.add_button (Gtk::Stock::OK, Gtk::RESPONSE_ACCEPT);
762 Gtk::Table* table = Gtk::manage (new Gtk::Table ());
763 table->set_col_spacings (20);
764 table->set_row_spacings (8);
765 table->signal_size_allocate ().connect (sigc::mem_fun (this, &Dialog::table_size_alloc));
767 _scroller.set_shadow_type(Gtk::SHADOW_NONE);
768 _scroller.set_border_width(0);
769 _scroller.add (*table);
770 _scroller.set_policy (Gtk::POLICY_NEVER, Gtk::POLICY_NEVER);
772 _ad.get_vbox ()->pack_start (_scroller);
777 for (DialogWidgets::const_iterator i = _widgets.begin (); i != _widgets.end (); ++i) {
778 int col = (*i)->col();
779 int cend = col + (*i)->span();
781 if (col < last_end) {
786 std::string const& label = (*i)->label ();
787 if (!label.empty ()) {
788 /* items with implicit label (title) */
789 Gtk::Label* lbl = Gtk::manage (new Gtk::Label (label + ":", Gtk::ALIGN_END, Gtk::ALIGN_CENTER, false));
790 if (cend - col > 1) {
791 table->attach (*lbl, col, col + 1, row, row + 1, Gtk::FILL | Gtk::EXPAND, Gtk::SHRINK);
792 table->attach (*((*i)->widget ()), col + 1, cend, row, row + 1, Gtk::FILL | Gtk::EXPAND, Gtk::SHRINK);
794 Gtk::HBox* hb = Gtk::manage (new Gtk::HBox());
796 hb->pack_start (*lbl, true, false);
797 hb->pack_start (*(*i)->widget (), true, false);
798 table->attach (*hb, col, cend, row, row + 1, Gtk::FILL | Gtk::EXPAND, Gtk::SHRINK);
801 table->attach (*((*i)->widget ()), col, cend, row, row + 1, Gtk::FILL | Gtk::EXPAND, Gtk::SHRINK);
808 for (DialogWidgets::const_iterator i = _widgets.begin (); i != _widgets.end () ; ++i) {
815 Dialog::run (lua_State *L)
817 _ad.get_vbox ()->show_all ();
818 switch (_ad.run ()) {
819 case Gtk::RESPONSE_ACCEPT:
826 luabridge::LuaRef rv (luabridge::newTable (L));
827 for (DialogWidgets::const_iterator i = _widgets.begin (); i != _widgets.end () ; ++i) {
830 luabridge::push (L, rv);
835 Dialog::table_size_alloc (Gtk::Allocation& allocation)
837 /* XXX: consider using 0.75 * screen-height instead of 512 */
838 if (allocation.get_height () > 512) {
839 _scroller.set_policy (Gtk::POLICY_NEVER, Gtk::POLICY_AUTOMATIC);
840 _ad.set_size_request (-1, 512);