2 * Copyright (C) 2017 Robin Gareus <robin@gareus.org>
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.
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.
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.
23 #include "ardour/dB.h"
24 #include "ardour/rc_configuration.h"
26 #include "gtkmm2ext/utils.h"
28 #include "widgets/ardour_dropdown.h"
29 #include "widgets/slider_controller.h"
31 #include "stripable_colorpicker.h"
32 #include "ardour_dialog.h"
33 #include "luadialog.h"
37 using namespace LuaDialog;
39 /*******************************************************************************
40 * Simple Message Dialog
42 Message::Message (std::string const& title, std::string const& msg, Message::MessageType mt, Message::ButtonType bt)
43 : _message_dialog (msg, false, to_gtk_mt (mt), to_gtk_bt (bt), true)
45 _message_dialog.set_title (title);
52 bool splash_pushed = false;
53 Splash* spl = Splash::instance();
54 if (spl && spl->is_visible()) {
55 spl->pop_back_for (_message_dialog);
59 int rv = _message_dialog.run ();
60 _message_dialog.hide ();
63 spl = Splash::instance();
70 case Gtk::RESPONSE_OK:
72 case Gtk::RESPONSE_CANCEL:
74 case Gtk::RESPONSE_CLOSE:
76 case Gtk::RESPONSE_YES:
78 case Gtk::RESPONSE_NO:
87 Message::to_gtk_bt (ButtonType bt)
91 return Gtk::BUTTONS_OK;
93 return Gtk::BUTTONS_CLOSE;
95 return Gtk::BUTTONS_CANCEL;
97 return Gtk::BUTTONS_YES_NO;
99 return Gtk::BUTTONS_OK_CANCEL;
102 return Gtk::BUTTONS_OK;
106 Message::to_gtk_mt (MessageType mt)
110 return Gtk::MESSAGE_INFO;
112 return Gtk::MESSAGE_WARNING;
114 return Gtk::MESSAGE_QUESTION;
116 return Gtk::MESSAGE_ERROR;
119 return Gtk::MESSAGE_INFO;
123 /* *****************************************************************************
127 class LuaDialogLabel : public LuaDialogWidget
130 LuaDialogLabel (std::string const& title, Gtk::AlignmentEnum xalign)
131 : LuaDialogWidget ("", "", 0, 2)
132 , _lbl (title, xalign, Gtk::ALIGN_CENTER, false)
135 Gtk::Widget* widget ()
140 void assign (luabridge::LuaRef* rv) const { }
146 class LuaDialogHeading : public LuaDialogLabel
149 LuaDialogHeading (std::string const& title, Gtk::AlignmentEnum xalign)
150 : LuaDialogLabel ("<b>" + title + "</b>", xalign)
152 _lbl.set_use_markup ();
156 class LuaHSeparator : public LuaDialogWidget
160 : LuaDialogWidget ("", "", 0, 2)
163 Gtk::Widget* widget ()
168 void assign (luabridge::LuaRef* rv) const { }
170 Gtk::HSeparator _sep;
173 class LuaColorPicker : public LuaDialogWidget
176 LuaColorPicker (std::string const& key)
177 : LuaDialogWidget (key, "", 0, 1)
180 Gtk::Widget* widget ()
184 void assign (luabridge::LuaRef* rv) const {
185 uint32_t rgba = ARDOUR_UI_UTILS::gdk_color_to_rgba(_cs.get_color());
189 Gtk::ColorButton _cs;
192 class LuaDialogCheckbox : public LuaDialogWidget
195 LuaDialogCheckbox (std::string const& key, std::string const& title, bool on)
196 : LuaDialogWidget (key, "", 1, 1)
198 if (!title.empty ()) {
199 _cb.add_label (title, false, 0);
204 Gtk::Widget* widget ()
209 void assign (luabridge::LuaRef* rv) const
211 (*rv)[_key] = _cb.get_active ();
215 Gtk::CheckButton _cb;
218 class LuaDialogEntry : public LuaDialogWidget
221 LuaDialogEntry (std::string const& key, std::string const& title, std::string const& dflt)
222 : LuaDialogWidget (key, title)
224 _entry.set_text (dflt);
227 Gtk::Widget* widget ()
232 void assign (luabridge::LuaRef* rv) const
234 (*rv)[_key] = std::string (_entry.get_text ());
241 class LuaDialogFader : public LuaDialogWidget
244 LuaDialogFader (std::string const& key, std::string const& title, double dflt)
245 : LuaDialogWidget (key, title)
246 , _db_adjustment (ARDOUR::gain_to_slider_position_with_max (1.0, ARDOUR::Config->get_max_gain ()), 0, 1, 0.01, 0.1)
248 _db_slider = Gtk::manage (new ArdourWidgets::HSliderController (&_db_adjustment, boost::shared_ptr<PBD::Controllable> (), 220, 18));
250 _fader_centering_box.pack_start (*_db_slider, true, false);
252 _box.set_spacing (4);
253 _box.set_homogeneous (false);
254 _box.pack_start (_fader_centering_box, false, false);
255 _box.pack_start (_db_display, false, false);
256 _box.pack_start (*Gtk::manage (new Gtk::Label ("dB")), false, false);
258 Gtkmm2ext::set_size_request_to_display_given_text (_db_display, "-99.00", 12, 0);
260 _db_adjustment.signal_value_changed ().connect (sigc::mem_fun (*this, &LuaDialogFader::db_changed));
261 _db_display.signal_activate ().connect (sigc::mem_fun (*this, &LuaDialogFader::on_activate));
262 _db_display.signal_key_press_event ().connect (sigc::mem_fun (*this, &LuaDialogFader::on_key_press), false);
264 double coeff_val = dB_to_coefficient (dflt);
265 _db_adjustment.set_value (ARDOUR::gain_to_slider_position_with_max (coeff_val, ARDOUR::Config->get_max_gain ()));
269 Gtk::Widget* widget ()
274 void assign (luabridge::LuaRef* rv) const
276 double const val = ARDOUR::slider_position_to_gain_with_max (_db_adjustment.get_value (), ARDOUR::Config->get_max_gain ());
277 (*rv)[_key] = accurate_coefficient_to_dB (val);
283 double const val = ARDOUR::slider_position_to_gain_with_max (_db_adjustment.get_value (), ARDOUR::Config->get_max_gain ());
286 snprintf (buf, sizeof (buf), "-inf");
288 snprintf (buf, sizeof (buf), "%.2f", accurate_coefficient_to_dB (val));
290 _db_display.set_text (buf);
295 float db_val = atof (_db_display.get_text ().c_str ());
296 double coeff_val = dB_to_coefficient (db_val);
297 _db_adjustment.set_value (ARDOUR::gain_to_slider_position_with_max (coeff_val, ARDOUR::Config->get_max_gain ()));
300 bool on_key_press (GdkEventKey* ev)
302 if (ARDOUR_UI_UTILS::key_is_legal_for_numeric_entry (ev->keyval)) {
308 Gtk::Adjustment _db_adjustment;
309 ArdourWidgets::HSliderController* _db_slider;
310 Gtk::Entry _db_display;
312 Gtk::VBox _fader_centering_box;
315 class LuaDialogSlider : public LuaDialogWidget
318 LuaDialogSlider (std::string const& key, std::string const& title, double lower, double upper, double dflt, int digits, luabridge::LuaRef scalepoints)
319 : LuaDialogWidget (key, title)
320 , _adj (dflt, lower, upper, 1, (upper - lower) / 20, 0)
323 _hscale.set_digits (digits);
324 _hscale.set_draw_value (true);
325 _hscale.set_value_pos (Gtk::POS_TOP);
327 if (!scalepoints.isTable ()) {
331 for (luabridge::Iterator i (scalepoints); !i.isNil (); ++i) {
332 if (!i.key ().isNumber ()) { continue; }
333 if (!i.value ().isString ()) { continue; }
334 _hscale.add_mark (i.key ().cast<double> (), Gtk::POS_BOTTOM, i.value ().cast<std::string> ());
338 Gtk::Widget* widget ()
343 void assign (luabridge::LuaRef* rv) const
345 (*rv)[_key] = _adj.get_value ();
349 Gtk::Adjustment _adj;
353 class LuaDialogSpinBox : public LuaDialogWidget
356 LuaDialogSpinBox (std::string const& key, std::string const& title, double lower, double upper, double dflt, double step, int digits)
357 : LuaDialogWidget (key, title)
358 , _adj (dflt, lower, upper, step, step, 0)
361 _spin.set_digits (digits);
364 Gtk::Widget* widget ()
369 void assign (luabridge::LuaRef* rv) const
371 (*rv)[_key] = _adj.get_value ();
375 Gtk::Adjustment _adj;
376 Gtk::SpinButton _spin;
379 class LuaDialogRadio : public LuaDialogWidget
382 LuaDialogRadio (std::string const& key, std::string const& title, luabridge::LuaRef values, std::string const& dflt)
383 : LuaDialogWidget (key, title)
386 for (luabridge::Iterator i (values); !i.isNil (); ++i) {
387 if (!i.key ().isString ()) { continue; }
388 std::string key = i.key ().cast<std::string> ();
389 Gtk::RadioButton* rb = Gtk::manage (new Gtk::RadioButton (_group, key));
390 _hbox.pack_start (*rb);
391 luabridge::LuaRef* ref = new luabridge::LuaRef (i.value ());
392 _refs.push_back (ref);
393 if (!_rv) { _rv = ref; }
394 rb->signal_toggled ().connect (sigc::bind (
395 sigc::mem_fun (*this, &LuaDialogRadio::rb_toggled), rb, ref
406 for (std::vector<luabridge::LuaRef*>::const_iterator i = _refs.begin (); i != _refs.end (); ++i) {
412 Gtk::Widget* widget ()
417 void assign (luabridge::LuaRef* rv) const
422 (*rv)[_key] = luabridge::Nil ();
427 LuaDialogRadio (LuaDialogRadio const&); // prevent cc
428 void rb_toggled (Gtk::RadioButton* b, luabridge::LuaRef* rv) {
429 if (b->get_active ()) {
435 Gtk::RadioButtonGroup _group;
436 std::vector<luabridge::LuaRef*> _refs;
437 luabridge::LuaRef* _rv;
440 class LuaDialogDropDown : public LuaDialogWidget
443 LuaDialogDropDown (std::string const& key, std::string const& title, luabridge::LuaRef values, std::string const& dflt)
444 : LuaDialogWidget (key, title)
447 populate (_dd.items (), values, dflt);
450 ~LuaDialogDropDown ()
452 for (std::vector<luabridge::LuaRef*>::const_iterator i = _refs.begin (); i != _refs.end (); ++i) {
458 Gtk::Widget* widget ()
463 void assign (luabridge::LuaRef* rv) const
468 (*rv)[_key] = luabridge::Nil ();
473 void populate (Gtk::Menu_Helpers::MenuList& items, luabridge::LuaRef values, std::string const& dflt)
475 using namespace Gtk::Menu_Helpers;
476 std::vector<std::string> keys;
478 for (luabridge::Iterator i (values); !i.isNil (); ++i) {
479 if (!i.key ().isString ()) { continue; }
480 keys.push_back (i.key ().cast<std::string> ());
483 std::sort (keys.begin(), keys.end());
485 for (std::vector<std::string>::const_iterator i = keys.begin (); i != keys.end(); ++i) {
486 std::string key = *i;
488 if (values[key].isTable ()) {
489 Gtk::Menu* menu = Gtk::manage (new Gtk::Menu);
490 items.push_back (MenuElem (key, *menu));
491 populate (menu->items (), values[key], dflt);
494 luabridge::LuaRef* ref = new luabridge::LuaRef (values[key]);
495 _refs.push_back (ref);
496 items.push_back (MenuElem (key,
497 sigc::bind (sigc::mem_fun (*this, &LuaDialogDropDown::dd_select), key, ref)));
499 if (!_rv || key == dflt) {
506 void dd_select (std::string const& key, luabridge::LuaRef* rv) {
511 ArdourWidgets::ArdourDropdown _dd;
512 std::vector<luabridge::LuaRef*> _refs;
513 luabridge::LuaRef* _rv;
516 class LuaFileChooser : public LuaDialogWidget
519 LuaFileChooser (std::string const& key, std::string const& title, Gtk::FileChooserAction a, const std::string& path)
520 : LuaDialogWidget (key, title)
523 if (!path.empty ()) {
525 case Gtk::FILE_CHOOSER_ACTION_OPEN:
526 case Gtk::FILE_CHOOSER_ACTION_SAVE:
527 case Gtk::FILE_CHOOSER_ACTION_SELECT_FOLDER:
528 _fc.set_filename (path);
530 case Gtk::FILE_CHOOSER_ACTION_CREATE_FOLDER:
536 Gtk::Widget* widget ()
541 void assign (luabridge::LuaRef* rv) const
543 (*rv)[_key] = std::string (_fc.get_filename ());
547 Gtk::FileChooserButton _fc;
552 /*******************************************************************************
553 * Lua Parameter Dialog
555 Dialog::Dialog (std::string const& title, luabridge::LuaRef lr)
556 :_ad (title, true, false)
559 if (!lr.isTable ()) {
562 for (luabridge::Iterator i (lr); !i.isNil (); ++i) {
563 if (!i.key ().isNumber ()) { continue; }
564 if (!i.value ().isTable ()) { continue; }
565 if (!i.value ()["title"].isString ()) { continue; }
566 if (!i.value ()["type"].isString ()) { continue; }
568 std::string title = i.value ()["title"].cast<std::string> ();
569 std::string type = i.value ()["type"].cast<std::string> ();
572 if (i.value ()["key"].isString ()) {
573 key = i.value ()["key"].cast<std::string> ();
576 LuaDialogWidget* w = NULL;
578 if (type == "heading") {
579 Gtk::AlignmentEnum xalign = Gtk::ALIGN_CENTER;
580 if (i.value ()["align"].isString ()) {
581 std::string align = i.value ()["align"].cast <std::string> ();
582 if (align == "left") {
583 xalign = Gtk::ALIGN_LEFT;
584 } else if (align == "right") {
585 xalign = Gtk::ALIGN_RIGHT;
588 w = new LuaDialogHeading (title, xalign);
589 } else if (type == "label") {
590 Gtk::AlignmentEnum xalign = Gtk::ALIGN_CENTER;
591 if (i.value ()["align"].isString ()) {
592 std::string align = i.value ()["align"].cast <std::string> ();
593 if (align == "left") {
594 xalign = Gtk::ALIGN_LEFT;
595 } else if (align == "right") {
596 xalign = Gtk::ALIGN_RIGHT;
599 w = new LuaDialogLabel (title, xalign);
600 } else if (type == "hseparator") {
601 w = new LuaHSeparator ();
603 /* the following widgets do require a key */
604 else if (key.empty ()) {
607 else if (type == "checkbox") {
609 if (i.value ()["default"].isBoolean ()) {
610 dflt = i.value ()["default"].cast<bool> ();
612 w = new LuaDialogCheckbox (key, title, dflt);
613 } else if (type == "entry") {
615 if (i.value ()["default"].isString ()) {
616 dflt = i.value ()["default"].cast<std::string> ();
618 w = new LuaDialogEntry (key, title, dflt);
619 } else if (type == "radio") {
621 if (!i.value ()["values"].isTable ()) {
624 if (i.value ()["default"].isString ()) {
625 dflt = i.value ()["default"].cast<std::string> ();
627 w = new LuaDialogRadio (key, title, i.value ()["values"], dflt);
628 } else if (type == "fader") {
630 if (i.value ()["default"].isNumber ()) {
631 dflt = i.value ()["default"].cast<double> ();
633 w = new LuaDialogFader (key, title, dflt);
634 } else if (type == "slider") {
635 double lower, upper, dflt;
637 if (!i.value ()["min"].isNumber ()) { continue; }
638 if (!i.value ()["max"].isNumber ()) { continue; }
639 lower = i.value ()["min"].cast<double> ();
640 upper = i.value ()["max"].cast<double> ();
641 if (i.value ()["default"].isNumber ()) {
642 dflt = i.value ()["default"].cast<double> ();
646 if (i.value ()["digits"].isNumber ()) {
647 digits = i.value ()["digits"].cast<int> ();
649 w = new LuaDialogSlider (key, title, lower, upper, dflt, digits, i.value ()["scalepoints"]);
650 } else if (type == "number") {
651 double lower, upper, dflt, step;
653 if (!i.value ()["min"].isNumber ()) { continue; }
654 if (!i.value ()["max"].isNumber ()) { continue; }
655 lower = i.value ()["min"].cast<double> ();
656 upper = i.value ()["max"].cast<double> ();
657 if (i.value ()["default"].isNumber ()) {
658 dflt = i.value ()["default"].cast<double> ();
662 if (i.value ()["step"].isNumber ()) {
663 step = i.value ()["step"].cast<double> ();
667 if (i.value ()["digits"].isNumber ()) {
668 digits = i.value ()["digits"].cast<int> ();
670 w = new LuaDialogSpinBox (key, title, lower, upper, dflt, step, digits);
671 } else if (type == "dropdown") {
673 if (!i.value ()["values"].isTable ()) {
676 if (i.value ()["default"].isString ()) {
677 dflt = i.value ()["default"].cast<std::string> ();
679 w = new LuaDialogDropDown (key, title, i.value ()["values"], dflt);
680 } else if (type == "file") {
682 if (i.value ()["path"].isString ()) {
683 path = i.value ()["path"].cast<std::string> ();
685 w = new LuaFileChooser (key, title, Gtk::FILE_CHOOSER_ACTION_OPEN, path);
686 } else if (type == "folder") {
688 if (i.value ()["path"].isString ()) {
689 path = i.value ()["path"].cast<std::string> ();
691 w = new LuaFileChooser (key, title, Gtk::FILE_CHOOSER_ACTION_SELECT_FOLDER, path);
692 } else if (type == "color") {
693 w = new LuaColorPicker (key);
697 if (i.value ()["col"].isNumber ()) {
698 w->set_col (i.value ()["col"].cast<int> ());
700 if (i.value ()["colspan"].isNumber ()) {
701 w->set_span (i.value ()["colspan"].cast<int> ());
703 _widgets.push_back(w);
707 _ad.add_button (Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL);
708 _ad.add_button (Gtk::Stock::OK, Gtk::RESPONSE_ACCEPT);
710 Gtk::Table* table = Gtk::manage (new Gtk::Table ());
711 table->set_col_spacings (20);
712 table->set_row_spacings (8);
713 table->signal_size_allocate ().connect (sigc::mem_fun (this, &Dialog::table_size_alloc));
715 _scroller.set_shadow_type(Gtk::SHADOW_NONE);
716 _scroller.set_border_width(0);
717 _scroller.add (*table);
718 _scroller.set_policy (Gtk::POLICY_NEVER, Gtk::POLICY_NEVER);
720 _ad.get_vbox ()->pack_start (_scroller);
725 for (DialogWidgets::const_iterator i = _widgets.begin (); i != _widgets.end (); ++i) {
726 int col = (*i)->col();
727 int cend = col + (*i)->span();
729 if (col < last_end) {
734 std::string const& label = (*i)->label ();
735 if (!label.empty ()) {
736 /* items with implicit label (title) */
737 Gtk::Label* lbl = Gtk::manage (new Gtk::Label (label + ":", Gtk::ALIGN_END, Gtk::ALIGN_CENTER, false));
738 if (cend - col > 1) {
739 table->attach (*lbl, col, col + 1, row, row + 1, Gtk::FILL | Gtk::EXPAND, Gtk::SHRINK);
740 table->attach (*((*i)->widget ()), col + 1, cend, row, row + 1, Gtk::FILL | Gtk::EXPAND, Gtk::SHRINK);
742 Gtk::HBox* hb = Gtk::manage (new Gtk::HBox());
744 hb->pack_start (*lbl, true, false);
745 hb->pack_start (*(*i)->widget (), true, false);
746 table->attach (*hb, col, cend, row, row + 1, Gtk::FILL | Gtk::EXPAND, Gtk::SHRINK);
749 table->attach (*((*i)->widget ()), col, cend, row, row + 1, Gtk::FILL | Gtk::EXPAND, Gtk::SHRINK);
756 for (DialogWidgets::const_iterator i = _widgets.begin (); i != _widgets.end () ; ++i) {
763 Dialog::run (lua_State *L)
765 _ad.get_vbox ()->show_all ();
766 switch (_ad.run ()) {
767 case Gtk::RESPONSE_ACCEPT:
774 luabridge::LuaRef rv (luabridge::newTable (L));
775 for (DialogWidgets::const_iterator i = _widgets.begin (); i != _widgets.end () ; ++i) {
778 luabridge::push (L, rv);
783 Dialog::table_size_alloc (Gtk::Allocation& allocation)
785 /* XXX: consider using 0.75 * screen-height instead of 512 */
786 if (allocation.get_height () > 512) {
787 _scroller.set_policy (Gtk::POLICY_NEVER, Gtk::POLICY_AUTOMATIC);
788 _ad.set_size_request (-1, 512);