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