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