2 Copyright (C) 1999-2006 Id Software, Inc. and contributors.
3 For a list of contributors, see the accompanying CONTRIBUTORS file.
5 This file is part of GtkRadiant.
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.
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.
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
25 // Leonardo Zide (leo@lokigames.com)
28 #include "patchdialog.h"
34 #include "debugging/debugging.h"
36 #include "gtkutil/idledraw.h"
37 #include "gtkutil/entry.h"
38 #include "gtkutil/button.h"
39 #include "gtkutil/nonmodal.h"
42 #include "mainframe.h"
43 #include "patchmanip.h"
46 #include "preferences.h"
47 #include "signal/isignal.h"
50 #include <gdk/gdkkeysyms.h>
52 // the increment we are using for the patch inspector (this is saved in the prefs)
68 pi_globals_t g_pi_globals;
70 class PatchFixedSubdivisions {
72 PatchFixedSubdivisions() : m_enabled(false), m_x(0), m_y(0)
76 PatchFixedSubdivisions(bool enabled, std::size_t x, std::size_t y) : m_enabled(enabled), m_x(x), m_y(y)
85 void Patch_getFixedSubdivisions(const Patch &patch, PatchFixedSubdivisions &subdivisions)
87 subdivisions.m_enabled = patch.m_patchDef3;
88 subdivisions.m_x = patch.m_subdivisions_x;
89 subdivisions.m_y = patch.m_subdivisions_y;
92 const std::size_t MAX_PATCH_SUBDIVISIONS = 32;
94 void Patch_setFixedSubdivisions(Patch &patch, const PatchFixedSubdivisions &subdivisions)
98 patch.m_patchDef3 = subdivisions.m_enabled;
99 patch.m_subdivisions_x = subdivisions.m_x;
100 patch.m_subdivisions_y = subdivisions.m_y;
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;
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;
114 Patch_textureChanged();
115 patch.controlPointsChanged();
118 class PatchGetFixedSubdivisions {
119 PatchFixedSubdivisions &m_subdivisions;
121 PatchGetFixedSubdivisions(PatchFixedSubdivisions &subdivisions) : m_subdivisions(subdivisions)
125 void operator()(Patch &patch)
127 Patch_getFixedSubdivisions(patch, m_subdivisions);
132 void Scene_PatchGetFixedSubdivisions(PatchFixedSubdivisions &subdivisions)
135 if (GlobalSelectionSystem().countSelected() != 0) {
136 Patch *patch = Node_getPatch(GlobalSelectionSystem().ultimateSelected().path().top());
138 Patch_getFixedSubdivisions(*patch, subdivisions);
142 Scene_forEachVisibleSelectedPatch( PatchGetFixedSubdivisions( subdivisions ) );
146 void Scene_PatchSetFixedSubdivisions(const PatchFixedSubdivisions &subdivisions)
148 UndoableCommand command("patchSetFixedSubdivisions");
149 Scene_forEachVisibleSelectedPatch([&](Patch &patch) {
150 Patch_setFixedSubdivisions(patch, subdivisions);
157 ui::CheckButton m_enabled;
158 ui::Entry m_horizontal;
159 ui::Entry m_vertical;
161 Subdivisions() : m_enabled(ui::null), m_horizontal(ui::null), m_vertical(ui::null)
167 PatchFixedSubdivisions subdivisions;
168 Scene_PatchGetFixedSubdivisions(subdivisions);
170 toggle_button_set_active_no_signal(m_enabled, subdivisions.m_enabled);
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);
178 m_horizontal.text("");
180 gtk_widget_set_sensitive(m_horizontal, FALSE);
181 gtk_widget_set_sensitive(m_vertical, FALSE);
190 typedef MemberCaller<Subdivisions, void(), &Subdivisions::cancel> CancelCaller;
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))
203 typedef MemberCaller<Subdivisions, void(), &Subdivisions::apply> ApplyCaller;
205 static void applyGtk(ui::ToggleButton toggle, Subdivisions *self)
211 class PatchInspector : public Dialog {
212 ui::Window BuildDialog();
214 Subdivisions m_subdivisions;
215 NonModalEntry m_horizontalSubdivisionsEntry;
216 NonModalEntry m_verticalSubdivisionsEntry;
219 WindowPositionTracker m_position_tracker;
223 CopiedString m_strName;
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;
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;
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))
263 m_bListenChanged = true;
265 m_position_tracker.setPosition(c_default_window_pos);
270 return GetWidget().visible();
273 // void UpdateInfo();
274 // void SetPatchInfo();
277 void UpdateSpinners(bool bUp, int nID);
279 // read the current patch on map and initialize m_fX m_fY accordingly
280 void UpdateRowColInfo();
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)
291 PatchInspector g_PatchInspector;
293 void PatchInspector_constructWindow(ui::Window main_window)
295 g_PatchInspector.m_parent = main_window;
296 g_PatchInspector.Create();
299 void PatchInspector_destroyWindow()
301 g_PatchInspector.Destroy();
304 void PatchInspector_queueDraw()
306 if (g_PatchInspector.visible()) {
307 g_PatchInspector.m_idleDraw.queueDraw();
311 void DoPatchInspector()
313 g_PatchInspector.GetPatchInfo();
314 if (!g_PatchInspector.visible()) {
315 g_PatchInspector.ShowDlg();
319 void PatchInspector_toggleShown()
321 if (g_PatchInspector.visible()) {
322 g_PatchInspector.m_Patch = 0;
323 g_PatchInspector.HideDlg();
330 // =============================================================================
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)
336 g_PatchInspector.exportData();
337 if (g_PatchInspector.m_Patch != 0) {
338 UndoableCommand command("patchSetTexture");
339 g_PatchInspector.m_Patch->undoSave();
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();
345 g_PatchInspector.m_Patch->SetShader(g_PatchInspector.m_strName.c_str());
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();
362 static void OnSelchangeComboColRow(ui::Widget widget, gpointer data)
364 if (!g_PatchInspector.m_bListenChanged) {
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();
375 void Scene_PatchTileTexture_Selected(scene::Graph &graph, float s, float t)
377 Scene_forEachVisibleSelectedPatch([&](Patch &patch) {
378 patch.SetTextureRepeat(s, t);
383 static void OnBtnPatchdetails(ui::Widget widget, gpointer data)
388 static void OnBtnPatchfit(ui::Widget widget, gpointer data)
393 static void OnBtnPatchnatural(ui::Widget widget, gpointer data)
395 Patch_NaturalTexture();
398 static void OnBtnPatchreset(ui::Widget widget, gpointer data)
400 Patch_ResetTexture();
403 static void OnBtnPatchFlipX(ui::Widget widget, gpointer data)
405 Patch_FlipTextureX();
408 static void OnBtnPatchFlipY(ui::Widget widget, gpointer data)
410 Patch_FlipTextureY();
413 void Scene_PatchRotateTexture_Selected(scene::Graph &graph, float angle)
415 Scene_forEachVisibleSelectedPatch([&](Patch &patch) {
416 patch.RotateTexture(angle);
420 float Patch_convertScale(float scale)
431 void Scene_PatchScaleTexture_Selected(scene::Graph &graph, float s, float t)
433 s = Patch_convertScale(s);
434 t = Patch_convertScale(t);
435 Scene_forEachVisibleSelectedPatch([&](Patch &patch) {
436 patch.ScaleTexture(s, t);
440 void Scene_PatchTranslateTexture_Selected(scene::Graph &graph, float s, float t)
442 Scene_forEachVisibleSelectedPatch([&](Patch &patch) {
443 patch.TranslateTexture(s, t);
447 static void OnBtnPatchAutoCap(ui::Widget widget, gpointer data)
449 Patch_AutoCapTexture();
452 static void OnSpinChanged(ui::Adjustment adj, gpointer data)
457 td.scale[0] = td.scale[1] = 0;
458 td.shift[0] = td.shift[1] = 0;
460 if (gtk_adjustment_get_value(adj) == 0) {
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))));
467 if (gtk_adjustment_get_value(adj) > 0) {
468 td.shift[0] = g_pi_globals.shift[0];
470 td.shift[0] = -g_pi_globals.shift[0];
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))));
475 if (gtk_adjustment_get_value(adj) > 0) {
476 td.shift[1] = g_pi_globals.shift[1];
478 td.shift[1] = -g_pi_globals.shift[1];
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) {
485 if (gtk_adjustment_get_value(adj) > 0) {
486 td.scale[0] = g_pi_globals.scale[0];
488 td.scale[0] = -g_pi_globals.scale[0];
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) {
495 if (gtk_adjustment_get_value(adj) > 0) {
496 td.scale[1] = g_pi_globals.scale[1];
498 td.scale[1] = -g_pi_globals.scale[1];
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))));
503 if (gtk_adjustment_get_value(adj) > 0) {
504 td.rotate = g_pi_globals.rotate;
506 td.rotate = -g_pi_globals.rotate;
510 gtk_adjustment_set_value(adj, 0);
512 // will scale shift rotate the patch accordingly
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);
526 // update the point-by-point view
527 OnSelchangeComboColRow(ui::root, 0);
530 static gint OnDialogKey(ui::Widget widget, GdkEventKey *event, gpointer data)
532 if (event->keyval == GDK_KEY_Return) {
533 OnApply(ui::root, 0);
535 } else if (event->keyval == GDK_KEY_Escape) {
536 g_PatchInspector.GetPatchInfo();
542 // =============================================================================
543 // PatchInspector class
545 ui::Window PatchInspector::BuildDialog()
547 ui::Window window = ui::Window(create_floating_window("Patch Properties", m_parent));
549 m_position_tracker.connect(window);
551 global_accel_connect_window(window);
553 window_connect_focus_in_clear_focus_widget(window);
557 auto vbox = ui::VBox(FALSE, 5);
558 gtk_container_set_border_width(GTK_CONTAINER(vbox), 5);
562 auto hbox = ui::HBox(FALSE, 5);
564 vbox.pack_start(hbox, TRUE, TRUE, 0);
566 auto vbox2 = ui::VBox(FALSE, 0);
567 gtk_container_set_border_width(GTK_CONTAINER(vbox2), 0);
569 hbox.pack_start(vbox2, TRUE, TRUE, 0);
571 auto frame = ui::Frame("Details");
573 vbox2.pack_start(frame, TRUE, TRUE, 0);
575 auto vbox3 = ui::VBox(FALSE, 5);
576 gtk_container_set_border_width(GTK_CONTAINER(vbox3), 5);
580 auto table = ui::Table(2, 2, FALSE);
582 vbox3.pack_start(table, TRUE, TRUE, 0);
583 gtk_table_set_row_spacings(table, 5);
584 gtk_table_set_col_spacings(table, 5);
586 auto label = ui::Label("Row:");
588 table.attach(label, {0, 1, 0, 1},
589 {(GtkAttachOptions) (GTK_EXPAND | GTK_FILL), (GtkAttachOptions) (0)},
593 auto label = ui::Label("Column:");
595 table.attach(label, {1, 2, 0, 1},
596 {(GtkAttachOptions) (GTK_EXPAND | GTK_FILL), (GtkAttachOptions) (0)},
600 auto combo = ui::ComboBoxText(ui::New);
601 combo.connect("changed", G_CALLBACK(OnSelchangeComboColRow), this);
602 AddDialogData(combo, m_nRow);
605 table.attach(combo, {0, 1, 1, 2},
606 {(GtkAttachOptions) (GTK_EXPAND | GTK_FILL), (GtkAttachOptions) (0)},
608 combo.dimensions(60, -1);
613 auto combo = ui::ComboBoxText(ui::New);
614 combo.connect("changed", G_CALLBACK(OnSelchangeComboColRow), this);
615 AddDialogData(combo, m_nCol);
618 table.attach(combo, {1, 2, 1, 2},
619 {(GtkAttachOptions) (GTK_EXPAND | GTK_FILL), (GtkAttachOptions) (0)},
621 combo.dimensions(60, -1);
625 auto table = ui::Table(5, 2, FALSE);
627 vbox3.pack_start(table, TRUE, TRUE, 0);
628 gtk_table_set_row_spacings(table, 5);
629 gtk_table_set_col_spacings(table, 5);
631 auto label = ui::Label("X:");
633 table.attach(label, {0, 1, 0, 1}, {GTK_EXPAND | GTK_FILL, 0});
636 auto label = ui::Label("Y:");
638 table.attach(label, {0, 1, 1, 2}, {GTK_EXPAND | GTK_FILL, 0});
641 auto label = ui::Label("Z:");
643 table.attach(label, {0, 1, 2, 3}, {GTK_EXPAND | GTK_FILL, 0});
646 auto label = ui::Label("S:");
648 table.attach(label, {0, 1, 3, 4}, {GTK_EXPAND | GTK_FILL, 0});
651 auto label = ui::Label("T:");
653 table.attach(label, {0, 1, 4, 5}, {GTK_EXPAND | GTK_FILL, 0});
656 auto entry = ui::Entry(ui::New);
658 table.attach(entry, {1, 2, 0, 1}, {GTK_EXPAND | GTK_FILL, 0});
659 AddDialogData(entry, m_fX);
661 entry.connect("key_press_event", G_CALLBACK(OnDialogKey), 0);
664 auto entry = ui::Entry(ui::New);
666 table.attach(entry, {1, 2, 1, 2}, {GTK_EXPAND | GTK_FILL, 0});
667 AddDialogData(entry, m_fY);
669 entry.connect("key_press_event", G_CALLBACK(OnDialogKey), 0);
672 auto entry = ui::Entry(ui::New);
674 table.attach(entry, {1, 2, 2, 3}, {GTK_EXPAND | GTK_FILL, 0});
675 AddDialogData(entry, m_fZ);
677 entry.connect("key_press_event", G_CALLBACK(OnDialogKey), 0);
680 auto entry = ui::Entry(ui::New);
682 table.attach(entry, {1, 2, 3, 4}, {GTK_EXPAND | GTK_FILL, 0});
683 AddDialogData(entry, m_fS);
685 entry.connect("key_press_event", G_CALLBACK(OnDialogKey), 0);
688 auto entry = ui::Entry(ui::New);
690 table.attach(entry, {1, 2, 4, 5}, {GTK_EXPAND | GTK_FILL, 0});
691 AddDialogData(entry, m_fT);
693 entry.connect("key_press_event", G_CALLBACK(OnDialogKey), 0);
697 if (g_pGameDescription->mGameType == "doom3") {
698 auto frame = ui::Frame("Tesselation");
700 vbox2.pack_start(frame, TRUE, TRUE, 0);
702 auto vbox3 = ui::VBox(FALSE, 5);
703 gtk_container_set_border_width(GTK_CONTAINER(vbox3), 5);
707 auto table = ui::Table(3, 2, FALSE);
709 vbox3.pack_start(table, TRUE, TRUE, 0);
710 gtk_table_set_row_spacings(table, 5);
711 gtk_table_set_col_spacings(table, 5);
713 auto label = ui::Label("Fixed");
715 table.attach(label, {0, 1, 0, 1}, {GTK_EXPAND | GTK_FILL, 0});
718 auto check = ui::CheckButton::from(gtk_check_button_new());
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),
724 g_object_set_data(G_OBJECT(check), "handler", gint_to_pointer(handler_id));
727 auto label = ui::Label("Horizontal");
729 table.attach(label, {0, 1, 1, 2}, {GTK_EXPAND | GTK_FILL, 0});
732 auto entry = ui::Entry(ui::New);
734 table.attach(entry, {1, 2, 1, 2}, {GTK_EXPAND | GTK_FILL, 0});
735 m_subdivisions.m_horizontal = entry;
736 m_horizontalSubdivisionsEntry.connect(entry);
739 auto label = ui::Label("Vertical");
741 table.attach(label, {0, 1, 2, 3}, {GTK_EXPAND | GTK_FILL, 0});
744 auto entry = ui::Entry(ui::New);
746 table.attach(entry, {1, 2, 2, 3}, {GTK_EXPAND | GTK_FILL, 0});
747 m_subdivisions.m_vertical = entry;
748 m_verticalSubdivisionsEntry.connect(entry);
755 auto frame = ui::Frame("Texturing");
757 hbox.pack_start(frame, TRUE, TRUE, 0);
759 auto vbox2 = ui::VBox(FALSE, 5);
762 gtk_container_set_border_width(GTK_CONTAINER(vbox2), 5);
764 auto label = ui::Label("Name:");
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);
771 auto entry = ui::Entry(ui::New);
772 // gtk_editable_set_editable (GTK_ENTRY (entry), false);
774 vbox2.pack_start(entry, TRUE, TRUE, 0);
775 AddDialogData(entry, m_strName);
777 entry.connect("key_press_event", G_CALLBACK(OnDialogKey), 0);
780 auto table = ui::Table(5, 4, FALSE);
782 vbox2.pack_start(table, TRUE, TRUE, 0);
783 gtk_table_set_row_spacings(table, 5);
784 gtk_table_set_col_spacings(table, 5);
786 auto label = ui::Label("Horizontal Shift Step");
788 table.attach(label, {2, 4, 0, 1}, {GTK_FILL | GTK_EXPAND, 0});
789 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
792 auto label = ui::Label("Vertical Shift Step");
794 table.attach(label, {2, 4, 1, 2}, {GTK_FILL | GTK_EXPAND, 0});
795 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
798 auto label = ui::Label("Horizontal Stretch Step");
800 table.attach(label, {2, 3, 2, 3}, {GTK_FILL | GTK_EXPAND, 0});
801 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
804 auto button = ui::Button("Flip");
806 table.attach(button, {3, 4, 2, 3}, {GTK_FILL, 0});
807 button.connect("clicked", G_CALLBACK(OnBtnPatchFlipX), 0);
808 button.dimensions(60, -1);
811 auto label = ui::Label("Vertical Stretch Step");
813 table.attach(label, {2, 3, 3, 4}, {GTK_FILL | GTK_EXPAND, 0});
814 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
817 auto button = ui::Button("Flip");
819 table.attach(button, {3, 4, 3, 4}, {GTK_FILL, 0});
820 button.connect("clicked", G_CALLBACK(OnBtnPatchFlipY), 0);
821 button.dimensions(60, -1);
824 auto label = ui::Label("Rotate Step");
826 table.attach(label, {2, 4, 4, 5}, {GTK_FILL | GTK_EXPAND, 0});
827 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
830 auto entry = ui::Entry(ui::New);
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]);
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);
843 auto spin = ui::SpinButton(adj, 1, 0);
845 table.attach(spin, {1, 2, 0, 1}, {0, 0});
846 spin.dimensions(10, -1);
847 gtk_widget_set_can_focus(spin, false);
850 auto entry = ui::Entry(ui::New);
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]);
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);
860 auto spin = ui::SpinButton(adj, 1, 0);
862 table.attach(spin, {1, 2, 1, 2}, {0, 0});
863 spin.dimensions(10, -1);
864 gtk_widget_set_can_focus(spin, false);
867 auto entry = ui::Entry(ui::New);
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]);
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);
877 auto spin = ui::SpinButton(adj, 1, 0);
879 table.attach(spin, {1, 2, 2, 3}, {0, 0});
880 spin.dimensions(10, -1);
881 gtk_widget_set_can_focus(spin, false);
884 auto entry = ui::Entry(ui::New);
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]);
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);
894 auto spin = ui::SpinButton(adj, 1, 0);
896 table.attach(spin, {1, 2, 3, 4}, {0, 0});
897 spin.dimensions(10, -1);
898 gtk_widget_set_can_focus(spin, false);
901 auto entry = ui::Entry(ui::New);
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);
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);
912 auto spin = ui::SpinButton(adj, 1, 0);
914 table.attach(spin, {1, 2, 4, 5}, {0, 0});
915 spin.dimensions(10, -1);
916 gtk_widget_set_can_focus(spin, false);
919 auto hbox2 = ui::HBox(TRUE, 5);
921 vbox2.pack_start(hbox2, TRUE, FALSE, 0);
923 auto button = ui::Button("Auto Cap");
925 hbox2.pack_end(button, TRUE, FALSE, 0);
926 button.connect("clicked", G_CALLBACK(OnBtnPatchAutoCap), 0);
927 button.dimensions(60, -1);
930 auto button = ui::Button("CAP");
932 hbox2.pack_end(button, TRUE, FALSE, 0);
933 button.connect("clicked", G_CALLBACK(OnBtnPatchdetails), 0);
934 button.dimensions(60, -1);
937 auto button = ui::Button("Set...");
939 hbox2.pack_end(button, TRUE, FALSE, 0);
940 button.connect("clicked", G_CALLBACK(OnBtnPatchreset), 0);
941 button.dimensions(60, -1);
944 auto button = ui::Button("Natural");
946 hbox2.pack_end(button, TRUE, FALSE, 0);
947 button.connect("clicked", G_CALLBACK(OnBtnPatchnatural), 0);
948 button.dimensions(60, -1);
951 auto button = ui::Button("Fit");
953 hbox2.pack_end(button, TRUE, FALSE, 0);
954 button.connect("clicked", G_CALLBACK(OnBtnPatchfit), 0);
955 button.dimensions(60, -1);
965 // sync the dialog our internal data structures
966 void PatchInspector::exportData()
968 m_bListenChanged = false;
969 Dialog::exportData();
970 m_bListenChanged = true;
973 void PatchInspector::importData()
975 m_bListenChanged = false;
976 Dialog::importData();
977 m_bListenChanged = true;
980 // read the map and feed in the stuff to the dialog box
981 void PatchInspector::GetPatchInfo()
983 if (g_pGameDescription->mGameType == "doom3") {
984 m_subdivisions.update();
987 if (GlobalSelectionSystem().countSelected() == 0) {
990 m_Patch = Node_getPatch(GlobalSelectionSystem().ultimateSelected().path().top());
994 m_strName = m_Patch->GetShader();
996 // fill in the numbers for Row / Col selection
997 m_bListenChanged = false;
1000 gtk_combo_box_set_active(m_pRowCombo, 0);
1002 for (std::size_t i = 0; i < m_countRows; ++i) {
1003 gtk_combo_box_text_remove(m_pRowCombo, gint(m_countRows - i - 1));
1006 m_countRows = m_Patch->getHeight();
1007 for (std::size_t i = 0; i < m_countRows; ++i) {
1009 sprintf(buffer, "%u", Unsigned(i));
1010 gtk_combo_box_text_append_text(m_pRowCombo, buffer);
1013 gtk_combo_box_set_active(m_pRowCombo, 0);
1017 gtk_combo_box_set_active(m_pColCombo, 0);
1019 for (std::size_t i = 0; i < m_countCols; ++i) {
1020 gtk_combo_box_text_remove(m_pColCombo, gint(m_countCols - i - 1));
1023 m_countCols = m_Patch->getWidth();
1024 for (std::size_t i = 0; i < m_countCols; ++i) {
1026 sprintf(buffer, "%u", Unsigned(i));
1027 gtk_combo_box_text_append_text(m_pColCombo, buffer);
1030 gtk_combo_box_set_active(m_pColCombo, 0);
1033 m_bListenChanged = true;
1036 //globalOutputStream() << "WARNING: no patch\n";
1038 // fill in our internal structs
1042 // now update the dialog box
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()
1050 m_fX = m_fY = m_fZ = m_fS = m_fT = 0.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];
1069 void PatchInspector_SelectionChanged(const Selectable &selectable)
1071 PatchInspector_queueDraw();
1075 #include "preferencesystem.h"
1078 void PatchInspector_Construct()
1080 GlobalCommands_insert("PatchInspector", makeCallbackF(PatchInspector_toggleShown),
1081 Accelerator('S', (GdkModifierType) GDK_SHIFT_MASK));
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));
1091 typedef FreeCaller<void(const Selectable &), PatchInspector_SelectionChanged> PatchInspectorSelectionChangedCaller;
1092 GlobalSelectionSystem().addSelectionChangeCallback(PatchInspectorSelectionChangedCaller());
1093 typedef FreeCaller<void(), PatchInspector_queueDraw> PatchInspectorQueueDrawCaller;
1094 Patch_addTextureChangedCallback(PatchInspectorQueueDrawCaller());
1097 void PatchInspector_Destroy()