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