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