]> de.git.xonotic.org Git - xonotic/netradiant.git/blob - radiant/select.cpp
Merge commit '09b97bb415e380e5eaf6e2f4acbdc7f740494b41' into garux-merge
[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 "patchmanip.h"
45 #include "patchdialog.h"
46 #include "selection.h"
47 #include "texwindow.h"
48 #include "gtkmisc.h"
49 #include "mainframe.h"
50 #include "grid.h"
51 #include "map.h"
52 #include "entityinspector.h"
53
54
55
56 select_workzone_t g_select_workzone;
57
58
59 /**
60    Loops over all selected brushes and stores their
61    world AABBs in the specified array.
62  */
63 class CollectSelectedBrushesBounds : public SelectionSystem::Visitor
64 {
65 AABB* m_bounds;     // array of AABBs
66 Unsigned m_max;     // max AABB-elements in array
67 Unsigned& m_count;  // count of valid AABBs stored in array
68
69 public:
70 CollectSelectedBrushesBounds( AABB* bounds, Unsigned max, Unsigned& count )
71         : m_bounds( bounds ),
72         m_max( max ),
73         m_count( count ){
74         m_count = 0;
75 }
76
77 void visit( scene::Instance& instance ) const {
78         ASSERT_MESSAGE( m_count <= m_max, "Invalid m_count in CollectSelectedBrushesBounds" );
79
80         // stop if the array is already full
81         if ( m_count == m_max ) {
82                 return;
83         }
84
85         Selectable* selectable = Instance_getSelectable( instance );
86         if ( ( selectable != 0 )
87                  && instance.isSelected() ) {
88                 // brushes only
89                 if ( Instance_getBrush( instance ) != 0 ) {
90                         m_bounds[m_count] = instance.worldAABB();
91                         ++m_count;
92                 }
93         }
94 }
95 };
96
97 /**
98    Selects all objects that intersect one of the bounding AABBs.
99    The exact intersection-method is specified through TSelectionPolicy
100  */
101 template<class TSelectionPolicy>
102 class SelectByBounds : public scene::Graph::Walker
103 {
104 AABB* m_aabbs;             // selection aabbs
105 Unsigned m_count;          // number of aabbs in m_aabbs
106 TSelectionPolicy policy;   // type that contains a custom intersection method aabb<->aabb
107
108 public:
109 SelectByBounds( AABB* aabbs, Unsigned count )
110         : m_aabbs( aabbs ),
111         m_count( count ){
112 }
113
114 bool pre( const scene::Path& path, scene::Instance& instance ) const {
115         if( path.top().get().visible() ){
116                 Selectable* selectable = Instance_getSelectable( instance );
117
118                 // ignore worldspawn
119                 Entity* entity = Node_getEntity( path.top() );
120                 if ( entity ) {
121                         if ( string_equal( entity->getKeyValue( "classname" ), "worldspawn" ) ) {
122                                 return true;
123                         }
124                 }
125
126                 if ( ( path.size() > 1 ) &&
127                         ( !path.top().get().isRoot() ) &&
128                         ( selectable != 0 ) &&
129                         ( !node_is_group( path.top() ) )
130                         ) {
131                         for ( Unsigned i = 0; i < m_count; ++i )
132                         {
133                                 if ( policy.Evaluate( m_aabbs[i], instance ) ) {
134                                         selectable->setSelected( true );
135                                 }
136                         }
137                 }
138         }
139         else{
140                 return false;
141         }
142
143         return true;
144 }
145
146 /**
147    Performs selection operation on the global scenegraph.
148    If delete_bounds_src is true, then the objects which were
149    used as source for the selection aabbs will be deleted.
150  */
151 static void DoSelection( bool delete_bounds_src = true ){
152         if ( GlobalSelectionSystem().Mode() == SelectionSystem::ePrimitive ) {
153                 // we may not need all AABBs since not all selected objects have to be brushes
154                 const Unsigned max = (Unsigned)GlobalSelectionSystem().countSelected();
155                 AABB* aabbs = new AABB[max];
156
157                 Unsigned count;
158                 CollectSelectedBrushesBounds collector( aabbs, max, count );
159                 GlobalSelectionSystem().foreachSelected( collector );
160
161                 // nothing usable in selection
162                 if ( !count ) {
163                         delete[] aabbs;
164                         return;
165                 }
166
167                 // delete selected objects
168                 if ( delete_bounds_src ) { // see deleteSelection
169                         UndoableCommand undo( "deleteSelected" );
170                         Select_Delete();
171                 }
172
173                 // select objects with bounds
174                 GlobalSceneGraph().traverse( SelectByBounds<TSelectionPolicy>( aabbs, count ) );
175
176                 SceneChangeNotify();
177                 delete[] aabbs;
178         }
179 }
180 };
181
182 /**
183    SelectionPolicy for SelectByBounds
184    Returns true if box and the AABB of instance intersect
185  */
186 class SelectionPolicy_Touching
187 {
188 public:
189 bool Evaluate( const AABB& box, scene::Instance& instance ) const {
190         const AABB& other( instance.worldAABB() );
191         for ( Unsigned i = 0; i < 3; ++i )
192         {
193                 if ( fabsf( box.origin[i] - other.origin[i] ) > ( box.extents[i] + other.extents[i] ) ) {
194                         return false;
195                 }
196         }
197         return true;
198 }
199 };
200
201 /**
202    SelectionPolicy for SelectByBounds
203    Returns true if the AABB of instance is inside box
204  */
205 class SelectionPolicy_Inside
206 {
207 public:
208 bool Evaluate( const AABB& box, scene::Instance& instance ) const {
209         const AABB& other( instance.worldAABB() );
210         for ( Unsigned i = 0; i < 3; ++i )
211         {
212                 if ( fabsf( box.origin[i] - other.origin[i] ) > ( box.extents[i] - other.extents[i] ) ) {
213                         return false;
214                 }
215         }
216         return true;
217 }
218 };
219
220 class DeleteSelected : public scene::Graph::Walker
221 {
222 mutable bool m_remove;
223 mutable bool m_removedChild;
224 public:
225 DeleteSelected()
226         : m_remove( false ), m_removedChild( false ){
227 }
228 bool pre( const scene::Path& path, scene::Instance& instance ) const {
229         m_removedChild = false;
230
231         Selectable* selectable = Instance_getSelectable( instance );
232         if ( selectable != 0
233                  && selectable->isSelected()
234                  && path.size() > 1
235                  && !path.top().get().isRoot() ) {
236                 m_remove = true;
237
238                 return false; // dont traverse into child elements
239         }
240         return true;
241 }
242 void post( const scene::Path& path, scene::Instance& instance ) const {
243
244         if ( m_removedChild ) {
245                 m_removedChild = false;
246
247                 // delete empty entities
248                 Entity* entity = Node_getEntity( path.top() );
249                 if ( entity != 0
250                          && path.top().get_pointer() != Map_FindWorldspawn( g_map )
251                          && Node_getTraversable( path.top() )->empty() ) {
252                         Path_deleteTop( path );
253                 }
254         }
255
256         // node should be removed
257         if ( m_remove ) {
258                 if ( Node_isEntity( path.parent() ) != 0 ) {
259                         m_removedChild = true;
260                 }
261
262                 m_remove = false;
263                 Path_deleteTop( path );
264         }
265 }
266 };
267
268 void Scene_DeleteSelected( scene::Graph& graph ){
269         graph.traverse( DeleteSelected() );
270         SceneChangeNotify();
271 }
272
273 void Select_Delete( void ){
274         Scene_DeleteSelected( GlobalSceneGraph() );
275 }
276
277 class InvertSelectionWalker : public scene::Graph::Walker
278 {
279 SelectionSystem::EMode m_mode;
280 mutable Selectable* m_selectable;
281 public:
282 InvertSelectionWalker( SelectionSystem::EMode mode )
283         : m_mode( mode ), m_selectable( 0 ){
284 }
285 bool pre( const scene::Path& path, scene::Instance& instance ) const {
286         if( !path.top().get().visible() ){
287                 m_selectable = 0;
288                 return false;
289         }
290         Selectable* selectable = Instance_getSelectable( instance );
291         if ( selectable ) {
292                 switch ( m_mode )
293                 {
294                 case SelectionSystem::eEntity:
295                         if ( Node_isEntity( path.top() ) != 0 ) {
296                                 m_selectable = path.top().get().visible() ? selectable : 0;
297                         }
298                         break;
299                 case SelectionSystem::ePrimitive:
300                         m_selectable = path.top().get().visible() ? selectable : 0;
301                         break;
302                 case SelectionSystem::eComponent:
303                         break;
304                 }
305         }
306         return true;
307 }
308 void post( const scene::Path& path, scene::Instance& instance ) const {
309         if ( m_selectable != 0 ) {
310                 m_selectable->setSelected( !m_selectable->isSelected() );
311                 m_selectable = 0;
312         }
313 }
314 };
315
316 void Scene_Invert_Selection( scene::Graph& graph ){
317         graph.traverse( InvertSelectionWalker( GlobalSelectionSystem().Mode() ) );
318 }
319
320 void Select_Invert(){
321         Scene_Invert_Selection( GlobalSceneGraph() );
322 }
323
324 class ExpandSelectionToEntitiesWalker : public scene::Graph::Walker
325 {
326 mutable std::size_t m_depth;
327 NodeSmartReference worldspawn;
328 public:
329 ExpandSelectionToEntitiesWalker() : m_depth( 0 ), worldspawn( Map_FindOrInsertWorldspawn( g_map ) ){
330 }
331 bool pre( const scene::Path& path, scene::Instance& instance ) const {
332         ++m_depth;
333
334         // ignore worldspawn
335         NodeSmartReference me( path.top().get() );
336         if ( me == worldspawn ) {
337                 return false;
338         }
339
340         if ( m_depth == 2 ) { // entity depth
341                 // traverse and select children if any one is selected
342                 if ( instance.childSelected() ) {
343                         Instance_setSelected( instance, true );
344                 }
345                 return Node_getEntity( path.top() )->isContainer() && instance.isSelected();
346         }
347         else if ( m_depth == 3 ) { // primitive depth
348                 Instance_setSelected( instance, true );
349                 return false;
350         }
351         return true;
352 }
353 void post( const scene::Path& path, scene::Instance& instance ) const {
354         --m_depth;
355 }
356 };
357
358 void Scene_ExpandSelectionToEntities(){
359         GlobalSceneGraph().traverse( ExpandSelectionToEntitiesWalker() );
360 }
361
362
363 namespace
364 {
365 void Selection_UpdateWorkzone(){
366         if ( GlobalSelectionSystem().countSelected() != 0 ) {
367                 Select_GetBounds( g_select_workzone.d_work_min, g_select_workzone.d_work_max );
368         }
369 }
370 typedef FreeCaller<void(), Selection_UpdateWorkzone> SelectionUpdateWorkzoneCaller;
371
372 IdleDraw g_idleWorkzone = IdleDraw( SelectionUpdateWorkzoneCaller() );
373 }
374
375 const select_workzone_t& Select_getWorkZone(){
376         g_idleWorkzone.flush();
377         return g_select_workzone;
378 }
379
380 void UpdateWorkzone_ForSelection(){
381         g_idleWorkzone.queueDraw();
382 }
383
384 // update the workzone to the current selection
385 void UpdateWorkzone_ForSelectionChanged( const Selectable& selectable ){
386         if ( selectable.isSelected() ) {
387                 UpdateWorkzone_ForSelection();
388         }
389 }
390
391 void Select_SetShader( const char* shader ){
392         if ( GlobalSelectionSystem().Mode() != SelectionSystem::eComponent ) {
393                 Scene_BrushSetShader_Selected( GlobalSceneGraph(), shader );
394                 Scene_PatchSetShader_Selected( GlobalSceneGraph(), shader );
395         }
396         Scene_BrushSetShader_Component_Selected( GlobalSceneGraph(), shader );
397 }
398
399 void Select_SetTexdef( const TextureProjection& projection ){
400         if ( GlobalSelectionSystem().Mode() != SelectionSystem::eComponent ) {
401                 Scene_BrushSetTexdef_Selected( GlobalSceneGraph(), projection );
402         }
403         Scene_BrushSetTexdef_Component_Selected( GlobalSceneGraph(), projection );
404 }
405
406 void Select_SetFlags( const ContentsFlagsValue& flags ){
407         if ( GlobalSelectionSystem().Mode() != SelectionSystem::eComponent ) {
408                 Scene_BrushSetFlags_Selected( GlobalSceneGraph(), flags );
409         }
410         Scene_BrushSetFlags_Component_Selected( GlobalSceneGraph(), flags );
411 }
412
413 void Select_GetBounds( Vector3& mins, Vector3& maxs ){
414         AABB bounds;
415         Scene_BoundsSelected( GlobalSceneGraph(), bounds );
416         maxs = vector3_added( bounds.origin, bounds.extents );
417         mins = vector3_subtracted( bounds.origin, bounds.extents );
418 }
419
420 void Select_GetMid( Vector3& mid ){
421         AABB bounds;
422         Scene_BoundsSelected( GlobalSceneGraph(), bounds );
423         mid = vector3_snapped( bounds.origin );
424 }
425
426
427 void Select_FlipAxis( int axis ){
428         Vector3 flip( 1, 1, 1 );
429         flip[axis] = -1;
430         GlobalSelectionSystem().scaleSelected( flip );
431 }
432
433
434 void Select_Scale( float x, float y, float z ){
435         GlobalSelectionSystem().scaleSelected( Vector3( x, y, z ) );
436 }
437
438 enum axis_t
439 {
440         eAxisX = 0,
441         eAxisY = 1,
442         eAxisZ = 2,
443 };
444
445 enum sign_t
446 {
447         eSignPositive = 1,
448         eSignNegative = -1,
449 };
450
451 inline Matrix4 matrix4_rotation_for_axis90( axis_t axis, sign_t sign ){
452         switch ( axis )
453         {
454         case eAxisX:
455                 if ( sign == eSignPositive ) {
456                         return matrix4_rotation_for_sincos_x( 1, 0 );
457                 }
458                 else
459                 {
460                         return matrix4_rotation_for_sincos_x( -1, 0 );
461                 }
462         case eAxisY:
463                 if ( sign == eSignPositive ) {
464                         return matrix4_rotation_for_sincos_y( 1, 0 );
465                 }
466                 else
467                 {
468                         return matrix4_rotation_for_sincos_y( -1, 0 );
469                 }
470         default: //case eAxisZ:
471                 if ( sign == eSignPositive ) {
472                         return matrix4_rotation_for_sincos_z( 1, 0 );
473                 }
474                 else
475                 {
476                         return matrix4_rotation_for_sincos_z( -1, 0 );
477                 }
478         }
479 }
480
481 inline void matrix4_rotate_by_axis90( Matrix4& matrix, axis_t axis, sign_t sign ){
482         matrix4_multiply_by_matrix4( matrix, matrix4_rotation_for_axis90( axis, sign ) );
483 }
484
485 inline void matrix4_pivoted_rotate_by_axis90( Matrix4& matrix, axis_t axis, sign_t sign, const Vector3& pivotpoint ){
486         matrix4_translate_by_vec3( matrix, pivotpoint );
487         matrix4_rotate_by_axis90( matrix, axis, sign );
488         matrix4_translate_by_vec3( matrix, vector3_negated( pivotpoint ) );
489 }
490
491 inline Quaternion quaternion_for_axis90( axis_t axis, sign_t sign ){
492 #if 1
493         switch ( axis )
494         {
495         case eAxisX:
496                 if ( sign == eSignPositive ) {
497                         return Quaternion( c_half_sqrt2f, 0, 0, c_half_sqrt2f );
498                 }
499                 else
500                 {
501                         return Quaternion( -c_half_sqrt2f, 0, 0, -c_half_sqrt2f );
502                 }
503         case eAxisY:
504                 if ( sign == eSignPositive ) {
505                         return Quaternion( 0, c_half_sqrt2f, 0, c_half_sqrt2f );
506                 }
507                 else
508                 {
509                         return Quaternion( 0, -c_half_sqrt2f, 0, -c_half_sqrt2f );
510                 }
511         default: //case eAxisZ:
512                 if ( sign == eSignPositive ) {
513                         return Quaternion( 0, 0, c_half_sqrt2f, c_half_sqrt2f );
514                 }
515                 else
516                 {
517                         return Quaternion( 0, 0, -c_half_sqrt2f, -c_half_sqrt2f );
518                 }
519         }
520 #else
521         quaternion_for_matrix4_rotation( matrix4_rotation_for_axis90( (axis_t)axis, ( deg > 0 ) ? eSignPositive : eSignNegative ) );
522 #endif
523 }
524
525 void Select_RotateAxis( int axis, float deg ){
526         if ( fabs( deg ) == 90.f ) {
527                 GlobalSelectionSystem().rotateSelected( quaternion_for_axis90( (axis_t)axis, ( deg > 0 ) ? eSignPositive : eSignNegative ) );
528         }
529         else
530         {
531                 switch ( axis )
532                 {
533                 case 0:
534                         GlobalSelectionSystem().rotateSelected( quaternion_for_matrix4_rotation( matrix4_rotation_for_x_degrees( deg ) ) );
535                         break;
536                 case 1:
537                         GlobalSelectionSystem().rotateSelected( quaternion_for_matrix4_rotation( matrix4_rotation_for_y_degrees( deg ) ) );
538                         break;
539                 case 2:
540                         GlobalSelectionSystem().rotateSelected( quaternion_for_matrix4_rotation( matrix4_rotation_for_z_degrees( deg ) ) );
541                         break;
542                 }
543         }
544 }
545
546
547 void Select_ShiftTexture( float x, float y ){
548         if ( GlobalSelectionSystem().Mode() != SelectionSystem::eComponent ) {
549                 Scene_BrushShiftTexdef_Selected( GlobalSceneGraph(), x, y );
550                 Scene_PatchTranslateTexture_Selected( GlobalSceneGraph(), x, y );
551         }
552         //globalOutputStream() << "shift selected face textures: s=" << x << " t=" << y << '\n';
553         Scene_BrushShiftTexdef_Component_Selected( GlobalSceneGraph(), x, y );
554 }
555
556 void Select_ScaleTexture( float x, float y ){
557         if ( GlobalSelectionSystem().Mode() != SelectionSystem::eComponent ) {
558                 Scene_BrushScaleTexdef_Selected( GlobalSceneGraph(), x, y );
559                 Scene_PatchScaleTexture_Selected( GlobalSceneGraph(), x, y );
560         }
561         Scene_BrushScaleTexdef_Component_Selected( GlobalSceneGraph(), x, y );
562 }
563
564 void Select_RotateTexture( float amt ){
565         if ( GlobalSelectionSystem().Mode() != SelectionSystem::eComponent ) {
566                 Scene_BrushRotateTexdef_Selected( GlobalSceneGraph(), amt );
567                 Scene_PatchRotateTexture_Selected( GlobalSceneGraph(), amt );
568         }
569         Scene_BrushRotateTexdef_Component_Selected( GlobalSceneGraph(), amt );
570 }
571
572 // TTimo modified to handle shader architecture:
573 // expects shader names at input, comparison relies on shader names .. texture names no longer relevant
574 void FindReplaceTextures( const char* pFind, const char* pReplace, bool bSelected ){
575         if ( !texdef_name_valid( pFind ) ) {
576                 globalErrorStream() << "FindReplaceTextures: invalid texture name: '" << pFind << "', aborted\n";
577                 return;
578         }
579         if ( !texdef_name_valid( pReplace ) ) {
580                 globalErrorStream() << "FindReplaceTextures: invalid texture name: '" << pReplace << "', aborted\n";
581                 return;
582         }
583
584         StringOutputStream command;
585         command << "textureFindReplace -find " << pFind << " -replace " << pReplace;
586         UndoableCommand undo( command.c_str() );
587
588         if ( bSelected ) {
589                 if ( GlobalSelectionSystem().Mode() != SelectionSystem::eComponent ) {
590                         Scene_BrushFindReplaceShader_Selected( GlobalSceneGraph(), pFind, pReplace );
591                         Scene_PatchFindReplaceShader_Selected( GlobalSceneGraph(), pFind, pReplace );
592                 }
593                 Scene_BrushFindReplaceShader_Component_Selected( GlobalSceneGraph(), pFind, pReplace );
594         }
595         else
596         {
597                 Scene_BrushFindReplaceShader( GlobalSceneGraph(), pFind, pReplace );
598                 Scene_PatchFindReplaceShader( GlobalSceneGraph(), pFind, pReplace );
599         }
600 }
601
602 typedef std::vector<const char*> PropertyValues;
603
604 bool propertyvalues_contain( const PropertyValues& propertyvalues, const char *str ){
605         for ( PropertyValues::const_iterator i = propertyvalues.begin(); i != propertyvalues.end(); ++i )
606         {
607                 if ( string_equal( str, *i ) ) {
608                         return true;
609                 }
610         }
611         return false;
612 }
613
614 class EntityFindByPropertyValueWalker : public scene::Graph::Walker
615 {
616 const PropertyValues& m_propertyvalues;
617 const char *m_prop;
618 public:
619 EntityFindByPropertyValueWalker( const char *prop, const PropertyValues& propertyvalues )
620         : m_propertyvalues( propertyvalues ), m_prop( prop ){
621 }
622 bool pre( const scene::Path& path, scene::Instance& instance ) const {
623         if( !path.top().get().visible() ){
624                 return false;
625         }
626         Entity* entity = Node_getEntity( path.top() );
627         if ( entity != 0
628                  && propertyvalues_contain( m_propertyvalues, entity->getKeyValue( m_prop ) ) ) {
629                 Instance_getSelectable( instance )->setSelected( true );
630         }
631         return true;
632 }
633 };
634
635 void Scene_EntitySelectByPropertyValues( scene::Graph& graph, const char *prop, const PropertyValues& propertyvalues ){
636         graph.traverse( EntityFindByPropertyValueWalker( prop, propertyvalues ) );
637 }
638
639 class EntityGetSelectedPropertyValuesWalker : public scene::Graph::Walker
640 {
641 PropertyValues& m_propertyvalues;
642 const char *m_prop;
643 public:
644 EntityGetSelectedPropertyValuesWalker( const char *prop, PropertyValues& propertyvalues )
645         : m_propertyvalues( propertyvalues ), m_prop( prop ){
646 }
647 bool pre( const scene::Path& path, scene::Instance& instance ) const {
648         Selectable* selectable = Instance_getSelectable( instance );
649         if ( selectable != 0
650                  && selectable->isSelected() ) {
651                 Entity* entity = Node_getEntity( path.top() );
652                 if ( entity != 0 ) {
653                         if ( !propertyvalues_contain( m_propertyvalues, entity->getKeyValue( m_prop ) ) ) {
654                                 m_propertyvalues.push_back( entity->getKeyValue( m_prop ) );
655                         }
656                 }
657         }
658         return true;
659 }
660 };
661
662 void Scene_EntityGetPropertyValues( scene::Graph& graph, const char *prop, PropertyValues& propertyvalues ){
663         graph.traverse( EntityGetSelectedPropertyValuesWalker( prop, propertyvalues ) );
664 }
665
666 void Select_AllOfType(){
667         if ( GlobalSelectionSystem().Mode() == SelectionSystem::eComponent ) {
668                 if ( GlobalSelectionSystem().ComponentMode() == SelectionSystem::eFace ) {
669                         GlobalSelectionSystem().setSelectedAllComponents( false );
670                         Scene_BrushSelectByShader_Component( GlobalSceneGraph(), TextureBrowser_GetSelectedShader( GlobalTextureBrowser() ) );
671                 }
672         }
673         else
674         {
675                 PropertyValues propertyvalues;
676                 const char *prop = EntityInspector_getCurrentKey();
677                 if ( !prop || !*prop ) {
678                         prop = "classname";
679                 }
680                 Scene_EntityGetPropertyValues( GlobalSceneGraph(), prop, propertyvalues );
681                 GlobalSelectionSystem().setSelectedAll( false );
682                 if ( !propertyvalues.empty() ) {
683                         Scene_EntitySelectByPropertyValues( GlobalSceneGraph(), prop, propertyvalues );
684                 }
685                 else
686                 {
687                         Scene_BrushSelectByShader( GlobalSceneGraph(), TextureBrowser_GetSelectedShader( GlobalTextureBrowser() ) );
688                         Scene_PatchSelectByShader( GlobalSceneGraph(), TextureBrowser_GetSelectedShader( GlobalTextureBrowser() ) );
689                 }
690         }
691 }
692
693 void Select_Inside( void ){
694         SelectByBounds<SelectionPolicy_Inside>::DoSelection();
695 }
696
697 void Select_Touching( void ){
698         SelectByBounds<SelectionPolicy_Touching>::DoSelection( false );
699 }
700
701 void Select_FitTexture( float horizontal, float vertical ){
702         if ( GlobalSelectionSystem().Mode() != SelectionSystem::eComponent ) {
703                 Scene_BrushFitTexture_Selected( GlobalSceneGraph(), horizontal, vertical );
704         }
705         Scene_BrushFitTexture_Component_Selected( GlobalSceneGraph(), horizontal, vertical );
706
707         SceneChangeNotify();
708 }
709
710 inline void hide_node( scene::Node& node, bool hide ){
711         hide
712         ? node.enable( scene::Node::eHidden )
713         : node.disable( scene::Node::eHidden );
714 }
715
716 class HideSelectedWalker : public scene::Graph::Walker
717 {
718 bool m_hide;
719 public:
720 HideSelectedWalker( bool hide )
721         : m_hide( hide ){
722 }
723 bool pre( const scene::Path& path, scene::Instance& instance ) const {
724         Selectable* selectable = Instance_getSelectable( instance );
725         if ( selectable != 0
726                  && selectable->isSelected() ) {
727                 hide_node( path.top(), m_hide );
728         }
729         return true;
730 }
731 };
732
733 void Scene_Hide_Selected( bool hide ){
734         GlobalSceneGraph().traverse( HideSelectedWalker( hide ) );
735 }
736
737 void Select_Hide(){
738         Scene_Hide_Selected( true );
739         SceneChangeNotify();
740 }
741
742 void HideSelected(){
743         Select_Hide();
744         GlobalSelectionSystem().setSelectedAll( false );
745 }
746
747
748 class HideAllWalker : public scene::Graph::Walker
749 {
750 bool m_hide;
751 public:
752 HideAllWalker( bool hide )
753         : m_hide( hide ){
754 }
755 bool pre( const scene::Path& path, scene::Instance& instance ) const {
756         hide_node( path.top(), m_hide );
757         return true;
758 }
759 };
760
761 void Scene_Hide_All( bool hide ){
762         GlobalSceneGraph().traverse( HideAllWalker( hide ) );
763 }
764
765 void Select_ShowAllHidden(){
766         Scene_Hide_All( false );
767         SceneChangeNotify();
768 }
769
770
771
772 void Selection_Flipx(){
773         UndoableCommand undo( "mirrorSelected -axis x" );
774         Select_FlipAxis( 0 );
775 }
776
777 void Selection_Flipy(){
778         UndoableCommand undo( "mirrorSelected -axis y" );
779         Select_FlipAxis( 1 );
780 }
781
782 void Selection_Flipz(){
783         UndoableCommand undo( "mirrorSelected -axis z" );
784         Select_FlipAxis( 2 );
785 }
786
787 void Selection_Rotatex(){
788         UndoableCommand undo( "rotateSelected -axis x -angle -90" );
789         Select_RotateAxis( 0,-90 );
790 }
791
792 void Selection_Rotatey(){
793         UndoableCommand undo( "rotateSelected -axis y -angle 90" );
794         Select_RotateAxis( 1, 90 );
795 }
796
797 void Selection_Rotatez(){
798         UndoableCommand undo( "rotateSelected -axis z -angle -90" );
799         Select_RotateAxis( 2,-90 );
800 }
801
802
803
804 void Nudge( int nDim, float fNudge ){
805         Vector3 translate( 0, 0, 0 );
806         translate[nDim] = fNudge;
807
808         GlobalSelectionSystem().translateSelected( translate );
809 }
810
811 void Selection_NudgeZ( float amount ){
812         StringOutputStream command;
813         command << "nudgeSelected -axis z -amount " << amount;
814         UndoableCommand undo( command.c_str() );
815
816         Nudge( 2, amount );
817 }
818
819 void Selection_MoveDown(){
820         Selection_NudgeZ( -GetGridSize() );
821 }
822
823 void Selection_MoveUp(){
824         Selection_NudgeZ( GetGridSize() );
825 }
826
827 void SceneSelectionChange( const Selectable& selectable ){
828         SceneChangeNotify();
829 }
830
831 SignalHandlerId Selection_boundsChanged;
832
833 void Selection_construct(){
834         typedef FreeCaller<void(const Selectable&), SceneSelectionChange> SceneSelectionChangeCaller;
835         GlobalSelectionSystem().addSelectionChangeCallback( SceneSelectionChangeCaller() );
836         typedef FreeCaller<void(const Selectable&), UpdateWorkzone_ForSelectionChanged> UpdateWorkzoneForSelectionChangedCaller;
837         GlobalSelectionSystem().addSelectionChangeCallback( UpdateWorkzoneForSelectionChangedCaller() );
838         typedef FreeCaller<void(), UpdateWorkzone_ForSelection> UpdateWorkzoneForSelectionCaller;
839         Selection_boundsChanged = GlobalSceneGraph().addBoundsChangedCallback( UpdateWorkzoneForSelectionCaller() );
840 }
841
842 void Selection_destroy(){
843         GlobalSceneGraph().removeBoundsChangedCallback( Selection_boundsChanged );
844 }
845
846
847 #include "gtkdlgs.h"
848 #include <gdk/gdkkeysyms.h>
849
850
851 inline Quaternion quaternion_for_euler_xyz_degrees( const Vector3& eulerXYZ ){
852 #if 0
853         return quaternion_for_matrix4_rotation( matrix4_rotation_for_euler_xyz_degrees( eulerXYZ ) );
854 #elif 0
855         return quaternion_multiplied_by_quaternion(
856                            quaternion_multiplied_by_quaternion(
857                                    quaternion_for_z( degrees_to_radians( eulerXYZ[2] ) ),
858                                    quaternion_for_y( degrees_to_radians( eulerXYZ[1] ) )
859                                    ),
860                            quaternion_for_x( degrees_to_radians( eulerXYZ[0] ) )
861                            );
862 #elif 1
863         double cx = cos( degrees_to_radians( eulerXYZ[0] * 0.5 ) );
864         double sx = sin( degrees_to_radians( eulerXYZ[0] * 0.5 ) );
865         double cy = cos( degrees_to_radians( eulerXYZ[1] * 0.5 ) );
866         double sy = sin( degrees_to_radians( eulerXYZ[1] * 0.5 ) );
867         double cz = cos( degrees_to_radians( eulerXYZ[2] * 0.5 ) );
868         double sz = sin( degrees_to_radians( eulerXYZ[2] * 0.5 ) );
869
870         return Quaternion(
871                            cz * cy * sx - sz * sy * cx,
872                            cz * sy * cx + sz * cy * sx,
873                            sz * cy * cx - cz * sy * sx,
874                            cz * cy * cx + sz * sy * sx
875                            );
876 #endif
877 }
878
879 struct RotateDialog
880 {
881         ui::SpinButton x{ui::null};
882         ui::SpinButton y{ui::null};
883         ui::SpinButton z{ui::null};
884         ui::Window window{ui::null};
885 };
886
887 static gboolean rotatedlg_apply( ui::Widget widget, RotateDialog* rotateDialog ){
888         Vector3 eulerXYZ;
889
890         gtk_spin_button_update ( rotateDialog->x );
891         gtk_spin_button_update ( rotateDialog->y );
892         gtk_spin_button_update ( rotateDialog->z );
893         eulerXYZ[0] = static_cast<float>( gtk_spin_button_get_value( rotateDialog->x ) );
894         eulerXYZ[1] = static_cast<float>( gtk_spin_button_get_value( rotateDialog->y ) );
895         eulerXYZ[2] = static_cast<float>( gtk_spin_button_get_value( rotateDialog->z ) );
896
897         StringOutputStream command;
898         command << "rotateSelectedEulerXYZ -x " << eulerXYZ[0] << " -y " << eulerXYZ[1] << " -z " << eulerXYZ[2];
899         UndoableCommand undo( command.c_str() );
900
901         GlobalSelectionSystem().rotateSelected( quaternion_for_euler_xyz_degrees( eulerXYZ ) );
902         return TRUE;
903 }
904
905 static gboolean rotatedlg_cancel( ui::Widget widget, RotateDialog* rotateDialog ){
906         rotateDialog->window.hide();
907
908         gtk_spin_button_set_value( rotateDialog->x, 0.0f ); // reset to 0 on close
909         gtk_spin_button_set_value( rotateDialog->y, 0.0f );
910         gtk_spin_button_set_value( rotateDialog->z, 0.0f );
911
912         return TRUE;
913 }
914
915 static gboolean rotatedlg_ok( ui::Widget widget, RotateDialog* rotateDialog ){
916         rotatedlg_apply( widget, rotateDialog );
917 //      rotatedlg_cancel( widget, rotateDialog );
918         rotateDialog->window.hide();
919         return TRUE;
920 }
921
922 static gboolean rotatedlg_delete( ui::Widget widget, GdkEventAny *event, RotateDialog* rotateDialog ){
923         rotatedlg_cancel( widget, rotateDialog );
924         return TRUE;
925 }
926
927 RotateDialog g_rotate_dialog;
928 void DoRotateDlg(){
929         if ( !g_rotate_dialog.window ) {
930                 g_rotate_dialog.window = MainFrame_getWindow().create_dialog_window("Arbitrary rotation", G_CALLBACK(rotatedlg_delete ), &g_rotate_dialog );
931
932                 auto accel = ui::AccelGroup(ui::New);
933                 g_rotate_dialog.window.add_accel_group( accel );
934
935                 {
936                         auto hbox = create_dialog_hbox( 4, 4 );
937                         g_rotate_dialog.window.add(hbox);
938                         {
939                                 auto table = create_dialog_table( 3, 2, 4, 4 );
940                                 hbox.pack_start( table, TRUE, TRUE, 0 );
941                                 {
942                                         ui::Widget label = ui::Label( "  X  " );
943                                         label.show();
944                     table.attach(label, {0, 1, 0, 1}, {0, 0});
945                                 }
946                                 {
947                                         ui::Widget label = ui::Label( "  Y  " );
948                                         label.show();
949                     table.attach(label, {0, 1, 1, 2}, {0, 0});
950                                 }
951                                 {
952                                         ui::Widget label = ui::Label( "  Z  " );
953                                         label.show();
954                     table.attach(label, {0, 1, 2, 3}, {0, 0});
955                                 }
956                                 {
957                                         auto adj = ui::Adjustment( 0, -359, 359, 1, 10, 0 );
958                                         auto spin = ui::SpinButton( adj, 1, 0 );
959                                         spin.show();
960                     table.attach(spin, {1, 2, 0, 1}, {GTK_EXPAND | GTK_FILL, 0});
961                     spin.dimensions(64, -1);
962                                         gtk_spin_button_set_wrap( spin, TRUE );
963
964                                         gtk_widget_grab_focus( spin  );
965
966                                         g_rotate_dialog.x = spin;
967                                 }
968                                 {
969                                         auto adj = ui::Adjustment( 0, -359, 359, 1, 10, 0 );
970                                         auto spin = ui::SpinButton( adj, 1, 0 );
971                                         spin.show();
972                     table.attach(spin, {1, 2, 1, 2}, {GTK_EXPAND | GTK_FILL, 0});
973                     spin.dimensions(64, -1);
974                                         gtk_spin_button_set_wrap( spin, TRUE );
975
976                                         g_rotate_dialog.y = spin;
977                                 }
978                                 {
979                                         auto adj = ui::Adjustment( 0, -359, 359, 1, 10, 0 );
980                                         auto spin = ui::SpinButton( adj, 1, 0 );
981                                         spin.show();
982                     table.attach(spin, {1, 2, 2, 3}, {GTK_EXPAND | GTK_FILL, 0});
983                     spin.dimensions(64, -1);
984                                         gtk_spin_button_set_wrap( spin, TRUE );
985
986                                         g_rotate_dialog.z = spin;
987                                 }
988                         }
989                         {
990                                 auto vbox = create_dialog_vbox( 4 );
991                                 hbox.pack_start( vbox, TRUE, TRUE, 0 );
992                                 {
993                                         auto button = create_dialog_button( "OK", G_CALLBACK( rotatedlg_ok ), &g_rotate_dialog );
994                                         vbox.pack_start( button, FALSE, FALSE, 0 );
995                                         widget_make_default( button );
996                                         gtk_widget_add_accelerator( button , "clicked", accel, GDK_KEY_Return, (GdkModifierType)0, (GtkAccelFlags)0 );
997                                 }
998                                 {
999                                         auto button = create_dialog_button( "Cancel", G_CALLBACK( rotatedlg_cancel ), &g_rotate_dialog );
1000                                         vbox.pack_start( button, FALSE, FALSE, 0 );
1001                                         gtk_widget_add_accelerator( button , "clicked", accel, GDK_KEY_Escape, (GdkModifierType)0, (GtkAccelFlags)0 );
1002                                 }
1003                                 {
1004                                         auto button = create_dialog_button( "Apply", G_CALLBACK( rotatedlg_apply ), &g_rotate_dialog );
1005                                         vbox.pack_start( button, FALSE, FALSE, 0 );
1006                                 }
1007                         }
1008                 }
1009         }
1010
1011         g_rotate_dialog.window.show();
1012 }
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022 struct ScaleDialog
1023 {
1024         ui::Entry x{ui::null};
1025         ui::Entry y{ui::null};
1026         ui::Entry z{ui::null};
1027         ui::Window window{ui::null};
1028 };
1029
1030 static gboolean scaledlg_apply( ui::Widget widget, ScaleDialog* scaleDialog ){
1031         float sx, sy, sz;
1032
1033         sx = static_cast<float>( atof( gtk_entry_get_text( GTK_ENTRY( scaleDialog->x ) ) ) );
1034         sy = static_cast<float>( atof( gtk_entry_get_text( GTK_ENTRY( scaleDialog->y ) ) ) );
1035         sz = static_cast<float>( atof( gtk_entry_get_text( GTK_ENTRY( scaleDialog->z ) ) ) );
1036
1037         StringOutputStream command;
1038         command << "scaleSelected -x " << sx << " -y " << sy << " -z " << sz;
1039         UndoableCommand undo( command.c_str() );
1040
1041         Select_Scale( sx, sy, sz );
1042
1043         return TRUE;
1044 }
1045
1046 static gboolean scaledlg_cancel( ui::Widget widget, ScaleDialog* scaleDialog ){
1047         scaleDialog->window.hide();
1048
1049         scaleDialog->x.text("1.0");
1050         scaleDialog->y.text("1.0");
1051         scaleDialog->z.text("1.0");
1052
1053         return TRUE;
1054 }
1055
1056 static gboolean scaledlg_ok( ui::Widget widget, ScaleDialog* scaleDialog ){
1057         scaledlg_apply( widget, scaleDialog );
1058         //scaledlg_cancel( widget, scaleDialog );
1059         scaleDialog->window.hide();
1060         return TRUE;
1061 }
1062
1063 static gboolean scaledlg_delete( ui::Widget widget, GdkEventAny *event, ScaleDialog* scaleDialog ){
1064         scaledlg_cancel( widget, scaleDialog );
1065         return TRUE;
1066 }
1067
1068 ScaleDialog g_scale_dialog;
1069
1070 void DoScaleDlg(){
1071         if ( !g_scale_dialog.window ) {
1072                 g_scale_dialog.window = MainFrame_getWindow().create_dialog_window("Arbitrary scale", G_CALLBACK(scaledlg_delete ), &g_scale_dialog );
1073
1074                 auto accel = ui::AccelGroup(ui::New);
1075                 g_scale_dialog.window.add_accel_group( accel );
1076
1077                 {
1078                         auto hbox = create_dialog_hbox( 4, 4 );
1079                         g_scale_dialog.window.add(hbox);
1080                         {
1081                                 auto table = create_dialog_table( 3, 2, 4, 4 );
1082                                 hbox.pack_start( table, TRUE, TRUE, 0 );
1083                                 {
1084                                         ui::Widget label = ui::Label( "  X  " );
1085                                         label.show();
1086                     table.attach(label, {0, 1, 0, 1}, {0, 0});
1087                                 }
1088                                 {
1089                                         ui::Widget label = ui::Label( "  Y  " );
1090                                         label.show();
1091                     table.attach(label, {0, 1, 1, 2}, {0, 0});
1092                                 }
1093                                 {
1094                                         ui::Widget label = ui::Label( "  Z  " );
1095                                         label.show();
1096                     table.attach(label, {0, 1, 2, 3}, {0, 0});
1097                                 }
1098                                 {
1099                                         auto entry = ui::Entry(ui::New);
1100                                         entry.text("1.0");
1101                                         entry.show();
1102                     table.attach(entry, {1, 2, 0, 1}, {GTK_EXPAND | GTK_FILL, 0});
1103
1104                                         g_scale_dialog.x = entry;
1105                                 }
1106                                 {
1107                                         auto entry = ui::Entry(ui::New);
1108                                         entry.text("1.0");
1109                                         entry.show();
1110                     table.attach(entry, {1, 2, 1, 2}, {GTK_EXPAND | GTK_FILL, 0});
1111
1112                                         g_scale_dialog.y = entry;
1113                                 }
1114                                 {
1115                                         auto entry = ui::Entry(ui::New);
1116                                         entry.text("1.0");
1117                                         entry.show();
1118                     table.attach(entry, {1, 2, 2, 3}, {GTK_EXPAND | GTK_FILL, 0});
1119
1120                                         g_scale_dialog.z = entry;
1121                                 }
1122                         }
1123                         {
1124                                 auto vbox = create_dialog_vbox( 4 );
1125                                 hbox.pack_start( vbox, TRUE, TRUE, 0 );
1126                                 {
1127                                         auto button = create_dialog_button( "OK", G_CALLBACK( scaledlg_ok ), &g_scale_dialog );
1128                                         vbox.pack_start( button, FALSE, FALSE, 0 );
1129                                         widget_make_default( button );
1130                                         gtk_widget_add_accelerator( button , "clicked", accel, GDK_KEY_Return, (GdkModifierType)0, (GtkAccelFlags)0 );
1131                                 }
1132                                 {
1133                                         auto button = create_dialog_button( "Cancel", G_CALLBACK( scaledlg_cancel ), &g_scale_dialog );
1134                                         vbox.pack_start( button, FALSE, FALSE, 0 );
1135                                         gtk_widget_add_accelerator( button , "clicked", accel, GDK_KEY_Escape, (GdkModifierType)0, (GtkAccelFlags)0 );
1136                                 }
1137                                 {
1138                                         auto button = create_dialog_button( "Apply", G_CALLBACK( scaledlg_apply ), &g_scale_dialog );
1139                                         vbox.pack_start( button, FALSE, FALSE, 0 );
1140                                 }
1141                         }
1142                 }
1143         }
1144
1145         g_scale_dialog.window.show();
1146 }