]> git.xonotic.org Git - xonotic/netradiant.git/blob - radiant/select.cpp
Merge branch 'NateEag-master-patch-12920' into 'master'
[xonotic/netradiant.git] / radiant / select.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 "select.h"
23
24 #include <gtk/gtk.h>
25
26 #include "debugging/debugging.h"
27
28 #include "ientity.h"
29 #include "iselection.h"
30 #include "iundo.h"
31
32 #include <vector>
33
34 #include "stream/stringstream.h"
35 #include "signal/isignal.h"
36 #include "shaderlib.h"
37 #include "scenelib.h"
38
39 #include "gtkutil/idledraw.h"
40 #include "gtkutil/dialog.h"
41 #include "gtkutil/widget.h"
42 #include "brushmanip.h"
43 #include "brush.h"
44 #include "patch.h"
45 #include "patchmanip.h"
46 #include "patchdialog.h"
47 #include "selection.h"
48 #include "texwindow.h"
49 #include "gtkmisc.h"
50 #include "mainframe.h"
51 #include "grid.h"
52 #include "map.h"
53 #include "entityinspector.h"
54
55
56
57 select_workzone_t g_select_workzone;
58
59
60 /**
61    Loops over all selected brushes and stores their
62    world AABBs in the specified array.
63  */
64 class CollectSelectedBrushesBounds : public SelectionSystem::Visitor
65 {
66 AABB* m_bounds;     // array of AABBs
67 Unsigned m_max;     // max AABB-elements in array
68 Unsigned& m_count;  // count of valid AABBs stored in array
69
70 public:
71 CollectSelectedBrushesBounds( AABB* bounds, Unsigned max, Unsigned& count )
72         : m_bounds( bounds ),
73         m_max( max ),
74         m_count( count ){
75         m_count = 0;
76 }
77
78 void visit( scene::Instance& instance ) const {
79         ASSERT_MESSAGE( m_count <= m_max, "Invalid m_count in CollectSelectedBrushesBounds" );
80
81         // stop if the array is already full
82         if ( m_count == m_max ) {
83                 return;
84         }
85
86         Selectable* selectable = Instance_getSelectable( instance );
87         if ( ( selectable != 0 )
88                  && instance.isSelected() ) {
89                 // brushes only
90                 if ( Instance_getBrush( instance ) != 0 ) {
91                         m_bounds[m_count] = instance.worldAABB();
92                         ++m_count;
93                 }
94         }
95 }
96 };
97
98 /**
99    Selects all objects that intersect one of the bounding AABBs.
100    The exact intersection-method is specified through TSelectionPolicy
101  */
102 template<class TSelectionPolicy>
103 class SelectByBounds : public scene::Graph::Walker
104 {
105 AABB* m_aabbs;             // selection aabbs
106 Unsigned m_count;          // number of aabbs in m_aabbs
107 TSelectionPolicy policy;   // type that contains a custom intersection method aabb<->aabb
108
109 public:
110 SelectByBounds( AABB* aabbs, Unsigned count )
111         : m_aabbs( aabbs ),
112         m_count( count ){
113 }
114
115 bool pre( const scene::Path& path, scene::Instance& instance ) const {
116         if( path.top().get().visible() ){
117                 Selectable* selectable = Instance_getSelectable( instance );
118
119                 // ignore worldspawn
120                 Entity* entity = Node_getEntity( path.top() );
121                 if ( entity ) {
122                         if ( string_equal( entity->getKeyValue( "classname" ), "worldspawn" ) ) {
123                                 return true;
124                         }
125                 }
126
127                 if ( ( path.size() > 1 ) &&
128                         ( !path.top().get().isRoot() ) &&
129                         ( selectable != 0 ) &&
130                         ( !node_is_group( path.top() ) )
131                         ) {
132                         for ( Unsigned i = 0; i < m_count; ++i )
133                         {
134                                 if ( policy.Evaluate( m_aabbs[i], instance ) ) {
135                                         selectable->setSelected( true );
136                                 }
137                         }
138                 }
139         }
140         else{
141                 return false;
142         }
143
144         return true;
145 }
146
147 /**
148    Performs selection operation on the global scenegraph.
149    If delete_bounds_src is true, then the objects which were
150    used as source for the selection aabbs will be deleted.
151  */
152 static void DoSelection( bool delete_bounds_src = true ){
153         if ( GlobalSelectionSystem().Mode() == SelectionSystem::ePrimitive ) {
154                 // we may not need all AABBs since not all selected objects have to be brushes
155                 const Unsigned max = (Unsigned)GlobalSelectionSystem().countSelected();
156                 AABB* aabbs = new AABB[max];
157
158                 Unsigned count;
159                 CollectSelectedBrushesBounds collector( aabbs, max, count );
160                 GlobalSelectionSystem().foreachSelected( collector );
161
162                 // nothing usable in selection
163                 if ( !count ) {
164                         delete[] aabbs;
165                         return;
166                 }
167
168                 // delete selected objects
169                 if ( delete_bounds_src ) { // see deleteSelection
170                         UndoableCommand undo( "deleteSelected" );
171                         Select_Delete();
172                 }
173
174                 // select objects with bounds
175                 GlobalSceneGraph().traverse( SelectByBounds<TSelectionPolicy>( aabbs, count ) );
176
177                 SceneChangeNotify();
178                 delete[] aabbs;
179         }
180 }
181 };
182
183 /**
184    SelectionPolicy for SelectByBounds
185    Returns true if box and the AABB of instance intersect
186  */
187 class SelectionPolicy_Touching
188 {
189 public:
190 bool Evaluate( const AABB& box, scene::Instance& instance ) const {
191         const AABB& other( instance.worldAABB() );
192         for ( Unsigned i = 0; i < 3; ++i )
193         {
194                 if ( fabsf( box.origin[i] - other.origin[i] ) > ( box.extents[i] + other.extents[i] ) ) {
195                         return false;
196                 }
197         }
198         return true;
199 }
200 };
201
202 /**
203    SelectionPolicy for SelectByBounds
204    Returns true if the AABB of instance is inside box
205  */
206 class SelectionPolicy_Inside
207 {
208 public:
209 bool Evaluate( const AABB& box, scene::Instance& instance ) const {
210         const AABB& other( instance.worldAABB() );
211         for ( Unsigned i = 0; i < 3; ++i )
212         {
213                 if ( fabsf( box.origin[i] - other.origin[i] ) > ( box.extents[i] - other.extents[i] ) ) {
214                         return false;
215                 }
216         }
217         return true;
218 }
219 };
220
221 class DeleteSelected : public scene::Graph::Walker
222 {
223 mutable bool m_remove;
224 mutable bool m_removedChild;
225 public:
226 DeleteSelected()
227         : m_remove( false ), m_removedChild( false ){
228 }
229 bool pre( const scene::Path& path, scene::Instance& instance ) const {
230         m_removedChild = false;
231
232         Selectable* selectable = Instance_getSelectable( instance );
233         if ( selectable != 0
234                  && selectable->isSelected()
235                  && path.size() > 1
236                  && !path.top().get().isRoot() ) {
237                 m_remove = true;
238
239                 return false; // dont traverse into child elements
240         }
241         return true;
242 }
243 void post( const scene::Path& path, scene::Instance& instance ) const {
244
245         if ( m_removedChild ) {
246                 m_removedChild = false;
247
248                 // delete empty entities
249                 Entity* entity = Node_getEntity( path.top() );
250                 if ( entity != 0
251                          && path.top().get_pointer() != Map_FindWorldspawn( g_map )
252                          && Node_getTraversable( path.top() )->empty() ) {
253                         Path_deleteTop( path );
254                 }
255         }
256
257         // node should be removed
258         if ( m_remove ) {
259                 if ( Node_isEntity( path.parent() ) != 0 ) {
260                         m_removedChild = true;
261                 }
262
263                 m_remove = false;
264                 Path_deleteTop( path );
265         }
266 }
267 };
268
269 void Scene_DeleteSelected( scene::Graph& graph ){
270         graph.traverse( DeleteSelected() );
271         SceneChangeNotify();
272 }
273
274 void Select_Delete( void ){
275         Scene_DeleteSelected( GlobalSceneGraph() );
276 }
277
278 class InvertSelectionWalker : public scene::Graph::Walker
279 {
280 SelectionSystem::EMode m_mode;
281 SelectionSystem::EComponentMode m_compmode;
282 mutable Selectable* m_selectable;
283 public:
284 InvertSelectionWalker( SelectionSystem::EMode mode, SelectionSystem::EComponentMode compmode )
285         : m_mode( mode ), m_compmode( compmode ), m_selectable( 0 ){
286 }
287 bool pre( const scene::Path& path, scene::Instance& instance ) const {
288         if( !path.top().get().visible() ){
289                 m_selectable = 0;
290                 return false;
291         }
292         Selectable* selectable = Instance_getSelectable( instance );
293         if ( selectable ) {
294                 switch ( m_mode )
295                 {
296                 case SelectionSystem::eEntity:
297                         if ( Node_isEntity( path.top() ) != 0 ) {
298                                 m_selectable = path.top().get().visible() ? selectable : 0;
299                         }
300                         break;
301                 case SelectionSystem::ePrimitive:
302                         m_selectable = path.top().get().visible() ? selectable : 0;
303                         break;
304                 case SelectionSystem::eComponent:
305                         BrushInstance* brushinstance = Instance_getBrush( instance );
306                         if( brushinstance != 0 ){
307                                 if( brushinstance->isSelected() )
308                                         brushinstance->invertComponentSelection( m_compmode );
309                         }
310                         else{
311                                 PatchInstance* patchinstance = Instance_getPatch( instance );
312                                 if( patchinstance != 0 && m_compmode == SelectionSystem::eVertex ){
313                                         if( patchinstance->isSelected() )
314                                                 patchinstance->invertComponentSelection();
315                                 }
316                         }
317                         break;
318                 }
319         }
320         return true;
321 }
322 void post( const scene::Path& path, scene::Instance& instance ) const {
323         if ( m_selectable != 0 ) {
324                 m_selectable->setSelected( !m_selectable->isSelected() );
325                 m_selectable = 0;
326         }
327 }
328 };
329
330 void Scene_Invert_Selection( scene::Graph& graph ){
331         graph.traverse( InvertSelectionWalker( GlobalSelectionSystem().Mode(), GlobalSelectionSystem().ComponentMode() ) );
332 }
333
334 void Select_Invert(){
335         Scene_Invert_Selection( GlobalSceneGraph() );
336 }
337
338 //interesting printings
339 class ExpandSelectionToEntitiesWalker_dbg : public scene::Graph::Walker
340 {
341 mutable std::size_t m_depth;
342 NodeSmartReference worldspawn;
343 public:
344 ExpandSelectionToEntitiesWalker_dbg() : m_depth( 0 ), worldspawn( Map_FindOrInsertWorldspawn( g_map ) ){
345 }
346 bool pre( const scene::Path& path, scene::Instance& instance ) const {
347         ++m_depth;
348         globalOutputStream() << "pre depth_" << m_depth;
349         globalOutputStream() << " path.size()_" << path.size();
350         if ( path.top().get() == worldspawn )
351                 globalOutputStream() << " worldspawn";
352         if( path.top().get().isRoot() )
353                 globalOutputStream() << " path.top().get().isRoot()";
354         Entity* entity = Node_getEntity( path.top() );
355         if ( entity != 0 ){
356                 globalOutputStream() << " entity!=0";
357                 if( entity->isContainer() ){
358                         globalOutputStream() << " entity->isContainer()";
359                 }
360                 globalOutputStream() << " classname_" << entity->getKeyValue( "classname" );
361         }
362         globalOutputStream() << "\n";
363 //      globalOutputStream() << "" <<  ;
364 //      globalOutputStream() << "" <<  ;
365 //      globalOutputStream() << "" <<  ;
366 //      globalOutputStream() << "" <<  ;
367         return true;
368 }
369 void post( const scene::Path& path, scene::Instance& instance ) const {
370         globalOutputStream() << "post depth_" << m_depth;
371         globalOutputStream() << " path.size()_" << path.size();
372         if ( path.top().get() == worldspawn )
373                 globalOutputStream() << " worldspawn";
374         if( path.top().get().isRoot() )
375                 globalOutputStream() << " path.top().get().isRoot()";
376         Entity* entity = Node_getEntity( path.top() );
377         if ( entity != 0 ){
378                 globalOutputStream() << " entity!=0";
379                 if( entity->isContainer() ){
380                         globalOutputStream() << " entity->isContainer()";
381                 }
382                 globalOutputStream() << " classname_" << entity->getKeyValue( "classname" );
383         }
384         globalOutputStream() << "\n";
385         --m_depth;
386 }
387 };
388
389 class ExpandSelectionToEntitiesWalker : public scene::Graph::Walker
390 {
391 mutable std::size_t m_depth;
392 NodeSmartReference worldspawn;
393 public:
394 ExpandSelectionToEntitiesWalker() : m_depth( 0 ), worldspawn( Map_FindOrInsertWorldspawn( g_map ) ){
395 }
396 bool pre( const scene::Path& path, scene::Instance& instance ) const {
397         ++m_depth;
398
399         // ignore worldspawn
400 //      NodeSmartReference me( path.top().get() );
401 //      if ( me == worldspawn ) {
402 //              return false;
403 //      }
404
405         if ( m_depth == 2 ) { // entity depth
406                 // traverse and select children if any one is selected
407                 bool beselected = false;
408                 if ( instance.childSelected() || instance.isSelected() ) {
409                         beselected = true;
410                         if( path.top().get() != worldspawn ){
411                                 Instance_setSelected( instance, true );
412                         }
413                 }
414                 return Node_getEntity( path.top() )->isContainer() && beselected;
415         }
416         else if ( m_depth == 3 ) { // primitive depth
417                 Instance_setSelected( instance, true );
418                 return false;
419         }
420         return true;
421 }
422 void post( const scene::Path& path, scene::Instance& instance ) const {
423         --m_depth;
424 }
425 };
426
427 void Scene_ExpandSelectionToEntities(){
428         GlobalSceneGraph().traverse( ExpandSelectionToEntitiesWalker() );
429 }
430
431
432 namespace
433 {
434 void Selection_UpdateWorkzone(){
435         if ( GlobalSelectionSystem().countSelected() != 0 ) {
436                 Select_GetBounds( g_select_workzone.d_work_min, g_select_workzone.d_work_max );
437         }
438 }
439 typedef FreeCaller<void(), Selection_UpdateWorkzone> SelectionUpdateWorkzoneCaller;
440
441 IdleDraw g_idleWorkzone = IdleDraw( SelectionUpdateWorkzoneCaller() );
442 }
443
444 const select_workzone_t& Select_getWorkZone(){
445         g_idleWorkzone.flush();
446         return g_select_workzone;
447 }
448
449 void UpdateWorkzone_ForSelection(){
450         g_idleWorkzone.queueDraw();
451 }
452
453 // update the workzone to the current selection
454 void UpdateWorkzone_ForSelectionChanged( const Selectable& selectable ){
455         //if ( selectable.isSelected() ) {
456                 UpdateWorkzone_ForSelection();
457         //}
458 }
459
460 void Select_SetShader( const char* shader ){
461         if ( GlobalSelectionSystem().Mode() != SelectionSystem::eComponent ) {
462                 Scene_BrushSetShader_Selected( GlobalSceneGraph(), shader );
463                 Scene_PatchSetShader_Selected( GlobalSceneGraph(), shader );
464         }
465         Scene_BrushSetShader_Component_Selected( GlobalSceneGraph(), shader );
466 }
467
468 void Select_SetShader_Undo( const char* shader ){
469         if ( GlobalSelectionSystem().countSelectedComponents() != 0 || GlobalSelectionSystem().countSelected() != 0 ) {
470                 UndoableCommand undo( "textureNameSetSelected" );
471                 Select_SetShader( shader );
472         }
473 }
474
475 void Select_SetTexdef( const TextureProjection& projection ){
476         if ( GlobalSelectionSystem().Mode() != SelectionSystem::eComponent ) {
477                 Scene_BrushSetTexdef_Selected( GlobalSceneGraph(), projection );
478         }
479         Scene_BrushSetTexdef_Component_Selected( GlobalSceneGraph(), projection );
480 }
481
482 void Select_SetFlags( const ContentsFlagsValue& flags ){
483         if ( GlobalSelectionSystem().Mode() != SelectionSystem::eComponent ) {
484                 Scene_BrushSetFlags_Selected( GlobalSceneGraph(), flags );
485         }
486         Scene_BrushSetFlags_Component_Selected( GlobalSceneGraph(), flags );
487 }
488
489 void Select_GetBounds( Vector3& mins, Vector3& maxs ){
490         AABB bounds;
491         Scene_BoundsSelected( GlobalSceneGraph(), bounds );
492         maxs = vector3_added( bounds.origin, bounds.extents );
493         mins = vector3_subtracted( bounds.origin, bounds.extents );
494 }
495
496 void Select_GetMid( Vector3& mid ){
497         AABB bounds;
498         Scene_BoundsSelected( GlobalSceneGraph(), bounds );
499         mid = vector3_snapped( bounds.origin );
500 }
501
502
503 void Select_FlipAxis( int axis ){
504         Vector3 flip( 1, 1, 1 );
505         flip[axis] = -1;
506         GlobalSelectionSystem().scaleSelected( flip );
507 }
508
509
510 void Select_Scale( float x, float y, float z ){
511         GlobalSelectionSystem().scaleSelected( Vector3( x, y, z ) );
512 }
513
514 enum axis_t
515 {
516         eAxisX = 0,
517         eAxisY = 1,
518         eAxisZ = 2,
519 };
520
521 enum sign_t
522 {
523         eSignPositive = 1,
524         eSignNegative = -1,
525 };
526
527 inline Matrix4 matrix4_rotation_for_axis90( axis_t axis, sign_t sign ){
528         switch ( axis )
529         {
530         case eAxisX:
531                 if ( sign == eSignPositive ) {
532                         return matrix4_rotation_for_sincos_x( 1, 0 );
533                 }
534                 else
535                 {
536                         return matrix4_rotation_for_sincos_x( -1, 0 );
537                 }
538         case eAxisY:
539                 if ( sign == eSignPositive ) {
540                         return matrix4_rotation_for_sincos_y( 1, 0 );
541                 }
542                 else
543                 {
544                         return matrix4_rotation_for_sincos_y( -1, 0 );
545                 }
546         default: //case eAxisZ:
547                 if ( sign == eSignPositive ) {
548                         return matrix4_rotation_for_sincos_z( 1, 0 );
549                 }
550                 else
551                 {
552                         return matrix4_rotation_for_sincos_z( -1, 0 );
553                 }
554         }
555 }
556
557 inline void matrix4_rotate_by_axis90( Matrix4& matrix, axis_t axis, sign_t sign ){
558         matrix4_multiply_by_matrix4( matrix, matrix4_rotation_for_axis90( axis, sign ) );
559 }
560
561 inline void matrix4_pivoted_rotate_by_axis90( Matrix4& matrix, axis_t axis, sign_t sign, const Vector3& pivotpoint ){
562         matrix4_translate_by_vec3( matrix, pivotpoint );
563         matrix4_rotate_by_axis90( matrix, axis, sign );
564         matrix4_translate_by_vec3( matrix, vector3_negated( pivotpoint ) );
565 }
566
567 inline Quaternion quaternion_for_axis90( axis_t axis, sign_t sign ){
568 #if 1
569         switch ( axis )
570         {
571         case eAxisX:
572                 if ( sign == eSignPositive ) {
573                         return Quaternion( c_half_sqrt2f, 0, 0, c_half_sqrt2f );
574                 }
575                 else
576                 {
577                         return Quaternion( -c_half_sqrt2f, 0, 0, -c_half_sqrt2f );
578                 }
579         case eAxisY:
580                 if ( sign == eSignPositive ) {
581                         return Quaternion( 0, c_half_sqrt2f, 0, c_half_sqrt2f );
582                 }
583                 else
584                 {
585                         return Quaternion( 0, -c_half_sqrt2f, 0, -c_half_sqrt2f );
586                 }
587         default: //case eAxisZ:
588                 if ( sign == eSignPositive ) {
589                         return Quaternion( 0, 0, c_half_sqrt2f, c_half_sqrt2f );
590                 }
591                 else
592                 {
593                         return Quaternion( 0, 0, -c_half_sqrt2f, -c_half_sqrt2f );
594                 }
595         }
596 #else
597         quaternion_for_matrix4_rotation( matrix4_rotation_for_axis90( (axis_t)axis, ( deg > 0 ) ? eSignPositive : eSignNegative ) );
598 #endif
599 }
600
601 void Select_RotateAxis( int axis, float deg ){
602         if ( fabs( deg ) == 90.f ) {
603                 GlobalSelectionSystem().rotateSelected( quaternion_for_axis90( (axis_t)axis, ( deg > 0 ) ? eSignPositive : eSignNegative ), true );
604         }
605         else
606         {
607                 switch ( axis )
608                 {
609                 case 0:
610                         GlobalSelectionSystem().rotateSelected( quaternion_for_matrix4_rotation( matrix4_rotation_for_x_degrees( deg ) ), false );
611                         break;
612                 case 1:
613                         GlobalSelectionSystem().rotateSelected( quaternion_for_matrix4_rotation( matrix4_rotation_for_y_degrees( deg ) ), false );
614                         break;
615                 case 2:
616                         GlobalSelectionSystem().rotateSelected( quaternion_for_matrix4_rotation( matrix4_rotation_for_z_degrees( deg ) ), false );
617                         break;
618                 }
619         }
620 }
621
622
623 void Select_ShiftTexture( float x, float y ){
624         if ( GlobalSelectionSystem().Mode() != SelectionSystem::eComponent ) {
625                 Scene_BrushShiftTexdef_Selected( GlobalSceneGraph(), x, y );
626                 Scene_PatchTranslateTexture_Selected( GlobalSceneGraph(), x, y );
627         }
628         //globalOutputStream() << "shift selected face textures: s=" << x << " t=" << y << '\n';
629         Scene_BrushShiftTexdef_Component_Selected( GlobalSceneGraph(), x, y );
630 }
631
632 void Select_ScaleTexture( float x, float y ){
633         if ( GlobalSelectionSystem().Mode() != SelectionSystem::eComponent ) {
634                 Scene_BrushScaleTexdef_Selected( GlobalSceneGraph(), x, y );
635                 Scene_PatchScaleTexture_Selected( GlobalSceneGraph(), x, y );
636         }
637         Scene_BrushScaleTexdef_Component_Selected( GlobalSceneGraph(), x, y );
638 }
639
640 void Select_RotateTexture( float amt ){
641         if ( GlobalSelectionSystem().Mode() != SelectionSystem::eComponent ) {
642                 Scene_BrushRotateTexdef_Selected( GlobalSceneGraph(), amt );
643                 Scene_PatchRotateTexture_Selected( GlobalSceneGraph(), amt );
644         }
645         Scene_BrushRotateTexdef_Component_Selected( GlobalSceneGraph(), amt );
646 }
647
648 // TTimo modified to handle shader architecture:
649 // expects shader names at input, comparison relies on shader names .. texture names no longer relevant
650 void FindReplaceTextures( const char* pFind, const char* pReplace, bool bSelected ){
651         if ( !texdef_name_valid( pFind ) ) {
652                 globalErrorStream() << "FindReplaceTextures: invalid texture name: '" << pFind << "', aborted\n";
653                 return;
654         }
655         if ( !texdef_name_valid( pReplace ) ) {
656                 globalErrorStream() << "FindReplaceTextures: invalid texture name: '" << pReplace << "', aborted\n";
657                 return;
658         }
659
660         StringOutputStream command;
661         command << "textureFindReplace -find " << pFind << " -replace " << pReplace;
662         UndoableCommand undo( command.c_str() );
663
664         if ( bSelected ) {
665                 if ( GlobalSelectionSystem().Mode() != SelectionSystem::eComponent ) {
666                         Scene_BrushFindReplaceShader_Selected( GlobalSceneGraph(), pFind, pReplace );
667                         Scene_PatchFindReplaceShader_Selected( GlobalSceneGraph(), pFind, pReplace );
668                 }
669                 Scene_BrushFindReplaceShader_Component_Selected( GlobalSceneGraph(), pFind, pReplace );
670         }
671         else
672         {
673                 Scene_BrushFindReplaceShader( GlobalSceneGraph(), pFind, pReplace );
674                 Scene_PatchFindReplaceShader( GlobalSceneGraph(), pFind, pReplace );
675         }
676 }
677
678 typedef std::vector<const char*> PropertyValues;
679
680 bool propertyvalues_contain( const PropertyValues& propertyvalues, const char *str ){
681         for ( PropertyValues::const_iterator i = propertyvalues.begin(); i != propertyvalues.end(); ++i )
682         {
683                 if ( string_equal( str, *i ) ) {
684                         return true;
685                 }
686         }
687         return false;
688 }
689
690 class EntityFindByPropertyValueWalker : public scene::Graph::Walker
691 {
692 const PropertyValues& m_propertyvalues;
693 const char *m_prop;
694 const NodeSmartReference worldspawn;
695 public:
696 EntityFindByPropertyValueWalker( const char *prop, const PropertyValues& propertyvalues )
697         : m_propertyvalues( propertyvalues ), m_prop( prop ), worldspawn( Map_FindOrInsertWorldspawn( g_map ) ){
698 }
699 bool pre( const scene::Path& path, scene::Instance& instance ) const {
700         if( !path.top().get().visible() ){
701                 return false;
702         }
703         // ignore worldspawn
704         if ( path.top().get() == worldspawn ) {
705                 return false;
706         }
707
708         Entity* entity = Node_getEntity( path.top() );
709         if ( entity != 0 ){
710                 if( propertyvalues_contain( m_propertyvalues, entity->getKeyValue( m_prop ) ) ) {
711                         Instance_getSelectable( instance )->setSelected( true );
712                         return true;
713                 }
714                 return false;
715         }
716         else if( path.size() > 2 && !path.top().get().isRoot() ){
717                 Selectable* selectable = Instance_getSelectable( instance );
718                 if( selectable != 0 )
719                         selectable->setSelected( true );
720         }
721         return true;
722 }
723 };
724
725 void Scene_EntitySelectByPropertyValues( scene::Graph& graph, const char *prop, const PropertyValues& propertyvalues ){
726         graph.traverse( EntityFindByPropertyValueWalker( prop, propertyvalues ) );
727 }
728
729 class EntityGetSelectedPropertyValuesWalker : public scene::Graph::Walker
730 {
731 PropertyValues& m_propertyvalues;
732 const char *m_prop;
733 const NodeSmartReference worldspawn;
734 public:
735 EntityGetSelectedPropertyValuesWalker( const char *prop, PropertyValues& propertyvalues )
736         : m_propertyvalues( propertyvalues ), m_prop( prop ), worldspawn( Map_FindOrInsertWorldspawn( g_map ) ){
737 }
738 bool pre( const scene::Path& path, scene::Instance& instance ) const {
739         Entity* entity = Node_getEntity( path.top() );
740         if ( entity != 0 ){
741                 if( path.top().get() != worldspawn ){
742                         Selectable* selectable = Instance_getSelectable( instance );
743                         if ( ( selectable != 0 && selectable->isSelected() ) || instance.childSelected() ) {
744                                 if ( !propertyvalues_contain( m_propertyvalues, entity->getKeyValue( m_prop ) ) ) {
745                                         m_propertyvalues.push_back( entity->getKeyValue( m_prop ) );
746                                 }
747                         }
748                 }
749                 return false;
750         }
751         return true;
752 }
753 };
754 /*
755 class EntityGetSelectedPropertyValuesWalker : public scene::Graph::Walker
756 {
757 PropertyValues& m_propertyvalues;
758 const char *m_prop;
759 mutable bool m_selected_children;
760 const NodeSmartReference worldspawn;
761 public:
762 EntityGetSelectedPropertyValuesWalker( const char *prop, PropertyValues& propertyvalues )
763         : m_propertyvalues( propertyvalues ), m_prop( prop ), m_selected_children( false ), worldspawn( Map_FindOrInsertWorldspawn( g_map ) ){
764 }
765 bool pre( const scene::Path& path, scene::Instance& instance ) const {
766         Selectable* selectable = Instance_getSelectable( instance );
767         if ( selectable != 0
768                  && selectable->isSelected() ) {
769                 Entity* entity = Node_getEntity( path.top() );
770                 if ( entity != 0 ) {
771                         if ( !propertyvalues_contain( m_propertyvalues, entity->getKeyValue( m_prop ) ) ) {
772                                 m_propertyvalues.push_back( entity->getKeyValue( m_prop ) );
773                         }
774                         return false;
775                 }
776                 else{
777                         m_selected_children = true;
778                 }
779         }
780         return true;
781 }
782 void post( const scene::Path& path, scene::Instance& instance ) const {
783         Entity* entity = Node_getEntity( path.top() );
784         if( entity != 0 && m_selected_children ){
785                 m_selected_children = false;
786                 if( path.top().get() == worldspawn )
787                         return;
788                 if ( !propertyvalues_contain( m_propertyvalues, entity->getKeyValue( m_prop ) ) ) {
789                         m_propertyvalues.push_back( entity->getKeyValue( m_prop ) );
790                 }
791         }
792 }
793 };
794 */
795 void Scene_EntityGetPropertyValues( scene::Graph& graph, const char *prop, PropertyValues& propertyvalues ){
796         graph.traverse( EntityGetSelectedPropertyValuesWalker( prop, propertyvalues ) );
797 }
798
799 void Select_AllOfType(){
800         if ( GlobalSelectionSystem().Mode() == SelectionSystem::eComponent ) {
801                 if ( GlobalSelectionSystem().ComponentMode() == SelectionSystem::eFace ) {
802                         GlobalSelectionSystem().setSelectedAllComponents( false );
803                         Scene_BrushSelectByShader_Component( GlobalSceneGraph(), TextureBrowser_GetSelectedShader( GlobalTextureBrowser() ) );
804                 }
805         }
806         else
807         {
808                 PropertyValues propertyvalues;
809                 const char *prop = EntityInspector_getCurrentKey();
810                 if ( !prop || !*prop ) {
811                         prop = "classname";
812                 }
813                 Scene_EntityGetPropertyValues( GlobalSceneGraph(), prop, propertyvalues );
814                 GlobalSelectionSystem().setSelectedAll( false );
815                 if ( !propertyvalues.empty() ) {
816                         Scene_EntitySelectByPropertyValues( GlobalSceneGraph(), prop, propertyvalues );
817                 }
818                 else
819                 {
820                         Scene_BrushSelectByShader( GlobalSceneGraph(), TextureBrowser_GetSelectedShader( GlobalTextureBrowser() ) );
821                         Scene_PatchSelectByShader( GlobalSceneGraph(), TextureBrowser_GetSelectedShader( GlobalTextureBrowser() ) );
822                 }
823         }
824 }
825
826 void Select_Inside( void ){
827         SelectByBounds<SelectionPolicy_Inside>::DoSelection();
828 }
829
830 void Select_Touching( void ){
831         SelectByBounds<SelectionPolicy_Touching>::DoSelection( false );
832 }
833
834 void Select_FitTexture( float horizontal, float vertical ){
835         if ( GlobalSelectionSystem().Mode() != SelectionSystem::eComponent ) {
836                 Scene_BrushFitTexture_Selected( GlobalSceneGraph(), horizontal, vertical );
837         }
838         Scene_BrushFitTexture_Component_Selected( GlobalSceneGraph(), horizontal, vertical );
839
840         SceneChangeNotify();
841 }
842
843
844 #include "commands.h"
845 #include "dialog.h"
846
847 inline void hide_node( scene::Node& node, bool hide ){
848         hide
849         ? node.enable( scene::Node::eHidden )
850         : node.disable( scene::Node::eHidden );
851 }
852
853 bool g_nodes_be_hidden = false;
854
855 ConstReferenceCaller<bool, void(const Callback<void(bool)> &), PropertyImpl<bool>::Export> g_hidden_caller( g_nodes_be_hidden );
856
857 ToggleItem g_hidden_item( g_hidden_caller );
858
859 class HideSelectedWalker : public scene::Graph::Walker
860 {
861 bool m_hide;
862 public:
863 HideSelectedWalker( bool hide )
864         : m_hide( hide ){
865 }
866 bool pre( const scene::Path& path, scene::Instance& instance ) const {
867         Selectable* selectable = Instance_getSelectable( instance );
868         if ( selectable != 0
869                  && selectable->isSelected() ) {
870                 g_nodes_be_hidden = m_hide;
871                 hide_node( path.top(), m_hide );
872         }
873         return true;
874 }
875 };
876
877 void Scene_Hide_Selected( bool hide ){
878         GlobalSceneGraph().traverse( HideSelectedWalker( hide ) );
879 }
880
881 void Select_Hide(){
882         Scene_Hide_Selected( true );
883         SceneChangeNotify();
884 }
885
886 void HideSelected(){
887         Select_Hide();
888         GlobalSelectionSystem().setSelectedAll( false );
889         g_hidden_item.update();
890 }
891
892
893 class HideAllWalker : public scene::Graph::Walker
894 {
895 bool m_hide;
896 public:
897 HideAllWalker( bool hide )
898         : m_hide( hide ){
899 }
900 bool pre( const scene::Path& path, scene::Instance& instance ) const {
901         hide_node( path.top(), m_hide );
902         return true;
903 }
904 };
905
906 void Scene_Hide_All( bool hide ){
907         GlobalSceneGraph().traverse( HideAllWalker( hide ) );
908 }
909
910 void Select_ShowAllHidden(){
911         Scene_Hide_All( false );
912         SceneChangeNotify();
913         g_nodes_be_hidden = false;
914         g_hidden_item.update();
915 }
916
917
918 void Selection_Flipx(){
919         UndoableCommand undo( "mirrorSelected -axis x" );
920         Select_FlipAxis( 0 );
921 }
922
923 void Selection_Flipy(){
924         UndoableCommand undo( "mirrorSelected -axis y" );
925         Select_FlipAxis( 1 );
926 }
927
928 void Selection_Flipz(){
929         UndoableCommand undo( "mirrorSelected -axis z" );
930         Select_FlipAxis( 2 );
931 }
932
933 void Selection_Rotatex(){
934         UndoableCommand undo( "rotateSelected -axis x -angle -90" );
935         Select_RotateAxis( 0,-90 );
936 }
937
938 void Selection_Rotatey(){
939         UndoableCommand undo( "rotateSelected -axis y -angle 90" );
940         Select_RotateAxis( 1, 90 );
941 }
942
943 void Selection_Rotatez(){
944         UndoableCommand undo( "rotateSelected -axis z -angle -90" );
945         Select_RotateAxis( 2,-90 );
946 }
947 #include "xywindow.h"
948 void Selection_FlipHorizontally(){
949         VIEWTYPE viewtype = GlobalXYWnd_getCurrentViewType();
950         switch ( viewtype )
951         {
952         case XY:
953         case XZ:
954                 Selection_Flipx();
955                 break;
956         default:
957                 Selection_Flipy();
958                 break;
959         }
960 }
961
962 void Selection_FlipVertically(){
963         VIEWTYPE viewtype = GlobalXYWnd_getCurrentViewType();
964         switch ( viewtype )
965         {
966         case XZ:
967         case YZ:
968                 Selection_Flipz();
969                 break;
970         default:
971                 Selection_Flipy();
972                 break;
973         }
974 }
975
976 void Selection_RotateClockwise(){
977         UndoableCommand undo( "rotateSelected Clockwise 90" );
978         VIEWTYPE viewtype = GlobalXYWnd_getCurrentViewType();
979         switch ( viewtype )
980         {
981         case XY:
982                 Select_RotateAxis( 2, -90 );
983                 break;
984         case XZ:
985                 Select_RotateAxis( 1, 90 );
986                 break;
987         default:
988                 Select_RotateAxis( 0, -90 );
989                 break;
990         }
991 }
992
993 void Selection_RotateAnticlockwise(){
994         UndoableCommand undo( "rotateSelected Anticlockwise 90" );
995         VIEWTYPE viewtype = GlobalXYWnd_getCurrentViewType();
996         switch ( viewtype )
997         {
998         case XY:
999                 Select_RotateAxis( 2, 90 );
1000                 break;
1001         case XZ:
1002                 Select_RotateAxis( 1, -90 );
1003                 break;
1004         default:
1005                 Select_RotateAxis( 0, 90 );
1006                 break;
1007         }
1008
1009 }
1010
1011
1012
1013 void Select_registerCommands(){
1014         GlobalCommands_insert( "ShowHidden", makeCallbackF( Select_ShowAllHidden ), Accelerator( 'H', (GdkModifierType)GDK_SHIFT_MASK ) );
1015         GlobalToggles_insert( "HideSelected", makeCallbackF( HideSelected ), ToggleItem::AddCallbackCaller( g_hidden_item ), Accelerator( 'H' ) );
1016
1017         GlobalCommands_insert( "MirrorSelectionX", makeCallbackF( Selection_Flipx ) );
1018         GlobalCommands_insert( "RotateSelectionX", makeCallbackF( Selection_Rotatex ) );
1019         GlobalCommands_insert( "MirrorSelectionY", makeCallbackF( Selection_Flipy ) );
1020         GlobalCommands_insert( "RotateSelectionY", makeCallbackF( Selection_Rotatey ) );
1021         GlobalCommands_insert( "MirrorSelectionZ", makeCallbackF( Selection_Flipz ) );
1022         GlobalCommands_insert( "RotateSelectionZ", makeCallbackF( Selection_Rotatez ) );
1023
1024         GlobalCommands_insert( "MirrorSelectionHorizontally", makeCallbackF( Selection_FlipHorizontally ) );
1025         GlobalCommands_insert( "MirrorSelectionVertically", makeCallbackF( Selection_FlipVertically ) );
1026
1027         GlobalCommands_insert( "RotateSelectionClockwise", makeCallbackF( Selection_RotateClockwise ) );
1028         GlobalCommands_insert( "RotateSelectionAnticlockwise", makeCallbackF( Selection_RotateAnticlockwise ) );
1029 }
1030
1031
1032 void Nudge( int nDim, float fNudge ){
1033         Vector3 translate( 0, 0, 0 );
1034         translate[nDim] = fNudge;
1035
1036         GlobalSelectionSystem().translateSelected( translate );
1037 }
1038
1039 void Selection_NudgeZ( float amount ){
1040         StringOutputStream command;
1041         command << "nudgeSelected -axis z -amount " << amount;
1042         UndoableCommand undo( command.c_str() );
1043
1044         Nudge( 2, amount );
1045 }
1046
1047 void Selection_MoveDown(){
1048         Selection_NudgeZ( -GetGridSize() );
1049 }
1050
1051 void Selection_MoveUp(){
1052         Selection_NudgeZ( GetGridSize() );
1053 }
1054
1055 void SceneSelectionChange( const Selectable& selectable ){
1056         SceneChangeNotify();
1057 }
1058
1059 SignalHandlerId Selection_boundsChanged;
1060
1061 void Selection_construct(){
1062         typedef FreeCaller<void(const Selectable&), SceneSelectionChange> SceneSelectionChangeCaller;
1063         GlobalSelectionSystem().addSelectionChangeCallback( SceneSelectionChangeCaller() );
1064         typedef FreeCaller<void(const Selectable&), UpdateWorkzone_ForSelectionChanged> UpdateWorkzoneForSelectionChangedCaller;
1065         GlobalSelectionSystem().addSelectionChangeCallback( UpdateWorkzoneForSelectionChangedCaller() );
1066         typedef FreeCaller<void(), UpdateWorkzone_ForSelection> UpdateWorkzoneForSelectionCaller;
1067         Selection_boundsChanged = GlobalSceneGraph().addBoundsChangedCallback( UpdateWorkzoneForSelectionCaller() );
1068 }
1069
1070 void Selection_destroy(){
1071         GlobalSceneGraph().removeBoundsChangedCallback( Selection_boundsChanged );
1072 }
1073
1074
1075 #include "gtkdlgs.h"
1076 #include <gdk/gdkkeysyms.h>
1077
1078
1079 inline Quaternion quaternion_for_euler_xyz_degrees( const Vector3& eulerXYZ ){
1080 #if 0
1081         return quaternion_for_matrix4_rotation( matrix4_rotation_for_euler_xyz_degrees( eulerXYZ ) );
1082 #elif 0
1083         return quaternion_multiplied_by_quaternion(
1084                            quaternion_multiplied_by_quaternion(
1085                                    quaternion_for_z( degrees_to_radians( eulerXYZ[2] ) ),
1086                                    quaternion_for_y( degrees_to_radians( eulerXYZ[1] ) )
1087                                    ),
1088                            quaternion_for_x( degrees_to_radians( eulerXYZ[0] ) )
1089                            );
1090 #elif 1
1091         double cx = cos( degrees_to_radians( eulerXYZ[0] * 0.5 ) );
1092         double sx = sin( degrees_to_radians( eulerXYZ[0] * 0.5 ) );
1093         double cy = cos( degrees_to_radians( eulerXYZ[1] * 0.5 ) );
1094         double sy = sin( degrees_to_radians( eulerXYZ[1] * 0.5 ) );
1095         double cz = cos( degrees_to_radians( eulerXYZ[2] * 0.5 ) );
1096         double sz = sin( degrees_to_radians( eulerXYZ[2] * 0.5 ) );
1097
1098         return Quaternion(
1099                            cz * cy * sx - sz * sy * cx,
1100                            cz * sy * cx + sz * cy * sx,
1101                            sz * cy * cx - cz * sy * sx,
1102                            cz * cy * cx + sz * sy * sx
1103                            );
1104 #endif
1105 }
1106
1107 struct RotateDialog
1108 {
1109         ui::SpinButton x{ui::null};
1110         ui::SpinButton y{ui::null};
1111         ui::SpinButton z{ui::null};
1112         ui::Window window{ui::null};
1113 };
1114
1115 static gboolean rotatedlg_apply( ui::Widget widget, RotateDialog* rotateDialog ){
1116         Vector3 eulerXYZ;
1117
1118         gtk_spin_button_update ( rotateDialog->x );
1119         gtk_spin_button_update ( rotateDialog->y );
1120         gtk_spin_button_update ( rotateDialog->z );
1121         eulerXYZ[0] = static_cast<float>( gtk_spin_button_get_value( rotateDialog->x ) );
1122         eulerXYZ[1] = static_cast<float>( gtk_spin_button_get_value( rotateDialog->y ) );
1123         eulerXYZ[2] = static_cast<float>( gtk_spin_button_get_value( rotateDialog->z ) );
1124
1125         StringOutputStream command;
1126         command << "rotateSelectedEulerXYZ -x " << eulerXYZ[0] << " -y " << eulerXYZ[1] << " -z " << eulerXYZ[2];
1127         UndoableCommand undo( command.c_str() );
1128
1129         GlobalSelectionSystem().rotateSelected( quaternion_for_euler_xyz_degrees( eulerXYZ ), false );
1130         return TRUE;
1131 }
1132
1133 static gboolean rotatedlg_cancel( ui::Widget widget, RotateDialog* rotateDialog ){
1134         rotateDialog->window.hide();
1135
1136         gtk_spin_button_set_value( rotateDialog->x, 0.0f ); // reset to 0 on close
1137         gtk_spin_button_set_value( rotateDialog->y, 0.0f );
1138         gtk_spin_button_set_value( rotateDialog->z, 0.0f );
1139
1140         return TRUE;
1141 }
1142
1143 static gboolean rotatedlg_ok( ui::Widget widget, RotateDialog* rotateDialog ){
1144         rotatedlg_apply( widget, rotateDialog );
1145 //      rotatedlg_cancel( widget, rotateDialog );
1146         rotateDialog->window.hide();
1147         return TRUE;
1148 }
1149
1150 static gboolean rotatedlg_delete( ui::Widget widget, GdkEventAny *event, RotateDialog* rotateDialog ){
1151         rotatedlg_cancel( widget, rotateDialog );
1152         return TRUE;
1153 }
1154
1155 RotateDialog g_rotate_dialog;
1156 void DoRotateDlg(){
1157         if ( !g_rotate_dialog.window ) {
1158                 g_rotate_dialog.window = MainFrame_getWindow().create_dialog_window("Arbitrary rotation", G_CALLBACK(rotatedlg_delete ), &g_rotate_dialog );
1159
1160                 auto accel = ui::AccelGroup(ui::New);
1161                 g_rotate_dialog.window.add_accel_group( accel );
1162
1163                 {
1164                         auto hbox = create_dialog_hbox( 4, 4 );
1165                         g_rotate_dialog.window.add(hbox);
1166                         {
1167                                 auto table = create_dialog_table( 3, 2, 4, 4 );
1168                                 hbox.pack_start( table, TRUE, TRUE, 0 );
1169                                 {
1170                                         ui::Widget label = ui::Label( "  X  " );
1171                                         label.show();
1172                     table.attach(label, {0, 1, 0, 1}, {0, 0});
1173                                 }
1174                                 {
1175                                         ui::Widget label = ui::Label( "  Y  " );
1176                                         label.show();
1177                     table.attach(label, {0, 1, 1, 2}, {0, 0});
1178                                 }
1179                                 {
1180                                         ui::Widget label = ui::Label( "  Z  " );
1181                                         label.show();
1182                     table.attach(label, {0, 1, 2, 3}, {0, 0});
1183                                 }
1184                                 {
1185                                         auto adj = ui::Adjustment( 0, -359, 359, 1, 10, 0 );
1186                                         auto spin = ui::SpinButton( adj, 1, 1 );
1187                                         spin.show();
1188                     table.attach(spin, {1, 2, 0, 1}, {GTK_EXPAND | GTK_FILL, 0});
1189                     spin.dimensions(64, -1);
1190                                         gtk_spin_button_set_wrap( spin, TRUE );
1191
1192                                         gtk_widget_grab_focus( spin  );
1193
1194                                         g_rotate_dialog.x = spin;
1195                                 }
1196                                 {
1197                                         auto adj = ui::Adjustment( 0, -359, 359, 1, 10, 0 );
1198                                         auto spin = ui::SpinButton( adj, 1, 1 );
1199                                         spin.show();
1200                     table.attach(spin, {1, 2, 1, 2}, {GTK_EXPAND | GTK_FILL, 0});
1201                     spin.dimensions(64, -1);
1202                                         gtk_spin_button_set_wrap( spin, TRUE );
1203
1204                                         g_rotate_dialog.y = spin;
1205                                 }
1206                                 {
1207                                         auto adj = ui::Adjustment( 0, -359, 359, 1, 10, 0 );
1208                                         auto spin = ui::SpinButton( adj, 1, 1 );
1209                                         spin.show();
1210                     table.attach(spin, {1, 2, 2, 3}, {GTK_EXPAND | GTK_FILL, 0});
1211                     spin.dimensions(64, -1);
1212                                         gtk_spin_button_set_wrap( spin, TRUE );
1213
1214                                         g_rotate_dialog.z = spin;
1215                                 }
1216                         }
1217                         {
1218                                 auto vbox = create_dialog_vbox( 4 );
1219                                 hbox.pack_start( vbox, TRUE, TRUE, 0 );
1220                                 {
1221                                         auto button = create_dialog_button( "OK", G_CALLBACK( rotatedlg_ok ), &g_rotate_dialog );
1222                                         vbox.pack_start( button, FALSE, FALSE, 0 );
1223                                         widget_make_default( button );
1224                                         gtk_widget_add_accelerator( button , "clicked", accel, GDK_KEY_Return, (GdkModifierType)0, (GtkAccelFlags)0 );
1225                                 }
1226                                 {
1227                                         auto button = create_dialog_button( "Cancel", G_CALLBACK( rotatedlg_cancel ), &g_rotate_dialog );
1228                                         vbox.pack_start( button, FALSE, FALSE, 0 );
1229                                         gtk_widget_add_accelerator( button , "clicked", accel, GDK_KEY_Escape, (GdkModifierType)0, (GtkAccelFlags)0 );
1230                                 }
1231                                 {
1232                                         auto button = create_dialog_button( "Apply", G_CALLBACK( rotatedlg_apply ), &g_rotate_dialog );
1233                                         vbox.pack_start( button, FALSE, FALSE, 0 );
1234                                 }
1235                         }
1236                 }
1237         }
1238
1239         g_rotate_dialog.window.show();
1240 }
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250 struct ScaleDialog
1251 {
1252         ui::Entry x{ui::null};
1253         ui::Entry y{ui::null};
1254         ui::Entry z{ui::null};
1255         ui::Window window{ui::null};
1256 };
1257
1258 static gboolean scaledlg_apply( ui::Widget widget, ScaleDialog* scaleDialog ){
1259         float sx, sy, sz;
1260
1261         sx = static_cast<float>( atof( gtk_entry_get_text( GTK_ENTRY( scaleDialog->x ) ) ) );
1262         sy = static_cast<float>( atof( gtk_entry_get_text( GTK_ENTRY( scaleDialog->y ) ) ) );
1263         sz = static_cast<float>( atof( gtk_entry_get_text( GTK_ENTRY( scaleDialog->z ) ) ) );
1264
1265         StringOutputStream command;
1266         command << "scaleSelected -x " << sx << " -y " << sy << " -z " << sz;
1267         UndoableCommand undo( command.c_str() );
1268
1269         Select_Scale( sx, sy, sz );
1270
1271         return TRUE;
1272 }
1273
1274 static gboolean scaledlg_cancel( ui::Widget widget, ScaleDialog* scaleDialog ){
1275         scaleDialog->window.hide();
1276
1277         scaleDialog->x.text("1.0");
1278         scaleDialog->y.text("1.0");
1279         scaleDialog->z.text("1.0");
1280
1281         return TRUE;
1282 }
1283
1284 static gboolean scaledlg_ok( ui::Widget widget, ScaleDialog* scaleDialog ){
1285         scaledlg_apply( widget, scaleDialog );
1286         //scaledlg_cancel( widget, scaleDialog );
1287         scaleDialog->window.hide();
1288         return TRUE;
1289 }
1290
1291 static gboolean scaledlg_delete( ui::Widget widget, GdkEventAny *event, ScaleDialog* scaleDialog ){
1292         scaledlg_cancel( widget, scaleDialog );
1293         return TRUE;
1294 }
1295
1296 ScaleDialog g_scale_dialog;
1297
1298 void DoScaleDlg(){
1299         if ( !g_scale_dialog.window ) {
1300                 g_scale_dialog.window = MainFrame_getWindow().create_dialog_window("Arbitrary scale", G_CALLBACK(scaledlg_delete ), &g_scale_dialog );
1301
1302                 auto accel = ui::AccelGroup(ui::New);
1303                 g_scale_dialog.window.add_accel_group( accel );
1304
1305                 {
1306                         auto hbox = create_dialog_hbox( 4, 4 );
1307                         g_scale_dialog.window.add(hbox);
1308                         {
1309                                 auto table = create_dialog_table( 3, 2, 4, 4 );
1310                                 hbox.pack_start( table, TRUE, TRUE, 0 );
1311                                 {
1312                                         ui::Widget label = ui::Label( "  X  " );
1313                                         label.show();
1314                     table.attach(label, {0, 1, 0, 1}, {0, 0});
1315                                 }
1316                                 {
1317                                         ui::Widget label = ui::Label( "  Y  " );
1318                                         label.show();
1319                     table.attach(label, {0, 1, 1, 2}, {0, 0});
1320                                 }
1321                                 {
1322                                         ui::Widget label = ui::Label( "  Z  " );
1323                                         label.show();
1324                     table.attach(label, {0, 1, 2, 3}, {0, 0});
1325                                 }
1326                                 {
1327                                         auto entry = ui::Entry(ui::New);
1328                                         entry.text("1.0");
1329                                         entry.show();
1330                     table.attach(entry, {1, 2, 0, 1}, {GTK_EXPAND | GTK_FILL, 0});
1331
1332                                         g_scale_dialog.x = entry;
1333                                 }
1334                                 {
1335                                         auto entry = ui::Entry(ui::New);
1336                                         entry.text("1.0");
1337                                         entry.show();
1338                     table.attach(entry, {1, 2, 1, 2}, {GTK_EXPAND | GTK_FILL, 0});
1339
1340                                         g_scale_dialog.y = entry;
1341                                 }
1342                                 {
1343                                         auto entry = ui::Entry(ui::New);
1344                                         entry.text("1.0");
1345                                         entry.show();
1346                     table.attach(entry, {1, 2, 2, 3}, {GTK_EXPAND | GTK_FILL, 0});
1347
1348                                         g_scale_dialog.z = entry;
1349                                 }
1350                         }
1351                         {
1352                                 auto vbox = create_dialog_vbox( 4 );
1353                                 hbox.pack_start( vbox, TRUE, TRUE, 0 );
1354                                 {
1355                                         auto button = create_dialog_button( "OK", G_CALLBACK( scaledlg_ok ), &g_scale_dialog );
1356                                         vbox.pack_start( button, FALSE, FALSE, 0 );
1357                                         widget_make_default( button );
1358                                         gtk_widget_add_accelerator( button , "clicked", accel, GDK_KEY_Return, (GdkModifierType)0, (GtkAccelFlags)0 );
1359                                 }
1360                                 {
1361                                         auto button = create_dialog_button( "Cancel", G_CALLBACK( scaledlg_cancel ), &g_scale_dialog );
1362                                         vbox.pack_start( button, FALSE, FALSE, 0 );
1363                                         gtk_widget_add_accelerator( button , "clicked", accel, GDK_KEY_Escape, (GdkModifierType)0, (GtkAccelFlags)0 );
1364                                 }
1365                                 {
1366                                         auto button = create_dialog_button( "Apply", G_CALLBACK( scaledlg_apply ), &g_scale_dialog );
1367                                         vbox.pack_start( button, FALSE, FALSE, 0 );
1368                                 }
1369                         }
1370                 }
1371         }
1372
1373         g_scale_dialog.window.show();
1374 }
1375
1376
1377 class EntityGetSelectedPropertyValuesWalker_nonEmpty : public scene::Graph::Walker
1378 {
1379 PropertyValues& m_propertyvalues;
1380 const char *m_prop;
1381 const NodeSmartReference worldspawn;
1382 public:
1383 EntityGetSelectedPropertyValuesWalker_nonEmpty( const char *prop, PropertyValues& propertyvalues )
1384         : m_propertyvalues( propertyvalues ), m_prop( prop ), worldspawn( Map_FindOrInsertWorldspawn( g_map ) ){
1385 }
1386 bool pre( const scene::Path& path, scene::Instance& instance ) const {
1387         Entity* entity = Node_getEntity( path.top() );
1388         if ( entity != 0 ){
1389                 if( path.top().get() != worldspawn ){
1390                         Selectable* selectable = Instance_getSelectable( instance );
1391                         if ( ( selectable != 0 && selectable->isSelected() ) || instance.childSelected() ) {
1392                                 const char* keyvalue = entity->getKeyValue( m_prop );
1393                                 if ( !string_empty( keyvalue ) && !propertyvalues_contain( m_propertyvalues, keyvalue ) ) {
1394                                         m_propertyvalues.push_back( keyvalue );
1395                                 }
1396                         }
1397                 }
1398                 return false;
1399         }
1400         return true;
1401 }
1402 };
1403
1404 void Scene_EntityGetPropertyValues_nonEmpty( scene::Graph& graph, const char *prop, PropertyValues& propertyvalues ){
1405         graph.traverse( EntityGetSelectedPropertyValuesWalker_nonEmpty( prop, propertyvalues ) );
1406 }
1407
1408 #include "preferences.h"
1409
1410 void Select_ConnectedEntities( bool targeting, bool targets, bool focus ){
1411         PropertyValues target_propertyvalues;
1412         PropertyValues targetname_propertyvalues;
1413         const char *target_prop = "target";
1414         const char *targetname_prop;
1415         if ( g_pGameDescription->mGameType == "doom3" ) {
1416                 targetname_prop = "name";
1417         }
1418         else{
1419                 targetname_prop = "targetname";
1420         }
1421
1422         if( targeting ){
1423                 Scene_EntityGetPropertyValues_nonEmpty( GlobalSceneGraph(), targetname_prop, targetname_propertyvalues );
1424         }
1425         if( targets ){
1426                 Scene_EntityGetPropertyValues_nonEmpty( GlobalSceneGraph(), target_prop, target_propertyvalues );
1427         }
1428
1429         if( target_propertyvalues.empty() && targetname_propertyvalues.empty() ){
1430                 globalErrorStream() << "SelectConnectedEntities: nothing found\n";
1431                 return;
1432         }
1433
1434         if( !targeting || !targets ){
1435                 GlobalSelectionSystem().setSelectedAll( false );
1436         }
1437         if ( targeting && !targetname_propertyvalues.empty() ) {
1438                 Scene_EntitySelectByPropertyValues( GlobalSceneGraph(), target_prop, targetname_propertyvalues );
1439         }
1440         if ( targets && !target_propertyvalues.empty() ) {
1441                 Scene_EntitySelectByPropertyValues( GlobalSceneGraph(), targetname_prop, target_propertyvalues );
1442         }
1443         if( focus ){
1444                 FocusAllViews();
1445         }
1446 }
1447
1448 void SelectConnectedEntities(){
1449         Select_ConnectedEntities( true, true, false );
1450 }