]> de.git.xonotic.org Git - xonotic/netradiant.git/blob - radiant/patchdialog.cpp
uncrustify iqmmodel code
[xonotic/netradiant.git] / radiant / patchdialog.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 //
23 // Patch Dialog
24 //
25 // Leonardo Zide (leo@lokigames.com)
26 //
27
28 #include "patchdialog.h"
29
30 #include <gtk/gtk.h>
31
32 #include "itexdef.h"
33
34 #include "debugging/debugging.h"
35
36 #include "gtkutil/idledraw.h"
37 #include "gtkutil/entry.h"
38 #include "gtkutil/button.h"
39 #include "gtkutil/nonmodal.h"
40 #include "dialog.h"
41 #include "gtkdlgs.h"
42 #include "mainframe.h"
43 #include "patchmanip.h"
44 #include "patch.h"
45 #include "commands.h"
46 #include "preferences.h"
47 #include "signal/isignal.h"
48
49
50 #include <gdk/gdkkeysyms.h>
51
52 // the increment we are using for the patch inspector (this is saved in the prefs)
53 struct pi_globals_t {
54     float shift[2];
55     float scale[2];
56     float rotate;
57
58     pi_globals_t()
59     {
60         shift[0] = 8.0f;
61         shift[1] = 8.0f;
62         scale[0] = 0.5f;
63         scale[1] = 0.5f;
64         rotate = 45.0f;
65     }
66 };
67
68 pi_globals_t g_pi_globals;
69
70 class PatchFixedSubdivisions {
71 public:
72     PatchFixedSubdivisions() : m_enabled(false), m_x(0), m_y(0)
73     {
74     }
75
76     PatchFixedSubdivisions(bool enabled, std::size_t x, std::size_t y) : m_enabled(enabled), m_x(x), m_y(y)
77     {
78     }
79
80     bool m_enabled;
81     std::size_t m_x;
82     std::size_t m_y;
83 };
84
85 void Patch_getFixedSubdivisions(const Patch &patch, PatchFixedSubdivisions &subdivisions)
86 {
87     subdivisions.m_enabled = patch.m_patchDef3;
88     subdivisions.m_x = patch.m_subdivisions_x;
89     subdivisions.m_y = patch.m_subdivisions_y;
90 }
91
92 const std::size_t MAX_PATCH_SUBDIVISIONS = 32;
93
94 void Patch_setFixedSubdivisions(Patch &patch, const PatchFixedSubdivisions &subdivisions)
95 {
96     patch.undoSave();
97
98     patch.m_patchDef3 = subdivisions.m_enabled;
99     patch.m_subdivisions_x = subdivisions.m_x;
100     patch.m_subdivisions_y = subdivisions.m_y;
101
102     if (patch.m_subdivisions_x == 0) {
103         patch.m_subdivisions_x = 4;
104     } else if (patch.m_subdivisions_x > MAX_PATCH_SUBDIVISIONS) {
105         patch.m_subdivisions_x = MAX_PATCH_SUBDIVISIONS;
106     }
107     if (patch.m_subdivisions_y == 0) {
108         patch.m_subdivisions_y = 4;
109     } else if (patch.m_subdivisions_y > MAX_PATCH_SUBDIVISIONS) {
110         patch.m_subdivisions_y = MAX_PATCH_SUBDIVISIONS;
111     }
112
113     SceneChangeNotify();
114     Patch_textureChanged();
115     patch.controlPointsChanged();
116 }
117
118 class PatchGetFixedSubdivisions {
119     PatchFixedSubdivisions &m_subdivisions;
120 public:
121     PatchGetFixedSubdivisions(PatchFixedSubdivisions &subdivisions) : m_subdivisions(subdivisions)
122     {
123     }
124
125     void operator()(Patch &patch)
126     {
127         Patch_getFixedSubdivisions(patch, m_subdivisions);
128         SceneChangeNotify();
129     }
130 };
131
132 void Scene_PatchGetFixedSubdivisions(PatchFixedSubdivisions &subdivisions)
133 {
134 #if 1
135     if (GlobalSelectionSystem().countSelected() != 0) {
136         Patch *patch = Node_getPatch(GlobalSelectionSystem().ultimateSelected().path().top());
137         if (patch != 0) {
138             Patch_getFixedSubdivisions(*patch, subdivisions);
139         }
140     }
141 #else
142     Scene_forEachVisibleSelectedPatch( PatchGetFixedSubdivisions( subdivisions ) );
143 #endif
144 }
145
146 void Scene_PatchSetFixedSubdivisions(const PatchFixedSubdivisions &subdivisions)
147 {
148     UndoableCommand command("patchSetFixedSubdivisions");
149     Scene_forEachVisibleSelectedPatch([&](Patch &patch) {
150         Patch_setFixedSubdivisions(patch, subdivisions);
151     });
152 }
153
154
155 class Subdivisions {
156 public:
157     ui::CheckButton m_enabled;
158     ui::Entry m_horizontal;
159     ui::Entry m_vertical;
160
161     Subdivisions() : m_enabled(ui::null), m_horizontal(ui::null), m_vertical(ui::null)
162     {
163     }
164
165     void update()
166     {
167         PatchFixedSubdivisions subdivisions;
168         Scene_PatchGetFixedSubdivisions(subdivisions);
169
170         toggle_button_set_active_no_signal(m_enabled, subdivisions.m_enabled);
171
172         if (subdivisions.m_enabled) {
173             entry_set_int(m_horizontal, static_cast<int>( subdivisions.m_x ));
174             entry_set_int(m_vertical, static_cast<int>( subdivisions.m_y ));
175             gtk_widget_set_sensitive(m_horizontal, TRUE);
176             gtk_widget_set_sensitive(m_vertical, TRUE);
177         } else {
178             m_horizontal.text("");
179             m_vertical.text("");
180             gtk_widget_set_sensitive(m_horizontal, FALSE);
181             gtk_widget_set_sensitive(m_vertical, FALSE);
182         }
183     }
184
185     void cancel()
186     {
187         update();
188     }
189
190     typedef MemberCaller<Subdivisions, void(), &Subdivisions::cancel> CancelCaller;
191
192     void apply()
193     {
194         Scene_PatchSetFixedSubdivisions(
195                 PatchFixedSubdivisions(
196                         gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(m_enabled)) != FALSE,
197                         static_cast<std::size_t>( entry_get_int(m_horizontal)),
198                         static_cast<std::size_t>( entry_get_int(m_vertical))
199                 )
200         );
201     }
202
203     typedef MemberCaller<Subdivisions, void(), &Subdivisions::apply> ApplyCaller;
204
205     static void applyGtk(ui::ToggleButton toggle, Subdivisions *self)
206     {
207         self->apply();
208     }
209 };
210
211 class PatchInspector : public Dialog {
212     ui::Window BuildDialog();
213
214     Subdivisions m_subdivisions;
215     NonModalEntry m_horizontalSubdivisionsEntry;
216     NonModalEntry m_verticalSubdivisionsEntry;
217 public:
218     IdleDraw m_idleDraw;
219     WindowPositionTracker m_position_tracker;
220
221     Patch *m_Patch;
222
223     CopiedString m_strName;
224     float m_fS;
225     float m_fT;
226     float m_fX;
227     float m_fY;
228     float m_fZ;
229 /*  float       m_fHScale;
230    float        m_fHShift;
231    float        m_fRotate;
232    float        m_fVScale;
233    float        m_fVShift; */
234     int m_nCol;
235     int m_nRow;
236     ui::ComboBoxText m_pRowCombo{ui::null};
237     ui::ComboBoxText m_pColCombo{ui::null};
238     std::size_t m_countRows;
239     std::size_t m_countCols;
240
241 // turn on/off processing of the "changed" "value_changed" messages
242 // (need to turn off when we are feeding data in)
243 // NOTE: much more simple than blocking signals
244     bool m_bListenChanged;
245
246     PatchInspector() :
247             m_horizontalSubdivisionsEntry(Subdivisions::ApplyCaller(m_subdivisions),
248                                           Subdivisions::CancelCaller(m_subdivisions)),
249             m_verticalSubdivisionsEntry(Subdivisions::ApplyCaller(m_subdivisions),
250                                         Subdivisions::CancelCaller(m_subdivisions)),
251             m_idleDraw(MemberCaller<PatchInspector, void(), &PatchInspector::GetPatchInfo>(*this))
252     {
253         m_fS = 0.0f;
254         m_fT = 0.0f;
255         m_fX = 0.0f;
256         m_fY = 0.0f;
257         m_fZ = 0.0f;
258         m_nCol = 0;
259         m_nRow = 0;
260         m_countRows = 0;
261         m_countCols = 0;
262         m_Patch = 0;
263         m_bListenChanged = true;
264
265         m_position_tracker.setPosition(c_default_window_pos);
266     }
267
268     bool visible()
269     {
270         return GetWidget().visible();
271     }
272
273 //  void UpdateInfo();
274 //  void SetPatchInfo();
275     void GetPatchInfo();
276
277     void UpdateSpinners(bool bUp, int nID);
278
279 // read the current patch on map and initialize m_fX m_fY accordingly
280     void UpdateRowColInfo();
281
282 // sync the dialog our internal data structures
283 // depending on the flag it will read or write
284 // we use m_nCol m_nRow m_fX m_fY m_fZ m_fS m_fT m_strName
285 // (NOTE: this doesn't actually commit stuff to the map or read from it)
286     void importData();
287
288     void exportData();
289 };
290
291 PatchInspector g_PatchInspector;
292
293 void PatchInspector_constructWindow(ui::Window main_window)
294 {
295     g_PatchInspector.m_parent = main_window;
296     g_PatchInspector.Create();
297 }
298
299 void PatchInspector_destroyWindow()
300 {
301     g_PatchInspector.Destroy();
302 }
303
304 void PatchInspector_queueDraw()
305 {
306     if (g_PatchInspector.visible()) {
307         g_PatchInspector.m_idleDraw.queueDraw();
308     }
309 }
310
311 void DoPatchInspector()
312 {
313     g_PatchInspector.GetPatchInfo();
314     if (!g_PatchInspector.visible()) {
315         g_PatchInspector.ShowDlg();
316     }
317 }
318
319 void PatchInspector_toggleShown()
320 {
321     if (g_PatchInspector.visible()) {
322         g_PatchInspector.m_Patch = 0;
323         g_PatchInspector.HideDlg();
324     } else {
325         DoPatchInspector();
326     }
327 }
328
329
330 // =============================================================================
331 // static functions
332
333 // memorize the current state (that is don't try to undo our do before changing something else)
334 static void OnApply(ui::Widget widget, gpointer data)
335 {
336     g_PatchInspector.exportData();
337     if (g_PatchInspector.m_Patch != 0) {
338         UndoableCommand command("patchSetTexture");
339         g_PatchInspector.m_Patch->undoSave();
340
341         if (!texdef_name_valid(g_PatchInspector.m_strName.c_str())) {
342             globalErrorStream() << "invalid texture name '" << g_PatchInspector.m_strName.c_str() << "'\n";
343             g_PatchInspector.m_strName = texdef_name_default();
344         }
345         g_PatchInspector.m_Patch->SetShader(g_PatchInspector.m_strName.c_str());
346
347         std::size_t r = g_PatchInspector.m_nRow;
348         std::size_t c = g_PatchInspector.m_nCol;
349         if (r < g_PatchInspector.m_Patch->getHeight()
350             && c < g_PatchInspector.m_Patch->getWidth()) {
351             PatchControl &p = g_PatchInspector.m_Patch->ctrlAt(r, c);
352             p.m_vertex[0] = g_PatchInspector.m_fX;
353             p.m_vertex[1] = g_PatchInspector.m_fY;
354             p.m_vertex[2] = g_PatchInspector.m_fZ;
355             p.m_texcoord[0] = g_PatchInspector.m_fS;
356             p.m_texcoord[1] = g_PatchInspector.m_fT;
357             g_PatchInspector.m_Patch->controlPointsChanged();
358         }
359     }
360 }
361
362 static void OnSelchangeComboColRow(ui::Widget widget, gpointer data)
363 {
364     if (!g_PatchInspector.m_bListenChanged) {
365         return;
366     }
367     // retrieve the current m_nRow and m_nCol, other params are not relevant
368     g_PatchInspector.exportData();
369     // read the changed values ourselves
370     g_PatchInspector.UpdateRowColInfo();
371     // now reflect our changes
372     g_PatchInspector.importData();
373 }
374
375 void Scene_PatchTileTexture_Selected(scene::Graph &graph, float s, float t)
376 {
377     Scene_forEachVisibleSelectedPatch([&](Patch &patch) {
378         patch.SetTextureRepeat(s, t);
379     });
380     SceneChangeNotify();
381 }
382
383 static void OnBtnPatchdetails(ui::Widget widget, gpointer data)
384 {
385     Patch_CapTexture();
386 }
387
388 static void OnBtnPatchfit(ui::Widget widget, gpointer data)
389 {
390     Patch_FitTexture();
391 }
392
393 static void OnBtnPatchnatural(ui::Widget widget, gpointer data)
394 {
395     Patch_NaturalTexture();
396 }
397
398 static void OnBtnPatchreset(ui::Widget widget, gpointer data)
399 {
400     Patch_ResetTexture();
401 }
402
403 static void OnBtnPatchFlipX(ui::Widget widget, gpointer data)
404 {
405     Patch_FlipTextureX();
406 }
407
408 static void OnBtnPatchFlipY(ui::Widget widget, gpointer data)
409 {
410     Patch_FlipTextureY();
411 }
412
413 void Scene_PatchRotateTexture_Selected(scene::Graph &graph, float angle)
414 {
415     Scene_forEachVisibleSelectedPatch([&](Patch &patch) {
416         patch.RotateTexture(angle);
417     });
418 }
419
420 float Patch_convertScale(float scale)
421 {
422     if (scale > 0) {
423         return scale;
424     }
425     if (scale < 0) {
426         return -1 / scale;
427     }
428     return 1;
429 }
430
431 void Scene_PatchScaleTexture_Selected(scene::Graph &graph, float s, float t)
432 {
433     s = Patch_convertScale(s);
434     t = Patch_convertScale(t);
435     Scene_forEachVisibleSelectedPatch([&](Patch &patch) {
436         patch.ScaleTexture(s, t);
437     });
438 }
439
440 void Scene_PatchTranslateTexture_Selected(scene::Graph &graph, float s, float t)
441 {
442     Scene_forEachVisibleSelectedPatch([&](Patch &patch) {
443         patch.TranslateTexture(s, t);
444     });
445 }
446
447 static void OnBtnPatchAutoCap(ui::Widget widget, gpointer data)
448 {
449     Patch_AutoCapTexture();
450 }
451
452 static void OnSpinChanged(ui::Adjustment adj, gpointer data)
453 {
454     texdef_t td;
455
456     td.rotate = 0;
457     td.scale[0] = td.scale[1] = 0;
458     td.shift[0] = td.shift[1] = 0;
459
460     if (gtk_adjustment_get_value(adj) == 0) {
461         return;
462     }
463
464     if (adj == g_object_get_data(G_OBJECT(g_PatchInspector.GetWidget()), "hshift_adj")) {
465         g_pi_globals.shift[0] = static_cast<float>( atof(gtk_entry_get_text(GTK_ENTRY(data))));
466
467         if (gtk_adjustment_get_value(adj) > 0) {
468             td.shift[0] = g_pi_globals.shift[0];
469         } else {
470             td.shift[0] = -g_pi_globals.shift[0];
471         }
472     } else if (adj == g_object_get_data(G_OBJECT(g_PatchInspector.GetWidget()), "vshift_adj")) {
473         g_pi_globals.shift[1] = static_cast<float>( atof(gtk_entry_get_text(GTK_ENTRY(data))));
474
475         if (gtk_adjustment_get_value(adj) > 0) {
476             td.shift[1] = g_pi_globals.shift[1];
477         } else {
478             td.shift[1] = -g_pi_globals.shift[1];
479         }
480     } else if (adj == g_object_get_data(G_OBJECT(g_PatchInspector.GetWidget()), "hscale_adj")) {
481         g_pi_globals.scale[0] = static_cast<float>( atof(gtk_entry_get_text(GTK_ENTRY(data))));
482         if (g_pi_globals.scale[0] == 0.0f) {
483             return;
484         }
485         if (gtk_adjustment_get_value(adj) > 0) {
486             td.scale[0] = g_pi_globals.scale[0];
487         } else {
488             td.scale[0] = -g_pi_globals.scale[0];
489         }
490     } else if (adj == g_object_get_data(G_OBJECT(g_PatchInspector.GetWidget()), "vscale_adj")) {
491         g_pi_globals.scale[1] = static_cast<float>( atof(gtk_entry_get_text(GTK_ENTRY(data))));
492         if (g_pi_globals.scale[1] == 0.0f) {
493             return;
494         }
495         if (gtk_adjustment_get_value(adj) > 0) {
496             td.scale[1] = g_pi_globals.scale[1];
497         } else {
498             td.scale[1] = -g_pi_globals.scale[1];
499         }
500     } else if (adj == g_object_get_data(G_OBJECT(g_PatchInspector.GetWidget()), "rotate_adj")) {
501         g_pi_globals.rotate = static_cast<float>( atof(gtk_entry_get_text(GTK_ENTRY(data))));
502
503         if (gtk_adjustment_get_value(adj) > 0) {
504             td.rotate = g_pi_globals.rotate;
505         } else {
506             td.rotate = -g_pi_globals.rotate;
507         }
508     }
509
510     gtk_adjustment_set_value(adj, 0);
511
512     // will scale shift rotate the patch accordingly
513
514
515     if (td.shift[0] || td.shift[1]) {
516         UndoableCommand command("patchTranslateTexture");
517         Scene_PatchTranslateTexture_Selected(GlobalSceneGraph(), td.shift[0], td.shift[1]);
518     } else if (td.scale[0] || td.scale[1]) {
519         UndoableCommand command("patchScaleTexture");
520         Scene_PatchScaleTexture_Selected(GlobalSceneGraph(), td.scale[0], td.scale[1]);
521     } else if (td.rotate) {
522         UndoableCommand command("patchRotateTexture");
523         Scene_PatchRotateTexture_Selected(GlobalSceneGraph(), td.rotate);
524     }
525
526     // update the point-by-point view
527     OnSelchangeComboColRow(ui::root, 0);
528 }
529
530 static gint OnDialogKey(ui::Widget widget, GdkEventKey *event, gpointer data)
531 {
532     if (event->keyval == GDK_KEY_Return) {
533         OnApply(ui::root, 0);
534         return TRUE;
535     } else if (event->keyval == GDK_KEY_Escape) {
536         g_PatchInspector.GetPatchInfo();
537         return TRUE;
538     }
539     return FALSE;
540 }
541
542 // =============================================================================
543 // PatchInspector class
544
545 ui::Window PatchInspector::BuildDialog()
546 {
547     ui::Window window = ui::Window(create_floating_window("Patch Properties", m_parent));
548
549     m_position_tracker.connect(window);
550
551     global_accel_connect_window(window);
552
553     window_connect_focus_in_clear_focus_widget(window);
554
555
556     {
557         auto vbox = ui::VBox(FALSE, 5);
558         gtk_container_set_border_width(GTK_CONTAINER(vbox), 5);
559         vbox.show();
560         window.add(vbox);
561         {
562             auto hbox = ui::HBox(FALSE, 5);
563             hbox.show();
564             vbox.pack_start(hbox, TRUE, TRUE, 0);
565             {
566                 auto vbox2 = ui::VBox(FALSE, 0);
567                 gtk_container_set_border_width(GTK_CONTAINER(vbox2), 0);
568                 vbox2.show();
569                 hbox.pack_start(vbox2, TRUE, TRUE, 0);
570                 {
571                     auto frame = ui::Frame("Details");
572                     frame.show();
573                     vbox2.pack_start(frame, TRUE, TRUE, 0);
574                     {
575                         auto vbox3 = ui::VBox(FALSE, 5);
576                         gtk_container_set_border_width(GTK_CONTAINER(vbox3), 5);
577                         vbox3.show();
578                         frame.add(vbox3);
579                         {
580                             auto table = ui::Table(2, 2, FALSE);
581                             table.show();
582                             vbox3.pack_start(table, TRUE, TRUE, 0);
583                             gtk_table_set_row_spacings(table, 5);
584                             gtk_table_set_col_spacings(table, 5);
585                             {
586                                 auto label = ui::Label("Row:");
587                                 label.show();
588                                 table.attach(label, {0, 1, 0, 1},
589                                              {(GtkAttachOptions) (GTK_EXPAND | GTK_FILL), (GtkAttachOptions) (0)},
590                                              {0, 0});
591                             }
592                             {
593                                 auto label = ui::Label("Column:");
594                                 label.show();
595                                 table.attach(label, {1, 2, 0, 1},
596                                              {(GtkAttachOptions) (GTK_EXPAND | GTK_FILL), (GtkAttachOptions) (0)},
597                                              {0, 0});
598                             }
599                             {
600                                 auto combo = ui::ComboBoxText(ui::New);
601                                 combo.connect("changed", G_CALLBACK(OnSelchangeComboColRow), this);
602                                 AddDialogData(combo, m_nRow);
603
604                                 combo.show();
605                                 table.attach(combo, {0, 1, 1, 2},
606                                              {(GtkAttachOptions) (GTK_EXPAND | GTK_FILL), (GtkAttachOptions) (0)},
607                                              {0, 0});
608                                 combo.dimensions(60, -1);
609                                 m_pRowCombo = combo;
610                             }
611
612                             {
613                                 auto combo = ui::ComboBoxText(ui::New);
614                                 combo.connect("changed", G_CALLBACK(OnSelchangeComboColRow), this);
615                                 AddDialogData(combo, m_nCol);
616
617                                 combo.show();
618                                 table.attach(combo, {1, 2, 1, 2},
619                                              {(GtkAttachOptions) (GTK_EXPAND | GTK_FILL), (GtkAttachOptions) (0)},
620                                              {0, 0});
621                                 combo.dimensions(60, -1);
622                                 m_pColCombo = combo;
623                             }
624                         }
625                         auto table = ui::Table(5, 2, FALSE);
626                         table.show();
627                         vbox3.pack_start(table, TRUE, TRUE, 0);
628                         gtk_table_set_row_spacings(table, 5);
629                         gtk_table_set_col_spacings(table, 5);
630                         {
631                             auto label = ui::Label("X:");
632                             label.show();
633                             table.attach(label, {0, 1, 0, 1}, {GTK_EXPAND | GTK_FILL, 0});
634                         }
635                         {
636                             auto label = ui::Label("Y:");
637                             label.show();
638                             table.attach(label, {0, 1, 1, 2}, {GTK_EXPAND | GTK_FILL, 0});
639                         }
640                         {
641                             auto label = ui::Label("Z:");
642                             label.show();
643                             table.attach(label, {0, 1, 2, 3}, {GTK_EXPAND | GTK_FILL, 0});
644                         }
645                         {
646                             auto label = ui::Label("S:");
647                             label.show();
648                             table.attach(label, {0, 1, 3, 4}, {GTK_EXPAND | GTK_FILL, 0});
649                         }
650                         {
651                             auto label = ui::Label("T:");
652                             label.show();
653                             table.attach(label, {0, 1, 4, 5}, {GTK_EXPAND | GTK_FILL, 0});
654                         }
655                         {
656                             auto entry = ui::Entry(ui::New);
657                             entry.show();
658                             table.attach(entry, {1, 2, 0, 1}, {GTK_EXPAND | GTK_FILL, 0});
659                             AddDialogData(entry, m_fX);
660
661                             entry.connect("key_press_event", G_CALLBACK(OnDialogKey), 0);
662                         }
663                         {
664                             auto entry = ui::Entry(ui::New);
665                             entry.show();
666                             table.attach(entry, {1, 2, 1, 2}, {GTK_EXPAND | GTK_FILL, 0});
667                             AddDialogData(entry, m_fY);
668
669                             entry.connect("key_press_event", G_CALLBACK(OnDialogKey), 0);
670                         }
671                         {
672                             auto entry = ui::Entry(ui::New);
673                             entry.show();
674                             table.attach(entry, {1, 2, 2, 3}, {GTK_EXPAND | GTK_FILL, 0});
675                             AddDialogData(entry, m_fZ);
676
677                             entry.connect("key_press_event", G_CALLBACK(OnDialogKey), 0);
678                         }
679                         {
680                             auto entry = ui::Entry(ui::New);
681                             entry.show();
682                             table.attach(entry, {1, 2, 3, 4}, {GTK_EXPAND | GTK_FILL, 0});
683                             AddDialogData(entry, m_fS);
684
685                             entry.connect("key_press_event", G_CALLBACK(OnDialogKey), 0);
686                         }
687                         {
688                             auto entry = ui::Entry(ui::New);
689                             entry.show();
690                             table.attach(entry, {1, 2, 4, 5}, {GTK_EXPAND | GTK_FILL, 0});
691                             AddDialogData(entry, m_fT);
692
693                             entry.connect("key_press_event", G_CALLBACK(OnDialogKey), 0);
694                         }
695                     }
696                 }
697                 if (g_pGameDescription->mGameType == "doom3") {
698                     auto frame = ui::Frame("Tesselation");
699                     frame.show();
700                     vbox2.pack_start(frame, TRUE, TRUE, 0);
701                     {
702                         auto vbox3 = ui::VBox(FALSE, 5);
703                         gtk_container_set_border_width(GTK_CONTAINER(vbox3), 5);
704                         vbox3.show();
705                         frame.add(vbox3);
706                         {
707                             auto table = ui::Table(3, 2, FALSE);
708                             table.show();
709                             vbox3.pack_start(table, TRUE, TRUE, 0);
710                             gtk_table_set_row_spacings(table, 5);
711                             gtk_table_set_col_spacings(table, 5);
712                             {
713                                 auto label = ui::Label("Fixed");
714                                 label.show();
715                                 table.attach(label, {0, 1, 0, 1}, {GTK_EXPAND | GTK_FILL, 0});
716                             }
717                             {
718                                 auto check = ui::CheckButton::from(gtk_check_button_new());
719                                 check.show();
720                                 table.attach(check, {1, 2, 0, 1}, {GTK_EXPAND | GTK_FILL, 0});
721                                 m_subdivisions.m_enabled = check;
722                                 guint handler_id = check.connect("toggled", G_CALLBACK(&Subdivisions::applyGtk),
723                                                                  &m_subdivisions);
724                                 g_object_set_data(G_OBJECT(check), "handler", gint_to_pointer(handler_id));
725                             }
726                             {
727                                 auto label = ui::Label("Horizontal");
728                                 label.show();
729                                 table.attach(label, {0, 1, 1, 2}, {GTK_EXPAND | GTK_FILL, 0});
730                             }
731                             {
732                                 auto entry = ui::Entry(ui::New);
733                                 entry.show();
734                                 table.attach(entry, {1, 2, 1, 2}, {GTK_EXPAND | GTK_FILL, 0});
735                                 m_subdivisions.m_horizontal = entry;
736                                 m_horizontalSubdivisionsEntry.connect(entry);
737                             }
738                             {
739                                 auto label = ui::Label("Vertical");
740                                 label.show();
741                                 table.attach(label, {0, 1, 2, 3}, {GTK_EXPAND | GTK_FILL, 0});
742                             }
743                             {
744                                 auto entry = ui::Entry(ui::New);
745                                 entry.show();
746                                 table.attach(entry, {1, 2, 2, 3}, {GTK_EXPAND | GTK_FILL, 0});
747                                 m_subdivisions.m_vertical = entry;
748                                 m_verticalSubdivisionsEntry.connect(entry);
749                             }
750                         }
751                     }
752                 }
753             }
754             {
755                 auto frame = ui::Frame("Texturing");
756                 frame.show();
757                 hbox.pack_start(frame, TRUE, TRUE, 0);
758                 {
759                     auto vbox2 = ui::VBox(FALSE, 5);
760                     vbox2.show();
761                     frame.add(vbox2);
762                     gtk_container_set_border_width(GTK_CONTAINER(vbox2), 5);
763                     {
764                         auto label = ui::Label("Name:");
765                         label.show();
766                         vbox2.pack_start(label, TRUE, TRUE, 0);
767                         gtk_label_set_justify(label, GTK_JUSTIFY_LEFT);
768                         gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
769                     }
770                     {
771                         auto entry = ui::Entry(ui::New);
772                         //  gtk_editable_set_editable (GTK_ENTRY (entry), false);
773                         entry.show();
774                         vbox2.pack_start(entry, TRUE, TRUE, 0);
775                         AddDialogData(entry, m_strName);
776
777                         entry.connect("key_press_event", G_CALLBACK(OnDialogKey), 0);
778                     }
779                     {
780                         auto table = ui::Table(5, 4, FALSE);
781                         table.show();
782                         vbox2.pack_start(table, TRUE, TRUE, 0);
783                         gtk_table_set_row_spacings(table, 5);
784                         gtk_table_set_col_spacings(table, 5);
785                         {
786                             auto label = ui::Label("Horizontal Shift Step");
787                             label.show();
788                             table.attach(label, {2, 4, 0, 1}, {GTK_FILL | GTK_EXPAND, 0});
789                             gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
790                         }
791                         {
792                             auto label = ui::Label("Vertical Shift Step");
793                             label.show();
794                             table.attach(label, {2, 4, 1, 2}, {GTK_FILL | GTK_EXPAND, 0});
795                             gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
796                         }
797                         {
798                             auto label = ui::Label("Horizontal Stretch Step");
799                             label.show();
800                             table.attach(label, {2, 3, 2, 3}, {GTK_FILL | GTK_EXPAND, 0});
801                             gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
802                         }
803                         {
804                             auto button = ui::Button("Flip");
805                             button.show();
806                             table.attach(button, {3, 4, 2, 3}, {GTK_FILL, 0});
807                             button.connect("clicked", G_CALLBACK(OnBtnPatchFlipX), 0);
808                             button.dimensions(60, -1);
809                         }
810                         {
811                             auto label = ui::Label("Vertical Stretch Step");
812                             label.show();
813                             table.attach(label, {2, 3, 3, 4}, {GTK_FILL | GTK_EXPAND, 0});
814                             gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
815                         }
816                         {
817                             auto button = ui::Button("Flip");
818                             button.show();
819                             table.attach(button, {3, 4, 3, 4}, {GTK_FILL, 0});
820                             button.connect("clicked", G_CALLBACK(OnBtnPatchFlipY), 0);
821                             button.dimensions(60, -1);
822                         }
823                         {
824                             auto label = ui::Label("Rotate Step");
825                             label.show();
826                             table.attach(label, {2, 4, 4, 5}, {GTK_FILL | GTK_EXPAND, 0});
827                             gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
828                         }
829                         {
830                             auto entry = ui::Entry(ui::New);
831                             entry.show();
832                             table.attach(entry, {0, 1, 0, 1}, {GTK_FILL, 0});
833                             entry.dimensions(50, -1);
834                             g_object_set_data(G_OBJECT(window), "hshift_entry", (void *) entry);
835                             // we fill in this data, if no patch is selected the widgets are unmodified when the inspector is raised
836                             // so we need to have at least one initialisation somewhere
837                             entry_set_float(entry, g_pi_globals.shift[0]);
838
839                             auto adj = ui::Adjustment(0, -8192, 8192, 1, 1, 0);
840                             adj.connect("value_changed", G_CALLBACK(OnSpinChanged), (gpointer) entry);
841                             g_object_set_data(G_OBJECT(window), "hshift_adj", (gpointer) adj);
842
843                             auto spin = ui::SpinButton(adj, 1, 0);
844                             spin.show();
845                             table.attach(spin, {1, 2, 0, 1}, {0, 0});
846                             spin.dimensions(10, -1);
847                             gtk_widget_set_can_focus(spin, false);
848                         }
849                         {
850                             auto entry = ui::Entry(ui::New);
851                             entry.show();
852                             table.attach(entry, {0, 1, 1, 2}, {GTK_FILL, 0});
853                             entry.dimensions(50, -1);
854                             entry_set_float(entry, g_pi_globals.shift[1]);
855
856                             auto adj = ui::Adjustment(0, -8192, 8192, 1, 1, 0);
857                             adj.connect("value_changed", G_CALLBACK(OnSpinChanged), entry);
858                             g_object_set_data(G_OBJECT(window), "vshift_adj", (gpointer) adj);
859
860                             auto spin = ui::SpinButton(adj, 1, 0);
861                             spin.show();
862                             table.attach(spin, {1, 2, 1, 2}, {0, 0});
863                             spin.dimensions(10, -1);
864                             gtk_widget_set_can_focus(spin, false);
865                         }
866                         {
867                             auto entry = ui::Entry(ui::New);
868                             entry.show();
869                             table.attach(entry, {0, 1, 2, 3}, {GTK_FILL, 0});
870                             entry.dimensions(50, -1);
871                             entry_set_float(entry, g_pi_globals.scale[0]);
872
873                             auto adj = ui::Adjustment(0, -1000, 1000, 1, 1, 0);
874                             adj.connect("value_changed", G_CALLBACK(OnSpinChanged), entry);
875                             g_object_set_data(G_OBJECT(window), "hscale_adj", (gpointer) adj);
876
877                             auto spin = ui::SpinButton(adj, 1, 0);
878                             spin.show();
879                             table.attach(spin, {1, 2, 2, 3}, {0, 0});
880                             spin.dimensions(10, -1);
881                             gtk_widget_set_can_focus(spin, false);
882                         }
883                         {
884                             auto entry = ui::Entry(ui::New);
885                             entry.show();
886                             table.attach(entry, {0, 1, 3, 4}, {GTK_FILL, 0});
887                             entry.dimensions(50, -1);
888                             entry_set_float(entry, g_pi_globals.scale[1]);
889
890                             auto adj = ui::Adjustment(0, -1000, 1000, 1, 1, 0);
891                             adj.connect("value_changed", G_CALLBACK(OnSpinChanged), entry);
892                             g_object_set_data(G_OBJECT(window), "vscale_adj", (gpointer) adj);
893
894                             auto spin = ui::SpinButton(adj, 1, 0);
895                             spin.show();
896                             table.attach(spin, {1, 2, 3, 4}, {0, 0});
897                             spin.dimensions(10, -1);
898                             gtk_widget_set_can_focus(spin, false);
899                         }
900                         {
901                             auto entry = ui::Entry(ui::New);
902                             entry.show();
903                             table.attach(entry, {0, 1, 4, 5}, {GTK_FILL, 0});
904                             entry.dimensions(50, -1);
905                             entry_set_float(entry, g_pi_globals.rotate);
906
907                             auto adj = ui::Adjustment(0, -1000, 1000, 1, 1,
908                                                       0); // NOTE: Arnout - this really should be 360 but can't change it anymore as it could break existing maps
909                             adj.connect("value_changed", G_CALLBACK(OnSpinChanged), entry);
910                             g_object_set_data(G_OBJECT(window), "rotate_adj", (gpointer) adj);
911
912                             auto spin = ui::SpinButton(adj, 1, 0);
913                             spin.show();
914                             table.attach(spin, {1, 2, 4, 5}, {0, 0});
915                             spin.dimensions(10, -1);
916                             gtk_widget_set_can_focus(spin, false);
917                         }
918                     }
919                     auto hbox2 = ui::HBox(TRUE, 5);
920                     hbox2.show();
921                     vbox2.pack_start(hbox2, TRUE, FALSE, 0);
922                     {
923                         auto button = ui::Button("Auto Cap");
924                         button.show();
925                         hbox2.pack_end(button, TRUE, FALSE, 0);
926                         button.connect("clicked", G_CALLBACK(OnBtnPatchAutoCap), 0);
927                         button.dimensions(60, -1);
928                     }
929                     {
930                         auto button = ui::Button("CAP");
931                         button.show();
932                         hbox2.pack_end(button, TRUE, FALSE, 0);
933                         button.connect("clicked", G_CALLBACK(OnBtnPatchdetails), 0);
934                         button.dimensions(60, -1);
935                     }
936                     {
937                         auto button = ui::Button("Set...");
938                         button.show();
939                         hbox2.pack_end(button, TRUE, FALSE, 0);
940                         button.connect("clicked", G_CALLBACK(OnBtnPatchreset), 0);
941                         button.dimensions(60, -1);
942                     }
943                     {
944                         auto button = ui::Button("Natural");
945                         button.show();
946                         hbox2.pack_end(button, TRUE, FALSE, 0);
947                         button.connect("clicked", G_CALLBACK(OnBtnPatchnatural), 0);
948                         button.dimensions(60, -1);
949                     }
950                     {
951                         auto button = ui::Button("Fit");
952                         button.show();
953                         hbox2.pack_end(button, TRUE, FALSE, 0);
954                         button.connect("clicked", G_CALLBACK(OnBtnPatchfit), 0);
955                         button.dimensions(60, -1);
956                     }
957                 }
958             }
959         }
960     }
961
962     return window;
963 }
964
965 // sync the dialog our internal data structures
966 void PatchInspector::exportData()
967 {
968     m_bListenChanged = false;
969     Dialog::exportData();
970     m_bListenChanged = true;
971 }
972
973 void PatchInspector::importData()
974 {
975     m_bListenChanged = false;
976     Dialog::importData();
977     m_bListenChanged = true;
978 }
979
980 // read the map and feed in the stuff to the dialog box
981 void PatchInspector::GetPatchInfo()
982 {
983     if (g_pGameDescription->mGameType == "doom3") {
984         m_subdivisions.update();
985     }
986
987     if (GlobalSelectionSystem().countSelected() == 0) {
988         m_Patch = 0;
989     } else {
990         m_Patch = Node_getPatch(GlobalSelectionSystem().ultimateSelected().path().top());
991     }
992
993     if (m_Patch != 0) {
994         m_strName = m_Patch->GetShader();
995
996         // fill in the numbers for Row / Col selection
997         m_bListenChanged = false;
998
999         {
1000             gtk_combo_box_set_active(m_pRowCombo, 0);
1001
1002             for (std::size_t i = 0; i < m_countRows; ++i) {
1003                 gtk_combo_box_text_remove(m_pRowCombo, gint(m_countRows - i - 1));
1004             }
1005
1006             m_countRows = m_Patch->getHeight();
1007             for (std::size_t i = 0; i < m_countRows; ++i) {
1008                 char buffer[16];
1009                 sprintf(buffer, "%u", Unsigned(i));
1010                 gtk_combo_box_text_append_text(m_pRowCombo, buffer);
1011             }
1012
1013             gtk_combo_box_set_active(m_pRowCombo, 0);
1014         }
1015
1016         {
1017             gtk_combo_box_set_active(m_pColCombo, 0);
1018
1019             for (std::size_t i = 0; i < m_countCols; ++i) {
1020                 gtk_combo_box_text_remove(m_pColCombo, gint(m_countCols - i - 1));
1021             }
1022
1023             m_countCols = m_Patch->getWidth();
1024             for (std::size_t i = 0; i < m_countCols; ++i) {
1025                 char buffer[16];
1026                 sprintf(buffer, "%u", Unsigned(i));
1027                 gtk_combo_box_text_append_text(m_pColCombo, buffer);
1028             }
1029
1030             gtk_combo_box_set_active(m_pColCombo, 0);
1031         }
1032
1033         m_bListenChanged = true;
1034
1035     } else {
1036         //globalOutputStream() << "WARNING: no patch\n";
1037     }
1038     // fill in our internal structs
1039     m_nRow = 0;
1040     m_nCol = 0;
1041     UpdateRowColInfo();
1042     // now update the dialog box
1043     importData();
1044 }
1045
1046 // read the current patch on map and initialize m_fX m_fY accordingly
1047 // NOTE: don't call UpdateData in there, it's not meant for
1048 void PatchInspector::UpdateRowColInfo()
1049 {
1050     m_fX = m_fY = m_fZ = m_fS = m_fT = 0.0;
1051
1052     if (m_Patch != 0) {
1053         // we rely on whatever active row/column has been set before we get called
1054         std::size_t r = m_nRow;
1055         std::size_t c = m_nCol;
1056         if (r < m_Patch->getHeight()
1057             && c < m_Patch->getWidth()) {
1058             const PatchControl &p = m_Patch->ctrlAt(r, c);
1059             m_fX = p.m_vertex[0];
1060             m_fY = p.m_vertex[1];
1061             m_fZ = p.m_vertex[2];
1062             m_fS = p.m_texcoord[0];
1063             m_fT = p.m_texcoord[1];
1064         }
1065     }
1066 }
1067
1068
1069 void PatchInspector_SelectionChanged(const Selectable &selectable)
1070 {
1071     PatchInspector_queueDraw();
1072 }
1073
1074
1075 #include "preferencesystem.h"
1076
1077
1078 void PatchInspector_Construct()
1079 {
1080     GlobalCommands_insert("PatchInspector", makeCallbackF(PatchInspector_toggleShown),
1081                           Accelerator('S', (GdkModifierType) GDK_SHIFT_MASK));
1082
1083     GlobalPreferenceSystem().registerPreference("PatchWnd", make_property<WindowPositionTracker_String>(
1084             g_PatchInspector.m_position_tracker));
1085     GlobalPreferenceSystem().registerPreference("SI_PatchTexdef_Scale1", make_property_string(g_pi_globals.scale[0]));
1086     GlobalPreferenceSystem().registerPreference("SI_PatchTexdef_Scale2", make_property_string(g_pi_globals.scale[1]));
1087     GlobalPreferenceSystem().registerPreference("SI_PatchTexdef_Shift1", make_property_string(g_pi_globals.shift[0]));
1088     GlobalPreferenceSystem().registerPreference("SI_PatchTexdef_Shift2", make_property_string(g_pi_globals.shift[1]));
1089     GlobalPreferenceSystem().registerPreference("SI_PatchTexdef_Rotate", make_property_string(g_pi_globals.rotate));
1090
1091     typedef FreeCaller<void(const Selectable &), PatchInspector_SelectionChanged> PatchInspectorSelectionChangedCaller;
1092     GlobalSelectionSystem().addSelectionChangeCallback(PatchInspectorSelectionChangedCaller());
1093     typedef FreeCaller<void(), PatchInspector_queueDraw> PatchInspectorQueueDrawCaller;
1094     Patch_addTextureChangedCallback(PatchInspectorQueueDrawCaller());
1095 }
1096
1097 void PatchInspector_Destroy()
1098 {
1099 }