]> de.git.xonotic.org Git - xonotic/netradiant.git/blob - radiant/patchmanip.cpp
Merge branch 'transfilterfix' into 'master'
[xonotic/netradiant.git] / radiant / patchmanip.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 "patchmanip.h"
23
24 #include <gtk/gtk.h>
25 #include <gdk/gdkkeysyms.h>
26
27 #include "debugging/debugging.h"
28
29
30 #include "iselection.h"
31 #include "ipatch.h"
32
33 #include "math/vector.h"
34 #include "math/aabb.h"
35 #include "generic/callback.h"
36
37 #include "gtkutil/menu.h"
38 #include "gtkutil/image.h"
39 #include "map.h"
40 #include "mainframe.h"
41 #include "commands.h"
42 #include "gtkmisc.h"
43 #include "gtkdlgs.h"
44 #include "texwindow.h"
45 #include "xywindow.h"
46 #include "select.h"
47 #include "patch.h"
48 #include "grid.h"
49
50 PatchCreator *g_patchCreator = 0;
51
52 void Scene_PatchConstructPrefab(scene::Graph &graph, const AABB aabb, const char *shader, EPatchPrefab eType, int axis,
53                                 std::size_t width = 3, std::size_t height = 3)
54 {
55     Select_Delete();
56     GlobalSelectionSystem().setSelectedAll(false);
57
58     NodeSmartReference node(g_patchCreator->createPatch());
59     Node_getTraversable(Map_FindOrInsertWorldspawn(g_map))->insert(node);
60
61     Patch *patch = Node_getPatch(node);
62     patch->SetShader(shader);
63
64     patch->ConstructPrefab(aabb, eType, axis, width, height);
65     patch->controlPointsChanged();
66
67     {
68         scene::Path patchpath(makeReference(GlobalSceneGraph().root()));
69         patchpath.push(makeReference(*Map_GetWorldspawn(g_map)));
70         patchpath.push(makeReference(node.get()));
71         Instance_getSelectable(*graph.find(patchpath))->setSelected(true);
72     }
73 }
74
75 void PatchAutoCapTexture(Patch &patch)
76 {
77
78     AABB box = patch.localAABB();
79     float x = box.extents.x();
80     float y = box.extents.y();
81     float z = box.extents.z();
82
83     int cap_direction = -1;
84     if (x < y && x < z) {
85         cap_direction = 0;
86     } else if (y < x && y < z) {
87         cap_direction = 1;
88     } else if (z < x && z < x) {
89         cap_direction = 2;
90     }
91
92     if (cap_direction >= 0) {
93         patch.ProjectTexture(cap_direction);
94     } else {
95         patch.NaturalTexture();
96     }
97 }
98
99 void Patch_AutoCapTexture()
100 {
101     UndoableCommand command("patchAutoCapTexture");
102     Scene_forEachVisibleSelectedPatch(&PatchAutoCapTexture);
103     SceneChangeNotify();
104 }
105
106 void Patch_makeCaps(Patch &patch, scene::Instance &instance, EPatchCap type, const char *shader)
107 {
108     if ((type == eCapEndCap || type == eCapIEndCap)
109         && patch.getWidth() != 5) {
110         globalErrorStream() << "cannot create end-cap - patch width != 5\n";
111         return;
112     }
113     if ((type == eCapBevel || type == eCapIBevel)
114         && patch.getWidth() != 3 && patch.getWidth() != 5) {
115         globalErrorStream() << "cannot create bevel-cap - patch width != 3\n";
116         return;
117     }
118     if (type == eCapCylinder
119         && patch.getWidth() != 9) {
120         globalErrorStream() << "cannot create cylinder-cap - patch width != 9\n";
121         return;
122     }
123
124     {
125         NodeSmartReference cap(g_patchCreator->createPatch());
126         Node_getTraversable(instance.path().parent())->insert(cap);
127
128         Patch *cap_patch = Node_getPatch(cap);
129         patch.MakeCap(cap_patch, type, ROW, true);
130         cap_patch->SetShader(shader);
131         PatchAutoCapTexture(*cap_patch);
132
133         scene::Path path(instance.path());
134         path.pop();
135         path.push(makeReference(cap.get()));
136         selectPath(path, true);
137     }
138
139     {
140         NodeSmartReference cap(g_patchCreator->createPatch());
141         Node_getTraversable(instance.path().parent())->insert(cap);
142
143         Patch *cap_patch = Node_getPatch(cap);
144         patch.MakeCap(cap_patch, type, ROW, false);
145         cap_patch->SetShader(shader);
146         PatchAutoCapTexture(*cap_patch);
147
148         scene::Path path(instance.path());
149         path.pop();
150         path.push(makeReference(cap.get()));
151         selectPath(path, true);
152     }
153 }
154
155 typedef std::vector<scene::Instance *> InstanceVector;
156
157 enum ECapDialog {
158     PATCHCAP_BEVEL = 0,
159     PATCHCAP_ENDCAP,
160     PATCHCAP_INVERTED_BEVEL,
161     PATCHCAP_INVERTED_ENDCAP,
162     PATCHCAP_CYLINDER
163 };
164
165 EMessageBoxReturn DoCapDlg(ECapDialog *type);
166
167 void Scene_PatchDoCap_Selected(scene::Graph &graph, const char *shader)
168 {
169     ECapDialog nType;
170
171     if (DoCapDlg(&nType) == eIDOK) {
172         EPatchCap eType;
173         switch (nType) {
174             case PATCHCAP_INVERTED_BEVEL:
175                 eType = eCapIBevel;
176                 break;
177             case PATCHCAP_BEVEL:
178                 eType = eCapBevel;
179                 break;
180             case PATCHCAP_INVERTED_ENDCAP:
181                 eType = eCapIEndCap;
182                 break;
183             case PATCHCAP_ENDCAP:
184                 eType = eCapEndCap;
185                 break;
186             case PATCHCAP_CYLINDER:
187                 eType = eCapCylinder;
188                 break;
189             default:
190                 ERROR_MESSAGE("invalid patch cap type");
191                 return;
192         }
193
194         InstanceVector instances;
195         Scene_forEachVisibleSelectedPatchInstance([&](PatchInstance &patch) {
196             instances.push_back(&patch);
197         });
198         for (InstanceVector::const_iterator i = instances.begin(); i != instances.end(); ++i) {
199             Patch_makeCaps(*Node_getPatch((*i)->path().top()), *(*i), eType, shader);
200         }
201     }
202 }
203
204 Patch *Scene_GetUltimateSelectedVisiblePatch()
205 {
206     if (GlobalSelectionSystem().countSelected() != 0) {
207         scene::Node &node = GlobalSelectionSystem().ultimateSelected().path().top();
208         if (node.visible()) {
209             return Node_getPatch(node);
210         }
211     }
212     return 0;
213 }
214
215
216 void Scene_PatchCapTexture_Selected(scene::Graph &graph)
217 {
218     Scene_forEachVisibleSelectedPatch([&](Patch &patch) {
219         patch.ProjectTexture(Patch::m_CycleCapIndex);
220     });
221     Patch::m_CycleCapIndex = (Patch::m_CycleCapIndex == 0) ? 1 : (Patch::m_CycleCapIndex == 1) ? 2 : 0;
222     SceneChangeNotify();
223 }
224
225 void Scene_PatchFlipTexture_Selected(scene::Graph &graph, int axis)
226 {
227     Scene_forEachVisibleSelectedPatch([&](Patch &patch) {
228         patch.FlipTexture(axis);
229     });
230 }
231
232 void Scene_PatchNaturalTexture_Selected(scene::Graph &graph)
233 {
234     Scene_forEachVisibleSelectedPatch([&](Patch &patch) {
235         patch.NaturalTexture();
236     });
237     SceneChangeNotify();
238 }
239
240
241 void Scene_PatchInsertRemove_Selected(scene::Graph &graph, bool bInsert, bool bColumn, bool bFirst)
242 {
243     Scene_forEachVisibleSelectedPatch([&](Patch &patch) {
244         patch.InsertRemove(bInsert, bColumn, bFirst);
245     });
246 }
247
248 void Scene_PatchInvert_Selected(scene::Graph &graph)
249 {
250     Scene_forEachVisibleSelectedPatch([&](Patch &patch) {
251         patch.InvertMatrix();
252     });
253 }
254
255 void Scene_PatchRedisperse_Selected(scene::Graph &graph, EMatrixMajor major)
256 {
257     Scene_forEachVisibleSelectedPatch([&](Patch &patch) {
258         patch.Redisperse(major);
259     });
260 }
261
262 void Scene_PatchSmooth_Selected(scene::Graph &graph, EMatrixMajor major)
263 {
264     Scene_forEachVisibleSelectedPatch([&](Patch &patch) {
265         patch.Smooth(major);
266     });
267 }
268
269 void Scene_PatchTranspose_Selected(scene::Graph &graph)
270 {
271     Scene_forEachVisibleSelectedPatch([&](Patch &patch) {
272         patch.TransposeMatrix();
273     });
274 }
275
276 void Scene_PatchSetShader_Selected(scene::Graph &graph, const char *name)
277 {
278     Scene_forEachVisibleSelectedPatch([&](Patch &patch) {
279         patch.SetShader(name);
280     });
281     SceneChangeNotify();
282 }
283
284 void Scene_PatchGetShader_Selected(scene::Graph &graph, CopiedString &name)
285 {
286     Patch *patch = Scene_GetUltimateSelectedVisiblePatch();
287     if (patch != 0) {
288         name = patch->GetShader();
289     }
290 }
291
292 void Scene_PatchSelectByShader(scene::Graph &graph, const char *name)
293 {
294     Scene_forEachVisiblePatchInstance([&](PatchInstance &patch) {
295         if (shader_equal(patch.getPatch().GetShader(), name)) {
296             patch.setSelected(true);
297         }
298     });
299 }
300
301
302 void Scene_PatchFindReplaceShader(scene::Graph &graph, const char *find, const char *replace)
303 {
304     Scene_forEachVisiblePatch([&](Patch &patch) {
305         if (shader_equal(patch.GetShader(), find)) {
306             patch.SetShader(replace);
307         }
308     });
309 }
310
311 void Scene_PatchFindReplaceShader_Selected(scene::Graph &graph, const char *find, const char *replace)
312 {
313     Scene_forEachVisibleSelectedPatch([&](Patch &patch) {
314         if (shader_equal(patch.GetShader(), find)) {
315             patch.SetShader(replace);
316         }
317     });
318 }
319
320
321 AABB PatchCreator_getBounds()
322 {
323     AABB aabb(aabb_for_minmax(Select_getWorkZone().d_work_min, Select_getWorkZone().d_work_max));
324
325     float gridSize = GetGridSize();
326
327     if (aabb.extents[0] == 0) {
328         aabb.extents[0] = gridSize;
329     }
330     if (aabb.extents[1] == 0) {
331         aabb.extents[1] = gridSize;
332     }
333     if (aabb.extents[2] == 0) {
334         aabb.extents[2] = gridSize;
335     }
336
337     if (aabb_valid(aabb)) {
338         return aabb;
339     }
340     return AABB(Vector3(0, 0, 0), Vector3(64, 64, 64));
341 }
342
343 void DoNewPatchDlg(EPatchPrefab prefab, int minrows, int mincols, int defrows, int defcols, int maxrows, int maxcols);
344
345 void Patch_XactCylinder()
346 {
347     UndoableCommand undo("patchCreateXactCylinder");
348
349     DoNewPatchDlg(eXactCylinder, 3, 7, 3, 13, 0, 0);
350 }
351
352 void Patch_XactSphere()
353 {
354     UndoableCommand undo("patchCreateXactSphere");
355
356     DoNewPatchDlg(eXactSphere, 5, 7, 7, 13, 0, 0);
357 }
358
359 void Patch_XactCone()
360 {
361     UndoableCommand undo("patchCreateXactCone");
362
363     DoNewPatchDlg(eXactCone, 3, 7, 3, 13, 0, 0);
364 }
365
366 void Patch_Cylinder()
367 {
368     UndoableCommand undo("patchCreateCylinder");
369
370     Scene_PatchConstructPrefab(GlobalSceneGraph(), PatchCreator_getBounds(),
371                                TextureBrowser_GetSelectedShader(GlobalTextureBrowser()), eCylinder,
372                                GlobalXYWnd_getCurrentViewType());
373 }
374
375 void Patch_DenseCylinder()
376 {
377     UndoableCommand undo("patchCreateDenseCylinder");
378
379     Scene_PatchConstructPrefab(GlobalSceneGraph(), PatchCreator_getBounds(),
380                                TextureBrowser_GetSelectedShader(GlobalTextureBrowser()), eDenseCylinder,
381                                GlobalXYWnd_getCurrentViewType());
382 }
383
384 void Patch_VeryDenseCylinder()
385 {
386     UndoableCommand undo("patchCreateVeryDenseCylinder");
387
388     Scene_PatchConstructPrefab(GlobalSceneGraph(), PatchCreator_getBounds(),
389                                TextureBrowser_GetSelectedShader(GlobalTextureBrowser()), eVeryDenseCylinder,
390                                GlobalXYWnd_getCurrentViewType());
391 }
392
393 void Patch_SquareCylinder()
394 {
395     UndoableCommand undo("patchCreateSquareCylinder");
396
397     Scene_PatchConstructPrefab(GlobalSceneGraph(), PatchCreator_getBounds(),
398                                TextureBrowser_GetSelectedShader(GlobalTextureBrowser()), eSqCylinder,
399                                GlobalXYWnd_getCurrentViewType());
400 }
401
402 void Patch_Endcap()
403 {
404     UndoableCommand undo("patchCreateCaps");
405
406     Scene_PatchConstructPrefab(GlobalSceneGraph(), PatchCreator_getBounds(),
407                                TextureBrowser_GetSelectedShader(GlobalTextureBrowser()), eEndCap,
408                                GlobalXYWnd_getCurrentViewType());
409 }
410
411 void Patch_Bevel()
412 {
413     UndoableCommand undo("patchCreateBevel");
414
415     Scene_PatchConstructPrefab(GlobalSceneGraph(), PatchCreator_getBounds(),
416                                TextureBrowser_GetSelectedShader(GlobalTextureBrowser()), eBevel,
417                                GlobalXYWnd_getCurrentViewType());
418 }
419
420 void Patch_Sphere()
421 {
422     UndoableCommand undo("patchCreateSphere");
423
424     Scene_PatchConstructPrefab(GlobalSceneGraph(), PatchCreator_getBounds(),
425                                TextureBrowser_GetSelectedShader(GlobalTextureBrowser()), eSphere,
426                                GlobalXYWnd_getCurrentViewType());
427 }
428
429 void Patch_SquareBevel()
430 {
431 }
432
433 void Patch_SquareEndcap()
434 {
435 }
436
437 void Patch_Cone()
438 {
439     UndoableCommand undo("patchCreateCone");
440
441     Scene_PatchConstructPrefab(GlobalSceneGraph(), PatchCreator_getBounds(),
442                                TextureBrowser_GetSelectedShader(GlobalTextureBrowser()), eCone,
443                                GlobalXYWnd_getCurrentViewType());
444 }
445
446 void Patch_Plane()
447 {
448     UndoableCommand undo("patchCreatePlane");
449
450     DoNewPatchDlg(ePlane, 3, 3, 3, 3, 0, 0);
451 }
452
453 void Patch_InsertInsertColumn()
454 {
455     UndoableCommand undo("patchInsertColumns");
456
457     Scene_PatchInsertRemove_Selected(GlobalSceneGraph(), true, true, false);
458 }
459
460 void Patch_InsertAddColumn()
461 {
462     UndoableCommand undo("patchAddColumns");
463
464     Scene_PatchInsertRemove_Selected(GlobalSceneGraph(), true, true, true);
465 }
466
467 void Patch_InsertInsertRow()
468 {
469     UndoableCommand undo("patchInsertRows");
470
471     Scene_PatchInsertRemove_Selected(GlobalSceneGraph(), true, false, false);
472 }
473
474 void Patch_InsertAddRow()
475 {
476     UndoableCommand undo("patchAddRows");
477
478     Scene_PatchInsertRemove_Selected(GlobalSceneGraph(), true, false, true);
479 }
480
481 void Patch_DeleteFirstColumn()
482 {
483     UndoableCommand undo("patchDeleteFirstColumns");
484
485     Scene_PatchInsertRemove_Selected(GlobalSceneGraph(), false, true, true);
486 }
487
488 void Patch_DeleteLastColumn()
489 {
490     UndoableCommand undo("patchDeleteLastColumns");
491
492     Scene_PatchInsertRemove_Selected(GlobalSceneGraph(), false, true, false);
493 }
494
495 void Patch_DeleteFirstRow()
496 {
497     UndoableCommand undo("patchDeleteFirstRows");
498
499     Scene_PatchInsertRemove_Selected(GlobalSceneGraph(), false, false, true);
500 }
501
502 void Patch_DeleteLastRow()
503 {
504     UndoableCommand undo("patchDeleteLastRows");
505
506     Scene_PatchInsertRemove_Selected(GlobalSceneGraph(), false, false, false);
507 }
508
509 void Patch_Invert()
510 {
511     UndoableCommand undo("patchInvert");
512
513     Scene_PatchInvert_Selected(GlobalSceneGraph());
514 }
515
516 void Patch_RedisperseRows()
517 {
518     UndoableCommand undo("patchRedisperseRows");
519
520     Scene_PatchRedisperse_Selected(GlobalSceneGraph(), ROW);
521 }
522
523 void Patch_RedisperseCols()
524 {
525     UndoableCommand undo("patchRedisperseColumns");
526
527     Scene_PatchRedisperse_Selected(GlobalSceneGraph(), COL);
528 }
529
530 void Patch_SmoothRows()
531 {
532     UndoableCommand undo("patchSmoothRows");
533
534     Scene_PatchSmooth_Selected(GlobalSceneGraph(), ROW);
535 }
536
537 void Patch_SmoothCols()
538 {
539     UndoableCommand undo("patchSmoothColumns");
540
541     Scene_PatchSmooth_Selected(GlobalSceneGraph(), COL);
542 }
543
544 void Patch_Transpose()
545 {
546     UndoableCommand undo("patchTranspose");
547
548     Scene_PatchTranspose_Selected(GlobalSceneGraph());
549 }
550
551 void Patch_Cap()
552 {
553     // FIXME: add support for patch cap creation
554     // Patch_CapCurrent();
555     UndoableCommand undo("patchCreateCaps");
556
557     Scene_PatchDoCap_Selected(GlobalSceneGraph(), TextureBrowser_GetSelectedShader(GlobalTextureBrowser()));
558 }
559
560 void Patch_CycleProjection()
561 {
562     UndoableCommand undo("patchCycleUVProjectionAxis");
563
564     Scene_PatchCapTexture_Selected(GlobalSceneGraph());
565 }
566
567 ///\todo Unfinished.
568 void Patch_OverlayOn()
569 {
570 }
571
572 ///\todo Unfinished.
573 void Patch_OverlayOff()
574 {
575 }
576
577 void Patch_FlipTextureX()
578 {
579     UndoableCommand undo("patchFlipTextureU");
580
581     Scene_PatchFlipTexture_Selected(GlobalSceneGraph(), 0);
582 }
583
584 void Patch_FlipTextureY()
585 {
586     UndoableCommand undo("patchFlipTextureV");
587
588     Scene_PatchFlipTexture_Selected(GlobalSceneGraph(), 1);
589 }
590
591 void Patch_NaturalTexture()
592 {
593     UndoableCommand undo("patchNaturalTexture");
594
595     Scene_PatchNaturalTexture_Selected(GlobalSceneGraph());
596 }
597
598 void Patch_CapTexture()
599 {
600     UndoableCommand command("patchCapTexture");
601
602     Scene_PatchCapTexture_Selected(GlobalSceneGraph());
603 }
604
605 void Patch_ResetTexture()
606 {
607     float fx, fy;
608     if (DoTextureLayout(&fx, &fy) == eIDOK) {
609         UndoableCommand command("patchTileTexture");
610         Scene_PatchTileTexture_Selected(GlobalSceneGraph(), fx, fy);
611     }
612 }
613
614 void Patch_FitTexture()
615 {
616     UndoableCommand command("patchFitTexture");
617
618     Scene_PatchTileTexture_Selected(GlobalSceneGraph(), 1, 1);
619 }
620
621 #include "ifilter.h"
622
623
624 class filter_patch_all : public PatchFilter {
625 public:
626     bool filter(const Patch &patch) const
627     {
628         return true;
629     }
630 };
631
632 class filter_patch_shader : public PatchFilter {
633     const char *m_shader;
634 public:
635     filter_patch_shader(const char *shader) : m_shader(shader)
636     {
637     }
638
639     bool filter(const Patch &patch) const
640     {
641         return shader_equal(patch.GetShader(), m_shader);
642     }
643 };
644
645 class filter_patch_flags : public PatchFilter {
646     int m_flags;
647 public:
648     filter_patch_flags(int flags) : m_flags(flags)
649     {
650     }
651
652     bool filter(const Patch &patch) const
653     {
654         return (patch.getShaderFlags() & m_flags) != 0;
655     }
656 };
657
658
659 filter_patch_all g_filter_patch_all;
660 filter_patch_shader g_filter_patch_clip("textures/common/clip");
661 filter_patch_shader g_filter_patch_weapclip("textures/common/weapclip");
662 filter_patch_flags g_filter_patch_translucent(QER_TRANS);
663
664 void PatchFilters_construct()
665 {
666     add_patch_filter(g_filter_patch_all, EXCLUDE_CURVES);
667     add_patch_filter(g_filter_patch_clip, EXCLUDE_CLIP);
668     add_patch_filter(g_filter_patch_weapclip, EXCLUDE_CLIP);
669     add_patch_filter(g_filter_patch_translucent, EXCLUDE_TRANSLUCENT);
670 }
671
672
673 #include "preferences.h"
674
675 void Patch_constructPreferences(PreferencesPage &page)
676 {
677     page.appendEntry("Patch Subdivide Threshold", g_PatchSubdivideThreshold);
678 }
679
680 void Patch_constructPage(PreferenceGroup &group)
681 {
682     PreferencesPage page(group.createPage("Patches", "Patch Display Preferences"));
683     Patch_constructPreferences(page);
684 }
685
686 void Patch_registerPreferencesPage()
687 {
688     PreferencesDialog_addDisplayPage(makeCallbackF(Patch_constructPage));
689 }
690
691
692 #include "preferencesystem.h"
693
694 void PatchPreferences_construct()
695 {
696     GlobalPreferenceSystem().registerPreference("Subdivisions", make_property_string(g_PatchSubdivideThreshold));
697 }
698
699
700 #include "generic/callback.h"
701
702 void Patch_registerCommands()
703 {
704     GlobalCommands_insert("InvertCurveTextureX", makeCallbackF(Patch_FlipTextureX),
705                           Accelerator('I', (GdkModifierType) (GDK_SHIFT_MASK | GDK_CONTROL_MASK)));
706     GlobalCommands_insert("InvertCurveTextureY", makeCallbackF(Patch_FlipTextureY),
707                           Accelerator('I', (GdkModifierType) GDK_SHIFT_MASK));
708     GlobalCommands_insert("NaturalizePatch", makeCallbackF(Patch_NaturalTexture),
709                           Accelerator('N', (GdkModifierType) GDK_CONTROL_MASK));
710     GlobalCommands_insert("PatchCylinder", makeCallbackF(Patch_Cylinder));
711     GlobalCommands_insert("PatchDenseCylinder", makeCallbackF(Patch_DenseCylinder));
712     GlobalCommands_insert("PatchVeryDenseCylinder", makeCallbackF(Patch_VeryDenseCylinder));
713     GlobalCommands_insert("PatchSquareCylinder", makeCallbackF(Patch_SquareCylinder));
714     GlobalCommands_insert("PatchXactCylinder", makeCallbackF(Patch_XactCylinder));
715     GlobalCommands_insert("PatchXactSphere", makeCallbackF(Patch_XactSphere));
716     GlobalCommands_insert("PatchXactCone", makeCallbackF(Patch_XactCone));
717     GlobalCommands_insert("PatchEndCap", makeCallbackF(Patch_Endcap));
718     GlobalCommands_insert("PatchBevel", makeCallbackF(Patch_Bevel));
719     GlobalCommands_insert("PatchSquareBevel", makeCallbackF(Patch_SquareBevel));
720     GlobalCommands_insert("PatchSquareEndcap", makeCallbackF(Patch_SquareEndcap));
721     GlobalCommands_insert("PatchCone", makeCallbackF(Patch_Cone));
722     GlobalCommands_insert("PatchSphere", makeCallbackF(Patch_Sphere));
723     GlobalCommands_insert("SimplePatchMesh", makeCallbackF(Patch_Plane),
724                           Accelerator('P', (GdkModifierType) GDK_SHIFT_MASK));
725     GlobalCommands_insert("PatchInsertInsertColumn", makeCallbackF(Patch_InsertInsertColumn),
726                           Accelerator(GDK_KEY_KP_Add, (GdkModifierType) (GDK_SHIFT_MASK | GDK_CONTROL_MASK)));
727     GlobalCommands_insert("PatchInsertAddColumn", makeCallbackF(Patch_InsertAddColumn));
728     GlobalCommands_insert("PatchInsertInsertRow", makeCallbackF(Patch_InsertInsertRow),
729                           Accelerator(GDK_KEY_KP_Add, (GdkModifierType) GDK_CONTROL_MASK));
730     GlobalCommands_insert("PatchInsertAddRow", makeCallbackF(Patch_InsertAddRow));
731     GlobalCommands_insert("PatchDeleteFirstColumn", makeCallbackF(Patch_DeleteFirstColumn));
732     GlobalCommands_insert("PatchDeleteLastColumn", makeCallbackF(Patch_DeleteLastColumn),
733                           Accelerator(GDK_KEY_KP_Subtract, (GdkModifierType) (GDK_SHIFT_MASK | GDK_CONTROL_MASK)));
734     GlobalCommands_insert("PatchDeleteFirstRow", makeCallbackF(Patch_DeleteFirstRow),
735                           Accelerator(GDK_KEY_KP_Subtract, (GdkModifierType) GDK_CONTROL_MASK));
736     GlobalCommands_insert("PatchDeleteLastRow", makeCallbackF(Patch_DeleteLastRow));
737     GlobalCommands_insert("InvertCurve", makeCallbackF(Patch_Invert),
738                           Accelerator('I', (GdkModifierType) GDK_CONTROL_MASK));
739     GlobalCommands_insert("RedisperseRows", makeCallbackF(Patch_RedisperseRows),
740                           Accelerator('E', (GdkModifierType) GDK_CONTROL_MASK));
741     GlobalCommands_insert("RedisperseCols", makeCallbackF(Patch_RedisperseCols),
742                           Accelerator('E', (GdkModifierType) (GDK_SHIFT_MASK | GDK_CONTROL_MASK)));
743     GlobalCommands_insert("SmoothRows", makeCallbackF(Patch_SmoothRows),
744                           Accelerator('W', (GdkModifierType) GDK_CONTROL_MASK));
745     GlobalCommands_insert("SmoothCols", makeCallbackF(Patch_SmoothCols),
746                           Accelerator('W', (GdkModifierType) (GDK_SHIFT_MASK | GDK_CONTROL_MASK)));
747     GlobalCommands_insert("MatrixTranspose", makeCallbackF(Patch_Transpose),
748                           Accelerator('M', (GdkModifierType) (GDK_SHIFT_MASK | GDK_CONTROL_MASK)));
749     GlobalCommands_insert("CapCurrentCurve", makeCallbackF(Patch_Cap),
750                           Accelerator('C', (GdkModifierType) GDK_SHIFT_MASK));
751     GlobalCommands_insert("CycleCapTexturePatch", makeCallbackF(Patch_CycleProjection),
752                           Accelerator('N', (GdkModifierType) (GDK_SHIFT_MASK | GDK_CONTROL_MASK)));
753     GlobalCommands_insert("MakeOverlayPatch", makeCallbackF(Patch_OverlayOn), Accelerator('Y'));
754     GlobalCommands_insert("ClearPatchOverlays", makeCallbackF(Patch_OverlayOff),
755                           Accelerator('L', (GdkModifierType) GDK_CONTROL_MASK));
756 }
757
758 void Patch_constructToolbar(ui::Toolbar toolbar)
759 {
760     toolbar_append_button(toolbar, "Put caps on the current patch (SHIFT + C)", "cap_curve.png", "CapCurrentCurve");
761 }
762
763 void Patch_constructMenu(ui::Menu menu)
764 {
765     create_menu_item_with_mnemonic(menu, "Cylinder", "PatchCylinder");
766     {
767         auto menu_in_menu = create_sub_menu_with_mnemonic(menu, "More Cylinders");
768         if (g_Layout_enableDetachableMenus.m_value) {
769             menu_tearoff(menu_in_menu);
770         }
771         create_menu_item_with_mnemonic(menu_in_menu, "Dense Cylinder", "PatchDenseCylinder");
772         create_menu_item_with_mnemonic(menu_in_menu, "Very Dense Cylinder", "PatchVeryDenseCylinder");
773         create_menu_item_with_mnemonic(menu_in_menu, "Square Cylinder", "PatchSquareCylinder");
774         create_menu_item_with_mnemonic(menu_in_menu, "Exact Cylinder...", "PatchXactCylinder");
775     }
776     menu_separator(menu);
777     create_menu_item_with_mnemonic(menu, "End cap", "PatchEndCap");
778     create_menu_item_with_mnemonic(menu, "Bevel", "PatchBevel");
779     {
780         auto menu_in_menu = create_sub_menu_with_mnemonic(menu, "More End caps, Bevels");
781         if (g_Layout_enableDetachableMenus.m_value) {
782             menu_tearoff(menu_in_menu);
783         }
784         create_menu_item_with_mnemonic(menu_in_menu, "Square Endcap", "PatchSquareBevel");
785         create_menu_item_with_mnemonic(menu_in_menu, "Square Bevel", "PatchSquareEndcap");
786     }
787     menu_separator(menu);
788     create_menu_item_with_mnemonic(menu, "Cone", "PatchCone");
789     create_menu_item_with_mnemonic(menu, "Exact Cone...", "PatchXactCone");
790     menu_separator(menu);
791     create_menu_item_with_mnemonic(menu, "Sphere", "PatchSphere");
792     create_menu_item_with_mnemonic(menu, "Exact Sphere...", "PatchXactSphere");
793     menu_separator(menu);
794     create_menu_item_with_mnemonic(menu, "Simple Patch Mesh...", "SimplePatchMesh");
795     menu_separator(menu);
796     {
797         auto menu_in_menu = create_sub_menu_with_mnemonic(menu, "Insert");
798         if (g_Layout_enableDetachableMenus.m_value) {
799             menu_tearoff(menu_in_menu);
800         }
801         create_menu_item_with_mnemonic(menu_in_menu, "Insert (2) Columns", "PatchInsertInsertColumn");
802         create_menu_item_with_mnemonic(menu_in_menu, "Add (2) Columns", "PatchInsertAddColumn");
803         menu_separator(menu_in_menu);
804         create_menu_item_with_mnemonic(menu_in_menu, "Insert (2) Rows", "PatchInsertInsertRow");
805         create_menu_item_with_mnemonic(menu_in_menu, "Add (2) Rows", "PatchInsertAddRow");
806     }
807     {
808         auto menu_in_menu = create_sub_menu_with_mnemonic(menu, "Delete");
809         if (g_Layout_enableDetachableMenus.m_value) {
810             menu_tearoff(menu_in_menu);
811         }
812         create_menu_item_with_mnemonic(menu_in_menu, "First (2) Columns", "PatchDeleteFirstColumn");
813         create_menu_item_with_mnemonic(menu_in_menu, "Last (2) Columns", "PatchDeleteLastColumn");
814         menu_separator(menu_in_menu);
815         create_menu_item_with_mnemonic(menu_in_menu, "First (2) Rows", "PatchDeleteFirstRow");
816         create_menu_item_with_mnemonic(menu_in_menu, "Last (2) Rows", "PatchDeleteLastRow");
817     }
818     menu_separator(menu);
819     {
820         auto menu_in_menu = create_sub_menu_with_mnemonic(menu, "Matrix");
821         if (g_Layout_enableDetachableMenus.m_value) {
822             menu_tearoff(menu_in_menu);
823         }
824         create_menu_item_with_mnemonic(menu_in_menu, "Invert", "InvertCurve");
825         auto menu_3 = create_sub_menu_with_mnemonic(menu_in_menu, "Re-disperse");
826         if (g_Layout_enableDetachableMenus.m_value) {
827             menu_tearoff(menu_3);
828         }
829         create_menu_item_with_mnemonic(menu_3, "Rows", "RedisperseRows");
830         create_menu_item_with_mnemonic(menu_3, "Columns", "RedisperseCols");
831         auto menu_4 = create_sub_menu_with_mnemonic(menu_in_menu, "Smooth");
832         if (g_Layout_enableDetachableMenus.m_value) {
833             menu_tearoff(menu_4);
834         }
835         create_menu_item_with_mnemonic(menu_4, "Rows", "SmoothRows");
836         create_menu_item_with_mnemonic(menu_4, "Columns", "SmoothCols");
837         create_menu_item_with_mnemonic(menu_in_menu, "Transpose", "MatrixTranspose");
838     }
839     menu_separator(menu);
840     create_menu_item_with_mnemonic(menu, "Cap Selection", "CapCurrentCurve");
841     create_menu_item_with_mnemonic(menu, "Cycle Cap Texture", "CycleCapTexturePatch");
842     menu_separator(menu);
843     {
844         auto menu_in_menu = create_sub_menu_with_mnemonic(menu, "Overlay");
845         if (g_Layout_enableDetachableMenus.m_value) {
846             menu_tearoff(menu_in_menu);
847         }
848         create_menu_item_with_mnemonic(menu_in_menu, "Set", "MakeOverlayPatch");
849         create_menu_item_with_mnemonic(menu_in_menu, "Clear", "ClearPatchOverlays");
850     }
851 }
852
853
854 #include "gtkutil/dialog.h"
855 #include "gtkutil/widget.h"
856
857 void DoNewPatchDlg(EPatchPrefab prefab, int minrows, int mincols, int defrows, int defcols, int maxrows, int maxcols)
858 {
859     ModalDialog dialog;
860
861     ui::Window window = MainFrame_getWindow().create_dialog_window("Patch density", G_CALLBACK(dialog_delete_callback),
862                                                                    &dialog);
863
864     auto accel = ui::AccelGroup(ui::New);
865     window.add_accel_group(accel);
866     auto width = ui::ComboBoxText(ui::New);
867     auto height = ui::ComboBoxText(ui::New);
868     {
869         auto hbox = create_dialog_hbox(4, 4);
870         window.add(hbox);
871         {
872             auto table = create_dialog_table(2, 2, 4, 4);
873             hbox.pack_start(table, TRUE, TRUE, 0);
874             {
875                 auto label = ui::Label("Width:");
876                 label.show();
877                 table.attach(label, {0, 1, 0, 1}, {GTK_FILL, 0});
878                 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
879             }
880             {
881                 auto label = ui::Label("Height:");
882                 label.show();
883                 table.attach(label, {0, 1, 1, 2}, {GTK_FILL, 0});
884                 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
885             }
886
887             {
888                 auto combo = width;
889 #define D_ITEM(x) if ( x >= mincols && ( !maxcols || x <= maxcols ) ) gtk_combo_box_text_append_text( combo, #x )
890                 D_ITEM(3);
891                 D_ITEM(5);
892                 D_ITEM(7);
893                 D_ITEM(9);
894                 D_ITEM(11);
895                 D_ITEM(13);
896                 D_ITEM(15);
897                 D_ITEM(17);
898                 D_ITEM(19);
899                 D_ITEM(21);
900                 D_ITEM(23);
901                 D_ITEM(25);
902                 D_ITEM(27);
903                 D_ITEM(29);
904                 D_ITEM(31); // MAX_PATCH_SIZE is 32, so we should be able to do 31...
905 #undef D_ITEM
906                 combo.show();
907                 table.attach(combo, {1, 2, 0, 1}, {GTK_EXPAND | GTK_FILL, 0});
908             }
909             {
910                 auto combo = height;
911 #define D_ITEM(x) if ( x >= minrows && ( !maxrows || x <= maxrows ) ) gtk_combo_box_text_append_text( combo, #x )
912                 D_ITEM(3);
913                 D_ITEM(5);
914                 D_ITEM(7);
915                 D_ITEM(9);
916                 D_ITEM(11);
917                 D_ITEM(13);
918                 D_ITEM(15);
919                 D_ITEM(17);
920                 D_ITEM(19);
921                 D_ITEM(21);
922                 D_ITEM(23);
923                 D_ITEM(25);
924                 D_ITEM(27);
925                 D_ITEM(29);
926                 D_ITEM(31); // MAX_PATCH_SIZE is 32, so we should be able to do 31...
927 #undef D_ITEM
928                 combo.show();
929                 table.attach(combo, {1, 2, 1, 2}, {GTK_EXPAND | GTK_FILL, 0});
930             }
931         }
932
933         {
934             auto vbox = create_dialog_vbox(4);
935             hbox.pack_start(vbox, TRUE, TRUE, 0);
936             {
937                 auto button = create_dialog_button("OK", G_CALLBACK(dialog_button_ok), &dialog);
938                 vbox.pack_start(button, FALSE, FALSE, 0);
939                 widget_make_default(button);
940                 gtk_widget_grab_focus(button);
941                 gtk_widget_add_accelerator(button, "clicked", accel, GDK_KEY_Return, (GdkModifierType) 0,
942                                            (GtkAccelFlags) 0);
943             }
944             {
945                 auto button = create_dialog_button("Cancel", G_CALLBACK(dialog_button_cancel), &dialog);
946                 vbox.pack_start(button, FALSE, FALSE, 0);
947                 gtk_widget_add_accelerator(button, "clicked", accel, GDK_KEY_Escape, (GdkModifierType) 0,
948                                            (GtkAccelFlags) 0);
949             }
950         }
951     }
952
953     // Initialize dialog
954     gtk_combo_box_set_active(width, (defcols - mincols) / 2);
955     gtk_combo_box_set_active(height, (defrows - minrows) / 2);
956
957     if (modal_dialog_show(window, dialog) == eIDOK) {
958         int w = gtk_combo_box_get_active(width) * 2 + mincols;
959         int h = gtk_combo_box_get_active(height) * 2 + minrows;
960
961         Scene_PatchConstructPrefab(GlobalSceneGraph(), PatchCreator_getBounds(),
962                                    TextureBrowser_GetSelectedShader(GlobalTextureBrowser()), prefab,
963                                    GlobalXYWnd_getCurrentViewType(), w, h);
964     }
965
966     window.destroy();
967 }
968
969
970 EMessageBoxReturn DoCapDlg(ECapDialog *type)
971 {
972     ModalDialog dialog;
973     ModalDialogButton ok_button(dialog, eIDOK);
974     ModalDialogButton cancel_button(dialog, eIDCANCEL);
975     ui::Widget bevel{ui::null};
976     ui::Widget ibevel{ui::null};
977     ui::Widget endcap{ui::null};
978     ui::Widget iendcap{ui::null};
979     ui::Widget cylinder{ui::null};
980
981     ui::Window window = MainFrame_getWindow().create_modal_dialog_window("Cap", dialog);
982
983     auto accel_group = ui::AccelGroup(ui::New);
984     window.add_accel_group(accel_group);
985
986     {
987         auto hbox = create_dialog_hbox(4, 4);
988         window.add(hbox);
989
990         {
991             // Gef: Added a vbox to contain the toggle buttons
992             auto radio_vbox = create_dialog_vbox(4);
993             hbox.add(radio_vbox);
994
995             {
996                 auto table = ui::Table(5, 2, FALSE);
997                 table.show();
998                 radio_vbox.pack_start(table, TRUE, TRUE, 0);
999                 gtk_table_set_row_spacings(table, 5);
1000                 gtk_table_set_col_spacings(table, 5);
1001
1002                 {
1003                     auto image = new_local_image("cap_bevel.png");
1004                     image.show();
1005                     table.attach(image, {0, 1, 0, 1}, {GTK_FILL, 0});
1006                 }
1007                 {
1008                     auto image = new_local_image("cap_endcap.png");
1009                     image.show();
1010                     table.attach(image, {0, 1, 1, 2}, {GTK_FILL, 0});
1011                 }
1012                 {
1013                     auto image = new_local_image("cap_ibevel.png");
1014                     image.show();
1015                     table.attach(image, {0, 1, 2, 3}, {GTK_FILL, 0});
1016                 }
1017                 {
1018                     auto image = new_local_image("cap_iendcap.png");
1019                     image.show();
1020                     table.attach(image, {0, 1, 3, 4}, {GTK_FILL, 0});
1021                 }
1022                 {
1023                     auto image = new_local_image("cap_cylinder.png");
1024                     image.show();
1025                     table.attach(image, {0, 1, 4, 5}, {GTK_FILL, 0});
1026                 }
1027
1028                 GSList *group = 0;
1029                 {
1030                     ui::Widget button = ui::Widget::from(gtk_radio_button_new_with_label(group, "Bevel"));
1031                     button.show();
1032                     table.attach(button, {1, 2, 0, 1}, {GTK_FILL | GTK_EXPAND, 0});
1033                     group = gtk_radio_button_get_group(GTK_RADIO_BUTTON(button));
1034
1035                     bevel = button;
1036                 }
1037                 {
1038                     ui::Widget button = ui::Widget::from(gtk_radio_button_new_with_label(group, "Endcap"));
1039                     button.show();
1040                     table.attach(button, {1, 2, 1, 2}, {GTK_FILL | GTK_EXPAND, 0});
1041                     group = gtk_radio_button_get_group(GTK_RADIO_BUTTON(button));
1042
1043                     endcap = button;
1044                 }
1045                 {
1046                     ui::Widget button = ui::Widget::from(gtk_radio_button_new_with_label(group, "Inverted Bevel"));
1047                     button.show();
1048                     table.attach(button, {1, 2, 2, 3}, {GTK_FILL | GTK_EXPAND, 0});
1049                     group = gtk_radio_button_get_group(GTK_RADIO_BUTTON(button));
1050
1051                     ibevel = button;
1052                 }
1053                 {
1054                     ui::Widget button = ui::Widget::from(gtk_radio_button_new_with_label(group, "Inverted Endcap"));
1055                     button.show();
1056                     table.attach(button, {1, 2, 3, 4}, {GTK_FILL | GTK_EXPAND, 0});
1057                     group = gtk_radio_button_get_group(GTK_RADIO_BUTTON(button));
1058
1059                     iendcap = button;
1060                 }
1061                 {
1062                     ui::Widget button = ui::Widget::from(gtk_radio_button_new_with_label(group, "Cylinder"));
1063                     button.show();
1064                     table.attach(button, {1, 2, 4, 5}, {GTK_FILL | GTK_EXPAND, 0});
1065                     group = gtk_radio_button_get_group(GTK_RADIO_BUTTON(button));
1066
1067                     cylinder = button;
1068                 }
1069             }
1070         }
1071
1072         {
1073             auto vbox = create_dialog_vbox(4);
1074             hbox.pack_start(vbox, FALSE, FALSE, 0);
1075             {
1076                 auto button = create_modal_dialog_button("OK", ok_button);
1077                 vbox.pack_start(button, FALSE, FALSE, 0);
1078                 widget_make_default(button);
1079                 gtk_widget_add_accelerator(button, "clicked", accel_group, GDK_KEY_Return, (GdkModifierType) 0,
1080                                            GTK_ACCEL_VISIBLE);
1081             }
1082             {
1083                 auto button = create_modal_dialog_button("Cancel", cancel_button);
1084                 vbox.pack_start(button, FALSE, FALSE, 0);
1085                 gtk_widget_add_accelerator(button, "clicked", accel_group, GDK_KEY_Escape, (GdkModifierType) 0,
1086                                            GTK_ACCEL_VISIBLE);
1087             }
1088         }
1089     }
1090
1091     // Initialize dialog
1092     gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(bevel), TRUE);
1093
1094     EMessageBoxReturn ret = modal_dialog_show(window, dialog);
1095     if (ret == eIDOK) {
1096         if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(bevel))) {
1097             *type = PATCHCAP_BEVEL;
1098         } else if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(endcap))) {
1099             *type = PATCHCAP_ENDCAP;
1100         } else if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(ibevel))) {
1101             *type = PATCHCAP_INVERTED_BEVEL;
1102         } else if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(iendcap))) {
1103             *type = PATCHCAP_INVERTED_ENDCAP;
1104         } else if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(cylinder))) {
1105             *type = PATCHCAP_CYLINDER;
1106         }
1107     }
1108
1109     window.destroy();
1110
1111     return ret;
1112 }