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