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