]> de.git.xonotic.org Git - xonotic/netradiant.git/blob - radiant/select.cpp
Merge commit 'de5fcea3362fe1e6cb68ccfab792469a993da215' 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
711 #include "commands.h"
712 #include "dialog.h"
713
714 inline void hide_node( scene::Node& node, bool hide ){
715         hide
716         ? node.enable( scene::Node::eHidden )
717         : node.disable( scene::Node::eHidden );
718 }
719
720 bool g_nodes_be_hidden = false;
721
722 ConstReferenceCaller<bool, void(const Callback<void(bool)> &), PropertyImpl<bool>::Export> g_hidden_caller( g_nodes_be_hidden );
723
724 ToggleItem g_hidden_item( g_hidden_caller );
725
726 class HideSelectedWalker : public scene::Graph::Walker
727 {
728 bool m_hide;
729 public:
730 HideSelectedWalker( bool hide )
731         : m_hide( hide ){
732 }
733 bool pre( const scene::Path& path, scene::Instance& instance ) const {
734         Selectable* selectable = Instance_getSelectable( instance );
735         if ( selectable != 0
736                  && selectable->isSelected() ) {
737                 g_nodes_be_hidden = m_hide;
738                 hide_node( path.top(), m_hide );
739         }
740         return true;
741 }
742 };
743
744 void Scene_Hide_Selected( bool hide ){
745         GlobalSceneGraph().traverse( HideSelectedWalker( hide ) );
746 }
747
748 void Select_Hide(){
749         Scene_Hide_Selected( true );
750         SceneChangeNotify();
751 }
752
753 void HideSelected(){
754         Select_Hide();
755         GlobalSelectionSystem().setSelectedAll( false );
756         g_hidden_item.update();
757 }
758
759
760 class HideAllWalker : public scene::Graph::Walker
761 {
762 bool m_hide;
763 public:
764 HideAllWalker( bool hide )
765         : m_hide( hide ){
766 }
767 bool pre( const scene::Path& path, scene::Instance& instance ) const {
768         hide_node( path.top(), m_hide );
769         return true;
770 }
771 };
772
773 void Scene_Hide_All( bool hide ){
774         GlobalSceneGraph().traverse( HideAllWalker( hide ) );
775 }
776
777 void Select_ShowAllHidden(){
778         Scene_Hide_All( false );
779         SceneChangeNotify();
780         g_nodes_be_hidden = false;
781         g_hidden_item.update();
782 }
783
784 void Hide_registerCommands(){
785         GlobalCommands_insert( "ShowHidden", makeCallbackF( Select_ShowAllHidden ), Accelerator( 'H', (GdkModifierType)GDK_SHIFT_MASK ) );
786         GlobalToggles_insert( "HideSelected", makeCallbackF( HideSelected ), ToggleItem::AddCallbackCaller( g_hidden_item ), Accelerator( 'H' ) );
787 }
788
789
790
791 void Selection_Flipx(){
792         UndoableCommand undo( "mirrorSelected -axis x" );
793         Select_FlipAxis( 0 );
794 }
795
796 void Selection_Flipy(){
797         UndoableCommand undo( "mirrorSelected -axis y" );
798         Select_FlipAxis( 1 );
799 }
800
801 void Selection_Flipz(){
802         UndoableCommand undo( "mirrorSelected -axis z" );
803         Select_FlipAxis( 2 );
804 }
805
806 void Selection_Rotatex(){
807         UndoableCommand undo( "rotateSelected -axis x -angle -90" );
808         Select_RotateAxis( 0,-90 );
809 }
810
811 void Selection_Rotatey(){
812         UndoableCommand undo( "rotateSelected -axis y -angle 90" );
813         Select_RotateAxis( 1, 90 );
814 }
815
816 void Selection_Rotatez(){
817         UndoableCommand undo( "rotateSelected -axis z -angle -90" );
818         Select_RotateAxis( 2,-90 );
819 }
820
821
822
823 void Nudge( int nDim, float fNudge ){
824         Vector3 translate( 0, 0, 0 );
825         translate[nDim] = fNudge;
826
827         GlobalSelectionSystem().translateSelected( translate );
828 }
829
830 void Selection_NudgeZ( float amount ){
831         StringOutputStream command;
832         command << "nudgeSelected -axis z -amount " << amount;
833         UndoableCommand undo( command.c_str() );
834
835         Nudge( 2, amount );
836 }
837
838 void Selection_MoveDown(){
839         Selection_NudgeZ( -GetGridSize() );
840 }
841
842 void Selection_MoveUp(){
843         Selection_NudgeZ( GetGridSize() );
844 }
845
846 void SceneSelectionChange( const Selectable& selectable ){
847         SceneChangeNotify();
848 }
849
850 SignalHandlerId Selection_boundsChanged;
851
852 void Selection_construct(){
853         typedef FreeCaller<void(const Selectable&), SceneSelectionChange> SceneSelectionChangeCaller;
854         GlobalSelectionSystem().addSelectionChangeCallback( SceneSelectionChangeCaller() );
855         typedef FreeCaller<void(const Selectable&), UpdateWorkzone_ForSelectionChanged> UpdateWorkzoneForSelectionChangedCaller;
856         GlobalSelectionSystem().addSelectionChangeCallback( UpdateWorkzoneForSelectionChangedCaller() );
857         typedef FreeCaller<void(), UpdateWorkzone_ForSelection> UpdateWorkzoneForSelectionCaller;
858         Selection_boundsChanged = GlobalSceneGraph().addBoundsChangedCallback( UpdateWorkzoneForSelectionCaller() );
859 }
860
861 void Selection_destroy(){
862         GlobalSceneGraph().removeBoundsChangedCallback( Selection_boundsChanged );
863 }
864
865
866 #include "gtkdlgs.h"
867 #include <gdk/gdkkeysyms.h>
868
869
870 inline Quaternion quaternion_for_euler_xyz_degrees( const Vector3& eulerXYZ ){
871 #if 0
872         return quaternion_for_matrix4_rotation( matrix4_rotation_for_euler_xyz_degrees( eulerXYZ ) );
873 #elif 0
874         return quaternion_multiplied_by_quaternion(
875                            quaternion_multiplied_by_quaternion(
876                                    quaternion_for_z( degrees_to_radians( eulerXYZ[2] ) ),
877                                    quaternion_for_y( degrees_to_radians( eulerXYZ[1] ) )
878                                    ),
879                            quaternion_for_x( degrees_to_radians( eulerXYZ[0] ) )
880                            );
881 #elif 1
882         double cx = cos( degrees_to_radians( eulerXYZ[0] * 0.5 ) );
883         double sx = sin( degrees_to_radians( eulerXYZ[0] * 0.5 ) );
884         double cy = cos( degrees_to_radians( eulerXYZ[1] * 0.5 ) );
885         double sy = sin( degrees_to_radians( eulerXYZ[1] * 0.5 ) );
886         double cz = cos( degrees_to_radians( eulerXYZ[2] * 0.5 ) );
887         double sz = sin( degrees_to_radians( eulerXYZ[2] * 0.5 ) );
888
889         return Quaternion(
890                            cz * cy * sx - sz * sy * cx,
891                            cz * sy * cx + sz * cy * sx,
892                            sz * cy * cx - cz * sy * sx,
893                            cz * cy * cx + sz * sy * sx
894                            );
895 #endif
896 }
897
898 struct RotateDialog
899 {
900         ui::SpinButton x{ui::null};
901         ui::SpinButton y{ui::null};
902         ui::SpinButton z{ui::null};
903         ui::Window window{ui::null};
904 };
905
906 static gboolean rotatedlg_apply( ui::Widget widget, RotateDialog* rotateDialog ){
907         Vector3 eulerXYZ;
908
909         gtk_spin_button_update ( rotateDialog->x );
910         gtk_spin_button_update ( rotateDialog->y );
911         gtk_spin_button_update ( rotateDialog->z );
912         eulerXYZ[0] = static_cast<float>( gtk_spin_button_get_value( rotateDialog->x ) );
913         eulerXYZ[1] = static_cast<float>( gtk_spin_button_get_value( rotateDialog->y ) );
914         eulerXYZ[2] = static_cast<float>( gtk_spin_button_get_value( rotateDialog->z ) );
915
916         StringOutputStream command;
917         command << "rotateSelectedEulerXYZ -x " << eulerXYZ[0] << " -y " << eulerXYZ[1] << " -z " << eulerXYZ[2];
918         UndoableCommand undo( command.c_str() );
919
920         GlobalSelectionSystem().rotateSelected( quaternion_for_euler_xyz_degrees( eulerXYZ ) );
921         return TRUE;
922 }
923
924 static gboolean rotatedlg_cancel( ui::Widget widget, RotateDialog* rotateDialog ){
925         rotateDialog->window.hide();
926
927         gtk_spin_button_set_value( rotateDialog->x, 0.0f ); // reset to 0 on close
928         gtk_spin_button_set_value( rotateDialog->y, 0.0f );
929         gtk_spin_button_set_value( rotateDialog->z, 0.0f );
930
931         return TRUE;
932 }
933
934 static gboolean rotatedlg_ok( ui::Widget widget, RotateDialog* rotateDialog ){
935         rotatedlg_apply( widget, rotateDialog );
936 //      rotatedlg_cancel( widget, rotateDialog );
937         rotateDialog->window.hide();
938         return TRUE;
939 }
940
941 static gboolean rotatedlg_delete( ui::Widget widget, GdkEventAny *event, RotateDialog* rotateDialog ){
942         rotatedlg_cancel( widget, rotateDialog );
943         return TRUE;
944 }
945
946 RotateDialog g_rotate_dialog;
947 void DoRotateDlg(){
948         if ( !g_rotate_dialog.window ) {
949                 g_rotate_dialog.window = MainFrame_getWindow().create_dialog_window("Arbitrary rotation", G_CALLBACK(rotatedlg_delete ), &g_rotate_dialog );
950
951                 auto accel = ui::AccelGroup(ui::New);
952                 g_rotate_dialog.window.add_accel_group( accel );
953
954                 {
955                         auto hbox = create_dialog_hbox( 4, 4 );
956                         g_rotate_dialog.window.add(hbox);
957                         {
958                                 auto table = create_dialog_table( 3, 2, 4, 4 );
959                                 hbox.pack_start( table, TRUE, TRUE, 0 );
960                                 {
961                                         ui::Widget label = ui::Label( "  X  " );
962                                         label.show();
963                     table.attach(label, {0, 1, 0, 1}, {0, 0});
964                                 }
965                                 {
966                                         ui::Widget label = ui::Label( "  Y  " );
967                                         label.show();
968                     table.attach(label, {0, 1, 1, 2}, {0, 0});
969                                 }
970                                 {
971                                         ui::Widget label = ui::Label( "  Z  " );
972                                         label.show();
973                     table.attach(label, {0, 1, 2, 3}, {0, 0});
974                                 }
975                                 {
976                                         auto adj = ui::Adjustment( 0, -359, 359, 1, 10, 0 );
977                                         auto spin = ui::SpinButton( adj, 1, 0 );
978                                         spin.show();
979                     table.attach(spin, {1, 2, 0, 1}, {GTK_EXPAND | GTK_FILL, 0});
980                     spin.dimensions(64, -1);
981                                         gtk_spin_button_set_wrap( spin, TRUE );
982
983                                         gtk_widget_grab_focus( spin  );
984
985                                         g_rotate_dialog.x = spin;
986                                 }
987                                 {
988                                         auto adj = ui::Adjustment( 0, -359, 359, 1, 10, 0 );
989                                         auto spin = ui::SpinButton( adj, 1, 0 );
990                                         spin.show();
991                     table.attach(spin, {1, 2, 1, 2}, {GTK_EXPAND | GTK_FILL, 0});
992                     spin.dimensions(64, -1);
993                                         gtk_spin_button_set_wrap( spin, TRUE );
994
995                                         g_rotate_dialog.y = spin;
996                                 }
997                                 {
998                                         auto adj = ui::Adjustment( 0, -359, 359, 1, 10, 0 );
999                                         auto spin = ui::SpinButton( adj, 1, 0 );
1000                                         spin.show();
1001                     table.attach(spin, {1, 2, 2, 3}, {GTK_EXPAND | GTK_FILL, 0});
1002                     spin.dimensions(64, -1);
1003                                         gtk_spin_button_set_wrap( spin, TRUE );
1004
1005                                         g_rotate_dialog.z = spin;
1006                                 }
1007                         }
1008                         {
1009                                 auto vbox = create_dialog_vbox( 4 );
1010                                 hbox.pack_start( vbox, TRUE, TRUE, 0 );
1011                                 {
1012                                         auto button = create_dialog_button( "OK", G_CALLBACK( rotatedlg_ok ), &g_rotate_dialog );
1013                                         vbox.pack_start( button, FALSE, FALSE, 0 );
1014                                         widget_make_default( button );
1015                                         gtk_widget_add_accelerator( button , "clicked", accel, GDK_KEY_Return, (GdkModifierType)0, (GtkAccelFlags)0 );
1016                                 }
1017                                 {
1018                                         auto button = create_dialog_button( "Cancel", G_CALLBACK( rotatedlg_cancel ), &g_rotate_dialog );
1019                                         vbox.pack_start( button, FALSE, FALSE, 0 );
1020                                         gtk_widget_add_accelerator( button , "clicked", accel, GDK_KEY_Escape, (GdkModifierType)0, (GtkAccelFlags)0 );
1021                                 }
1022                                 {
1023                                         auto button = create_dialog_button( "Apply", G_CALLBACK( rotatedlg_apply ), &g_rotate_dialog );
1024                                         vbox.pack_start( button, FALSE, FALSE, 0 );
1025                                 }
1026                         }
1027                 }
1028         }
1029
1030         g_rotate_dialog.window.show();
1031 }
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041 struct ScaleDialog
1042 {
1043         ui::Entry x{ui::null};
1044         ui::Entry y{ui::null};
1045         ui::Entry z{ui::null};
1046         ui::Window window{ui::null};
1047 };
1048
1049 static gboolean scaledlg_apply( ui::Widget widget, ScaleDialog* scaleDialog ){
1050         float sx, sy, sz;
1051
1052         sx = static_cast<float>( atof( gtk_entry_get_text( GTK_ENTRY( scaleDialog->x ) ) ) );
1053         sy = static_cast<float>( atof( gtk_entry_get_text( GTK_ENTRY( scaleDialog->y ) ) ) );
1054         sz = static_cast<float>( atof( gtk_entry_get_text( GTK_ENTRY( scaleDialog->z ) ) ) );
1055
1056         StringOutputStream command;
1057         command << "scaleSelected -x " << sx << " -y " << sy << " -z " << sz;
1058         UndoableCommand undo( command.c_str() );
1059
1060         Select_Scale( sx, sy, sz );
1061
1062         return TRUE;
1063 }
1064
1065 static gboolean scaledlg_cancel( ui::Widget widget, ScaleDialog* scaleDialog ){
1066         scaleDialog->window.hide();
1067
1068         scaleDialog->x.text("1.0");
1069         scaleDialog->y.text("1.0");
1070         scaleDialog->z.text("1.0");
1071
1072         return TRUE;
1073 }
1074
1075 static gboolean scaledlg_ok( ui::Widget widget, ScaleDialog* scaleDialog ){
1076         scaledlg_apply( widget, scaleDialog );
1077         //scaledlg_cancel( widget, scaleDialog );
1078         scaleDialog->window.hide();
1079         return TRUE;
1080 }
1081
1082 static gboolean scaledlg_delete( ui::Widget widget, GdkEventAny *event, ScaleDialog* scaleDialog ){
1083         scaledlg_cancel( widget, scaleDialog );
1084         return TRUE;
1085 }
1086
1087 ScaleDialog g_scale_dialog;
1088
1089 void DoScaleDlg(){
1090         if ( !g_scale_dialog.window ) {
1091                 g_scale_dialog.window = MainFrame_getWindow().create_dialog_window("Arbitrary scale", G_CALLBACK(scaledlg_delete ), &g_scale_dialog );
1092
1093                 auto accel = ui::AccelGroup(ui::New);
1094                 g_scale_dialog.window.add_accel_group( accel );
1095
1096                 {
1097                         auto hbox = create_dialog_hbox( 4, 4 );
1098                         g_scale_dialog.window.add(hbox);
1099                         {
1100                                 auto table = create_dialog_table( 3, 2, 4, 4 );
1101                                 hbox.pack_start( table, TRUE, TRUE, 0 );
1102                                 {
1103                                         ui::Widget label = ui::Label( "  X  " );
1104                                         label.show();
1105                     table.attach(label, {0, 1, 0, 1}, {0, 0});
1106                                 }
1107                                 {
1108                                         ui::Widget label = ui::Label( "  Y  " );
1109                                         label.show();
1110                     table.attach(label, {0, 1, 1, 2}, {0, 0});
1111                                 }
1112                                 {
1113                                         ui::Widget label = ui::Label( "  Z  " );
1114                                         label.show();
1115                     table.attach(label, {0, 1, 2, 3}, {0, 0});
1116                                 }
1117                                 {
1118                                         auto entry = ui::Entry(ui::New);
1119                                         entry.text("1.0");
1120                                         entry.show();
1121                     table.attach(entry, {1, 2, 0, 1}, {GTK_EXPAND | GTK_FILL, 0});
1122
1123                                         g_scale_dialog.x = entry;
1124                                 }
1125                                 {
1126                                         auto entry = ui::Entry(ui::New);
1127                                         entry.text("1.0");
1128                                         entry.show();
1129                     table.attach(entry, {1, 2, 1, 2}, {GTK_EXPAND | GTK_FILL, 0});
1130
1131                                         g_scale_dialog.y = entry;
1132                                 }
1133                                 {
1134                                         auto entry = ui::Entry(ui::New);
1135                                         entry.text("1.0");
1136                                         entry.show();
1137                     table.attach(entry, {1, 2, 2, 3}, {GTK_EXPAND | GTK_FILL, 0});
1138
1139                                         g_scale_dialog.z = entry;
1140                                 }
1141                         }
1142                         {
1143                                 auto vbox = create_dialog_vbox( 4 );
1144                                 hbox.pack_start( vbox, TRUE, TRUE, 0 );
1145                                 {
1146                                         auto button = create_dialog_button( "OK", G_CALLBACK( scaledlg_ok ), &g_scale_dialog );
1147                                         vbox.pack_start( button, FALSE, FALSE, 0 );
1148                                         widget_make_default( button );
1149                                         gtk_widget_add_accelerator( button , "clicked", accel, GDK_KEY_Return, (GdkModifierType)0, (GtkAccelFlags)0 );
1150                                 }
1151                                 {
1152                                         auto button = create_dialog_button( "Cancel", G_CALLBACK( scaledlg_cancel ), &g_scale_dialog );
1153                                         vbox.pack_start( button, FALSE, FALSE, 0 );
1154                                         gtk_widget_add_accelerator( button , "clicked", accel, GDK_KEY_Escape, (GdkModifierType)0, (GtkAccelFlags)0 );
1155                                 }
1156                                 {
1157                                         auto button = create_dialog_button( "Apply", G_CALLBACK( scaledlg_apply ), &g_scale_dialog );
1158                                         vbox.pack_start( button, FALSE, FALSE, 0 );
1159                                 }
1160                         }
1161                 }
1162         }
1163
1164         g_scale_dialog.window.show();
1165 }