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