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