minor refactoring
[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 "patchmanip.h"
42 #include "patchdialog.h"
43 #include "selection.h"
44 #include "texwindow.h"
45 #include "gtkmisc.h"
46 #include "mainframe.h"
47 #include "grid.h"
48 #include "map.h"
49
50
51
52 select_workzone_t g_select_workzone;
53
54
55 class DeleteSelected : public scene::Graph::Walker
56 {
57   mutable bool m_remove;
58   mutable bool m_removedChild;
59 public:
60   DeleteSelected()
61     : m_remove(false), m_removedChild(false)
62   {
63   }
64   bool pre(const scene::Path& path, scene::Instance& instance) const
65   {
66     m_removedChild = false;
67
68     Selectable* selectable = Instance_getSelectable(instance);
69     if(selectable != 0
70       && selectable->isSelected()
71       && path.size() > 1
72       && !path.top().get().isRoot())
73     {
74       m_remove = true;
75
76       return false;
77     }
78     return true;
79   }
80   void post(const scene::Path& path, scene::Instance& instance) const
81   {
82     if(m_removedChild)
83     {
84       m_removedChild = false;
85
86       // delete empty entities
87       Entity* entity = Node_getEntity(path.top());
88       if(entity != 0
89         && path.top().get_pointer() != Map_FindWorldspawn(g_map)
90         && Node_getTraversable(path.top())->empty())
91       {
92         Path_deleteTop(path);
93       }
94     }
95
96     if(m_remove)
97     {
98       if(Node_isEntity(path.parent()) != 0)
99       {
100         m_removedChild = true;
101       }
102
103       m_remove = false;
104       Path_deleteTop(path);
105     }
106   }
107 };
108
109 void Scene_DeleteSelected(scene::Graph& graph)
110 {
111   graph.traverse(DeleteSelected());
112   SceneChangeNotify();
113 }
114
115 void Select_Delete (void)
116 {
117   Scene_DeleteSelected(GlobalSceneGraph());
118 }
119
120 class InvertSelectionWalker : public scene::Graph::Walker
121 {
122   SelectionSystem::EMode m_mode;
123   mutable Selectable* m_selectable;
124 public:
125   InvertSelectionWalker(SelectionSystem::EMode mode)
126     : m_mode(mode), m_selectable(0)
127   {
128   }
129   bool pre(const scene::Path& path, scene::Instance& instance) const
130   {
131     Selectable* selectable = Instance_getSelectable(instance);
132     if(selectable)
133     {
134       switch(m_mode)
135       {
136       case SelectionSystem::eEntity:
137         if(Node_isEntity(path.top()) != 0)
138         {
139           m_selectable = path.top().get().visible() ? selectable : 0;
140         }
141         break;
142       case SelectionSystem::ePrimitive:
143         m_selectable = path.top().get().visible() ? selectable : 0;
144         break;
145       case SelectionSystem::eComponent:
146         break;
147       }
148     }
149     return true;
150   }
151   void post(const scene::Path& path, scene::Instance& instance) const
152   {
153     if(m_selectable != 0)
154     {
155       m_selectable->setSelected(!m_selectable->isSelected());
156       m_selectable = 0;
157     }
158   }
159 };
160
161 void Scene_Invert_Selection(scene::Graph& graph)
162 {
163   graph.traverse(InvertSelectionWalker(GlobalSelectionSystem().Mode()));
164 }
165
166 void Select_Invert()
167 {
168   Scene_Invert_Selection(GlobalSceneGraph());
169 }
170
171 class ExpandSelectionToEntitiesWalker : public scene::Graph::Walker
172 {
173   mutable std::size_t m_depth;
174 public:
175   ExpandSelectionToEntitiesWalker() : m_depth(0)
176   {
177   }
178   bool pre(const scene::Path& path, scene::Instance& instance) const
179   {
180     ++m_depth;
181     if(m_depth == 2) // entity depth
182     {
183       // traverse and select children if any one is selected
184       return Node_getEntity(path.top())->isContainer() && instance.childSelected();
185     }
186     else if(m_depth == 3) // primitive depth
187     {
188       Instance_setSelected(instance, true);
189       return false;
190     }
191     return true;
192   }
193   void post(const scene::Path& path, scene::Instance& instance) const
194   {
195     --m_depth;
196   }
197 };
198
199 void Scene_ExpandSelectionToEntities()
200 {
201   GlobalSceneGraph().traverse(ExpandSelectionToEntitiesWalker());
202 }
203
204
205 namespace
206 {
207   void Selection_UpdateWorkzone()
208   {
209     if(GlobalSelectionSystem().countSelected() != 0)
210     {
211       Select_GetBounds(g_select_workzone.d_work_min, g_select_workzone.d_work_max);
212     }
213   }
214   typedef FreeCaller<Selection_UpdateWorkzone> SelectionUpdateWorkzoneCaller;
215
216   IdleDraw g_idleWorkzone = IdleDraw(SelectionUpdateWorkzoneCaller());
217 }
218
219 const select_workzone_t& Select_getWorkZone()
220 {
221   g_idleWorkzone.flush();
222   return g_select_workzone;
223 }
224
225 void UpdateWorkzone_ForSelection()
226 {
227   g_idleWorkzone.queueDraw();
228 }
229
230 // update the workzone to the current selection
231 void UpdateWorkzone_ForSelectionChanged(const Selectable& selectable)
232 {
233   if(selectable.isSelected())
234   {
235     UpdateWorkzone_ForSelection();
236   }
237 }
238
239 void Select_SetShader(const char* shader)
240 {
241   if(GlobalSelectionSystem().Mode() != SelectionSystem::eComponent)
242   {
243     Scene_BrushSetShader_Selected(GlobalSceneGraph(), shader);
244     Scene_PatchSetShader_Selected(GlobalSceneGraph(), shader);
245   }
246   Scene_BrushSetShader_Component_Selected(GlobalSceneGraph(), shader);
247 }
248
249 void Select_SetTexdef(const TextureProjection& projection)
250 {
251   if(GlobalSelectionSystem().Mode() != SelectionSystem::eComponent)
252   {
253     Scene_BrushSetTexdef_Selected(GlobalSceneGraph(), projection);
254   }
255   Scene_BrushSetTexdef_Component_Selected(GlobalSceneGraph(), projection);
256 }
257
258 void Select_SetFlags(const ContentsFlagsValue& flags)
259 {
260   if(GlobalSelectionSystem().Mode() != SelectionSystem::eComponent)
261   {
262     Scene_BrushSetFlags_Selected(GlobalSceneGraph(), flags);
263   }
264   Scene_BrushSetFlags_Component_Selected(GlobalSceneGraph(), flags);
265 }
266
267 void Select_GetBounds (Vector3& mins, Vector3& maxs)
268 {
269   AABB bounds;
270   Scene_BoundsSelected(GlobalSceneGraph(), bounds);
271   maxs = vector3_added(bounds.origin, bounds.extents);
272   mins = vector3_subtracted(bounds.origin, bounds.extents);
273 }
274
275 void Select_GetMid (Vector3& mid)
276 {
277   AABB bounds;
278   Scene_BoundsSelected(GlobalSceneGraph(), bounds);
279   mid = vector3_snapped(bounds.origin);
280 }
281
282
283 void Select_FlipAxis (int axis)
284 {
285   Vector3 flip(1, 1, 1);
286   flip[axis] = -1;
287   GlobalSelectionSystem().scaleSelected(flip);
288 }
289
290
291 void Select_Scale(float x, float y, float z)
292 {
293   GlobalSelectionSystem().scaleSelected(Vector3(x, y, z));
294 }
295
296 enum axis_t
297 {
298   eAxisX = 0,
299   eAxisY = 1,
300   eAxisZ = 2,
301 };
302
303 enum sign_t
304 {
305   eSignPositive = 1,
306   eSignNegative = -1,
307 };
308
309 inline Matrix4 matrix4_rotation_for_axis90(axis_t axis, sign_t sign)
310 {
311   switch(axis)
312   {
313   case eAxisX:
314     if(sign == eSignPositive)
315     {
316       return matrix4_rotation_for_sincos_x(1, 0);
317     }
318     else
319     {
320       return matrix4_rotation_for_sincos_x(-1, 0);
321     }
322   case eAxisY:
323     if(sign == eSignPositive)
324     {
325       return matrix4_rotation_for_sincos_y(1, 0);
326     }
327     else
328     {
329       return matrix4_rotation_for_sincos_y(-1, 0);
330     }
331   default://case eAxisZ:
332     if(sign == eSignPositive)
333     {
334       return matrix4_rotation_for_sincos_z(1, 0);
335     }
336     else
337     {
338       return matrix4_rotation_for_sincos_z(-1, 0);
339     }
340   }
341 }
342
343 inline void matrix4_rotate_by_axis90(Matrix4& matrix, axis_t axis, sign_t sign)
344 {
345   matrix4_multiply_by_matrix4(matrix, matrix4_rotation_for_axis90(axis, sign));
346 }
347
348 inline void matrix4_pivoted_rotate_by_axis90(Matrix4& matrix, axis_t axis, sign_t sign, const Vector3& pivotpoint)
349 {
350   matrix4_translate_by_vec3(matrix, pivotpoint);
351   matrix4_rotate_by_axis90(matrix, axis, sign);
352   matrix4_translate_by_vec3(matrix, vector3_negated(pivotpoint));
353 }
354
355 inline Quaternion quaternion_for_axis90(axis_t axis, sign_t sign)
356 {
357 #if 1
358   switch(axis)
359   {
360   case eAxisX:
361     if(sign == eSignPositive)
362     {
363       return Quaternion(c_half_sqrt2f, 0, 0, c_half_sqrt2f);
364     }
365     else
366     {
367       return Quaternion(-c_half_sqrt2f, 0, 0, -c_half_sqrt2f);
368     }
369   case eAxisY:
370     if(sign == eSignPositive)
371     {
372       return Quaternion(0, c_half_sqrt2f, 0, c_half_sqrt2f);
373     }
374     else
375     {
376       return Quaternion(0, -c_half_sqrt2f, 0, -c_half_sqrt2f);
377     }
378   default://case eAxisZ:
379     if(sign == eSignPositive)
380     {
381       return Quaternion(0, 0, c_half_sqrt2f, c_half_sqrt2f);
382     }
383     else
384     {
385       return Quaternion(0, 0, -c_half_sqrt2f, -c_half_sqrt2f);
386     }
387   }
388 #else
389   quaternion_for_matrix4_rotation(matrix4_rotation_for_axis90((axis_t)axis, (deg > 0) ? eSignPositive : eSignNegative));
390 #endif
391 }
392
393 void Select_RotateAxis (int axis, float deg)
394 {
395   if(fabs(deg) == 90.f)
396   {
397     GlobalSelectionSystem().rotateSelected(quaternion_for_axis90((axis_t)axis, (deg > 0) ? eSignPositive : eSignNegative));
398   }
399   else
400   {
401     switch(axis)
402     {
403     case 0:
404       GlobalSelectionSystem().rotateSelected(quaternion_for_matrix4_rotation(matrix4_rotation_for_x_degrees(deg)));
405       break;
406     case 1:
407       GlobalSelectionSystem().rotateSelected(quaternion_for_matrix4_rotation(matrix4_rotation_for_y_degrees(deg)));
408       break;
409     case 2:
410       GlobalSelectionSystem().rotateSelected(quaternion_for_matrix4_rotation(matrix4_rotation_for_z_degrees(deg)));
411       break;
412     }
413   }
414 }
415
416
417 void Select_ShiftTexture(float x, float y)
418 {
419   if(GlobalSelectionSystem().Mode() != SelectionSystem::eComponent)
420   {
421     Scene_BrushShiftTexdef_Selected(GlobalSceneGraph(), x, y);
422     Scene_PatchTranslateTexture_Selected(GlobalSceneGraph(), x, y);
423   }
424   //globalOutputStream() << "shift selected face textures: s=" << x << " t=" << y << '\n';
425   Scene_BrushShiftTexdef_Component_Selected(GlobalSceneGraph(), x, y);
426 }
427
428 void Select_ScaleTexture(float x, float y)
429 {
430   if(GlobalSelectionSystem().Mode() != SelectionSystem::eComponent)
431   {
432     Scene_BrushScaleTexdef_Selected(GlobalSceneGraph(), x, y);
433     Scene_PatchScaleTexture_Selected(GlobalSceneGraph(), x, y);
434   }
435   Scene_BrushScaleTexdef_Component_Selected(GlobalSceneGraph(), x, y);
436 }
437
438 void Select_RotateTexture(float amt)
439 {
440   if(GlobalSelectionSystem().Mode() != SelectionSystem::eComponent)
441   {
442     Scene_BrushRotateTexdef_Selected(GlobalSceneGraph(), amt);
443     Scene_PatchRotateTexture_Selected(GlobalSceneGraph(), amt);
444   }
445   Scene_BrushRotateTexdef_Component_Selected(GlobalSceneGraph(), amt);
446 }
447
448 // TTimo modified to handle shader architecture:
449 // expects shader names at input, comparison relies on shader names .. texture names no longer relevant
450 void FindReplaceTextures(const char* pFind, const char* pReplace, bool bSelected)
451 {
452   if(!texdef_name_valid(pFind))
453   {
454     globalErrorStream() << "FindReplaceTextures: invalid texture name: '" << pFind << "', aborted\n";
455     return;
456   }
457   if(!texdef_name_valid(pReplace))
458   {
459     globalErrorStream() << "FindReplaceTextures: invalid texture name: '" << pReplace << "', aborted\n";
460     return;
461   }
462
463   StringOutputStream command;
464   command << "textureFindReplace -find " << pFind << " -replace " << pReplace;
465   UndoableCommand undo(command.c_str());
466
467   if(bSelected)
468   {
469     if(GlobalSelectionSystem().Mode() != SelectionSystem::eComponent)
470     {
471       Scene_BrushFindReplaceShader_Selected(GlobalSceneGraph(), pFind, pReplace);
472       Scene_PatchFindReplaceShader_Selected(GlobalSceneGraph(), pFind, pReplace);
473     }
474     Scene_BrushFindReplaceShader_Component_Selected(GlobalSceneGraph(), pFind, pReplace);
475   }
476   else
477   {
478     Scene_BrushFindReplaceShader(GlobalSceneGraph(), pFind, pReplace);
479     Scene_PatchFindReplaceShader(GlobalSceneGraph(), pFind, pReplace);
480   }
481 }
482
483 typedef std::vector<const char*> Classnames;
484
485 bool classnames_match_entity(const Classnames& classnames, Entity* entity)
486 {
487   for(Classnames::const_iterator i = classnames.begin(); i != classnames.end(); ++i)
488   {
489     if(string_equal(entity->getKeyValue("classname"), *i))
490     {
491       return true;
492     }
493   }
494   return false;
495 }
496
497 class EntityFindByClassnameWalker : public scene::Graph::Walker
498 {
499   const Classnames& m_classnames;
500 public:
501   EntityFindByClassnameWalker(const Classnames& classnames)
502     : m_classnames(classnames)
503   {
504   }
505   bool pre(const scene::Path& path, scene::Instance& instance) const
506   {
507     Entity* entity = Node_getEntity(path.top());
508     if(entity != 0
509       && classnames_match_entity(m_classnames, entity))
510     {
511       Instance_getSelectable(instance)->setSelected(true);
512     }
513     return true;
514   }
515 };
516
517 void Scene_EntitySelectByClassnames(scene::Graph& graph, const Classnames& classnames)
518 {
519   graph.traverse(EntityFindByClassnameWalker(classnames));
520 }
521
522 class EntityGetSelectedClassnamesWalker : public scene::Graph::Walker
523 {
524   Classnames& m_classnames;
525 public:
526   EntityGetSelectedClassnamesWalker(Classnames& classnames)
527     : m_classnames(classnames)
528   {
529   }
530   bool pre(const scene::Path& path, scene::Instance& instance) const
531   {
532     Selectable* selectable = Instance_getSelectable(instance);
533     if(selectable != 0
534       && selectable->isSelected())
535     {
536       Entity* entity = Node_getEntity(path.top());
537       if(entity != 0)
538       {
539         m_classnames.push_back(entity->getKeyValue("classname"));
540       }
541     }
542     return true;
543   }
544 };
545
546 void Scene_EntityGetClassnames(scene::Graph& graph, Classnames& classnames)
547 {
548   graph.traverse(EntityGetSelectedClassnamesWalker(classnames));
549 }
550
551 void Select_AllOfType()
552 {
553   if(GlobalSelectionSystem().Mode() == SelectionSystem::eComponent)
554   {
555     if(GlobalSelectionSystem().ComponentMode() == SelectionSystem::eFace)
556     {
557       GlobalSelectionSystem().setSelectedAllComponents(false);
558       Scene_BrushSelectByShader_Component(GlobalSceneGraph(), TextureBrowser_GetSelectedShader(GlobalTextureBrowser()));
559     }
560   }
561   else
562   {
563     Classnames classnames;
564     Scene_EntityGetClassnames(GlobalSceneGraph(), classnames);
565     GlobalSelectionSystem().setSelectedAll(false);
566     if(!classnames.empty())
567     {
568       Scene_EntitySelectByClassnames(GlobalSceneGraph(), classnames);
569     }
570     else
571     {
572       Scene_BrushSelectByShader(GlobalSceneGraph(), TextureBrowser_GetSelectedShader(GlobalTextureBrowser()));
573       Scene_PatchSelectByShader(GlobalSceneGraph(), TextureBrowser_GetSelectedShader(GlobalTextureBrowser()));
574     }
575   }
576 }
577
578
579
580 void Select_FitTexture(float horizontal, float vertical)
581 {
582   if(GlobalSelectionSystem().Mode() != SelectionSystem::eComponent)
583   {
584     Scene_BrushFitTexture_Selected(GlobalSceneGraph(), horizontal, vertical);
585   }
586   Scene_BrushFitTexture_Component_Selected(GlobalSceneGraph(), horizontal, vertical);
587
588   SceneChangeNotify();
589 }
590
591 inline void hide_node(scene::Node& node, bool hide)
592 {
593   hide
594     ? node.enable(scene::Node::eHidden)
595     : node.disable(scene::Node::eHidden);
596 }
597
598 class HideSelectedWalker : public scene::Graph::Walker
599 {
600   bool m_hide;
601 public:
602   HideSelectedWalker(bool hide)
603     : m_hide(hide)
604   {
605   }
606   bool pre(const scene::Path& path, scene::Instance& instance) const
607   {
608     Selectable* selectable = Instance_getSelectable(instance);
609     if(selectable != 0
610       && selectable->isSelected())
611     {
612       hide_node(path.top(), m_hide);
613     }
614     return true;
615   }
616 };
617
618 void Scene_Hide_Selected(bool hide)
619 {
620   GlobalSceneGraph().traverse(HideSelectedWalker(hide));
621 }
622
623 void Select_Hide()
624 {
625   Scene_Hide_Selected(true);
626   SceneChangeNotify();
627 }
628
629 void HideSelected()
630 {
631   Select_Hide();
632   GlobalSelectionSystem().setSelectedAll(false);
633 }
634
635
636 class HideAllWalker : public scene::Graph::Walker
637 {
638   bool m_hide;
639 public:
640   HideAllWalker(bool hide)
641     : m_hide(hide)
642   {
643   }
644   bool pre(const scene::Path& path, scene::Instance& instance) const
645   {
646     hide_node(path.top(), m_hide);
647     return true;
648   }
649 };
650
651 void Scene_Hide_All(bool hide)
652 {
653   GlobalSceneGraph().traverse(HideAllWalker(hide));
654 }
655
656 void Select_ShowAllHidden()
657 {
658   Scene_Hide_All(false);
659   SceneChangeNotify();
660 }
661
662
663
664 void Selection_Flipx()
665 {
666   UndoableCommand undo("mirrorSelected -axis x");
667   Select_FlipAxis(0);
668 }
669
670 void Selection_Flipy()
671 {
672   UndoableCommand undo("mirrorSelected -axis y");
673   Select_FlipAxis(1);
674 }
675
676 void Selection_Flipz()
677 {
678   UndoableCommand undo("mirrorSelected -axis z");
679   Select_FlipAxis(2);
680 }
681
682 void Selection_Rotatex()
683 {
684   UndoableCommand undo("rotateSelected -axis x -angle -90");
685   Select_RotateAxis(0,-90);
686 }
687
688 void Selection_Rotatey()
689 {
690   UndoableCommand undo("rotateSelected -axis y -angle 90");
691   Select_RotateAxis(1, 90);
692 }
693
694 void Selection_Rotatez()
695 {
696   UndoableCommand undo("rotateSelected -axis z -angle -90");
697   Select_RotateAxis(2,-90);
698 }
699
700
701
702 void Nudge(int nDim, float fNudge)
703 {
704   Vector3 translate(0, 0, 0);
705   translate[nDim] = fNudge;
706   
707   GlobalSelectionSystem().translateSelected(translate);
708 }
709
710 void Selection_NudgeZ(float amount)
711 {
712   StringOutputStream command;
713   command << "nudgeSelected -axis z -amount " << amount;
714   UndoableCommand undo(command.c_str());
715
716   Nudge(2, amount);
717 }
718
719 void Selection_MoveDown()
720 {
721   Selection_NudgeZ(-GetGridSize());
722 }
723
724 void Selection_MoveUp()
725 {
726   Selection_NudgeZ(GetGridSize());
727 }
728
729 void SceneSelectionChange(const Selectable& selectable)
730 {
731   SceneChangeNotify();
732 }
733
734 SignalHandlerId Selection_boundsChanged;
735
736 void Selection_construct()
737 {
738   typedef FreeCaller1<const Selectable&, SceneSelectionChange> SceneSelectionChangeCaller;
739   GlobalSelectionSystem().addSelectionChangeCallback(SceneSelectionChangeCaller());
740   typedef FreeCaller1<const Selectable&, UpdateWorkzone_ForSelectionChanged> UpdateWorkzoneForSelectionChangedCaller;
741   GlobalSelectionSystem().addSelectionChangeCallback(UpdateWorkzoneForSelectionChangedCaller());
742   typedef FreeCaller<UpdateWorkzone_ForSelection> UpdateWorkzoneForSelectionCaller;
743   Selection_boundsChanged = GlobalSceneGraph().addBoundsChangedCallback(UpdateWorkzoneForSelectionCaller());
744 }
745
746 void Selection_destroy()
747 {
748   GlobalSceneGraph().removeBoundsChangedCallback(Selection_boundsChanged);
749 }
750
751
752 #include "gtkdlgs.h"
753 #include <gtk/gtkbox.h>
754 #include <gtk/gtkspinbutton.h>
755 #include <gtk/gtktable.h>
756 #include <gtk/gtklabel.h>
757 #include <gdk/gdkkeysyms.h>
758
759 inline Quaternion quaternion_for_euler_xyz_degrees(const Vector3& eulerXYZ)
760 {
761   double cx = cos(degrees_to_radians(eulerXYZ[0] * 0.5));
762   double sx = sin(degrees_to_radians(eulerXYZ[0] * 0.5));
763   double cy = cos(degrees_to_radians(eulerXYZ[1] * 0.5));
764   double sy = sin(degrees_to_radians(eulerXYZ[1] * 0.5));
765   double cz = cos(degrees_to_radians(eulerXYZ[2] * 0.5));
766   double sz = sin(degrees_to_radians(eulerXYZ[2] * 0.5));
767
768   return Quaternion(
769     static_cast<float>(cx * sy * cz - sx * cy * sz),
770     static_cast<float>(cx * sy * sz + sx * cy * cz),
771     static_cast<float>(cx * cy * sz - sx * sy * cz),
772     static_cast<float>(cx * cy * cz + sx * sy * sz)
773   );
774 }
775
776 struct RotateDialog
777 {
778   GtkSpinButton* x;
779   GtkSpinButton* y;
780   GtkSpinButton* z;
781 };
782
783 static void rotatedlg_apply (GtkWidget *widget, RotateDialog* rotateDialog)
784 {
785   Vector3 eulerXYZ;
786
787   eulerXYZ[0] = static_cast<float>(gtk_spin_button_get_value(rotateDialog->x));
788   gtk_spin_button_set_value(rotateDialog->x, 0.0f); // reset to 0 on Apply
789   
790   eulerXYZ[1] = static_cast<float>(gtk_spin_button_get_value(rotateDialog->y));
791   gtk_spin_button_set_value(rotateDialog->y, 0.0f);
792   
793   eulerXYZ[2] = static_cast<float>(gtk_spin_button_get_value(rotateDialog->z));
794   gtk_spin_button_set_value(rotateDialog->z, 0.0f);
795
796   StringOutputStream command;
797   command << "rotateSelectedEulerXYZ -x " << eulerXYZ[0] << " -y " << eulerXYZ[1] << " -z " << eulerXYZ[2];
798   UndoableCommand undo(command.c_str());
799
800   GlobalSelectionSystem().rotateSelected(quaternion_for_euler_xyz_degrees(eulerXYZ));
801 }
802
803 void DoRotateDlg()
804 {
805   ModalDialog dialog;
806   RotateDialog rotateDialog;
807
808   GtkWindow* window = create_dialog_window(MainFrame_getWindow(), "Arbitrary rotation", G_CALLBACK(dialog_delete_callback), &dialog);
809
810   GtkAccelGroup* accel = gtk_accel_group_new();
811   gtk_window_add_accel_group(window, accel);
812
813   {
814     GtkHBox* hbox = create_dialog_hbox(4, 4);
815     gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(hbox));
816     {
817       GtkTable* table = create_dialog_table(3, 2, 4, 4);
818       gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(table), TRUE, TRUE, 0);
819       {
820         GtkWidget* label = gtk_label_new ("  X  ");
821         gtk_widget_show (label);
822         gtk_table_attach(table, label, 0, 1, 0, 1,
823                           (GtkAttachOptions) (0),
824                           (GtkAttachOptions) (0), 0, 0);
825       }
826       {
827         GtkWidget* label = gtk_label_new ("  Y  ");
828         gtk_widget_show (label);
829         gtk_table_attach(table, label, 0, 1, 1, 2,
830                           (GtkAttachOptions) (0),
831                           (GtkAttachOptions) (0), 0, 0);
832       }
833       {
834         GtkWidget* label = gtk_label_new ("  Z  ");
835         gtk_widget_show (label);
836         gtk_table_attach(table, label, 0, 1, 2, 3,
837                           (GtkAttachOptions) (0),
838                           (GtkAttachOptions) (0), 0, 0);
839       }
840       {
841         GtkAdjustment* adj = GTK_ADJUSTMENT(gtk_adjustment_new(0, -359, 359, 1, 10, 10));
842         GtkSpinButton* spin = GTK_SPIN_BUTTON(gtk_spin_button_new(adj, 1, 0));
843         gtk_widget_show(GTK_WIDGET(spin));
844         gtk_table_attach(table, GTK_WIDGET(spin), 1, 2, 0, 1,
845                           (GtkAttachOptions) (GTK_EXPAND | GTK_FILL),
846                           (GtkAttachOptions) (0), 0, 0);
847         gtk_widget_set_size_request(GTK_WIDGET(spin), 64, -1);
848         gtk_spin_button_set_wrap(spin, TRUE);
849
850         gtk_widget_grab_focus(GTK_WIDGET(spin));
851
852         rotateDialog.x = spin;
853       }
854       {
855         GtkAdjustment* adj = GTK_ADJUSTMENT(gtk_adjustment_new(0, -359, 359, 1, 10, 10));
856         GtkSpinButton* spin = GTK_SPIN_BUTTON(gtk_spin_button_new(adj, 1, 0));
857         gtk_widget_show(GTK_WIDGET(spin));
858         gtk_table_attach(table, GTK_WIDGET(spin), 1, 2, 1, 2,
859                           (GtkAttachOptions) (GTK_EXPAND | GTK_FILL),
860                           (GtkAttachOptions) (0), 0, 0);
861         gtk_widget_set_size_request(GTK_WIDGET(spin), 64, -1);
862         gtk_spin_button_set_wrap(spin, TRUE);
863
864         rotateDialog.y = spin;
865       }
866       {
867         GtkAdjustment* adj = GTK_ADJUSTMENT(gtk_adjustment_new(0, -359, 359, 1, 10, 10));
868         GtkSpinButton* spin = GTK_SPIN_BUTTON(gtk_spin_button_new(adj, 1, 0));
869         gtk_widget_show(GTK_WIDGET(spin));
870         gtk_table_attach(table, GTK_WIDGET(spin), 1, 2, 2, 3,
871                           (GtkAttachOptions) (GTK_EXPAND | GTK_FILL),
872                           (GtkAttachOptions) (0), 0, 0);
873         gtk_widget_set_size_request(GTK_WIDGET(spin), 64, -1);
874         gtk_spin_button_set_wrap(spin, TRUE);
875
876         rotateDialog.z = spin;
877       }
878     }
879     {
880       GtkVBox* vbox = create_dialog_vbox(4);
881       gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(vbox), TRUE, TRUE, 0);
882       {
883         GtkButton* button = create_dialog_button("OK", G_CALLBACK(dialog_button_ok), &dialog);
884         gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(button), FALSE, FALSE, 0);
885         widget_make_default(GTK_WIDGET(button));
886         gtk_widget_add_accelerator(GTK_WIDGET(button), "clicked", accel, GDK_Return, (GdkModifierType)0, (GtkAccelFlags)0);
887       }
888       {
889         GtkButton* button = create_dialog_button("Cancel", G_CALLBACK(dialog_button_cancel), &dialog);
890         gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(button), FALSE, FALSE, 0);
891         gtk_widget_add_accelerator(GTK_WIDGET(button), "clicked", accel, GDK_Escape, (GdkModifierType)0, (GtkAccelFlags)0);
892       }
893       {
894         GtkButton* button = create_dialog_button("Apply", G_CALLBACK(rotatedlg_apply), &rotateDialog);
895         gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(button), FALSE, FALSE, 0);
896       }
897     }
898   }
899
900   if(modal_dialog_show(window, dialog) == eIDOK)
901   {
902     rotatedlg_apply(0, &rotateDialog);
903   }
904
905   gtk_widget_destroy(GTK_WIDGET(window));
906 }
907
908 void DoScaleDlg()
909 {
910   ModalDialog dialog;
911   GtkWidget* x;
912   GtkWidget* y;
913   GtkWidget* z;
914
915   GtkWindow* window = create_dialog_window(MainFrame_getWindow(), "Scale", G_CALLBACK(dialog_delete_callback), &dialog);
916
917   GtkAccelGroup* accel = gtk_accel_group_new();
918   gtk_window_add_accel_group(window, accel);
919
920   {
921     GtkHBox* hbox = create_dialog_hbox(4, 4);
922     gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(hbox));
923     {
924       GtkTable* table = create_dialog_table(3, 2, 4, 4);
925       gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(table), TRUE, TRUE, 0);
926       {
927         GtkWidget* label = gtk_label_new ("X:");
928         gtk_widget_show (label);
929         gtk_table_attach(table, label, 0, 1, 0, 1,
930                           (GtkAttachOptions) (GTK_FILL),
931                           (GtkAttachOptions) (0), 0, 0);
932         gtk_misc_set_alignment (GTK_MISC (label), 0, 0.5);
933       }
934       {
935         GtkWidget* label = gtk_label_new ("Y:");
936         gtk_widget_show (label);
937         gtk_table_attach(table, label, 0, 1, 1, 2,
938                           (GtkAttachOptions) (GTK_FILL),
939                           (GtkAttachOptions) (0), 0, 0);
940         gtk_misc_set_alignment (GTK_MISC (label), 0, 0.5);
941       }
942       {
943         GtkWidget* label = gtk_label_new ("Z:");
944         gtk_widget_show (label);
945         gtk_table_attach(table, label, 0, 1, 2, 3,
946                           (GtkAttachOptions) (GTK_FILL),
947                           (GtkAttachOptions) (0), 0, 0);
948         gtk_misc_set_alignment (GTK_MISC (label), 0, 0.5);
949       }
950       {
951         GtkWidget* entry = gtk_entry_new();
952         gtk_widget_show (entry);
953         gtk_table_attach(table, entry, 1, 2, 0, 1,
954                           (GtkAttachOptions) (GTK_EXPAND | GTK_FILL),
955                           (GtkAttachOptions) (0), 0, 0);
956
957         gtk_widget_grab_focus(entry);
958
959         x = entry;
960       }
961       {
962         GtkWidget* entry = gtk_entry_new();
963         gtk_widget_show (entry);
964         gtk_table_attach(table, entry, 1, 2, 1, 2,
965                           (GtkAttachOptions) (GTK_EXPAND | GTK_FILL),
966                           (GtkAttachOptions) (0), 0, 0);
967
968         y = entry;
969       }
970       {
971         GtkWidget* entry = gtk_entry_new();
972         gtk_widget_show (entry);
973         gtk_table_attach(table, entry, 1, 2, 2, 3,
974                           (GtkAttachOptions) (GTK_EXPAND | GTK_FILL),
975                           (GtkAttachOptions) (0), 0, 0);
976
977         z = entry;
978       }
979     }
980     {
981       GtkVBox* vbox = create_dialog_vbox(4);
982       gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(vbox), TRUE, TRUE, 0);
983       {
984         GtkButton* button = create_dialog_button("OK", G_CALLBACK(dialog_button_ok), &dialog);
985         gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(button), FALSE, FALSE, 0);
986         widget_make_default(GTK_WIDGET(button));
987         gtk_widget_add_accelerator(GTK_WIDGET(button), "clicked", accel, GDK_Return, (GdkModifierType)0, (GtkAccelFlags)0);
988       }
989       {
990         GtkButton* button = create_dialog_button("Cancel", G_CALLBACK(dialog_button_cancel), &dialog);
991         gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(button), FALSE, FALSE, 0);
992         gtk_widget_add_accelerator(GTK_WIDGET(button), "clicked", accel, GDK_Escape, (GdkModifierType)0, (GtkAccelFlags)0);
993       }
994     }
995   }
996
997   // Initialize dialog
998   gtk_entry_set_text (GTK_ENTRY (x), "1.0");
999   gtk_entry_set_text (GTK_ENTRY (y), "1.0");
1000   gtk_entry_set_text (GTK_ENTRY (z), "1.0");
1001
1002   if(modal_dialog_show(window, dialog) == eIDOK)
1003   {
1004     float sx, sy, sz;
1005     sx = static_cast<float>(atof(gtk_entry_get_text (GTK_ENTRY (x))));
1006     sy = static_cast<float>(atof(gtk_entry_get_text (GTK_ENTRY (y))));
1007     sz = static_cast<float>(atof(gtk_entry_get_text (GTK_ENTRY (z))));
1008
1009     if (sx > 0 && sy > 0 && sz > 0)
1010     {
1011       StringOutputStream command;
1012       command << "scaleSelected -x " << sx << " -y " << sy << " -z " << sz;
1013       UndoableCommand undo(command.c_str());
1014
1015       Select_Scale(sx, sy, sz);
1016     }
1017     else
1018     {
1019       globalOutputStream() << "Warning.. Tried to scale by a zero value.";
1020     }
1021   }
1022
1023   gtk_widget_destroy(GTK_WIDGET(window));
1024 }