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