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