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