]> de.git.xonotic.org Git - xonotic/netradiant.git/blob - radiant/dialog.cpp
add an opt-out setting to not write entity and brush number comment on map write
[xonotic/netradiant.git] / radiant / dialog.cpp
1 /*
2    Copyright (C) 1999-2006 Id Software, Inc. and contributors.
3    For a list of contributors, see the accompanying CONTRIBUTORS file.
4
5    This file is part of GtkRadiant.
6
7    GtkRadiant is free software; you can redistribute it and/or modify
8    it under the terms of the GNU General Public License as published by
9    the Free Software Foundation; either version 2 of the License, or
10    (at your option) any later version.
11
12    GtkRadiant is distributed in the hope that it will be useful,
13    but WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15    GNU General Public License for more details.
16
17    You should have received a copy of the GNU General Public License
18    along with GtkRadiant; if not, write to the Free Software
19    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
20  */
21
22 //
23 // Base dialog class, provides a way to run modal dialogs and
24 // set/get the widget values in member variables.
25 //
26 // Leonardo Zide (leo@lokigames.com)
27 //
28
29 #include "dialog.h"
30
31 #include <gtk/gtk.h>
32
33 #include "debugging/debugging.h"
34
35
36 #include "mainframe.h"
37
38 #include <stdlib.h>
39
40 #include "stream/stringstream.h"
41 #include "convert.h"
42 #include "gtkutil/dialog.h"
43 #include "gtkutil/button.h"
44 #include "gtkutil/entry.h"
45 #include "gtkutil/image.h"
46
47 #include "gtkmisc.h"
48
49
50 ui::Entry DialogEntry_new()
51 {
52     auto entry = ui::Entry(ui::New);
53     entry.show();
54     entry.dimensions(64, -1);
55     return entry;
56 }
57
58 class DialogEntryRow {
59 public:
60     DialogEntryRow(ui::Widget row, ui::Entry entry) : m_row(row), m_entry(entry)
61     {
62     }
63
64     ui::Widget m_row;
65     ui::Entry m_entry;
66 };
67
68 DialogEntryRow DialogEntryRow_new(const char *name)
69 {
70     auto alignment = ui::Alignment(0.0, 0.5, 0.0, 0.0);
71     alignment.show();
72
73     auto entry = DialogEntry_new();
74     alignment.add(entry);
75
76     return DialogEntryRow(ui::Widget(DialogRow_new(name, alignment)), entry);
77 }
78
79
80 ui::SpinButton DialogSpinner_new(double value, double lower, double upper, int fraction)
81 {
82     double step = 1.0 / double(fraction);
83     unsigned int digits = 0;
84     for (; fraction > 1; fraction /= 10) {
85         ++digits;
86     }
87     auto spin = ui::SpinButton(ui::Adjustment(value, lower, upper, step, 10, 0), step, digits);
88     spin.show();
89     spin.dimensions(64, -1);
90     return spin;
91 }
92
93 class DialogSpinnerRow {
94 public:
95     DialogSpinnerRow(ui::Widget row, ui::SpinButton spin) : m_row(row), m_spin(spin)
96     {
97     }
98
99     ui::Widget m_row;
100     ui::SpinButton m_spin;
101 };
102
103 DialogSpinnerRow DialogSpinnerRow_new(const char *name, double value, double lower, double upper, int fraction)
104 {
105     auto alignment = ui::Alignment(0.0, 0.5, 0.0, 0.0);
106     alignment.show();
107
108     auto spin = DialogSpinner_new(value, lower, upper, fraction);
109     alignment.add(spin);
110
111     return DialogSpinnerRow(ui::Widget(DialogRow_new(name, alignment)), spin);
112 }
113
114
115 struct BoolToggle {
116     static void Export(const ui::ToggleButton &self, const Callback<void(bool)> &returnz)
117     {
118         returnz(self.active());
119     }
120
121     static void Import(ui::ToggleButton &self, bool value)
122     {
123         self.active(value);
124     }
125 };
126
127 using BoolToggleImportExport = PropertyAdaptor<ui::ToggleButton, bool, BoolToggle>;
128
129 struct IntEntry {
130     static void Export(const ui::Entry &self, const Callback<void(int)> &returnz)
131     {
132         returnz(atoi(gtk_entry_get_text(self)));
133     }
134
135     static void Import(ui::Entry &self, int value)
136     {
137         entry_set_int(self, value);
138     }
139 };
140
141 using IntEntryImportExport = PropertyAdaptor<ui::Entry, int, IntEntry>;
142
143 struct IntRadio {
144     static void Export(const ui::RadioButton &self, const Callback<void(int)> &returnz)
145     {
146         returnz(radio_button_get_active(self));
147     }
148
149     static void Import(ui::RadioButton &self, int value)
150     {
151         radio_button_set_active(self, value);
152     }
153 };
154
155 using IntRadioImportExport = PropertyAdaptor<ui::RadioButton, int, IntRadio>;
156
157 struct IntCombo {
158     static void Export(const ui::ComboBox &self, const Callback<void(int)> &returnz)
159     {
160         returnz(gtk_combo_box_get_active(self));
161     }
162
163     static void Import(ui::ComboBox &self, int value)
164     {
165         gtk_combo_box_set_active(self, value);
166     }
167 };
168
169 using IntComboImportExport = PropertyAdaptor<ui::ComboBox, int, IntCombo>;
170
171 struct IntAdjustment {
172     static void Export(const ui::Adjustment &self, const Callback<void(int)> &returnz)
173     {
174         returnz(int(gtk_adjustment_get_value(self)));
175     }
176
177     static void Import(ui::Adjustment &self, int value)
178     {
179         gtk_adjustment_set_value(self, value);
180     }
181 };
182
183 using IntAdjustmentImportExport = PropertyAdaptor<ui::Adjustment, int, IntAdjustment>;
184
185 struct IntSpinner {
186     static void Export(const ui::SpinButton &self, const Callback<void(int)> &returnz)
187     {
188         returnz(gtk_spin_button_get_value_as_int(self));
189     }
190
191     static void Import(ui::SpinButton &self, int value)
192     {
193         gtk_spin_button_set_value(self, value);
194     }
195 };
196
197 using IntSpinnerImportExport = PropertyAdaptor<ui::SpinButton, int, IntSpinner>;
198
199 struct TextEntry {
200     static void Export(const ui::Entry &self, const Callback<void(const char *)> &returnz)
201     {
202         returnz(gtk_entry_get_text(self));
203     }
204
205     static void Import(ui::Entry &self, const char *value)
206     {
207         self.text(value);
208     }
209 };
210
211 using TextEntryImportExport = PropertyAdaptor<ui::Entry, const char *, TextEntry>;
212
213 struct SizeEntry {
214     static void Export(const ui::Entry &self, const Callback<void(std::size_t)> &returnz)
215     {
216         int value = atoi(gtk_entry_get_text(self));
217         if (value < 0) {
218             value = 0;
219         }
220         returnz(value);
221     }
222
223     static void Import(ui::Entry &self, std::size_t value)
224     {
225         entry_set_int(self, int(value));
226     }
227 };
228
229 using SizeEntryImportExport = PropertyAdaptor<ui::Entry, std::size_t, SizeEntry>;
230
231 struct FloatEntry {
232     static void Export(const ui::Entry &self, const Callback<void(float)> &returnz)
233     {
234         returnz(float(atof(gtk_entry_get_text(self))));
235     }
236
237     static void Import(ui::Entry &self, float value)
238     {
239         entry_set_float(self, value);
240     }
241 };
242
243 using FloatEntryImportExport = PropertyAdaptor<ui::Entry, float, FloatEntry>;
244
245 struct FloatSpinner {
246     static void Export(const ui::SpinButton &self, const Callback<void(float)> &returnz)
247     {
248         returnz(float(gtk_spin_button_get_value(self)));
249     }
250
251     static void Import(ui::SpinButton &self, float value)
252     {
253         gtk_spin_button_set_value(self, value);
254     }
255 };
256
257 using FloatSpinnerImportExport = PropertyAdaptor<ui::SpinButton, float, FloatSpinner>;
258
259
260 template<typename T>
261 class CallbackDialogData : public DLG_DATA {
262     Property<T> m_pWidget;
263     Property<T> m_pData;
264
265 public:
266     CallbackDialogData(const Property<T> &pWidget, const Property<T> &pData)
267             : m_pWidget(pWidget), m_pData(pData)
268     {
269     }
270
271     void release()
272     {
273         delete this;
274     }
275
276     void importData() const
277     {
278         m_pData.get(m_pWidget.set);
279     }
280
281     void exportData() const
282     {
283         m_pWidget.get(m_pData.set);
284     }
285 };
286
287 template<class Widget>
288 void AddDataCustom(DialogDataList &self, typename Widget::Type widget, Property<typename Widget::Other> const &property)
289 {
290     using Self = typename Widget::Type;
291     using T = typename Widget::Other;
292     using native = typename std::remove_pointer<typename Self::native>::type;
293     struct Wrapper {
294         static void Export(const native &self, const Callback<void(T)> &returnz)
295         {
296             native *p = &const_cast<native &>(self);
297             auto widget = Self::from(p);
298             Widget::Get::thunk_(widget, returnz);
299         }
300
301         static void Import(native &self, T value)
302         {
303             native *p = &self;
304             auto widget = Self::from(p);
305             Widget::Set::thunk_(widget, value);
306         }
307     };
308     self.push_back(new CallbackDialogData<typename Widget::Other>(
309             make_property<PropertyAdaptor<native, T, Wrapper>>(*static_cast<native *>(widget)),
310             property
311     ));
312 }
313
314 template<class Widget, class D>
315 void AddData(DialogDataList &self, typename Widget::Type widget, D &data)
316 {
317     AddDataCustom<Widget>(self, widget, make_property<PropertyAdaptor<D, typename Widget::Other>>(data));
318 }
319
320 // =============================================================================
321 // Dialog class
322
323 Dialog::Dialog() : m_window(ui::null), m_parent(ui::null)
324 {
325 }
326
327 Dialog::~Dialog()
328 {
329     for (DialogDataList::iterator i = m_data.begin(); i != m_data.end(); ++i) {
330         (*i)->release();
331     }
332
333     ASSERT_MESSAGE(!m_window, "dialog window not destroyed");
334 }
335
336 void Dialog::ShowDlg()
337 {
338     ASSERT_MESSAGE(m_window, "dialog was not constructed");
339     importData();
340     m_window.show();
341 }
342
343 void Dialog::HideDlg()
344 {
345     ASSERT_MESSAGE(m_window, "dialog was not constructed");
346     exportData();
347     m_window.hide();
348 }
349
350 static gint delete_event_callback(ui::Widget widget, GdkEvent *event, gpointer data)
351 {
352     reinterpret_cast<Dialog *>( data )->HideDlg();
353     reinterpret_cast<Dialog *>( data )->EndModal(eIDCANCEL);
354     return TRUE;
355 }
356
357 void Dialog::Create()
358 {
359     ASSERT_MESSAGE(!m_window, "dialog cannot be constructed");
360
361     m_window = BuildDialog();
362     m_window.connect("delete_event", G_CALLBACK(delete_event_callback), this);
363 }
364
365 void Dialog::Destroy()
366 {
367     ASSERT_MESSAGE(m_window, "dialog cannot be destroyed");
368
369     m_window.destroy();
370     m_window = ui::Window{ui::null};
371 }
372
373
374 void Dialog::AddBoolToggleData(ui::ToggleButton widget, Property<bool> const &cb)
375 {
376     AddDataCustom<BoolToggleImportExport>(m_data, widget, cb);
377 }
378
379 void Dialog::AddIntRadioData(ui::RadioButton widget, Property<int> const &cb)
380 {
381     AddDataCustom<IntRadioImportExport>(m_data, widget, cb);
382 }
383
384 void Dialog::AddTextEntryData(ui::Entry widget, Property<const char *> const &cb)
385 {
386     AddDataCustom<TextEntryImportExport>(m_data, widget, cb);
387 }
388
389 void Dialog::AddIntEntryData(ui::Entry widget, Property<int> const &cb)
390 {
391     AddDataCustom<IntEntryImportExport>(m_data, widget, cb);
392 }
393
394 void Dialog::AddSizeEntryData(ui::Entry widget, Property<std::size_t> const &cb)
395 {
396     AddDataCustom<SizeEntryImportExport>(m_data, widget, cb);
397 }
398
399 void Dialog::AddFloatEntryData(ui::Entry widget, Property<float> const &cb)
400 {
401     AddDataCustom<FloatEntryImportExport>(m_data, widget, cb);
402 }
403
404 void Dialog::AddFloatSpinnerData(ui::SpinButton widget, Property<float> const &cb)
405 {
406     AddDataCustom<FloatSpinnerImportExport>(m_data, widget, cb);
407 }
408
409 void Dialog::AddIntSpinnerData(ui::SpinButton widget, Property<int> const &cb)
410 {
411     AddDataCustom<IntSpinnerImportExport>(m_data, widget, cb);
412 }
413
414 void Dialog::AddIntAdjustmentData(ui::Adjustment widget, Property<int> const &cb)
415 {
416     AddDataCustom<IntAdjustmentImportExport>(m_data, widget, cb);
417 }
418
419 void Dialog::AddIntComboData(ui::ComboBox widget, Property<int> const &cb)
420 {
421     AddDataCustom<IntComboImportExport>(m_data, widget, cb);
422 }
423
424
425 void Dialog::AddDialogData(ui::ToggleButton widget, bool &data)
426 {
427     AddData<BoolToggleImportExport>(m_data, widget, data);
428 }
429
430 void Dialog::AddDialogData(ui::RadioButton widget, int &data)
431 {
432     AddData<IntRadioImportExport>(m_data, widget, data);
433 }
434
435 void Dialog::AddDialogData(ui::Entry widget, CopiedString &data)
436 {
437     AddData<TextEntryImportExport>(m_data, widget, data);
438 }
439
440 void Dialog::AddDialogData(ui::Entry widget, int &data)
441 {
442     AddData<IntEntryImportExport>(m_data, widget, data);
443 }
444
445 void Dialog::AddDialogData(ui::Entry widget, std::size_t &data)
446 {
447     AddData<SizeEntryImportExport>(m_data, widget, data);
448 }
449
450 void Dialog::AddDialogData(ui::Entry widget, float &data)
451 {
452     AddData<FloatEntryImportExport>(m_data, widget, data);
453 }
454
455 void Dialog::AddDialogData(ui::SpinButton widget, float &data)
456 {
457     AddData<FloatSpinnerImportExport>(m_data, widget, data);
458 }
459
460 void Dialog::AddDialogData(ui::SpinButton widget, int &data)
461 {
462     AddData<IntSpinnerImportExport>(m_data, widget, data);
463 }
464
465 void Dialog::AddDialogData(ui::Adjustment widget, int &data)
466 {
467     AddData<IntAdjustmentImportExport>(m_data, widget, data);
468 }
469
470 void Dialog::AddDialogData(ui::ComboBox widget, int &data)
471 {
472     AddData<IntComboImportExport>(m_data, widget, data);
473 }
474
475 void Dialog::exportData()
476 {
477     for (DialogDataList::iterator i = m_data.begin(); i != m_data.end(); ++i) {
478         (*i)->exportData();
479     }
480 }
481
482 void Dialog::importData()
483 {
484     for (DialogDataList::iterator i = m_data.begin(); i != m_data.end(); ++i) {
485         (*i)->importData();
486     }
487 }
488
489 void Dialog::EndModal(EMessageBoxReturn code)
490 {
491     m_modal.loop = 0;
492     m_modal.ret = code;
493 }
494
495 EMessageBoxReturn Dialog::DoModal()
496 {
497     importData();
498
499     PreModal();
500
501     EMessageBoxReturn ret = modal_dialog_show(m_window, m_modal);
502     ASSERT_TRUE((bool) m_window);
503     if (ret == eIDOK) {
504         exportData();
505     }
506
507     m_window.hide();
508
509     PostModal(m_modal.ret);
510
511     return m_modal.ret;
512 }
513
514
515 ui::CheckButton Dialog::addCheckBox(ui::VBox vbox, const char *name, const char *flag, Property<bool> const &cb)
516 {
517     auto check = ui::CheckButton(flag);
518     check.show();
519     AddBoolToggleData(check, cb);
520
521     DialogVBox_packRow(vbox, ui::Widget(DialogRow_new(name, check)));
522     return check;
523 }
524
525 ui::CheckButton Dialog::addCheckBox(ui::VBox vbox, const char *name, const char *flag, bool &data)
526 {
527     return addCheckBox(vbox, name, flag, make_property(data));
528 }
529
530 void Dialog::addCombo(ui::VBox vbox, const char *name, StringArrayRange values, Property<int> const &cb)
531 {
532     auto alignment = ui::Alignment(0.0, 0.5, 0.0, 0.0);
533     alignment.show();
534     {
535         auto combo = ui::ComboBoxText(ui::New);
536
537         for (StringArrayRange::Iterator i = values.first; i != values.last; ++i) {
538             gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combo), *i);
539         }
540
541         AddIntComboData(combo, cb);
542
543         combo.show();
544         alignment.add(combo);
545     }
546
547     auto row = DialogRow_new(name, alignment);
548     DialogVBox_packRow(vbox, row);
549 }
550
551 void Dialog::addCombo(ui::VBox vbox, const char *name, int &data, StringArrayRange values)
552 {
553     addCombo(vbox, name, values, make_property(data));
554 }
555
556 void
557 Dialog::addSlider(ui::VBox vbox, const char *name, int &data, gboolean draw_value, const char *low, const char *high,
558                   double value, double lower, double upper, double step_increment, double page_increment)
559 {
560 #if 0
561     if ( draw_value == FALSE ) {
562         auto hbox2 = ui::HBox( FALSE, 0 );
563         hbox2.show();
564         vbox.pack_start( hbox2 , FALSE, FALSE, 0 );
565         {
566             ui::Widget label = ui::Label( low );
567             label.show();
568             hbox2.pack_start( label, FALSE, FALSE, 0 );
569         }
570         {
571             ui::Widget label = ui::Label( high );
572             label.show();
573             hbox2.pack_end(label, FALSE, FALSE, 0);
574         }
575     }
576 #endif
577
578     // adjustment
579     auto adj = ui::Adjustment(value, lower, upper, step_increment, page_increment, 0);
580     AddIntAdjustmentData(adj, make_property(data));
581
582     // scale
583     auto alignment = ui::Alignment(0.0, 0.5, 1.0, 0.0);
584     alignment.show();
585
586     ui::Widget scale = ui::HScale(adj);
587     gtk_scale_set_value_pos(GTK_SCALE(scale), GTK_POS_LEFT);
588     scale.show();
589     alignment.add(scale);
590
591     gtk_scale_set_draw_value(GTK_SCALE(scale), draw_value);
592     gtk_scale_set_digits(GTK_SCALE(scale), 0);
593
594     auto row = DialogRow_new(name, alignment);
595     DialogVBox_packRow(vbox, row);
596 }
597
598 void Dialog::addRadio(ui::VBox vbox, const char *name, StringArrayRange names, Property<int> const &cb)
599 {
600     auto alignment = ui::Alignment(0.0, 0.5, 0.0, 0.0);
601     alignment.show();;
602     {
603         RadioHBox radioBox = RadioHBox_new(names);
604         alignment.add(radioBox.m_hbox);
605         AddIntRadioData(radioBox.m_radio, cb);
606     }
607
608     auto row = DialogRow_new(name, alignment);
609     DialogVBox_packRow(vbox, row);
610 }
611
612 void Dialog::addRadio(ui::VBox vbox, const char *name, int &data, StringArrayRange names)
613 {
614     addRadio(vbox, name, names, make_property(data));
615 }
616
617 void Dialog::addRadioIcons(ui::VBox vbox, const char *name, StringArrayRange icons, Property<int> const &cb)
618 {
619     auto table = ui::Table(2, icons.last - icons.first, FALSE);
620     table.show();
621
622     gtk_table_set_row_spacings(table, 5);
623     gtk_table_set_col_spacings(table, 5);
624
625     GSList *group = 0;
626     ui::RadioButton radio{ui::null};
627     for (StringArrayRange::Iterator icon = icons.first; icon != icons.last; ++icon) {
628         guint pos = static_cast<guint>( icon - icons.first );
629         auto image = new_local_image(*icon);
630         image.show();
631         table.attach(image, {pos, pos + 1, 0, 1}, {0, 0});
632
633         radio = ui::RadioButton::from(gtk_radio_button_new(group));
634         radio.show();
635         table.attach(radio, {pos, pos + 1, 1, 2}, {0, 0});
636
637         group = gtk_radio_button_get_group(GTK_RADIO_BUTTON(radio));
638     }
639
640     AddIntRadioData(radio, cb);
641
642     DialogVBox_packRow(vbox, DialogRow_new(name, table));
643 }
644
645 void Dialog::addRadioIcons(ui::VBox vbox, const char *name, int &data, StringArrayRange icons)
646 {
647     addRadioIcons(vbox, name, icons, make_property(data));
648 }
649
650 ui::Widget Dialog::addIntEntry(ui::VBox vbox, const char *name, Property<int> const &cb)
651 {
652     DialogEntryRow row(DialogEntryRow_new(name));
653     AddIntEntryData(row.m_entry, cb);
654     DialogVBox_packRow(vbox, row.m_row);
655     return row.m_row;
656 }
657
658 ui::Widget Dialog::addSizeEntry(ui::VBox vbox, const char *name, Property<std::size_t> const &cb)
659 {
660     DialogEntryRow row(DialogEntryRow_new(name));
661     AddSizeEntryData(row.m_entry, cb);
662     DialogVBox_packRow(vbox, row.m_row);
663     return row.m_row;
664 }
665
666 ui::Widget Dialog::addFloatEntry(ui::VBox vbox, const char *name, Property<float> const &cb)
667 {
668     DialogEntryRow row(DialogEntryRow_new(name));
669     AddFloatEntryData(row.m_entry, cb);
670     DialogVBox_packRow(vbox, row.m_row);
671     return row.m_row;
672 }
673
674 ui::Widget
675 Dialog::addPathEntry(ui::VBox vbox, const char *name, bool browse_directory, Property<const char *> const &cb)
676 {
677     PathEntry pathEntry = PathEntry_new();
678     pathEntry.m_button.connect("clicked", G_CALLBACK(
679                                        browse_directory ? button_clicked_entry_browse_directory : button_clicked_entry_browse_file),
680                                pathEntry.m_entry);
681
682     AddTextEntryData(pathEntry.m_entry, cb);
683
684     auto row = DialogRow_new(name, ui::Widget(pathEntry.m_frame));
685     DialogVBox_packRow(vbox, row);
686
687     return row;
688 }
689
690 ui::Widget Dialog::addPathEntry(ui::VBox vbox, const char *name, CopiedString &data, bool browse_directory)
691 {
692     return addPathEntry(vbox, name, browse_directory, make_property<CopiedString, const char *>(data));
693 }
694
695 ui::SpinButton
696 Dialog::addSpinner(ui::VBox vbox, const char *name, double value, double lower, double upper, Property<int> const &cb)
697 {
698     DialogSpinnerRow row(DialogSpinnerRow_new(name, value, lower, upper, 1));
699     AddIntSpinnerData(row.m_spin, cb);
700     DialogVBox_packRow(vbox, row.m_row);
701     return row.m_spin;
702 }
703
704 ui::SpinButton Dialog::addSpinner(ui::VBox vbox, const char *name, int &data, double value, double lower, double upper)
705 {
706     return addSpinner(vbox, name, value, lower, upper, make_property(data));
707 }
708
709 ui::SpinButton
710 Dialog::addSpinner(ui::VBox vbox, const char *name, double value, double lower, double upper, Property<float> const &cb)
711 {
712     DialogSpinnerRow row(DialogSpinnerRow_new(name, value, lower, upper, 10));
713     AddFloatSpinnerData(row.m_spin, cb);
714     DialogVBox_packRow(vbox, row.m_row);
715     return row.m_spin;
716 }