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