]> de.git.xonotic.org Git - xonotic/netradiant.git/blob - radiant/select.cpp
- Radiant is now Vista compatible (Aero must be disabled)
[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       return Node_getEntity(path.top())->isContainer() && instance.childSelected();
354     }
355     else if(m_depth == 3) // primitive depth
356     {
357       Instance_setSelected(instance, true);
358       return false;
359     }
360     return true;
361   }
362   void post(const scene::Path& path, scene::Instance& instance) const
363   {
364     --m_depth;
365   }
366 };
367
368 void Scene_ExpandSelectionToEntities()
369 {
370   GlobalSceneGraph().traverse(ExpandSelectionToEntitiesWalker());
371 }
372
373
374 namespace
375 {
376   void Selection_UpdateWorkzone()
377   {
378     if(GlobalSelectionSystem().countSelected() != 0)
379     {
380       Select_GetBounds(g_select_workzone.d_work_min, g_select_workzone.d_work_max);
381     }
382   }
383   typedef FreeCaller<Selection_UpdateWorkzone> SelectionUpdateWorkzoneCaller;
384
385   IdleDraw g_idleWorkzone = IdleDraw(SelectionUpdateWorkzoneCaller());
386 }
387
388 const select_workzone_t& Select_getWorkZone()
389 {
390   g_idleWorkzone.flush();
391   return g_select_workzone;
392 }
393
394 void UpdateWorkzone_ForSelection()
395 {
396   g_idleWorkzone.queueDraw();
397 }
398
399 // update the workzone to the current selection
400 void UpdateWorkzone_ForSelectionChanged(const Selectable& selectable)
401 {
402   if(selectable.isSelected())
403   {
404     UpdateWorkzone_ForSelection();
405   }
406 }
407
408 void Select_SetShader(const char* shader)
409 {
410   if(GlobalSelectionSystem().Mode() != SelectionSystem::eComponent)
411   {
412     Scene_BrushSetShader_Selected(GlobalSceneGraph(), shader);
413     Scene_PatchSetShader_Selected(GlobalSceneGraph(), shader);
414   }
415   Scene_BrushSetShader_Component_Selected(GlobalSceneGraph(), shader);
416 }
417
418 void Select_SetTexdef(const TextureProjection& projection)
419 {
420   if(GlobalSelectionSystem().Mode() != SelectionSystem::eComponent)
421   {
422     Scene_BrushSetTexdef_Selected(GlobalSceneGraph(), projection);
423   }
424   Scene_BrushSetTexdef_Component_Selected(GlobalSceneGraph(), projection);
425 }
426
427 void Select_SetFlags(const ContentsFlagsValue& flags)
428 {
429   if(GlobalSelectionSystem().Mode() != SelectionSystem::eComponent)
430   {
431     Scene_BrushSetFlags_Selected(GlobalSceneGraph(), flags);
432   }
433   Scene_BrushSetFlags_Component_Selected(GlobalSceneGraph(), flags);
434 }
435
436 void Select_GetBounds (Vector3& mins, Vector3& maxs)
437 {
438   AABB bounds;
439   Scene_BoundsSelected(GlobalSceneGraph(), bounds);
440   maxs = vector3_added(bounds.origin, bounds.extents);
441   mins = vector3_subtracted(bounds.origin, bounds.extents);
442 }
443
444 void Select_GetMid (Vector3& mid)
445 {
446   AABB bounds;
447   Scene_BoundsSelected(GlobalSceneGraph(), bounds);
448   mid = vector3_snapped(bounds.origin);
449 }
450
451
452 void Select_FlipAxis (int axis)
453 {
454   Vector3 flip(1, 1, 1);
455   flip[axis] = -1;
456   GlobalSelectionSystem().scaleSelected(flip);
457 }
458
459
460 void Select_Scale(float x, float y, float z)
461 {
462   GlobalSelectionSystem().scaleSelected(Vector3(x, y, z));
463 }
464
465 enum axis_t
466 {
467   eAxisX = 0,
468   eAxisY = 1,
469   eAxisZ = 2,
470 };
471
472 enum sign_t
473 {
474   eSignPositive = 1,
475   eSignNegative = -1,
476 };
477
478 inline Matrix4 matrix4_rotation_for_axis90(axis_t axis, sign_t sign)
479 {
480   switch(axis)
481   {
482   case eAxisX:
483     if(sign == eSignPositive)
484     {
485       return matrix4_rotation_for_sincos_x(1, 0);
486     }
487     else
488     {
489       return matrix4_rotation_for_sincos_x(-1, 0);
490     }
491   case eAxisY:
492     if(sign == eSignPositive)
493     {
494       return matrix4_rotation_for_sincos_y(1, 0);
495     }
496     else
497     {
498       return matrix4_rotation_for_sincos_y(-1, 0);
499     }
500   default://case eAxisZ:
501     if(sign == eSignPositive)
502     {
503       return matrix4_rotation_for_sincos_z(1, 0);
504     }
505     else
506     {
507       return matrix4_rotation_for_sincos_z(-1, 0);
508     }
509   }
510 }
511
512 inline void matrix4_rotate_by_axis90(Matrix4& matrix, axis_t axis, sign_t sign)
513 {
514   matrix4_multiply_by_matrix4(matrix, matrix4_rotation_for_axis90(axis, sign));
515 }
516
517 inline void matrix4_pivoted_rotate_by_axis90(Matrix4& matrix, axis_t axis, sign_t sign, const Vector3& pivotpoint)
518 {
519   matrix4_translate_by_vec3(matrix, pivotpoint);
520   matrix4_rotate_by_axis90(matrix, axis, sign);
521   matrix4_translate_by_vec3(matrix, vector3_negated(pivotpoint));
522 }
523
524 inline Quaternion quaternion_for_axis90(axis_t axis, sign_t sign)
525 {
526 #if 1
527   switch(axis)
528   {
529   case eAxisX:
530     if(sign == eSignPositive)
531     {
532       return Quaternion(c_half_sqrt2f, 0, 0, c_half_sqrt2f);
533     }
534     else
535     {
536       return Quaternion(-c_half_sqrt2f, 0, 0, -c_half_sqrt2f);
537     }
538   case eAxisY:
539     if(sign == eSignPositive)
540     {
541       return Quaternion(0, c_half_sqrt2f, 0, c_half_sqrt2f);
542     }
543     else
544     {
545       return Quaternion(0, -c_half_sqrt2f, 0, -c_half_sqrt2f);
546     }
547   default://case eAxisZ:
548     if(sign == eSignPositive)
549     {
550       return Quaternion(0, 0, c_half_sqrt2f, c_half_sqrt2f);
551     }
552     else
553     {
554       return Quaternion(0, 0, -c_half_sqrt2f, -c_half_sqrt2f);
555     }
556   }
557 #else
558   quaternion_for_matrix4_rotation(matrix4_rotation_for_axis90((axis_t)axis, (deg > 0) ? eSignPositive : eSignNegative));
559 #endif
560 }
561
562 void Select_RotateAxis (int axis, float deg)
563 {
564   if(fabs(deg) == 90.f)
565   {
566     GlobalSelectionSystem().rotateSelected(quaternion_for_axis90((axis_t)axis, (deg > 0) ? eSignPositive : eSignNegative));
567   }
568   else
569   {
570     switch(axis)
571     {
572     case 0:
573       GlobalSelectionSystem().rotateSelected(quaternion_for_matrix4_rotation(matrix4_rotation_for_x_degrees(deg)));
574       break;
575     case 1:
576       GlobalSelectionSystem().rotateSelected(quaternion_for_matrix4_rotation(matrix4_rotation_for_y_degrees(deg)));
577       break;
578     case 2:
579       GlobalSelectionSystem().rotateSelected(quaternion_for_matrix4_rotation(matrix4_rotation_for_z_degrees(deg)));
580       break;
581     }
582   }
583 }
584
585
586 void Select_ShiftTexture(float x, float y)
587 {
588   if(GlobalSelectionSystem().Mode() != SelectionSystem::eComponent)
589   {
590     Scene_BrushShiftTexdef_Selected(GlobalSceneGraph(), x, y);
591     Scene_PatchTranslateTexture_Selected(GlobalSceneGraph(), x, y);
592   }
593   //globalOutputStream() << "shift selected face textures: s=" << x << " t=" << y << '\n';
594   Scene_BrushShiftTexdef_Component_Selected(GlobalSceneGraph(), x, y);
595 }
596
597 void Select_ScaleTexture(float x, float y)
598 {
599   if(GlobalSelectionSystem().Mode() != SelectionSystem::eComponent)
600   {
601     Scene_BrushScaleTexdef_Selected(GlobalSceneGraph(), x, y);
602     Scene_PatchScaleTexture_Selected(GlobalSceneGraph(), x, y);
603   }
604   Scene_BrushScaleTexdef_Component_Selected(GlobalSceneGraph(), x, y);
605 }
606
607 void Select_RotateTexture(float amt)
608 {
609   if(GlobalSelectionSystem().Mode() != SelectionSystem::eComponent)
610   {
611     Scene_BrushRotateTexdef_Selected(GlobalSceneGraph(), amt);
612     Scene_PatchRotateTexture_Selected(GlobalSceneGraph(), amt);
613   }
614   Scene_BrushRotateTexdef_Component_Selected(GlobalSceneGraph(), amt);
615 }
616
617 // TTimo modified to handle shader architecture:
618 // expects shader names at input, comparison relies on shader names .. texture names no longer relevant
619 void FindReplaceTextures(const char* pFind, const char* pReplace, bool bSelected)
620 {
621   if(!texdef_name_valid(pFind))
622   {
623     globalErrorStream() << "FindReplaceTextures: invalid texture name: '" << pFind << "', aborted\n";
624     return;
625   }
626   if(!texdef_name_valid(pReplace))
627   {
628     globalErrorStream() << "FindReplaceTextures: invalid texture name: '" << pReplace << "', aborted\n";
629     return;
630   }
631
632   StringOutputStream command;
633   command << "textureFindReplace -find " << pFind << " -replace " << pReplace;
634   UndoableCommand undo(command.c_str());
635
636   if(bSelected)
637   {
638     if(GlobalSelectionSystem().Mode() != SelectionSystem::eComponent)
639     {
640       Scene_BrushFindReplaceShader_Selected(GlobalSceneGraph(), pFind, pReplace);
641       Scene_PatchFindReplaceShader_Selected(GlobalSceneGraph(), pFind, pReplace);
642     }
643     Scene_BrushFindReplaceShader_Component_Selected(GlobalSceneGraph(), pFind, pReplace);
644   }
645   else
646   {
647     Scene_BrushFindReplaceShader(GlobalSceneGraph(), pFind, pReplace);
648     Scene_PatchFindReplaceShader(GlobalSceneGraph(), pFind, pReplace);
649   }
650 }
651
652 typedef std::vector<const char*> Classnames;
653
654 bool classnames_match_entity(const Classnames& classnames, Entity* entity)
655 {
656   for(Classnames::const_iterator i = classnames.begin(); i != classnames.end(); ++i)
657   {
658     if(string_equal(entity->getKeyValue("classname"), *i))
659     {
660       return true;
661     }
662   }
663   return false;
664 }
665
666 class EntityFindByClassnameWalker : public scene::Graph::Walker
667 {
668   const Classnames& m_classnames;
669 public:
670   EntityFindByClassnameWalker(const Classnames& classnames)
671     : m_classnames(classnames)
672   {
673   }
674   bool pre(const scene::Path& path, scene::Instance& instance) const
675   {
676     Entity* entity = Node_getEntity(path.top());
677     if(entity != 0
678       && classnames_match_entity(m_classnames, entity))
679     {
680       Instance_getSelectable(instance)->setSelected(true);
681     }
682     return true;
683   }
684 };
685
686 void Scene_EntitySelectByClassnames(scene::Graph& graph, const Classnames& classnames)
687 {
688   graph.traverse(EntityFindByClassnameWalker(classnames));
689 }
690
691 class EntityGetSelectedClassnamesWalker : public scene::Graph::Walker
692 {
693   Classnames& m_classnames;
694 public:
695   EntityGetSelectedClassnamesWalker(Classnames& classnames)
696     : m_classnames(classnames)
697   {
698   }
699   bool pre(const scene::Path& path, scene::Instance& instance) const
700   {
701     Selectable* selectable = Instance_getSelectable(instance);
702     if(selectable != 0
703       && selectable->isSelected())
704     {
705       Entity* entity = Node_getEntity(path.top());
706       if(entity != 0)
707       {
708         m_classnames.push_back(entity->getKeyValue("classname"));
709       }
710     }
711     return true;
712   }
713 };
714
715 void Scene_EntityGetClassnames(scene::Graph& graph, Classnames& classnames)
716 {
717   graph.traverse(EntityGetSelectedClassnamesWalker(classnames));
718 }
719
720 void Select_AllOfType()
721 {
722   if(GlobalSelectionSystem().Mode() == SelectionSystem::eComponent)
723   {
724     if(GlobalSelectionSystem().ComponentMode() == SelectionSystem::eFace)
725     {
726       GlobalSelectionSystem().setSelectedAllComponents(false);
727       Scene_BrushSelectByShader_Component(GlobalSceneGraph(), TextureBrowser_GetSelectedShader(GlobalTextureBrowser()));
728     }
729   }
730   else
731   {
732     Classnames classnames;
733     Scene_EntityGetClassnames(GlobalSceneGraph(), classnames);
734     GlobalSelectionSystem().setSelectedAll(false);
735     if(!classnames.empty())
736     {
737       Scene_EntitySelectByClassnames(GlobalSceneGraph(), classnames);
738     }
739     else
740     {
741       Scene_BrushSelectByShader(GlobalSceneGraph(), TextureBrowser_GetSelectedShader(GlobalTextureBrowser()));
742       Scene_PatchSelectByShader(GlobalSceneGraph(), TextureBrowser_GetSelectedShader(GlobalTextureBrowser()));
743     }
744   }
745 }
746
747 void Select_Inside(void)
748 {
749         SelectByBounds<SelectionPolicy_Inside>::DoSelection();
750 }
751
752 void Select_Touching(void)
753 {
754         SelectByBounds<SelectionPolicy_Touching>::DoSelection(false);
755 }
756
757 void Select_FitTexture(float horizontal, float vertical)
758 {
759   if(GlobalSelectionSystem().Mode() != SelectionSystem::eComponent)
760   {
761     Scene_BrushFitTexture_Selected(GlobalSceneGraph(), horizontal, vertical);
762   }
763   Scene_BrushFitTexture_Component_Selected(GlobalSceneGraph(), horizontal, vertical);
764
765   SceneChangeNotify();
766 }
767
768 inline void hide_node(scene::Node& node, bool hide)
769 {
770   hide
771     ? node.enable(scene::Node::eHidden)
772     : node.disable(scene::Node::eHidden);
773 }
774
775 class HideSelectedWalker : public scene::Graph::Walker
776 {
777   bool m_hide;
778 public:
779   HideSelectedWalker(bool hide)
780     : m_hide(hide)
781   {
782   }
783   bool pre(const scene::Path& path, scene::Instance& instance) const
784   {
785     Selectable* selectable = Instance_getSelectable(instance);
786     if(selectable != 0
787       && selectable->isSelected())
788     {
789       hide_node(path.top(), m_hide);
790     }
791     return true;
792   }
793 };
794
795 void Scene_Hide_Selected(bool hide)
796 {
797   GlobalSceneGraph().traverse(HideSelectedWalker(hide));
798 }
799
800 void Select_Hide()
801 {
802   Scene_Hide_Selected(true);
803   SceneChangeNotify();
804 }
805
806 void HideSelected()
807 {
808   Select_Hide();
809   GlobalSelectionSystem().setSelectedAll(false);
810 }
811
812
813 class HideAllWalker : public scene::Graph::Walker
814 {
815   bool m_hide;
816 public:
817   HideAllWalker(bool hide)
818     : m_hide(hide)
819   {
820   }
821   bool pre(const scene::Path& path, scene::Instance& instance) const
822   {
823     hide_node(path.top(), m_hide);
824     return true;
825   }
826 };
827
828 void Scene_Hide_All(bool hide)
829 {
830   GlobalSceneGraph().traverse(HideAllWalker(hide));
831 }
832
833 void Select_ShowAllHidden()
834 {
835   Scene_Hide_All(false);
836   SceneChangeNotify();
837 }
838
839
840
841 void Selection_Flipx()
842 {
843   UndoableCommand undo("mirrorSelected -axis x");
844   Select_FlipAxis(0);
845 }
846
847 void Selection_Flipy()
848 {
849   UndoableCommand undo("mirrorSelected -axis y");
850   Select_FlipAxis(1);
851 }
852
853 void Selection_Flipz()
854 {
855   UndoableCommand undo("mirrorSelected -axis z");
856   Select_FlipAxis(2);
857 }
858
859 void Selection_Rotatex()
860 {
861   UndoableCommand undo("rotateSelected -axis x -angle -90");
862   Select_RotateAxis(0,-90);
863 }
864
865 void Selection_Rotatey()
866 {
867   UndoableCommand undo("rotateSelected -axis y -angle 90");
868   Select_RotateAxis(1, 90);
869 }
870
871 void Selection_Rotatez()
872 {
873   UndoableCommand undo("rotateSelected -axis z -angle -90");
874   Select_RotateAxis(2,-90);
875 }
876
877
878
879 void Nudge(int nDim, float fNudge)
880 {
881   Vector3 translate(0, 0, 0);
882   translate[nDim] = fNudge;
883   
884   GlobalSelectionSystem().translateSelected(translate);
885 }
886
887 void Selection_NudgeZ(float amount)
888 {
889   StringOutputStream command;
890   command << "nudgeSelected -axis z -amount " << amount;
891   UndoableCommand undo(command.c_str());
892
893   Nudge(2, amount);
894 }
895
896 void Selection_MoveDown()
897 {
898   Selection_NudgeZ(-GetGridSize());
899 }
900
901 void Selection_MoveUp()
902 {
903   Selection_NudgeZ(GetGridSize());
904 }
905
906 void SceneSelectionChange(const Selectable& selectable)
907 {
908   SceneChangeNotify();
909 }
910
911 SignalHandlerId Selection_boundsChanged;
912
913 void Selection_construct()
914 {
915   typedef FreeCaller1<const Selectable&, SceneSelectionChange> SceneSelectionChangeCaller;
916   GlobalSelectionSystem().addSelectionChangeCallback(SceneSelectionChangeCaller());
917   typedef FreeCaller1<const Selectable&, UpdateWorkzone_ForSelectionChanged> UpdateWorkzoneForSelectionChangedCaller;
918   GlobalSelectionSystem().addSelectionChangeCallback(UpdateWorkzoneForSelectionChangedCaller());
919   typedef FreeCaller<UpdateWorkzone_ForSelection> UpdateWorkzoneForSelectionCaller;
920   Selection_boundsChanged = GlobalSceneGraph().addBoundsChangedCallback(UpdateWorkzoneForSelectionCaller());
921 }
922
923 void Selection_destroy()
924 {
925   GlobalSceneGraph().removeBoundsChangedCallback(Selection_boundsChanged);
926 }
927
928
929 #include "gtkdlgs.h"
930 #include <gtk/gtkbox.h>
931 #include <gtk/gtkspinbutton.h>
932 #include <gtk/gtktable.h>
933 #include <gtk/gtklabel.h>
934 #include <gdk/gdkkeysyms.h>
935
936
937 inline Quaternion quaternion_for_euler_xyz_degrees(const Vector3& eulerXYZ)
938 {
939 #if 0
940   return quaternion_for_matrix4_rotation(matrix4_rotation_for_euler_xyz_degrees(eulerXYZ));
941 #elif 0
942   return quaternion_multiplied_by_quaternion(
943     quaternion_multiplied_by_quaternion(
944       quaternion_for_z(degrees_to_radians(eulerXYZ[2])),
945       quaternion_for_y(degrees_to_radians(eulerXYZ[1]))
946     ),
947     quaternion_for_x(degrees_to_radians(eulerXYZ[0]))
948   );
949 #elif 1
950   double cx = cos(degrees_to_radians(eulerXYZ[0] * 0.5));
951   double sx = sin(degrees_to_radians(eulerXYZ[0] * 0.5));
952   double cy = cos(degrees_to_radians(eulerXYZ[1] * 0.5));
953   double sy = sin(degrees_to_radians(eulerXYZ[1] * 0.5));
954   double cz = cos(degrees_to_radians(eulerXYZ[2] * 0.5));
955   double sz = sin(degrees_to_radians(eulerXYZ[2] * 0.5));
956
957   return Quaternion(
958     cz * cy * sx - sz * sy * cx,
959     cz * sy * cx + sz * cy * sx,
960     sz * cy * cx - cz * sy * sx,
961     cz * cy * cx + sz * sy * sx
962   );
963 #endif
964 }
965
966 struct RotateDialog
967 {
968   GtkSpinButton* x;
969   GtkSpinButton* y;
970   GtkSpinButton* z;
971 };
972
973 static void rotatedlg_apply (GtkWidget *widget, RotateDialog* rotateDialog)
974 {
975   Vector3 eulerXYZ;
976
977   eulerXYZ[0] = static_cast<float>(gtk_spin_button_get_value(rotateDialog->x));
978   gtk_spin_button_set_value(rotateDialog->x, 0.0f); // reset to 0 on Apply
979   
980   eulerXYZ[1] = static_cast<float>(gtk_spin_button_get_value(rotateDialog->y));
981   gtk_spin_button_set_value(rotateDialog->y, 0.0f);
982   
983   eulerXYZ[2] = static_cast<float>(gtk_spin_button_get_value(rotateDialog->z));
984   gtk_spin_button_set_value(rotateDialog->z, 0.0f);
985
986   StringOutputStream command;
987   command << "rotateSelectedEulerXYZ -x " << eulerXYZ[0] << " -y " << eulerXYZ[1] << " -z " << eulerXYZ[2];
988   UndoableCommand undo(command.c_str());
989
990   GlobalSelectionSystem().rotateSelected(quaternion_for_euler_xyz_degrees(eulerXYZ));
991 }
992
993 void DoRotateDlg()
994 {
995   ModalDialog dialog;
996   RotateDialog rotateDialog;
997
998   GtkWindow* window = create_dialog_window(MainFrame_getWindow(), "Arbitrary rotation", G_CALLBACK(dialog_delete_callback), &dialog);
999
1000   GtkAccelGroup* accel = gtk_accel_group_new();
1001   gtk_window_add_accel_group(window, accel);
1002
1003   {
1004     GtkHBox* hbox = create_dialog_hbox(4, 4);
1005     gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(hbox));
1006     {
1007       GtkTable* table = create_dialog_table(3, 2, 4, 4);
1008       gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(table), TRUE, TRUE, 0);
1009       {
1010         GtkWidget* label = gtk_label_new ("  X  ");
1011         gtk_widget_show (label);
1012         gtk_table_attach(table, label, 0, 1, 0, 1,
1013                           (GtkAttachOptions) (0),
1014                           (GtkAttachOptions) (0), 0, 0);
1015       }
1016       {
1017         GtkWidget* label = gtk_label_new ("  Y  ");
1018         gtk_widget_show (label);
1019         gtk_table_attach(table, label, 0, 1, 1, 2,
1020                           (GtkAttachOptions) (0),
1021                           (GtkAttachOptions) (0), 0, 0);
1022       }
1023       {
1024         GtkWidget* label = gtk_label_new ("  Z  ");
1025         gtk_widget_show (label);
1026         gtk_table_attach(table, label, 0, 1, 2, 3,
1027                           (GtkAttachOptions) (0),
1028                           (GtkAttachOptions) (0), 0, 0);
1029       }
1030       {
1031         GtkAdjustment* adj = GTK_ADJUSTMENT(gtk_adjustment_new(0, -359, 359, 1, 10, 10));
1032         GtkSpinButton* spin = GTK_SPIN_BUTTON(gtk_spin_button_new(adj, 1, 0));
1033         gtk_widget_show(GTK_WIDGET(spin));
1034         gtk_table_attach(table, GTK_WIDGET(spin), 1, 2, 0, 1,
1035                           (GtkAttachOptions) (GTK_EXPAND | GTK_FILL),
1036                           (GtkAttachOptions) (0), 0, 0);
1037         gtk_widget_set_size_request(GTK_WIDGET(spin), 64, -1);
1038         gtk_spin_button_set_wrap(spin, TRUE);
1039
1040         gtk_widget_grab_focus(GTK_WIDGET(spin));
1041
1042         rotateDialog.x = spin;
1043       }
1044       {
1045         GtkAdjustment* adj = GTK_ADJUSTMENT(gtk_adjustment_new(0, -359, 359, 1, 10, 10));
1046         GtkSpinButton* spin = GTK_SPIN_BUTTON(gtk_spin_button_new(adj, 1, 0));
1047         gtk_widget_show(GTK_WIDGET(spin));
1048         gtk_table_attach(table, GTK_WIDGET(spin), 1, 2, 1, 2,
1049                           (GtkAttachOptions) (GTK_EXPAND | GTK_FILL),
1050                           (GtkAttachOptions) (0), 0, 0);
1051         gtk_widget_set_size_request(GTK_WIDGET(spin), 64, -1);
1052         gtk_spin_button_set_wrap(spin, TRUE);
1053
1054         rotateDialog.y = spin;
1055       }
1056       {
1057         GtkAdjustment* adj = GTK_ADJUSTMENT(gtk_adjustment_new(0, -359, 359, 1, 10, 10));
1058         GtkSpinButton* spin = GTK_SPIN_BUTTON(gtk_spin_button_new(adj, 1, 0));
1059         gtk_widget_show(GTK_WIDGET(spin));
1060         gtk_table_attach(table, GTK_WIDGET(spin), 1, 2, 2, 3,
1061                           (GtkAttachOptions) (GTK_EXPAND | GTK_FILL),
1062                           (GtkAttachOptions) (0), 0, 0);
1063         gtk_widget_set_size_request(GTK_WIDGET(spin), 64, -1);
1064         gtk_spin_button_set_wrap(spin, TRUE);
1065
1066         rotateDialog.z = spin;
1067       }
1068     }
1069     {
1070       GtkVBox* vbox = create_dialog_vbox(4);
1071       gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(vbox), TRUE, TRUE, 0);
1072       {
1073         GtkButton* button = create_dialog_button("OK", G_CALLBACK(dialog_button_ok), &dialog);
1074         gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(button), FALSE, FALSE, 0);
1075         widget_make_default(GTK_WIDGET(button));
1076         gtk_widget_add_accelerator(GTK_WIDGET(button), "clicked", accel, GDK_Return, (GdkModifierType)0, (GtkAccelFlags)0);
1077       }
1078       {
1079         GtkButton* button = create_dialog_button("Cancel", G_CALLBACK(dialog_button_cancel), &dialog);
1080         gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(button), FALSE, FALSE, 0);
1081         gtk_widget_add_accelerator(GTK_WIDGET(button), "clicked", accel, GDK_Escape, (GdkModifierType)0, (GtkAccelFlags)0);
1082       }
1083       {
1084         GtkButton* button = create_dialog_button("Apply", G_CALLBACK(rotatedlg_apply), &rotateDialog);
1085         gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(button), FALSE, FALSE, 0);
1086       }
1087     }
1088   }
1089
1090   if(modal_dialog_show(window, dialog) == eIDOK)
1091   {
1092     rotatedlg_apply(0, &rotateDialog);
1093   }
1094
1095   gtk_widget_destroy(GTK_WIDGET(window));
1096 }
1097
1098 void DoScaleDlg()
1099 {
1100   ModalDialog dialog;
1101   GtkWidget* x;
1102   GtkWidget* y;
1103   GtkWidget* z;
1104
1105   GtkWindow* window = create_dialog_window(MainFrame_getWindow(), "Scale", G_CALLBACK(dialog_delete_callback), &dialog);
1106
1107   GtkAccelGroup* accel = gtk_accel_group_new();
1108   gtk_window_add_accel_group(window, accel);
1109
1110   {
1111     GtkHBox* hbox = create_dialog_hbox(4, 4);
1112     gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(hbox));
1113     {
1114       GtkTable* table = create_dialog_table(3, 2, 4, 4);
1115       gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(table), TRUE, TRUE, 0);
1116       {
1117         GtkWidget* label = gtk_label_new ("X:");
1118         gtk_widget_show (label);
1119         gtk_table_attach(table, label, 0, 1, 0, 1,
1120                           (GtkAttachOptions) (GTK_FILL),
1121                           (GtkAttachOptions) (0), 0, 0);
1122         gtk_misc_set_alignment (GTK_MISC (label), 0, 0.5);
1123       }
1124       {
1125         GtkWidget* label = gtk_label_new ("Y:");
1126         gtk_widget_show (label);
1127         gtk_table_attach(table, label, 0, 1, 1, 2,
1128                           (GtkAttachOptions) (GTK_FILL),
1129                           (GtkAttachOptions) (0), 0, 0);
1130         gtk_misc_set_alignment (GTK_MISC (label), 0, 0.5);
1131       }
1132       {
1133         GtkWidget* label = gtk_label_new ("Z:");
1134         gtk_widget_show (label);
1135         gtk_table_attach(table, label, 0, 1, 2, 3,
1136                           (GtkAttachOptions) (GTK_FILL),
1137                           (GtkAttachOptions) (0), 0, 0);
1138         gtk_misc_set_alignment (GTK_MISC (label), 0, 0.5);
1139       }
1140       {
1141         GtkWidget* entry = gtk_entry_new();
1142         gtk_widget_show (entry);
1143         gtk_table_attach(table, entry, 1, 2, 0, 1,
1144                           (GtkAttachOptions) (GTK_EXPAND | GTK_FILL),
1145                           (GtkAttachOptions) (0), 0, 0);
1146
1147         gtk_widget_grab_focus(entry);
1148
1149         x = entry;
1150       }
1151       {
1152         GtkWidget* entry = gtk_entry_new();
1153         gtk_widget_show (entry);
1154         gtk_table_attach(table, entry, 1, 2, 1, 2,
1155                           (GtkAttachOptions) (GTK_EXPAND | GTK_FILL),
1156                           (GtkAttachOptions) (0), 0, 0);
1157
1158         y = entry;
1159       }
1160       {
1161         GtkWidget* entry = gtk_entry_new();
1162         gtk_widget_show (entry);
1163         gtk_table_attach(table, entry, 1, 2, 2, 3,
1164                           (GtkAttachOptions) (GTK_EXPAND | GTK_FILL),
1165                           (GtkAttachOptions) (0), 0, 0);
1166
1167         z = entry;
1168       }
1169     }
1170     {
1171       GtkVBox* vbox = create_dialog_vbox(4);
1172       gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(vbox), TRUE, TRUE, 0);
1173       {
1174         GtkButton* button = create_dialog_button("OK", G_CALLBACK(dialog_button_ok), &dialog);
1175         gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(button), FALSE, FALSE, 0);
1176         widget_make_default(GTK_WIDGET(button));
1177         gtk_widget_add_accelerator(GTK_WIDGET(button), "clicked", accel, GDK_Return, (GdkModifierType)0, (GtkAccelFlags)0);
1178       }
1179       {
1180         GtkButton* button = create_dialog_button("Cancel", G_CALLBACK(dialog_button_cancel), &dialog);
1181         gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(button), FALSE, FALSE, 0);
1182         gtk_widget_add_accelerator(GTK_WIDGET(button), "clicked", accel, GDK_Escape, (GdkModifierType)0, (GtkAccelFlags)0);
1183       }
1184     }
1185   }
1186
1187   // Initialize dialog
1188   gtk_entry_set_text (GTK_ENTRY (x), "1.0");
1189   gtk_entry_set_text (GTK_ENTRY (y), "1.0");
1190   gtk_entry_set_text (GTK_ENTRY (z), "1.0");
1191
1192   if(modal_dialog_show(window, dialog) == eIDOK)
1193   {
1194     float sx, sy, sz;
1195     sx = static_cast<float>(atof(gtk_entry_get_text (GTK_ENTRY (x))));
1196     sy = static_cast<float>(atof(gtk_entry_get_text (GTK_ENTRY (y))));
1197     sz = static_cast<float>(atof(gtk_entry_get_text (GTK_ENTRY (z))));
1198
1199     if (sx > 0 && sy > 0 && sz > 0)
1200     {
1201       StringOutputStream command;
1202       command << "scaleSelected -x " << sx << " -y " << sy << " -z " << sz;
1203       UndoableCommand undo(command.c_str());
1204
1205       Select_Scale(sx, sy, sz);
1206     }
1207     else
1208     {
1209       globalOutputStream() << "Warning.. Tried to scale by a zero value.";
1210     }
1211   }
1212
1213   gtk_widget_destroy(GTK_WIDGET(window));
1214 }