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