Optimize automation-event process splitting
[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 <algorithm>
20
21 #include <gtkmm.h>
22
23 #include "ardour/dB.h"
24 #include "ardour/rc_configuration.h"
25
26 #include "gtkmm2ext/utils.h"
27
28 #include "widgets/ardour_dropdown.h"
29 #include "widgets/slider_controller.h"
30
31 #include "stripable_colorpicker.h"
32 #include "ardour_dialog.h"
33 #include "luadialog.h"
34 #include "splash.h"
35 #include "utils.h"
36
37 using namespace LuaDialog;
38
39 /*******************************************************************************
40  * Simple Message Dialog
41  */
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)
44 {
45         _message_dialog.set_title (title);
46 }
47
48 int
49 Message::run ()
50 {
51
52         bool splash_pushed = false;
53         Splash* spl = Splash::instance();
54         if (spl && spl->is_visible()) {
55                 spl->pop_back_for (_message_dialog);
56                 splash_pushed = true;
57         }
58
59         int rv = _message_dialog.run ();
60         _message_dialog.hide ();
61
62         if (splash_pushed) {
63                 spl = Splash::instance();
64                 if (spl) {
65                         spl->pop_front();
66                 }
67         }
68
69         switch (rv) {
70                 case Gtk::RESPONSE_OK:
71                         return 0;
72                 case Gtk::RESPONSE_CANCEL:
73                         return 1;
74                 case Gtk::RESPONSE_CLOSE:
75                         return 2;
76                 case Gtk::RESPONSE_YES:
77                         return 3;
78                 case Gtk::RESPONSE_NO:
79                         return 4;
80                 default:
81                         break;
82         }
83         return -1;
84 }
85
86 Gtk::ButtonsType
87 Message::to_gtk_bt (ButtonType bt)
88 {
89         switch (bt) {
90                 case OK:
91                         return Gtk::BUTTONS_OK;
92                 case Close:
93                         return Gtk::BUTTONS_CLOSE;
94                 case Cancel:
95                         return Gtk::BUTTONS_CANCEL;
96                 case Yes_No:
97                         return Gtk::BUTTONS_YES_NO;
98                 case OK_Cancel:
99                         return Gtk::BUTTONS_OK_CANCEL;
100         }
101         assert (0);
102         return Gtk::BUTTONS_OK;
103 }
104
105 Gtk::MessageType
106 Message::to_gtk_mt (MessageType mt)
107 {
108         switch (mt) {
109                 case Info:
110                         return Gtk::MESSAGE_INFO;
111                 case Warning:
112                         return Gtk::MESSAGE_WARNING;
113                 case Question:
114                         return Gtk::MESSAGE_QUESTION;
115                 case Error:
116                         return Gtk::MESSAGE_ERROR;
117         }
118         assert (0);
119         return Gtk::MESSAGE_INFO;
120 }
121
122
123 /* *****************************************************************************
124  * Lua Dialog Widgets
125  */
126
127 class LuaDialogLabel : public LuaDialogWidget
128 {
129 public:
130         LuaDialogLabel (std::string const& title, Gtk::AlignmentEnum xalign)
131                 : LuaDialogWidget ("", "", 0, 2)
132                 , _lbl (title, xalign, Gtk::ALIGN_CENTER, false)
133         { }
134
135         Gtk::Widget* widget ()
136         {
137                 return &_lbl;
138         }
139
140         void assign (luabridge::LuaRef* rv) const { }
141 protected:
142         Gtk::Label _lbl;
143 };
144
145
146 class LuaDialogHeading : public LuaDialogLabel
147 {
148 public:
149         LuaDialogHeading (std::string const& title, Gtk::AlignmentEnum xalign)
150         : LuaDialogLabel ("<b>" + title + "</b>", xalign)
151         {
152                 _lbl.set_use_markup ();
153         }
154 };
155
156 class LuaHSeparator : public LuaDialogWidget
157 {
158 public:
159         LuaHSeparator ()
160                 : LuaDialogWidget ("", "", 0, 2)
161         {}
162
163         Gtk::Widget* widget ()
164         {
165                 return &_sep;
166         }
167
168         void assign (luabridge::LuaRef* rv) const { }
169 protected:
170         Gtk::HSeparator _sep;
171 };
172
173 class LuaColorPicker : public LuaDialogWidget
174 {
175 public:
176         LuaColorPicker (std::string const& key)
177                 : LuaDialogWidget (key, "", 0, 1)
178         {}
179
180         Gtk::Widget* widget ()
181         {
182                 return &_cs;
183         }
184         void assign (luabridge::LuaRef* rv) const {
185                 uint32_t rgba = ARDOUR_UI_UTILS::gdk_color_to_rgba(_cs.get_color());
186                 (*rv)[_key] = rgba;
187         }
188 protected:
189         Gtk::ColorButton _cs;
190 };
191
192 class LuaDialogCheckbox : public LuaDialogWidget
193 {
194 public:
195         LuaDialogCheckbox (std::string const& key, std::string const& title, bool on)
196                 : LuaDialogWidget (key, "", 1, 1)
197         {
198                 if (!title.empty ()) {
199                         _cb.add_label (title, false, 0);
200                 }
201                 _cb.set_active (on);
202         }
203
204         Gtk::Widget* widget ()
205         {
206                 return &_cb;
207         }
208
209         void assign (luabridge::LuaRef* rv) const
210         {
211                 (*rv)[_key] = _cb.get_active ();
212         }
213
214 protected:
215         Gtk::CheckButton _cb;
216 };
217
218 class LuaDialogEntry : public LuaDialogWidget
219 {
220 public:
221         LuaDialogEntry (std::string const& key, std::string const& title, std::string const& dflt)
222                 : LuaDialogWidget (key, title)
223         {
224                 _entry.set_text (dflt);
225         }
226
227         Gtk::Widget* widget ()
228         {
229                 return &_entry;
230         }
231
232         void assign (luabridge::LuaRef* rv) const
233         {
234                 (*rv)[_key] = std::string (_entry.get_text ());
235         }
236
237 protected:
238         Gtk::Entry _entry;
239 };
240
241 class LuaDialogFader : public LuaDialogWidget
242 {
243 public:
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)
247         {
248                 _db_slider = Gtk::manage (new ArdourWidgets::HSliderController (&_db_adjustment, boost::shared_ptr<PBD::Controllable> (), 220, 18));
249
250                 _fader_centering_box.pack_start (*_db_slider, true, false);
251
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);
257
258                 Gtkmm2ext::set_size_request_to_display_given_text (_db_display, "-99.00", 12, 0);
259
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);
263
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 ()));
266                 db_changed ();
267         }
268
269         Gtk::Widget* widget ()
270         {
271                 return &_box;
272         }
273
274         void assign (luabridge::LuaRef* rv) const
275         {
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);
278         }
279
280 protected:
281         void db_changed ()
282         {
283                 double const val = ARDOUR::slider_position_to_gain_with_max (_db_adjustment.get_value (), ARDOUR::Config->get_max_gain ());
284                 char buf[16];
285                 if (val == 0.0) {
286                         snprintf (buf, sizeof (buf), "-inf");
287                 } else {
288                         snprintf (buf, sizeof (buf), "%.2f", accurate_coefficient_to_dB (val));
289                 }
290                 _db_display.set_text (buf);
291         }
292
293         void on_activate ()
294         {
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 ()));
298         }
299
300         bool on_key_press (GdkEventKey* ev)
301         {
302                 if (ARDOUR_UI_UTILS::key_is_legal_for_numeric_entry (ev->keyval)) {
303                         return false;
304                 }
305                 return true;
306         }
307
308         Gtk::Adjustment _db_adjustment;
309         ArdourWidgets::HSliderController* _db_slider;
310         Gtk::Entry _db_display;
311         Gtk::HBox _box;
312         Gtk::VBox _fader_centering_box;
313 };
314
315 class LuaDialogSlider : public LuaDialogWidget
316 {
317 public:
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)
321                 , _hscale (_adj)
322         {
323                 _hscale.set_digits (digits);
324                 _hscale.set_draw_value (true);
325                 _hscale.set_value_pos (Gtk::POS_TOP);
326
327                 if (!scalepoints.isTable ()) {
328                         return;
329                 }
330
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> ());
335                 }
336         }
337
338         Gtk::Widget* widget ()
339         {
340                 return &_hscale;
341         }
342
343         void assign (luabridge::LuaRef* rv) const
344         {
345                 (*rv)[_key] = _adj.get_value ();
346         }
347
348 protected:
349         Gtk::Adjustment _adj;
350         Gtk::HScale _hscale;
351 };
352
353 class LuaDialogSpinBox : public LuaDialogWidget
354 {
355 public:
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)
359                 , _spin (_adj)
360         {
361                 _spin.set_digits (digits);
362         }
363
364         Gtk::Widget* widget ()
365         {
366                 return &_spin;
367         }
368
369         void assign (luabridge::LuaRef* rv) const
370         {
371                 (*rv)[_key] = _adj.get_value ();
372         }
373
374 protected:
375         Gtk::Adjustment _adj;
376         Gtk::SpinButton _spin;
377 };
378
379 class LuaDialogRadio : public LuaDialogWidget
380 {
381 public:
382         LuaDialogRadio (std::string const& key, std::string const& title, luabridge::LuaRef values, std::string const& dflt)
383                 : LuaDialogWidget (key, title)
384                 , _rv (0)
385         {
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
396                                                 ) , false);
397
398                         if (key == dflt) {
399                                 rb->set_active ();
400                         }
401                 }
402         }
403
404         ~LuaDialogRadio ()
405         {
406                 for (std::vector<luabridge::LuaRef*>::const_iterator i = _refs.begin (); i != _refs.end (); ++i) {
407                         delete *i;
408                 }
409                 _refs.clear ();
410         }
411
412         Gtk::Widget* widget ()
413         {
414                 return &_hbox;
415         }
416
417         void assign (luabridge::LuaRef* rv) const
418         {
419                 if (_rv) {
420                         (*rv)[_key] = *_rv;
421                 } else {
422                         (*rv)[_key] = luabridge::Nil ();
423                 }
424         }
425
426 protected:
427         LuaDialogRadio (LuaDialogRadio const&); // prevent cc
428         void rb_toggled (Gtk::RadioButton* b, luabridge::LuaRef* rv) {
429                 if (b->get_active ()) {
430                         _rv = rv;
431                 }
432         }
433
434         Gtk::HBox _hbox;
435         Gtk::RadioButtonGroup _group;
436         std::vector<luabridge::LuaRef*> _refs;
437         luabridge::LuaRef* _rv;
438 };
439
440 class LuaDialogDropDown : public LuaDialogWidget
441 {
442 public:
443         LuaDialogDropDown (std::string const& key, std::string const& title, luabridge::LuaRef values, std::string const& dflt)
444                 : LuaDialogWidget (key, title)
445                 , _rv (0)
446         {
447                 populate (_dd.items (), values, dflt);
448         }
449
450         ~LuaDialogDropDown ()
451         {
452                 for (std::vector<luabridge::LuaRef*>::const_iterator i = _refs.begin (); i != _refs.end (); ++i) {
453                         delete *i;
454                 }
455                 _refs.clear ();
456         }
457
458         Gtk::Widget* widget ()
459         {
460                 return &_dd;
461         }
462
463         void assign (luabridge::LuaRef* rv) const
464         {
465                 if (_rv) {
466                         (*rv)[_key] = *_rv;
467                 } else {
468                         (*rv)[_key] = luabridge::Nil ();
469                 }
470         }
471
472 protected:
473         void populate (Gtk::Menu_Helpers::MenuList& items, luabridge::LuaRef values, std::string const& dflt)
474         {
475                 using namespace Gtk::Menu_Helpers;
476                 std::vector<std::string> keys;
477
478                 for (luabridge::Iterator i (values); !i.isNil (); ++i) {
479                         if (!i.key ().isString ())  { continue; }
480                         keys.push_back (i.key ().cast<std::string> ());
481                 }
482
483                 std::sort (keys.begin(), keys.end());
484
485                 for (std::vector<std::string>::const_iterator i = keys.begin (); i != keys.end(); ++i) {
486                         std::string key = *i;
487
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);
492                                 continue;
493                         }
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)));
498
499                         if (!_rv || key == dflt) {
500                                 _rv = ref;
501                                 _dd.set_text (key);
502                         }
503                 }
504         }
505
506         void dd_select (std::string const& key, luabridge::LuaRef* rv) {
507                 _dd.set_text (key);
508                 _rv = rv;
509         }
510
511         ArdourWidgets::ArdourDropdown _dd;
512         std::vector<luabridge::LuaRef*> _refs;
513         luabridge::LuaRef* _rv;
514 };
515
516 class LuaFileChooser : public LuaDialogWidget
517 {
518 public:
519         LuaFileChooser (std::string const& key, std::string const& title, Gtk::FileChooserAction a, const std::string& path)
520                 : LuaDialogWidget (key, title)
521                 , _fc (a)
522         {
523                 if (!path.empty ()) {
524                         switch (a) {
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);
529                                         break;
530                                 case Gtk::FILE_CHOOSER_ACTION_CREATE_FOLDER:
531                                         break;
532                         }
533                 }
534         }
535
536         Gtk::Widget* widget ()
537         {
538                 return &_fc;
539         }
540
541         void assign (luabridge::LuaRef* rv) const
542         {
543                 (*rv)[_key] = std::string (_fc.get_filename ());
544         }
545
546 protected:
547         Gtk::FileChooserButton _fc;
548 };
549
550
551
552 /*******************************************************************************
553  * Lua Parameter Dialog
554  */
555 Dialog::Dialog (std::string const& title, luabridge::LuaRef lr)
556         :_ad (title, true, false)
557         , _title (title)
558 {
559         if (!lr.isTable ()) {
560                 return;
561         }
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; }
567
568                 std::string title = i.value ()["title"].cast<std::string> ();
569                 std::string type = i.value ()["type"].cast<std::string> ();
570                 std::string key;
571
572                 if (i.value ()["key"].isString ()) {
573                         key = i.value ()["key"].cast<std::string> ();
574                 }
575
576                 LuaDialogWidget* w = NULL;
577
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;
586                                 }
587                         }
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;
597                                 }
598                         }
599                         w = new LuaDialogLabel (title, xalign);
600                 } else if (type == "hseparator") {
601                         w = new LuaHSeparator ();
602                 }
603                 /* the following widgets do require a key */
604                 else if (key.empty ()) {
605                         continue;
606                 }
607                 else if (type == "checkbox") {
608                         bool dflt = false;
609                         if (i.value ()["default"].isBoolean ()) {
610                                 dflt = i.value ()["default"].cast<bool> ();
611                         }
612                         w = new LuaDialogCheckbox (key, title, dflt);
613                 } else if (type == "entry") {
614                         std::string dflt;
615                         if (i.value ()["default"].isString ()) {
616                                 dflt = i.value ()["default"].cast<std::string> ();
617                         }
618                         w = new LuaDialogEntry (key, title, dflt);
619                 } else if (type == "radio") {
620                         std::string dflt;
621                         if (!i.value ()["values"].isTable ()) {
622                                 continue;
623                         }
624                         if (i.value ()["default"].isString ()) {
625                                 dflt = i.value ()["default"].cast<std::string> ();
626                         }
627                         w = new LuaDialogRadio (key, title, i.value ()["values"], dflt);
628                 } else if (type == "fader") {
629                         double dflt = 0;
630                         if (i.value ()["default"].isNumber ()) {
631                                 dflt = i.value ()["default"].cast<double> ();
632                         }
633                         w = new LuaDialogFader (key, title, dflt);
634                 } else if (type == "slider") {
635                         double lower, upper, dflt;
636                         int digits = 0;
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> ();
643                         } else {
644                                 dflt = lower;
645                         }
646                         if (i.value ()["digits"].isNumber ()) {
647                                 digits = i.value ()["digits"].cast<int> ();
648                         }
649                         w = new LuaDialogSlider (key, title, lower, upper, dflt, digits, i.value ()["scalepoints"]);
650                 } else if (type == "number") {
651                         double lower, upper, dflt, step;
652                         int digits = 0;
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> ();
659                         } else {
660                                 dflt = lower;
661                         }
662                         if (i.value ()["step"].isNumber ()) {
663                                 step = i.value ()["step"].cast<double> ();
664                         } else {
665                                 step = 1.0;
666                         }
667                         if (i.value ()["digits"].isNumber ()) {
668                                 digits = i.value ()["digits"].cast<int> ();
669                         }
670                         w = new LuaDialogSpinBox (key, title, lower, upper, dflt, step, digits);
671                 } else if (type == "dropdown") {
672                         std::string dflt;
673                         if (!i.value ()["values"].isTable ()) {
674                                 continue;
675                         }
676                         if (i.value ()["default"].isString ()) {
677                                 dflt = i.value ()["default"].cast<std::string> ();
678                         }
679                         w = new LuaDialogDropDown (key, title, i.value ()["values"], dflt);
680                 } else if (type == "file") {
681                         std::string path;
682                         if (i.value ()["path"].isString ()) {
683                                 path = i.value ()["path"].cast<std::string> ();
684                         }
685                         w = new LuaFileChooser (key, title, Gtk::FILE_CHOOSER_ACTION_OPEN, path);
686                 } else if (type == "folder") {
687                         std::string path;
688                         if (i.value ()["path"].isString ()) {
689                                 path = i.value ()["path"].cast<std::string> ();
690                         }
691                         w = new LuaFileChooser (key, title, Gtk::FILE_CHOOSER_ACTION_SELECT_FOLDER, path);
692                 } else if (type == "color") {
693                         w = new LuaColorPicker (key);
694                 }
695
696                 if (w) {
697                         if (i.value ()["col"].isNumber ()) {
698                                 w->set_col (i.value ()["col"].cast<int> ());
699                         }
700                         if (i.value ()["colspan"].isNumber ()) {
701                                 w->set_span (i.value ()["colspan"].cast<int> ());
702                         }
703                         _widgets.push_back(w);
704                 }
705         }
706
707         _ad.add_button (Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL);
708         _ad.add_button (Gtk::Stock::OK, Gtk::RESPONSE_ACCEPT);
709
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));
714
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);
719
720         _ad.get_vbox ()->pack_start (_scroller);
721
722         int row = 0;
723         int last_end = -1;
724
725         for (DialogWidgets::const_iterator i = _widgets.begin (); i != _widgets.end (); ++i) {
726                 int col = (*i)->col();
727                 int cend = col + (*i)->span();
728
729                 if (col < last_end) {
730                         ++row;
731                 }
732                 last_end = cend;
733
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);
741                         } else {
742                                 Gtk::HBox* hb = Gtk::manage (new Gtk::HBox());
743                                 hb->set_spacing(4);
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);
747                         }
748                 } else {
749                         table->attach (*((*i)->widget ()), col, cend, row, row + 1, Gtk::FILL | Gtk::EXPAND, Gtk::SHRINK);
750                 }
751         }
752 }
753
754 Dialog::~Dialog ()
755 {
756         for (DialogWidgets::const_iterator i = _widgets.begin (); i != _widgets.end () ; ++i) {
757                 delete *i;
758         }
759         _widgets.clear ();
760 }
761
762 int
763 Dialog::run (lua_State *L)
764 {
765         _ad.get_vbox ()->show_all ();
766         switch (_ad.run ()) {
767                 case Gtk::RESPONSE_ACCEPT:
768                         break;
769                 default:
770                         lua_pushnil (L);
771                         return 1;
772         }
773
774         luabridge::LuaRef rv (luabridge::newTable (L));
775         for (DialogWidgets::const_iterator i = _widgets.begin (); i != _widgets.end () ; ++i) {
776                 (*i)->assign (&rv);
777         }
778         luabridge::push (L, rv);
779         return 1;
780 }
781
782 void
783 Dialog::table_size_alloc (Gtk::Allocation& allocation)
784 {
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);
789         }
790 }