]> de.git.xonotic.org Git - xonotic/netradiant.git/blob - radiant/entityinspector.cpp
load notex textures from a builtin vfs
[xonotic/netradiant.git] / radiant / entityinspector.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 #include "entityinspector.h"
23
24 #include "debugging/debugging.h"
25 #include <gtk/gtk.h>
26
27 #include "ientity.h"
28 #include "ifilesystem.h"
29 #include "imodel.h"
30 #include "iscenegraph.h"
31 #include "iselection.h"
32 #include "iundo.h"
33
34 #include <map>
35 #include <set>
36 #include <gdk/gdkkeysyms.h>
37 #include <uilib/uilib.h>
38
39
40 #include "os/path.h"
41 #include "eclasslib.h"
42 #include "scenelib.h"
43 #include "generic/callback.h"
44 #include "os/file.h"
45 #include "stream/stringstream.h"
46 #include "moduleobserver.h"
47 #include "convert.h"
48 #include "stringio.h"
49
50 #include "gtkutil/accelerator.h"
51 #include "gtkutil/dialog.h"
52 #include "gtkutil/filechooser.h"
53 #include "gtkutil/messagebox.h"
54 #include "gtkutil/nonmodal.h"
55 #include "gtkutil/button.h"
56 #include "gtkutil/entry.h"
57 #include "gtkutil/container.h"
58
59 #include "qe3.h"
60 #include "gtkmisc.h"
61 #include "gtkdlgs.h"
62 #include "entity.h"
63 #include "mainframe.h"
64 #include "textureentry.h"
65 #include "groupdialog.h"
66
67 ui::Entry numeric_entry_new()
68 {
69     auto entry = ui::Entry(ui::New);
70     entry.show();
71     entry.dimensions(64, -1);
72     return entry;
73 }
74
75 namespace {
76     typedef std::map<CopiedString, CopiedString> KeyValues;
77     KeyValues g_selectedKeyValues;
78     KeyValues g_selectedDefaultKeyValues;
79 }
80
81 const char *SelectedEntity_getValueForKey(const char *key)
82 {
83     {
84         KeyValues::const_iterator i = g_selectedKeyValues.find(key);
85         if (i != g_selectedKeyValues.end()) {
86             return (*i).second.c_str();
87         }
88     }
89     {
90         KeyValues::const_iterator i = g_selectedDefaultKeyValues.find(key);
91         if (i != g_selectedDefaultKeyValues.end()) {
92             return (*i).second.c_str();
93         }
94     }
95     return "";
96 }
97
98 void Scene_EntitySetKeyValue_Selected_Undoable(const char *key, const char *value)
99 {
100     StringOutputStream command(256);
101     command << "entitySetKeyValue -key " << makeQuoted(key) << " -value " << makeQuoted(value);
102     UndoableCommand undo(command.c_str());
103     Scene_EntitySetKeyValue_Selected(key, value);
104 }
105
106 class EntityAttribute {
107 public:
108     virtual ~EntityAttribute() = default;
109
110     virtual ui::Widget getWidget() const = 0;
111
112     virtual void update() = 0;
113
114     virtual void release() = 0;
115 };
116
117 class BooleanAttribute : public EntityAttribute {
118     CopiedString m_key;
119     ui::CheckButton m_check;
120
121     static gboolean toggled(ui::Widget widget, BooleanAttribute *self)
122     {
123         self->apply();
124         return FALSE;
125     }
126
127 public:
128     BooleanAttribute(const char *key) :
129             m_key(key),
130             m_check(ui::null)
131     {
132         auto check = ui::CheckButton(ui::New);
133         check.show();
134
135         m_check = check;
136
137         guint handler = check.connect("toggled", G_CALLBACK(toggled), this);
138         g_object_set_data(G_OBJECT(check), "handler", gint_to_pointer(handler));
139
140         update();
141     }
142
143     ui::Widget getWidget() const
144     {
145         return m_check;
146     }
147
148     void release()
149     {
150         delete this;
151     }
152
153     void apply()
154     {
155         Scene_EntitySetKeyValue_Selected_Undoable(m_key.c_str(), m_check.active() ? "1" : "0");
156     }
157
158     typedef MemberCaller<BooleanAttribute, void(), &BooleanAttribute::apply> ApplyCaller;
159
160     void update()
161     {
162         const char *value = SelectedEntity_getValueForKey(m_key.c_str());
163         if (!string_empty(value)) {
164             toggle_button_set_active_no_signal(m_check, atoi(value) != 0);
165         } else {
166             toggle_button_set_active_no_signal(m_check, false);
167         }
168     }
169
170     typedef MemberCaller<BooleanAttribute, void(), &BooleanAttribute::update> UpdateCaller;
171 };
172
173
174 class StringAttribute : public EntityAttribute {
175     CopiedString m_key;
176     ui::Entry m_entry;
177     NonModalEntry m_nonModal;
178 public:
179     StringAttribute(const char *key) :
180             m_key(key),
181             m_entry(ui::null),
182             m_nonModal(ApplyCaller(*this), UpdateCaller(*this))
183     {
184         auto entry = ui::Entry(ui::New);
185         entry.show();
186         entry.dimensions(50, -1);
187
188         m_entry = entry;
189         m_nonModal.connect(m_entry);
190     }
191
192     ui::Widget getWidget() const
193     {
194         return m_entry;
195     }
196
197     ui::Entry getEntry() const
198     {
199         return m_entry;
200     }
201
202     void release()
203     {
204         delete this;
205     }
206
207     void apply()
208     {
209         StringOutputStream value(64);
210         value << m_entry.text();
211         Scene_EntitySetKeyValue_Selected_Undoable(m_key.c_str(), value.c_str());
212     }
213
214     typedef MemberCaller<StringAttribute, void(), &StringAttribute::apply> ApplyCaller;
215
216     void update()
217     {
218         StringOutputStream value(64);
219         value << SelectedEntity_getValueForKey(m_key.c_str());
220         m_entry.text(value.c_str());
221     }
222
223     typedef MemberCaller<StringAttribute, void(), &StringAttribute::update> UpdateCaller;
224 };
225
226 class ShaderAttribute : public StringAttribute {
227 public:
228     ShaderAttribute(const char *key) : StringAttribute(key)
229     {
230         GlobalShaderEntryCompletion::instance().connect(StringAttribute::getEntry());
231     }
232 };
233
234
235 class ModelAttribute : public EntityAttribute {
236     CopiedString m_key;
237     BrowsedPathEntry m_entry;
238     NonModalEntry m_nonModal;
239 public:
240     ModelAttribute(const char *key) :
241             m_key(key),
242             m_entry(BrowseCaller(*this)),
243             m_nonModal(ApplyCaller(*this), UpdateCaller(*this))
244     {
245         m_nonModal.connect(m_entry.m_entry.m_entry);
246     }
247
248     void release()
249     {
250         delete this;
251     }
252
253     ui::Widget getWidget() const
254     {
255         return m_entry.m_entry.m_frame;
256     }
257
258     void apply()
259     {
260         StringOutputStream value(64);
261         value << m_entry.m_entry.m_entry.text();
262         Scene_EntitySetKeyValue_Selected_Undoable(m_key.c_str(), value.c_str());
263     }
264
265     typedef MemberCaller<ModelAttribute, void(), &ModelAttribute::apply> ApplyCaller;
266
267     void update()
268     {
269         StringOutputStream value(64);
270         value << SelectedEntity_getValueForKey(m_key.c_str());
271         m_entry.m_entry.m_entry.text(value.c_str());
272     }
273
274     typedef MemberCaller<ModelAttribute, void(), &ModelAttribute::update> UpdateCaller;
275
276     void browse(const BrowsedPathEntry::SetPathCallback &setPath)
277     {
278         const char *filename = misc_model_dialog(m_entry.m_entry.m_frame.window());
279
280         if (filename != 0) {
281             setPath(filename);
282             apply();
283         }
284     }
285
286     typedef MemberCaller<ModelAttribute, void(
287             const BrowsedPathEntry::SetPathCallback &), &ModelAttribute::browse> BrowseCaller;
288 };
289
290 const char *browse_sound(ui::Widget parent)
291 {
292     StringOutputStream buffer(1024);
293
294     buffer << g_qeglobals.m_userGamePath.c_str() << "sound/";
295
296     if (!file_readable(buffer.c_str())) {
297         // just go to fsmain
298         buffer.clear();
299         buffer << g_qeglobals.m_userGamePath.c_str() << "/";
300     }
301
302     const char *filename = parent.file_dialog(TRUE, "Open Wav File", buffer.c_str(), "sound");
303     if (filename != 0) {
304         const char *relative = path_make_relative(filename, GlobalFileSystem().findRoot(filename));
305         if (relative == filename) {
306             globalOutputStream() << "WARNING: could not extract the relative path, using full path instead\n";
307         }
308         return relative;
309     }
310     return filename;
311 }
312
313 class SoundAttribute : public EntityAttribute {
314     CopiedString m_key;
315     BrowsedPathEntry m_entry;
316     NonModalEntry m_nonModal;
317 public:
318     SoundAttribute(const char *key) :
319             m_key(key),
320             m_entry(BrowseCaller(*this)),
321             m_nonModal(ApplyCaller(*this), UpdateCaller(*this))
322     {
323         m_nonModal.connect(m_entry.m_entry.m_entry);
324     }
325
326     void release()
327     {
328         delete this;
329     }
330
331     ui::Widget getWidget() const
332     {
333         return ui::Widget(m_entry.m_entry.m_frame);
334     }
335
336     void apply()
337     {
338         StringOutputStream value(64);
339         value << m_entry.m_entry.m_entry.text();
340         Scene_EntitySetKeyValue_Selected_Undoable(m_key.c_str(), value.c_str());
341     }
342
343     typedef MemberCaller<SoundAttribute, void(), &SoundAttribute::apply> ApplyCaller;
344
345     void update()
346     {
347         StringOutputStream value(64);
348         value << SelectedEntity_getValueForKey(m_key.c_str());
349         m_entry.m_entry.m_entry.text(value.c_str());
350     }
351
352     typedef MemberCaller<SoundAttribute, void(), &SoundAttribute::update> UpdateCaller;
353
354     void browse(const BrowsedPathEntry::SetPathCallback &setPath)
355     {
356         const char *filename = browse_sound(m_entry.m_entry.m_frame.window());
357
358         if (filename != 0) {
359             setPath(filename);
360             apply();
361         }
362     }
363
364     typedef MemberCaller<SoundAttribute, void(
365             const BrowsedPathEntry::SetPathCallback &), &SoundAttribute::browse> BrowseCaller;
366 };
367
368 inline double angle_normalised(double angle)
369 {
370     return float_mod(angle, 360.0);
371 }
372
373 class AngleAttribute : public EntityAttribute {
374     CopiedString m_key;
375     ui::Entry m_entry;
376     NonModalEntry m_nonModal;
377 public:
378     AngleAttribute(const char *key) :
379             m_key(key),
380             m_entry(ui::null),
381             m_nonModal(ApplyCaller(*this), UpdateCaller(*this))
382     {
383         auto entry = numeric_entry_new();
384         m_entry = entry;
385         m_nonModal.connect(m_entry);
386     }
387
388     void release()
389     {
390         delete this;
391     }
392
393     ui::Widget getWidget() const
394     {
395         return ui::Widget(m_entry);
396     }
397
398     void apply()
399     {
400         StringOutputStream angle(32);
401         angle << angle_normalised(entry_get_float(m_entry));
402         Scene_EntitySetKeyValue_Selected_Undoable(m_key.c_str(), angle.c_str());
403     }
404
405     typedef MemberCaller<AngleAttribute, void(), &AngleAttribute::apply> ApplyCaller;
406
407     void update()
408     {
409         const char *value = SelectedEntity_getValueForKey(m_key.c_str());
410         if (!string_empty(value)) {
411             StringOutputStream angle(32);
412             angle << angle_normalised(atof(value));
413             m_entry.text(angle.c_str());
414         } else {
415             m_entry.text("0");
416         }
417     }
418
419     typedef MemberCaller<AngleAttribute, void(), &AngleAttribute::update> UpdateCaller;
420 };
421
422 namespace {
423     typedef const char *String;
424     const String buttons[] = {"up", "down", "z-axis"};
425 }
426
427 class DirectionAttribute : public EntityAttribute {
428     CopiedString m_key;
429     ui::Entry m_entry;
430     NonModalEntry m_nonModal;
431     RadioHBox m_radio;
432     NonModalRadio m_nonModalRadio;
433     ui::HBox m_hbox{ui::null};
434 public:
435     DirectionAttribute(const char *key) :
436             m_key(key),
437             m_entry(ui::null),
438             m_nonModal(ApplyCaller(*this), UpdateCaller(*this)),
439             m_radio(RadioHBox_new(STRING_ARRAY_RANGE(buttons))),
440             m_nonModalRadio(ApplyRadioCaller(*this))
441     {
442         auto entry = numeric_entry_new();
443         m_entry = entry;
444         m_nonModal.connect(m_entry);
445
446         m_nonModalRadio.connect(m_radio.m_radio);
447
448         m_hbox = ui::HBox(FALSE, 4);
449         m_hbox.show();
450
451         m_hbox.pack_start(m_radio.m_hbox, TRUE, TRUE, 0);
452         m_hbox.pack_start(m_entry, TRUE, TRUE, 0);
453     }
454
455     void release()
456     {
457         delete this;
458     }
459
460     ui::Widget getWidget() const
461     {
462         return ui::Widget(m_hbox);
463     }
464
465     void apply()
466     {
467         StringOutputStream angle(32);
468         angle << angle_normalised(entry_get_float(m_entry));
469         Scene_EntitySetKeyValue_Selected_Undoable(m_key.c_str(), angle.c_str());
470     }
471
472     typedef MemberCaller<DirectionAttribute, void(), &DirectionAttribute::apply> ApplyCaller;
473
474     void update()
475     {
476         const char *value = SelectedEntity_getValueForKey(m_key.c_str());
477         if (!string_empty(value)) {
478             float f = float(atof(value));
479             if (f == -1) {
480                 gtk_widget_set_sensitive(m_entry, FALSE);
481                 radio_button_set_active_no_signal(m_radio.m_radio, 0);
482                 m_entry.text("");
483             } else if (f == -2) {
484                 gtk_widget_set_sensitive(m_entry, FALSE);
485                 radio_button_set_active_no_signal(m_radio.m_radio, 1);
486                 m_entry.text("");
487             } else {
488                 gtk_widget_set_sensitive(m_entry, TRUE);
489                 radio_button_set_active_no_signal(m_radio.m_radio, 2);
490                 StringOutputStream angle(32);
491                 angle << angle_normalised(f);
492                 m_entry.text(angle.c_str());
493             }
494         } else {
495             m_entry.text("0");
496         }
497     }
498
499     typedef MemberCaller<DirectionAttribute, void(), &DirectionAttribute::update> UpdateCaller;
500
501     void applyRadio()
502     {
503         int index = radio_button_get_active(m_radio.m_radio);
504         if (index == 0) {
505             Scene_EntitySetKeyValue_Selected_Undoable(m_key.c_str(), "-1");
506         } else if (index == 1) {
507             Scene_EntitySetKeyValue_Selected_Undoable(m_key.c_str(), "-2");
508         } else if (index == 2) {
509             apply();
510         }
511     }
512
513     typedef MemberCaller<DirectionAttribute, void(), &DirectionAttribute::applyRadio> ApplyRadioCaller;
514 };
515
516
517 class AnglesEntry {
518 public:
519     ui::Entry m_roll;
520     ui::Entry m_pitch;
521     ui::Entry m_yaw;
522
523     AnglesEntry() : m_roll(ui::null), m_pitch(ui::null), m_yaw(ui::null)
524     {
525     }
526 };
527
528 typedef BasicVector3<double> DoubleVector3;
529
530 class AnglesAttribute : public EntityAttribute {
531     CopiedString m_key;
532     AnglesEntry m_angles;
533     NonModalEntry m_nonModal;
534     ui::HBox m_hbox;
535 public:
536     AnglesAttribute(const char *key) :
537             m_key(key),
538             m_nonModal(ApplyCaller(*this), UpdateCaller(*this)),
539             m_hbox(ui::HBox(TRUE, 4))
540     {
541         m_hbox.show();
542         {
543             auto entry = numeric_entry_new();
544             m_hbox.pack_start(entry, TRUE, TRUE, 0);
545             m_angles.m_pitch = entry;
546             m_nonModal.connect(m_angles.m_pitch);
547         }
548         {
549             auto entry = numeric_entry_new();
550             m_hbox.pack_start(entry, TRUE, TRUE, 0);
551             m_angles.m_yaw = entry;
552             m_nonModal.connect(m_angles.m_yaw);
553         }
554         {
555             auto entry = numeric_entry_new();
556             m_hbox.pack_start(entry, TRUE, TRUE, 0);
557             m_angles.m_roll = entry;
558             m_nonModal.connect(m_angles.m_roll);
559         }
560     }
561
562     void release()
563     {
564         delete this;
565     }
566
567     ui::Widget getWidget() const
568     {
569         return ui::Widget(m_hbox);
570     }
571
572     void apply()
573     {
574         StringOutputStream angles(64);
575         angles << angle_normalised(entry_get_float(m_angles.m_pitch))
576                << " " << angle_normalised(entry_get_float(m_angles.m_yaw))
577                << " " << angle_normalised(entry_get_float(m_angles.m_roll));
578         Scene_EntitySetKeyValue_Selected_Undoable(m_key.c_str(), angles.c_str());
579     }
580
581     typedef MemberCaller<AnglesAttribute, void(), &AnglesAttribute::apply> ApplyCaller;
582
583     void update()
584     {
585         StringOutputStream angle(32);
586         const char *value = SelectedEntity_getValueForKey(m_key.c_str());
587         if (!string_empty(value)) {
588             DoubleVector3 pitch_yaw_roll;
589             if (!string_parse_vector3(value, pitch_yaw_roll)) {
590                 pitch_yaw_roll = DoubleVector3(0, 0, 0);
591             }
592
593             angle << angle_normalised(pitch_yaw_roll.x());
594             m_angles.m_pitch.text(angle.c_str());
595             angle.clear();
596
597             angle << angle_normalised(pitch_yaw_roll.y());
598             m_angles.m_yaw.text(angle.c_str());
599             angle.clear();
600
601             angle << angle_normalised(pitch_yaw_roll.z());
602             m_angles.m_roll.text(angle.c_str());
603             angle.clear();
604         } else {
605             m_angles.m_pitch.text("0");
606             m_angles.m_yaw.text("0");
607             m_angles.m_roll.text("0");
608         }
609     }
610
611     typedef MemberCaller<AnglesAttribute, void(), &AnglesAttribute::update> UpdateCaller;
612 };
613
614 class Vector3Entry {
615 public:
616     ui::Entry m_x;
617     ui::Entry m_y;
618     ui::Entry m_z;
619
620     Vector3Entry() : m_x(ui::null), m_y(ui::null), m_z(ui::null)
621     {
622     }
623 };
624
625 class Vector3Attribute : public EntityAttribute {
626     CopiedString m_key;
627     Vector3Entry m_vector3;
628     NonModalEntry m_nonModal;
629     ui::Box m_hbox{ui::null};
630 public:
631     Vector3Attribute(const char *key) :
632             m_key(key),
633             m_nonModal(ApplyCaller(*this), UpdateCaller(*this))
634     {
635         m_hbox = ui::HBox(TRUE, 4);
636         m_hbox.show();
637         {
638             auto entry = numeric_entry_new();
639             m_hbox.pack_start(entry, TRUE, TRUE, 0);
640             m_vector3.m_x = entry;
641             m_nonModal.connect(m_vector3.m_x);
642         }
643         {
644             auto entry = numeric_entry_new();
645             m_hbox.pack_start(entry, TRUE, TRUE, 0);
646             m_vector3.m_y = entry;
647             m_nonModal.connect(m_vector3.m_y);
648         }
649         {
650             auto entry = numeric_entry_new();
651             m_hbox.pack_start(entry, TRUE, TRUE, 0);
652             m_vector3.m_z = entry;
653             m_nonModal.connect(m_vector3.m_z);
654         }
655     }
656
657     void release()
658     {
659         delete this;
660     }
661
662     ui::Widget getWidget() const
663     {
664         return ui::Widget(m_hbox);
665     }
666
667     void apply()
668     {
669         StringOutputStream vector3(64);
670         vector3 << entry_get_float(m_vector3.m_x)
671                 << " " << entry_get_float(m_vector3.m_y)
672                 << " " << entry_get_float(m_vector3.m_z);
673         Scene_EntitySetKeyValue_Selected_Undoable(m_key.c_str(), vector3.c_str());
674     }
675
676     typedef MemberCaller<Vector3Attribute, void(), &Vector3Attribute::apply> ApplyCaller;
677
678     void update()
679     {
680         StringOutputStream buffer(32);
681         const char *value = SelectedEntity_getValueForKey(m_key.c_str());
682         if (!string_empty(value)) {
683             DoubleVector3 x_y_z;
684             if (!string_parse_vector3(value, x_y_z)) {
685                 x_y_z = DoubleVector3(0, 0, 0);
686             }
687
688             buffer << x_y_z.x();
689             m_vector3.m_x.text(buffer.c_str());
690             buffer.clear();
691
692             buffer << x_y_z.y();
693             m_vector3.m_y.text(buffer.c_str());
694             buffer.clear();
695
696             buffer << x_y_z.z();
697             m_vector3.m_z.text(buffer.c_str());
698             buffer.clear();
699         } else {
700             m_vector3.m_x.text("0");
701             m_vector3.m_y.text("0");
702             m_vector3.m_z.text("0");
703         }
704     }
705
706     typedef MemberCaller<Vector3Attribute, void(), &Vector3Attribute::update> UpdateCaller;
707 };
708
709 class NonModalComboBox {
710     Callback<void()> m_changed;
711     guint m_changedHandler;
712
713     static gboolean changed(ui::ComboBox widget, NonModalComboBox *self)
714     {
715         self->m_changed();
716         return FALSE;
717     }
718
719 public:
720     NonModalComboBox(const Callback<void()> &changed) : m_changed(changed), m_changedHandler(0)
721     {
722     }
723
724     void connect(ui::ComboBox combo)
725     {
726         m_changedHandler = combo.connect("changed", G_CALLBACK(changed), this);
727     }
728
729     void setActive(ui::ComboBox combo, int value)
730     {
731         g_signal_handler_disconnect(G_OBJECT(combo), m_changedHandler);
732         gtk_combo_box_set_active(combo, value);
733         connect(combo);
734     }
735 };
736
737 class ListAttribute : public EntityAttribute {
738     CopiedString m_key;
739     ui::ComboBox m_combo;
740     NonModalComboBox m_nonModal;
741     const ListAttributeType &m_type;
742 public:
743     ListAttribute(const char *key, const ListAttributeType &type) :
744             m_key(key),
745             m_combo(ui::null),
746             m_nonModal(ApplyCaller(*this)),
747             m_type(type)
748     {
749         auto combo = ui::ComboBoxText(ui::New);
750
751         for (ListAttributeType::const_iterator i = type.begin(); i != type.end(); ++i) {
752             gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combo), (*i).first.c_str());
753         }
754
755         combo.show();
756         m_nonModal.connect(combo);
757
758         m_combo = combo;
759     }
760
761     void release()
762     {
763         delete this;
764     }
765
766     ui::Widget getWidget() const
767     {
768         return ui::Widget(m_combo);
769     }
770
771     void apply()
772     {
773         Scene_EntitySetKeyValue_Selected_Undoable(m_key.c_str(),
774                                                   m_type[gtk_combo_box_get_active(m_combo)].second.c_str());
775     }
776
777     typedef MemberCaller<ListAttribute, void(), &ListAttribute::apply> ApplyCaller;
778
779     void update()
780     {
781         const char *value = SelectedEntity_getValueForKey(m_key.c_str());
782         ListAttributeType::const_iterator i = m_type.findValue(value);
783         if (i != m_type.end()) {
784             m_nonModal.setActive(m_combo, static_cast<int>( std::distance(m_type.begin(), i)));
785         } else {
786             m_nonModal.setActive(m_combo, 0);
787         }
788     }
789
790     typedef MemberCaller<ListAttribute, void(), &ListAttribute::update> UpdateCaller;
791 };
792
793
794 namespace {
795     ui::Widget g_entity_split1{ui::null};
796     ui::Widget g_entity_split2{ui::null};
797     int g_entitysplit1_position;
798     int g_entitysplit2_position;
799
800     bool g_entityInspector_windowConstructed = false;
801
802     ui::TreeView g_entityClassList{ui::null};
803     ui::TextView g_entityClassComment{ui::null};
804
805     GtkCheckButton *g_entitySpawnflagsCheck[MAX_FLAGS];
806
807     ui::Entry g_entityKeyEntry{ui::null};
808     ui::Entry g_entityValueEntry{ui::null};
809
810     ui::ListStore g_entlist_store{ui::null};
811     ui::ListStore g_entprops_store{ui::null};
812     const EntityClass *g_current_flags = 0;
813     const EntityClass *g_current_comment = 0;
814     const EntityClass *g_current_attributes = 0;
815
816 // the number of active spawnflags
817     int g_spawnflag_count;
818 // table: index, match spawnflag item to the spawnflag index (i.e. which bit)
819     int spawn_table[MAX_FLAGS];
820 // we change the layout depending on how many spawn flags we need to display
821 // the table is a 4x4 in which we need to put the comment box g_entityClassComment and the spawn flags..
822     ui::Table g_spawnflagsTable{ui::null};
823
824     ui::VBox g_attributeBox{ui::null};
825     typedef std::vector<EntityAttribute *> EntityAttributes;
826     EntityAttributes g_entityAttributes;
827 }
828
829 void GlobalEntityAttributes_clear()
830 {
831     for (EntityAttributes::iterator i = g_entityAttributes.begin(); i != g_entityAttributes.end(); ++i) {
832         (*i)->release();
833     }
834     g_entityAttributes.clear();
835 }
836
837 class GetKeyValueVisitor : public Entity::Visitor {
838     KeyValues &m_keyvalues;
839 public:
840     GetKeyValueVisitor(KeyValues &keyvalues)
841             : m_keyvalues(keyvalues)
842     {
843     }
844
845     void visit(const char *key, const char *value)
846     {
847         m_keyvalues.insert(KeyValues::value_type(CopiedString(key), CopiedString(value)));
848     }
849
850 };
851
852 void Entity_GetKeyValues(const Entity &entity, KeyValues &keyvalues, KeyValues &defaultValues)
853 {
854     GetKeyValueVisitor visitor(keyvalues);
855
856     entity.forEachKeyValue(visitor);
857
858     const EntityClassAttributes &attributes = entity.getEntityClass().m_attributes;
859
860     for (EntityClassAttributes::const_iterator i = attributes.begin(); i != attributes.end(); ++i) {
861         defaultValues.insert(KeyValues::value_type((*i).first, (*i).second.m_value));
862     }
863 }
864
865 void Entity_GetKeyValues_Selected(KeyValues &keyvalues, KeyValues &defaultValues)
866 {
867     class EntityGetKeyValues : public SelectionSystem::Visitor {
868         KeyValues &m_keyvalues;
869         KeyValues &m_defaultValues;
870         mutable std::set<Entity *> m_visited;
871     public:
872         EntityGetKeyValues(KeyValues &keyvalues, KeyValues &defaultValues)
873                 : m_keyvalues(keyvalues), m_defaultValues(defaultValues)
874         {
875         }
876
877         void visit(scene::Instance &instance) const
878         {
879             Entity *entity = Node_getEntity(instance.path().top());
880             if (entity == 0 && instance.path().size() != 1) {
881                 entity = Node_getEntity(instance.path().parent());
882             }
883             if (entity != 0 && m_visited.insert(entity).second) {
884                 Entity_GetKeyValues(*entity, m_keyvalues, m_defaultValues);
885             }
886         }
887     } visitor(keyvalues, defaultValues);
888     GlobalSelectionSystem().foreachSelected(visitor);
889 }
890
891 const char *keyvalues_valueforkey(KeyValues &keyvalues, const char *key)
892 {
893     KeyValues::iterator i = keyvalues.find(CopiedString(key));
894     if (i != keyvalues.end()) {
895         return (*i).second.c_str();
896     }
897     return "";
898 }
899
900 class EntityClassListStoreAppend : public EntityClassVisitor {
901     ui::ListStore store;
902 public:
903     EntityClassListStoreAppend(ui::ListStore store_) : store(store_)
904     {
905     }
906
907     void visit(EntityClass *e)
908     {
909         store.append(0, e->name(), 1, e);
910     }
911 };
912
913 void EntityClassList_fill()
914 {
915     EntityClassListStoreAppend append(g_entlist_store);
916     GlobalEntityClassManager().forEach(append);
917 }
918
919 void EntityClassList_clear()
920 {
921     g_entlist_store.clear();
922 }
923
924 void SetComment(EntityClass *eclass)
925 {
926     if (eclass == g_current_comment) {
927         return;
928     }
929
930     g_current_comment = eclass;
931
932     g_entityClassComment.text(eclass->comments());
933 }
934
935 void SurfaceFlags_setEntityClass(EntityClass *eclass)
936 {
937     if (eclass == g_current_flags) {
938         return;
939     }
940
941     g_current_flags = eclass;
942
943     unsigned int spawnflag_count = 0;
944
945     {
946         // do a first pass to count the spawn flags, don't touch the widgets, we don't know in what state they are
947         for (int i = 0; i < MAX_FLAGS; i++) {
948             if (eclass->flagnames[i] && eclass->flagnames[i][0] != 0 && strcmp(eclass->flagnames[i], "-")) {
949                 spawn_table[spawnflag_count] = i;
950                 spawnflag_count++;
951             }
952         }
953     }
954
955     // disable all remaining boxes
956     // NOTE: these boxes might not even be on display
957     {
958         for (int i = 0; i < g_spawnflag_count; ++i) {
959             auto widget = ui::CheckButton::from(g_entitySpawnflagsCheck[i]);
960             auto label = ui::Label::from(gtk_bin_get_child(GTK_BIN(widget)));
961             label.text(" ");
962             widget.hide();
963             widget.ref();
964             g_spawnflagsTable.remove(widget);
965         }
966     }
967
968     g_spawnflag_count = spawnflag_count;
969
970     {
971         for (unsigned int i = 0; (int) i < g_spawnflag_count; ++i) {
972             auto widget = ui::CheckButton::from(g_entitySpawnflagsCheck[i]);
973             widget.show();
974
975             StringOutputStream str(16);
976             str << LowerCase(eclass->flagnames[spawn_table[i]]);
977
978             g_spawnflagsTable.attach(widget, {i % 4, i % 4 + 1, i / 4, i / 4 + 1}, {GTK_FILL, GTK_FILL});
979             widget.unref();
980
981             auto label = ui::Label::from(gtk_bin_get_child(GTK_BIN(widget)));
982             label.text(str.c_str());
983         }
984     }
985 }
986
987 void EntityClassList_selectEntityClass(EntityClass *eclass)
988 {
989     auto model = g_entlist_store;
990     GtkTreeIter iter;
991     for (gboolean good = gtk_tree_model_get_iter_first(model, &iter);
992          good != FALSE; good = gtk_tree_model_iter_next(model, &iter)) {
993         char *text;
994         gtk_tree_model_get(model, &iter, 0, &text, -1);
995         if (strcmp(text, eclass->name()) == 0) {
996             auto view = ui::TreeView(g_entityClassList);
997             auto path = gtk_tree_model_get_path(model, &iter);
998             gtk_tree_selection_select_path(gtk_tree_view_get_selection(view), path);
999             if (gtk_widget_get_realized(view)) {
1000                 gtk_tree_view_scroll_to_cell(view, path, 0, FALSE, 0, 0);
1001             }
1002             gtk_tree_path_free(path);
1003             good = FALSE;
1004         }
1005         g_free(text);
1006     }
1007 }
1008
1009 void EntityInspector_appendAttribute(const char *name, EntityAttribute &attribute)
1010 {
1011     auto row = DialogRow_new(name, attribute.getWidget());
1012     DialogVBox_packRow(ui::VBox(g_attributeBox), row);
1013 }
1014
1015
1016 template<typename Attribute>
1017 class StatelessAttributeCreator {
1018 public:
1019     static EntityAttribute *create(const char *name)
1020     {
1021         return new Attribute(name);
1022     }
1023 };
1024
1025 class EntityAttributeFactory {
1026     typedef EntityAttribute *( *CreateFunc )(const char *name);
1027
1028     typedef std::map<const char *, CreateFunc, RawStringLess> Creators;
1029     Creators m_creators;
1030 public:
1031     EntityAttributeFactory()
1032     {
1033         m_creators.insert(Creators::value_type("string", &StatelessAttributeCreator<StringAttribute>::create));
1034         m_creators.insert(Creators::value_type("color", &StatelessAttributeCreator<StringAttribute>::create));
1035         m_creators.insert(Creators::value_type("integer", &StatelessAttributeCreator<StringAttribute>::create));
1036         m_creators.insert(Creators::value_type("real", &StatelessAttributeCreator<StringAttribute>::create));
1037         m_creators.insert(Creators::value_type("shader", &StatelessAttributeCreator<ShaderAttribute>::create));
1038         m_creators.insert(Creators::value_type("boolean", &StatelessAttributeCreator<BooleanAttribute>::create));
1039         m_creators.insert(Creators::value_type("angle", &StatelessAttributeCreator<AngleAttribute>::create));
1040         m_creators.insert(Creators::value_type("direction", &StatelessAttributeCreator<DirectionAttribute>::create));
1041         m_creators.insert(Creators::value_type("angles", &StatelessAttributeCreator<AnglesAttribute>::create));
1042         m_creators.insert(Creators::value_type("model", &StatelessAttributeCreator<ModelAttribute>::create));
1043         m_creators.insert(Creators::value_type("sound", &StatelessAttributeCreator<SoundAttribute>::create));
1044         m_creators.insert(Creators::value_type("vector3", &StatelessAttributeCreator<Vector3Attribute>::create));
1045         m_creators.insert(Creators::value_type("real3", &StatelessAttributeCreator<Vector3Attribute>::create));
1046     }
1047
1048     EntityAttribute *create(const char *type, const char *name)
1049     {
1050         Creators::iterator i = m_creators.find(type);
1051         if (i != m_creators.end()) {
1052             return (*i).second(name);
1053         }
1054         const ListAttributeType *listType = GlobalEntityClassManager().findListType(type);
1055         if (listType != 0) {
1056             return new ListAttribute(name, *listType);
1057         }
1058         return 0;
1059     }
1060 };
1061
1062 typedef Static<EntityAttributeFactory> GlobalEntityAttributeFactory;
1063
1064 void EntityInspector_setEntityClass(EntityClass *eclass)
1065 {
1066     EntityClassList_selectEntityClass(eclass);
1067     SurfaceFlags_setEntityClass(eclass);
1068
1069     if (eclass != g_current_attributes) {
1070         g_current_attributes = eclass;
1071
1072         container_remove_all(g_attributeBox);
1073         GlobalEntityAttributes_clear();
1074
1075         for (EntityClassAttributes::const_iterator i = eclass->m_attributes.begin();
1076              i != eclass->m_attributes.end(); ++i) {
1077             EntityAttribute *attribute = GlobalEntityAttributeFactory::instance().create((*i).second.m_type.c_str(),
1078                                                                                          (*i).first.c_str());
1079             if (attribute != 0) {
1080                 g_entityAttributes.push_back(attribute);
1081                 EntityInspector_appendAttribute(EntityClassAttributePair_getName(*i), *g_entityAttributes.back());
1082             }
1083         }
1084     }
1085 }
1086
1087 void EntityInspector_updateSpawnflags()
1088 {
1089     {
1090         int f = atoi(SelectedEntity_getValueForKey("spawnflags"));
1091         for (int i = 0; i < g_spawnflag_count; ++i) {
1092             int v = !!(f & (1 << spawn_table[i]));
1093
1094             toggle_button_set_active_no_signal(ui::ToggleButton::from(g_entitySpawnflagsCheck[i]), v);
1095         }
1096     }
1097     {
1098         // take care of the remaining ones
1099         for (int i = g_spawnflag_count; i < MAX_FLAGS; ++i) {
1100             toggle_button_set_active_no_signal(ui::ToggleButton::from(g_entitySpawnflagsCheck[i]), FALSE);
1101         }
1102     }
1103 }
1104
1105 void EntityInspector_applySpawnflags()
1106 {
1107     int f, i, v;
1108     char sz[32];
1109
1110     f = 0;
1111     for (i = 0; i < g_spawnflag_count; ++i) {
1112         v = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(g_entitySpawnflagsCheck[i]));
1113         f |= v << spawn_table[i];
1114     }
1115
1116     sprintf(sz, "%i", f);
1117     const char *value = (f == 0) ? "" : sz;
1118
1119     {
1120         StringOutputStream command;
1121         command << "entitySetFlags -flags " << f;
1122         UndoableCommand undo("entitySetSpawnflags");
1123
1124         Scene_EntitySetKeyValue_Selected("spawnflags", value);
1125     }
1126 }
1127
1128
1129 void EntityInspector_updateKeyValues()
1130 {
1131     g_selectedKeyValues.clear();
1132     g_selectedDefaultKeyValues.clear();
1133     Entity_GetKeyValues_Selected(g_selectedKeyValues, g_selectedDefaultKeyValues);
1134
1135     EntityInspector_setEntityClass(
1136             GlobalEntityClassManager().findOrInsert(keyvalues_valueforkey(g_selectedKeyValues, "classname"), false));
1137
1138     EntityInspector_updateSpawnflags();
1139
1140     ui::ListStore store = g_entprops_store;
1141
1142     // save current key/val pair around filling epair box
1143     // row_select wipes it and sets to first in list
1144     CopiedString strKey(g_entityKeyEntry.text());
1145     CopiedString strVal(g_entityValueEntry.text());
1146
1147     store.clear();
1148     // Walk through list and add pairs
1149     for (KeyValues::iterator i = g_selectedKeyValues.begin(); i != g_selectedKeyValues.end(); ++i) {
1150         StringOutputStream key(64);
1151         key << (*i).first.c_str();
1152         StringOutputStream value(64);
1153         value << (*i).second.c_str();
1154         store.append(0, key.c_str(), 1, value.c_str());
1155     }
1156
1157     g_entityKeyEntry.text(strKey.c_str());
1158     g_entityValueEntry.text(strVal.c_str());
1159
1160     for (EntityAttributes::const_iterator i = g_entityAttributes.begin(); i != g_entityAttributes.end(); ++i) {
1161         (*i)->update();
1162     }
1163 }
1164
1165 class EntityInspectorDraw {
1166     IdleDraw m_idleDraw;
1167 public:
1168     EntityInspectorDraw() : m_idleDraw(makeCallbackF(EntityInspector_updateKeyValues))
1169     {
1170     }
1171
1172     void queueDraw()
1173     {
1174         m_idleDraw.queueDraw();
1175     }
1176 };
1177
1178 EntityInspectorDraw g_EntityInspectorDraw;
1179
1180
1181 void EntityInspector_keyValueChanged()
1182 {
1183     g_EntityInspectorDraw.queueDraw();
1184 }
1185
1186 void EntityInspector_selectionChanged(const Selectable &)
1187 {
1188     EntityInspector_keyValueChanged();
1189 }
1190
1191 // Creates a new entity based on the currently selected brush and entity type.
1192 //
1193 void EntityClassList_createEntity()
1194 {
1195     auto view = g_entityClassList;
1196
1197     // find out what type of entity we are trying to create
1198     GtkTreeModel *model;
1199     GtkTreeIter iter;
1200     if (gtk_tree_selection_get_selected(gtk_tree_view_get_selection(g_entityClassList), &model, &iter) == FALSE) {
1201         ui::alert(view.window(), "You must have a selected class to create an entity", "info");
1202         return;
1203     }
1204
1205     char *text;
1206     gtk_tree_model_get(model, &iter, 0, &text, -1);
1207
1208     {
1209         StringOutputStream command;
1210         command << "entityCreate -class " << text;
1211
1212         UndoableCommand undo(command.c_str());
1213
1214         Entity_createFromSelection(text, g_vector3_identity);
1215     }
1216     g_free(text);
1217 }
1218
1219 void EntityInspector_applyKeyValue()
1220 {
1221     // Get current selection text
1222     StringOutputStream key(64);
1223     key << gtk_entry_get_text(g_entityKeyEntry);
1224     StringOutputStream value(64);
1225     value << gtk_entry_get_text(g_entityValueEntry);
1226
1227
1228     // TTimo: if you change the classname to worldspawn you won't merge back in the structural brushes but create a parasite entity
1229     if (!strcmp(key.c_str(), "classname") && !strcmp(value.c_str(), "worldspawn")) {
1230         ui::alert(g_entityKeyEntry.window(), "Cannot change \"classname\" key back to worldspawn.", 0,
1231                   ui::alert_type::OK);
1232         return;
1233     }
1234
1235
1236     // RR2DO2: we don't want spaces in entity keys
1237     if (strstr(key.c_str(), " ")) {
1238         ui::alert(g_entityKeyEntry.window(), "No spaces are allowed in entity keys.", 0, ui::alert_type::OK);
1239         return;
1240     }
1241
1242     if (strcmp(key.c_str(), "classname") == 0) {
1243         StringOutputStream command;
1244         command << "entitySetClass -class " << value.c_str();
1245         UndoableCommand undo(command.c_str());
1246         Scene_EntitySetClassname_Selected(value.c_str());
1247     } else {
1248         Scene_EntitySetKeyValue_Selected_Undoable(key.c_str(), value.c_str());
1249     }
1250 }
1251
1252 void EntityInspector_clearKeyValue()
1253 {
1254     // Get current selection text
1255     StringOutputStream key(64);
1256     key << gtk_entry_get_text(g_entityKeyEntry);
1257
1258     if (strcmp(key.c_str(), "classname") != 0) {
1259         StringOutputStream command;
1260         command << "entityDeleteKey -key " << key.c_str();
1261         UndoableCommand undo(command.c_str());
1262         Scene_EntitySetKeyValue_Selected(key.c_str(), "");
1263     }
1264 }
1265
1266 void EntityInspector_clearAllKeyValues()
1267 {
1268     UndoableCommand undo("entityClear");
1269
1270     // remove all keys except classname
1271     for (KeyValues::iterator i = g_selectedKeyValues.begin(); i != g_selectedKeyValues.end(); ++i) {
1272         if (strcmp((*i).first.c_str(), "classname") != 0) {
1273             Scene_EntitySetKeyValue_Selected((*i).first.c_str(), "");
1274         }
1275     }
1276 }
1277
1278 // =============================================================================
1279 // callbacks
1280
1281 static void EntityClassList_selection_changed(ui::TreeSelection selection, gpointer data)
1282 {
1283     GtkTreeModel *model;
1284     GtkTreeIter selected;
1285     if (gtk_tree_selection_get_selected(selection, &model, &selected)) {
1286         EntityClass *eclass;
1287         gtk_tree_model_get(model, &selected, 1, &eclass, -1);
1288         if (eclass != 0) {
1289             SetComment(eclass);
1290         }
1291     }
1292 }
1293
1294 static gint EntityClassList_button_press(ui::Widget widget, GdkEventButton *event, gpointer data)
1295 {
1296     if (event->type == GDK_2BUTTON_PRESS) {
1297         EntityClassList_createEntity();
1298         return TRUE;
1299     }
1300     return FALSE;
1301 }
1302
1303 static gint EntityClassList_keypress(ui::Widget widget, GdkEventKey *event, gpointer data)
1304 {
1305     unsigned int code = gdk_keyval_to_upper(event->keyval);
1306
1307     if (event->keyval == GDK_KEY_Return) {
1308         EntityClassList_createEntity();
1309         return TRUE;
1310     }
1311
1312     // select the entity that starts with the key pressed
1313     if (code <= 'Z' && code >= 'A') {
1314         auto view = ui::TreeView(g_entityClassList);
1315         GtkTreeModel *model;
1316         GtkTreeIter iter;
1317         if (gtk_tree_selection_get_selected(gtk_tree_view_get_selection(view), &model, &iter) == FALSE
1318             || gtk_tree_model_iter_next(model, &iter) == FALSE) {
1319             gtk_tree_model_get_iter_first(model, &iter);
1320         }
1321
1322         for (std::size_t count = gtk_tree_model_iter_n_children(model, 0); count > 0; --count) {
1323             char *text;
1324             gtk_tree_model_get(model, &iter, 0, &text, -1);
1325
1326             if (toupper(text[0]) == (int) code) {
1327                 auto path = gtk_tree_model_get_path(model, &iter);
1328                 gtk_tree_selection_select_path(gtk_tree_view_get_selection(view), path);
1329                 if (gtk_widget_get_realized(view)) {
1330                     gtk_tree_view_scroll_to_cell(view, path, 0, FALSE, 0, 0);
1331                 }
1332                 gtk_tree_path_free(path);
1333                 count = 1;
1334             }
1335
1336             g_free(text);
1337
1338             if (gtk_tree_model_iter_next(model, &iter) == FALSE) {
1339                 gtk_tree_model_get_iter_first(model, &iter);
1340             }
1341         }
1342
1343         return TRUE;
1344     }
1345     return FALSE;
1346 }
1347
1348 static void EntityProperties_selection_changed(ui::TreeSelection selection, gpointer data)
1349 {
1350     // find out what type of entity we are trying to create
1351     GtkTreeModel *model;
1352     GtkTreeIter iter;
1353     if (gtk_tree_selection_get_selected(selection, &model, &iter) == FALSE) {
1354         return;
1355     }
1356
1357     char *key;
1358     char *val;
1359     gtk_tree_model_get(model, &iter, 0, &key, 1, &val, -1);
1360
1361     g_entityKeyEntry.text(key);
1362     g_entityValueEntry.text(val);
1363
1364     g_free(key);
1365     g_free(val);
1366 }
1367
1368 static void SpawnflagCheck_toggled(ui::Widget widget, gpointer data)
1369 {
1370     EntityInspector_applySpawnflags();
1371 }
1372
1373 static gint EntityEntry_keypress(ui::Entry widget, GdkEventKey *event, gpointer data)
1374 {
1375     if (event->keyval == GDK_KEY_Return) {
1376         if (widget._handle == g_entityKeyEntry._handle) {
1377             g_entityValueEntry.text("");
1378             gtk_window_set_focus(widget.window(), g_entityValueEntry);
1379         } else {
1380             EntityInspector_applyKeyValue();
1381         }
1382         return TRUE;
1383     }
1384     if (event->keyval == GDK_KEY_Escape) {
1385         gtk_window_set_focus(widget.window(), NULL);
1386         return TRUE;
1387     }
1388
1389     return FALSE;
1390 }
1391
1392 void EntityInspector_destroyWindow(ui::Widget widget, gpointer data)
1393 {
1394     g_entitysplit1_position = gtk_paned_get_position(GTK_PANED(g_entity_split1));
1395     g_entitysplit2_position = gtk_paned_get_position(GTK_PANED(g_entity_split2));
1396
1397     g_entityInspector_windowConstructed = false;
1398     GlobalEntityAttributes_clear();
1399 }
1400
1401 ui::Widget EntityInspector_constructWindow(ui::Window toplevel)
1402 {
1403     auto vbox = ui::VBox(FALSE, 2);
1404     vbox.show();
1405     gtk_container_set_border_width(GTK_CONTAINER(vbox), 2);
1406
1407     vbox.connect("destroy", G_CALLBACK(EntityInspector_destroyWindow), 0);
1408
1409     {
1410         auto split1 = ui::VPaned(ui::New);
1411         vbox.pack_start(split1, TRUE, TRUE, 0);
1412         split1.show();
1413
1414         g_entity_split1 = split1;
1415
1416         {
1417             ui::Widget split2 = ui::VPaned(ui::New);
1418             gtk_paned_add1(GTK_PANED(split1), split2);
1419             split2.show();
1420
1421             g_entity_split2 = split2;
1422
1423             {
1424                 // class list
1425                 auto scr = ui::ScrolledWindow(ui::New);
1426                 scr.show();
1427                 gtk_paned_add1(GTK_PANED(split2), scr);
1428                 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scr), GTK_POLICY_NEVER, GTK_POLICY_ALWAYS);
1429                 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scr), GTK_SHADOW_IN);
1430
1431                 {
1432                     ui::ListStore store = ui::ListStore::from(gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_POINTER));
1433
1434                     auto view = ui::TreeView(ui::TreeModel::from(store._handle));
1435                     gtk_tree_view_set_enable_search(view, FALSE);
1436                     gtk_tree_view_set_headers_visible(view, FALSE);
1437                     view.connect("button_press_event", G_CALLBACK(EntityClassList_button_press), 0);
1438                     view.connect("key_press_event", G_CALLBACK(EntityClassList_keypress), 0);
1439
1440                     {
1441                         auto renderer = ui::CellRendererText(ui::New);
1442                         auto column = ui::TreeViewColumn("Key", renderer, {{"text", 0}});
1443                         gtk_tree_view_append_column(view, column);
1444                     }
1445
1446                     {
1447                         auto selection = ui::TreeSelection::from(gtk_tree_view_get_selection(view));
1448                         selection.connect("changed", G_CALLBACK(EntityClassList_selection_changed), 0);
1449                     }
1450
1451                     view.show();
1452
1453                     scr.add(view);
1454
1455                     store.unref();
1456                     g_entityClassList = view;
1457                     g_entlist_store = store;
1458                 }
1459             }
1460
1461             {
1462                 auto scr = ui::ScrolledWindow(ui::New);
1463                 scr.show();
1464                 gtk_paned_add2(GTK_PANED(split2), scr);
1465                 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scr), GTK_POLICY_NEVER, GTK_POLICY_ALWAYS);
1466                 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scr), GTK_SHADOW_IN);
1467
1468                 {
1469                     auto text = ui::TextView(ui::New);
1470                     text.dimensions(0, -1); // allow shrinking
1471                     gtk_text_view_set_wrap_mode(text, GTK_WRAP_WORD);
1472                     gtk_text_view_set_editable(text, FALSE);
1473                     text.show();
1474                     scr.add(text);
1475                     g_entityClassComment = text;
1476                 }
1477             }
1478         }
1479
1480         {
1481             ui::Widget split2 = ui::VPaned(ui::New);
1482             gtk_paned_add2(GTK_PANED(split1), split2);
1483             split2.show();
1484
1485             {
1486                 auto vbox2 = ui::VBox(FALSE, 2);
1487                 vbox2.show();
1488                 gtk_paned_pack1(GTK_PANED(split2), vbox2, FALSE, FALSE);
1489
1490                 {
1491                     // Spawnflags (4 colums wide max, or window gets too wide.)
1492                     auto table = ui::Table(4, 4, FALSE);
1493                     vbox2.pack_start(table, FALSE, TRUE, 0);
1494                     table.show();
1495
1496                     g_spawnflagsTable = table;
1497
1498                     for (int i = 0; i < MAX_FLAGS; i++) {
1499                         auto check = ui::CheckButton("");
1500                         check.ref();
1501                         g_object_set_data(G_OBJECT(check), "handler", gint_to_pointer(
1502                                 check.connect("toggled", G_CALLBACK(SpawnflagCheck_toggled), 0)));
1503                         g_entitySpawnflagsCheck[i] = check;
1504                     }
1505                 }
1506
1507                 {
1508                     // key/value list
1509                     auto scr = ui::ScrolledWindow(ui::New);
1510                     scr.show();
1511                     vbox2.pack_start(scr, TRUE, TRUE, 0);
1512                     gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scr), GTK_POLICY_AUTOMATIC,
1513                                                    GTK_POLICY_AUTOMATIC);
1514                     gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scr), GTK_SHADOW_IN);
1515
1516                     {
1517                         ui::ListStore store = ui::ListStore::from(gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_STRING));
1518
1519                         auto view = ui::TreeView(ui::TreeModel::from(store._handle));
1520                         gtk_tree_view_set_enable_search(view, FALSE);
1521                         gtk_tree_view_set_headers_visible(view, FALSE);
1522
1523                         {
1524                             auto renderer = ui::CellRendererText(ui::New);
1525                             auto column = ui::TreeViewColumn("", renderer, {{"text", 0}});
1526                             gtk_tree_view_append_column(view, column);
1527                         }
1528
1529                         {
1530                             auto renderer = ui::CellRendererText(ui::New);
1531                             auto column = ui::TreeViewColumn("", renderer, {{"text", 1}});
1532                             gtk_tree_view_append_column(view, column);
1533                         }
1534
1535                         {
1536                             auto selection = ui::TreeSelection::from(gtk_tree_view_get_selection(view));
1537                             selection.connect("changed", G_CALLBACK(EntityProperties_selection_changed), 0);
1538                         }
1539
1540                         view.show();
1541
1542                         scr.add(view);
1543
1544                         store.unref();
1545
1546                         g_entprops_store = store;
1547                     }
1548                 }
1549
1550                 {
1551                     // key/value entry
1552                     auto table = ui::Table(2, 2, FALSE);
1553                     table.show();
1554                     vbox2.pack_start(table, FALSE, TRUE, 0);
1555                     gtk_table_set_row_spacings(table, 3);
1556                     gtk_table_set_col_spacings(table, 5);
1557
1558                     {
1559                         auto entry = ui::Entry(ui::New);
1560                         entry.show();
1561                         table.attach(entry, {1, 2, 0, 1}, {GTK_EXPAND | GTK_FILL, 0});
1562                         gtk_widget_set_events(entry, GDK_KEY_PRESS_MASK);
1563                         entry.connect("key_press_event", G_CALLBACK(EntityEntry_keypress), 0);
1564                         g_entityKeyEntry = entry;
1565                     }
1566
1567                     {
1568                         auto entry = ui::Entry(ui::New);
1569                         entry.show();
1570                         table.attach(entry, {1, 2, 1, 2}, {GTK_EXPAND | GTK_FILL, 0});
1571                         gtk_widget_set_events(entry, GDK_KEY_PRESS_MASK);
1572                         entry.connect("key_press_event", G_CALLBACK(EntityEntry_keypress), 0);
1573                         g_entityValueEntry = entry;
1574                     }
1575
1576                     {
1577                         auto label = ui::Label("Value");
1578                         label.show();
1579                         table.attach(label, {0, 1, 1, 2}, {GTK_FILL, 0});
1580                         gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
1581                     }
1582
1583                     {
1584                         auto label = ui::Label("Key");
1585                         label.show();
1586                         table.attach(label, {0, 1, 0, 1}, {GTK_FILL, 0});
1587                         gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
1588                     }
1589                 }
1590
1591                 {
1592                     auto hbox = ui::HBox(TRUE, 4);
1593                     hbox.show();
1594                     vbox2.pack_start(hbox, FALSE, TRUE, 0);
1595
1596                     {
1597                         auto button = ui::Button("Clear All");
1598                         button.show();
1599                         button.connect("clicked", G_CALLBACK(EntityInspector_clearAllKeyValues), 0);
1600                         hbox.pack_start(button, TRUE, TRUE, 0);
1601                     }
1602                     {
1603                         auto button = ui::Button("Delete Key");
1604                         button.show();
1605                         button.connect("clicked", G_CALLBACK(EntityInspector_clearKeyValue), 0);
1606                         hbox.pack_start(button, TRUE, TRUE, 0);
1607                     }
1608                 }
1609             }
1610
1611             {
1612                 auto scr = ui::ScrolledWindow(ui::New);
1613                 scr.show();
1614                 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scr), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
1615
1616                 auto viewport = ui::Container::from(gtk_viewport_new(0, 0));
1617                 viewport.show();
1618                 gtk_viewport_set_shadow_type(GTK_VIEWPORT(viewport), GTK_SHADOW_NONE);
1619
1620                 g_attributeBox = ui::VBox(FALSE, 2);
1621                 g_attributeBox.show();
1622
1623                 viewport.add(g_attributeBox);
1624                 scr.add(viewport);
1625                 gtk_paned_pack2(GTK_PANED(split2), scr, FALSE, FALSE);
1626             }
1627         }
1628     }
1629
1630
1631     {
1632         // show the sliders in any case
1633         if (g_entitysplit2_position > 22) {
1634             gtk_paned_set_position(GTK_PANED(g_entity_split2), g_entitysplit2_position);
1635         } else {
1636             g_entitysplit2_position = 22;
1637             gtk_paned_set_position(GTK_PANED(g_entity_split2), 22);
1638         }
1639         if ((g_entitysplit1_position - g_entitysplit2_position) > 27) {
1640             gtk_paned_set_position(GTK_PANED(g_entity_split1), g_entitysplit1_position);
1641         } else {
1642             gtk_paned_set_position(GTK_PANED(g_entity_split1), g_entitysplit2_position + 27);
1643         }
1644     }
1645
1646     g_entityInspector_windowConstructed = true;
1647     EntityClassList_fill();
1648
1649     typedef FreeCaller<void(
1650             const Selectable &), EntityInspector_selectionChanged> EntityInspectorSelectionChangedCaller;
1651     GlobalSelectionSystem().addSelectionChangeCallback(EntityInspectorSelectionChangedCaller());
1652     GlobalEntityCreator().setKeyValueChangedFunc(EntityInspector_keyValueChanged);
1653
1654     // hack
1655     gtk_container_set_focus_chain(GTK_CONTAINER(vbox), NULL);
1656
1657     return vbox;
1658 }
1659
1660 class EntityInspector : public ModuleObserver {
1661     std::size_t m_unrealised;
1662 public:
1663     EntityInspector() : m_unrealised(1)
1664     {
1665     }
1666
1667     void realise()
1668     {
1669         if (--m_unrealised == 0) {
1670             if (g_entityInspector_windowConstructed) {
1671                 //globalOutputStream() << "Entity Inspector: realise\n";
1672                 EntityClassList_fill();
1673             }
1674         }
1675     }
1676
1677     void unrealise()
1678     {
1679         if (++m_unrealised == 1) {
1680             if (g_entityInspector_windowConstructed) {
1681                 //globalOutputStream() << "Entity Inspector: unrealise\n";
1682                 EntityClassList_clear();
1683             }
1684         }
1685     }
1686 };
1687
1688 EntityInspector g_EntityInspector;
1689
1690 #include "preferencesystem.h"
1691 #include "stringio.h"
1692
1693 void EntityInspector_construct()
1694 {
1695     GlobalEntityClassManager().attach(g_EntityInspector);
1696
1697     GlobalPreferenceSystem().registerPreference("EntitySplit1", make_property_string(g_entitysplit1_position));
1698     GlobalPreferenceSystem().registerPreference("EntitySplit2", make_property_string(g_entitysplit2_position));
1699
1700 }
1701
1702 void EntityInspector_destroy()
1703 {
1704     GlobalEntityClassManager().detach(g_EntityInspector);
1705 }
1706
1707 const char *EntityInspector_getCurrentKey()
1708 {
1709     if (!GroupDialog_isShown()) {
1710         return 0;
1711     }
1712     if (GroupDialog_getPage() != g_page_entity) {
1713         return 0;
1714     }
1715     return gtk_entry_get_text(g_entityKeyEntry);
1716 }