]> de.git.xonotic.org Git - xonotic/netradiant.git/blob - contrib/sunplug/sunplug.cpp
reformat code! now the code is only ugly on the *inside*
[xonotic/netradiant.git] / contrib / sunplug / sunplug.cpp
1 /*
2    Sunplug plugin for GtkRadiant
3    Copyright (C) 2004 Topsun
4    Thanks to SPoG for help!
5
6    This library is free software; you can redistribute it and/or
7    modify it under the terms of the GNU Lesser General Public
8    License as published by the Free Software Foundation; either
9    version 2.1 of the License, or (at your option) any later version.
10
11    This library is distributed in the hope that it will be useful,
12    but WITHOUT ANY WARRANTY; without even the implied warranty of
13    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14    Lesser General Public License for more details.
15
16    You should have received a copy of the GNU Lesser General Public
17    License along with this library; if not, write to the Free Software
18    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19  */
20
21 #include "sunplug.h"
22 #include "globaldefs.h"
23
24 #include "debugging/debugging.h"
25
26 #include "iplugin.h"
27
28 #include "string/string.h"
29 #include "modulesystem/singletonmodule.h"
30
31 #include "iundo.h"       // declaration of undo system
32 #include "ientity.h"     // declaration of entity system
33 #include "iscenegraph.h" // declaration of datastructure of the map
34
35 #include "scenelib.h"    // declaration of datastructure of the map
36 #include "qerplugin.h"   // declaration to use other interfaces as a plugin
37
38 #include <gtk/gtk.h>     // to display something with gtk (windows, buttons etc.), the whole package might not be necessary
39
40 void about_plugin_window();
41
42 void MapCoordinator();
43
44 #if !GDEF_OS_WINDOWS
45
46 // linux itoa implementation
47 char *itoa(int value, char *result, int base)
48 {
49     // check that the base if valid
50     if (base < 2 || base > 16) {
51         *result = 0;
52         return result;
53     }
54
55     char *out = result;
56     int quotient = value;
57
58     do {
59         *out = "0123456789abcdef"[abs(quotient % base)];
60         ++out;
61
62         quotient /= base;
63     } while (quotient);
64
65     // Only apply negative sign for base 10
66     if (value < 0 && base == 10) {
67         *out++ = '-';
68     }
69
70     std::reverse(result, out);
71
72     *out = 0;
73     return result;
74 }
75
76 #endif
77
78 typedef struct _mapcoord_setting_packet {
79     ui::SpinButton spinner1{ui::null}, spinner2{ui::null}, spinner3{ui::null}, spinner4{ui::null};
80     Entity *worldspawn;
81 } mapcoord_setting_packet;
82
83 static int map_minX, map_maxX, map_minY, map_maxY;
84 static int minX, maxX, minY, maxY;
85 mapcoord_setting_packet msp;
86
87 //  **************************
88 // ** find entities by class **  from radiant/map.cpp
89 //  **************************
90 class EntityFindByClassname : public scene::Graph::Walker {
91     const char *m_name;
92     Entity *&m_entity;
93 public:
94     EntityFindByClassname(const char *name, Entity *&entity) : m_name(name), m_entity(entity)
95     {
96         m_entity = 0;
97     }
98
99     bool pre(const scene::Path &path, scene::Instance &instance) const
100     {
101         if (m_entity == 0) {
102             Entity *entity = Node_getEntity(path.top());
103             if (entity != 0
104                 && string_equal(m_name, entity->getKeyValue("classname"))) {
105                 m_entity = entity;
106             }
107         }
108         return true;
109     }
110 };
111
112 Entity *Scene_FindEntityByClass(const char *name)
113 {
114     Entity *entity;
115     GlobalSceneGraph().traverse(EntityFindByClassname(name, entity));
116     return entity;
117 }
118
119 //  **************************
120 // ** GTK callback functions **
121 //  **************************
122
123 static gboolean delete_event(ui::Widget widget, GdkEvent *event, gpointer data)
124 {
125     /* If you return FALSE in the "delete_event" signal handler,
126      * GTK will emit the "destroy" signal. Returning TRUE means
127      * you don't want the window to be destroyed.
128      * This is useful for popping up 'are you sure you want to quit?'
129      * type dialogs. */
130
131     return FALSE;
132 }
133
134 // destroy widget if destroy signal is passed to widget
135 static void destroy(ui::Widget widget, gpointer data)
136 {
137     widget.destroy();
138 }
139
140 // function for close button to destroy the toplevel widget
141 static void close_window(ui::Widget widget, gpointer data)
142 {
143     widget.window().destroy();
144 }
145
146 // callback function to assign the optimal mapcoords to the spinboxes
147 static void input_optimal(ui::Widget widget, gpointer data)
148 {
149     gtk_spin_button_set_value(msp.spinner1, minX);
150     gtk_spin_button_set_value(msp.spinner2, maxY);
151     gtk_spin_button_set_value(msp.spinner3, maxX);
152     gtk_spin_button_set_value(msp.spinner4, minY);
153 }
154
155 // Spinner return value function
156 gint grab_int_value(ui::SpinButton a_spinner, gpointer user_data)
157 {
158     return gtk_spin_button_get_value_as_int(a_spinner);
159 }
160
161 // write the values of the Spinner-Boxes to the worldspawn
162 static void set_coordinates(ui::Widget widget, gpointer data)
163 {
164     //Str str_min, str_max;
165     char buffer[10], str_min[20], str_max[20];
166
167     itoa(gtk_spin_button_get_value_as_int(msp.spinner1), str_min, 10);
168     itoa(gtk_spin_button_get_value_as_int(msp.spinner2), buffer, 10);
169     strcat(str_min, " ");
170     strcat(str_min, buffer);
171     msp.worldspawn->setKeyValue("mapcoordsmins", str_min);
172
173     itoa(gtk_spin_button_get_value_as_int(msp.spinner3), str_max, 10);
174     itoa(gtk_spin_button_get_value_as_int(msp.spinner4), buffer, 10);
175     strcat(str_max, " ");
176     strcat(str_max, buffer);
177     UndoableCommand undo("SunPlug.entitySetMapcoords");
178     msp.worldspawn->setKeyValue("mapcoordsmaxs", str_max);
179
180     close_window(widget, NULL);
181 }
182
183 class SunPlugPluginDependencies :
184         public GlobalRadiantModuleRef,  // basic class for all other module refs
185         public GlobalUndoModuleRef,     // used to say radiant that something has changed and to undo that
186         public GlobalSceneGraphModuleRef, // necessary to handle data in the mapfile (change, retrieve data)
187         public GlobalEntityModuleRef    // to access and modify the entities
188 {
189 public:
190     SunPlugPluginDependencies() :
191             GlobalEntityModuleRef(GlobalRadiant().getRequiredGameDescriptionKeyValue("entities"))
192     { //,
193     }
194 };
195
196 //  *************************
197 // ** standard plugin stuff **
198 //  *************************
199 namespace SunPlug {
200     ui::Window main_window{ui::null};
201     char MenuList[100] = "";
202
203     const char *init(void *hApp, void *pMainWidget)
204     {
205         main_window = ui::Window::from(pMainWidget);
206         return "Initializing SunPlug for GTKRadiant";
207     }
208
209     const char *getName()
210     {
211         return "SunPlug"; // name that is shown in the menue
212     }
213
214     const char *getCommandList()
215     {
216         const char about[] = "About...";
217         const char etMapCoordinator[] = ";ET-MapCoordinator";
218
219         strcat(MenuList, about);
220         if (strncmp(GlobalRadiant().getGameName(), "etmain", 6) == 0) {
221             strcat(MenuList, etMapCoordinator);
222         }
223         return (const char *) MenuList;
224     }
225
226     const char *getCommandTitleList()
227     {
228         return "";
229     }
230
231     void dispatch(const char *command, float *vMin, float *vMax, bool bSingleBrush)
232     { // message processing
233         if (string_equal(command, "About...")) {
234             about_plugin_window();
235         }
236         if (string_equal(command, "ET-MapCoordinator")) {
237             MapCoordinator();
238         }
239     }
240 } // namespace
241
242 class SunPlugModule : public TypeSystemRef {
243     _QERPluginTable m_plugin;
244 public:
245     typedef _QERPluginTable Type;
246
247     STRING_CONSTANT(Name, "SunPlug");
248
249     SunPlugModule()
250     {
251         m_plugin.m_pfnQERPlug_Init = &SunPlug::init;
252         m_plugin.m_pfnQERPlug_GetName = &SunPlug::getName;
253         m_plugin.m_pfnQERPlug_GetCommandList = &SunPlug::getCommandList;
254         m_plugin.m_pfnQERPlug_GetCommandTitleList = &SunPlug::getCommandTitleList;
255         m_plugin.m_pfnQERPlug_Dispatch = &SunPlug::dispatch;
256     }
257
258     _QERPluginTable *getTable()
259     {
260         return &m_plugin;
261     }
262 };
263
264 typedef SingletonModule<SunPlugModule, SunPlugPluginDependencies> SingletonSunPlugModule;
265
266 SingletonSunPlugModule g_SunPlugModule;
267
268
269 extern "C" void RADIANT_DLLEXPORT Radiant_RegisterModules(ModuleServer &server)
270 {
271     initialiseModule(server);
272
273     g_SunPlugModule.selfRegister();
274 }
275
276 //  ************
277 // ** my stuff **
278 //  ************
279
280 // About dialog
281 void about_plugin_window()
282 {
283     auto window = ui::Window(ui::window_type::TOP); // create a window
284     gtk_window_set_transient_for(window, SunPlug::main_window); // make the window to stay in front of the main window
285     window.connect("delete_event", G_CALLBACK(delete_event), NULL); // connect the delete event
286     window.connect("destroy", G_CALLBACK(destroy), NULL); // connect the destroy event for the window
287     gtk_window_set_title(window, "About SunPlug"); // set the title of the window for the window
288     gtk_window_set_resizable(window, FALSE); // don't let the user resize the window
289     gtk_window_set_modal(window, TRUE); // force the user not to do something with the other windows
290     gtk_container_set_border_width(GTK_CONTAINER(window), 10); // set the border of the window
291
292     auto vbox = ui::VBox(FALSE, 10); // create a box to arrange new objects vertically
293     window.add(vbox);
294
295     auto label = ui::Label("SunPlug v1.0 for NetRadiant 1.5\nby Topsun"); // create a label
296     gtk_label_set_justify(GTK_LABEL(label), GTK_JUSTIFY_LEFT); // text align left
297     vbox.pack_start(label, FALSE, FALSE, 2); // insert the label in the box
298
299     auto button = ui::Button("OK"); // create a button with text
300     g_signal_connect_swapped(G_OBJECT(button), "clicked", G_CALLBACK(gtk_widget_destroy),
301                              (void *) window); // connect the click event to close the window
302     vbox.pack_start(button, FALSE, FALSE, 2); // insert the button in the box
303
304     gtk_window_set_position(window, GTK_WIN_POS_CENTER); // center the window on screen
305
306     gtk_widget_show_all(window); // show the window and all subelements
307 }
308
309 // get the current bounding box and return the optimal coordinates
310 void GetOptimalCoordinates(AABB *levelBoundingBox)
311 {
312     int half_width, half_heigth, center_x, center_y;
313
314     half_width = levelBoundingBox->extents.x();
315     half_heigth = levelBoundingBox->extents.y();
316     center_x = levelBoundingBox->origin.x();
317     center_y = levelBoundingBox->origin.y();
318
319     if (half_width > 175 || half_heigth > 175) { // the square must be at least 350x350 units
320         // the wider side is the indicator for the square
321         if (half_width >= half_heigth) {
322             minX = center_x - half_width;
323             maxX = center_x + half_width;
324             minY = center_y - half_width;
325             maxY = center_y + half_width;
326         } else {
327             minX = center_x - half_heigth;
328             maxX = center_x + half_heigth;
329             minY = center_y - half_heigth;
330             maxY = center_y + half_heigth;
331         }
332     } else {
333         minX = center_x - 175;
334         maxX = center_x + 175;
335         minY = center_y - 175;
336         maxY = center_y + 175;
337     }
338 }
339
340 // MapCoordinator dialog window
341 void MapCoordinator()
342 {
343     Entity *theWorldspawn = NULL;
344     const char *buffer;
345     char line[20];
346
347     // in any case we need a window to show the user what to do
348     auto window = ui::Window(ui::window_type::TOP); // create the window
349     gtk_window_set_transient_for(window, SunPlug::main_window); // make the window to stay in front of the main window
350     window.connect("delete_event", G_CALLBACK(delete_event), NULL); // connect the delete event for the window
351     window.connect("destroy", G_CALLBACK(destroy), NULL); // connect the destroy event for the window
352     gtk_window_set_title(window, "ET-MapCoordinator"); // set the title of the window for the window
353     gtk_window_set_resizable(window, FALSE); // don't let the user resize the window
354     gtk_window_set_modal(window, TRUE); // force the user not to do something with the other windows
355     gtk_container_set_border_width(GTK_CONTAINER(window), 10); // set the border of the window
356
357     auto vbox = ui::VBox(FALSE, 10); // create a box to arrange new objects vertically
358     window.add(vbox);
359
360     scene::Path path = makeReference(GlobalSceneGraph().root()); // get the path to the root element of the graph
361     scene::Instance *instance = GlobalSceneGraph().find(path); // find the instance to the given path
362     AABB levelBoundingBox = instance->worldAABB(); // get the bounding box of the level
363
364     theWorldspawn = Scene_FindEntityByClass("worldspawn"); // find the entity worldspawn
365     if (theWorldspawn != 0) { // need to have a worldspawn otherwise setting a value crashes the radiant
366         // next two if's: get the current values of the mapcoords
367         buffer = theWorldspawn->getKeyValue("mapcoordsmins"); // upper left corner
368         if (strlen(buffer) > 0) {
369             strncpy(line, buffer, 19);
370             map_minX = atoi(strtok(line, " ")); // minimum of x value
371             map_minY = atoi(strtok(NULL, " ")); // maximum of y value
372         } else {
373             map_minX = 0;
374             map_minY = 0;
375         }
376         buffer = theWorldspawn->getKeyValue("mapcoordsmaxs"); // lower right corner
377         if (strlen(buffer) > 0) {
378             strncpy(line, buffer, 19);
379             map_maxX = atoi(strtok(line, " ")); // maximum of x value
380             map_maxY = atoi(strtok(NULL, " ")); // minimum of y value
381         } else {
382             map_maxX = 0;
383             map_maxY = 0;
384         }
385
386         globalOutputStream()
387                 << "SunPlug: calculating optimal coordinates\n"; // write to console that we are calculating the coordinates
388         GetOptimalCoordinates(
389                 &levelBoundingBox); // calculate optimal mapcoords with the dimensions of the level bounding box
390         globalOutputStream() << "SunPlug: adviced mapcoordsmins=" << minX << " " << maxY
391                              << "\n"; // console info about mapcoordsmins
392         globalOutputStream() << "SunPlug: adviced mapcoordsmaxs=" << maxX << " " << minY
393                              << "\n"; // console info about mapcoordsmaxs
394
395         auto spinner_adj_MinX = ui::Adjustment(map_minX, -65536.0, 65536.0, 1.0, 5.0,
396                                                0); // create adjustment for value and range of minimum x value
397         auto spinner_adj_MinY = ui::Adjustment(map_minY, -65536.0, 65536.0, 1.0, 5.0,
398                                                0); // create adjustment for value and range of minimum y value
399         auto spinner_adj_MaxX = ui::Adjustment(map_maxX, -65536.0, 65536.0, 1.0, 5.0,
400                                                0); // create adjustment for value and range of maximum x value
401         auto spinner_adj_MaxY = ui::Adjustment(map_maxY, -65536.0, 65536.0, 1.0, 5.0,
402                                                0); // create adjustment for value and range of maximum y value
403
404         auto button = ui::Button("Get optimal mapcoords"); // create button with text
405         button.connect("clicked", G_CALLBACK(input_optimal), NULL); // connect button with callback function
406         vbox.pack_start(button, FALSE, FALSE, 2); // insert button into vbox
407
408         vbox.pack_start(ui::Widget::from(gtk_hseparator_new()), FALSE, FALSE, 2); // insert separator into vbox
409
410         auto table = ui::Table(4, 3, TRUE); // create table
411         gtk_table_set_row_spacings(table, 8); // set row spacings
412         gtk_table_set_col_spacings(table, 8); // set column spacings
413         vbox.pack_start(table, FALSE, FALSE, 2); // insert table into vbox
414
415         auto label = ui::Label("x"); // create label
416         gtk_label_set_justify(GTK_LABEL(label), GTK_JUSTIFY_LEFT); // align text to the left side
417         table.attach(label, {1, 2, 0, 1}); // insert label into table
418
419         label = ui::Label("y"); // create label
420         gtk_label_set_justify(GTK_LABEL(label), GTK_JUSTIFY_LEFT); // align text to the left side
421         table.attach(label, {2, 3, 0, 1}); // insert label into table
422
423         label = ui::Label("mapcoordsmins"); // create label
424         gtk_label_set_justify(GTK_LABEL(label), GTK_JUSTIFY_LEFT); // align text to the left side
425         table.attach(label, {0, 1, 1, 2}); // insert label into table
426
427         auto spinnerMinX = ui::SpinButton(spinner_adj_MinX, 1.0,
428                                           0); // create textbox wiht value spin, value and value range
429         table.attach(spinnerMinX, {1, 2, 1, 2}); // insert spinbox into table
430
431         auto spinnerMinY = ui::SpinButton(spinner_adj_MinY, 1.0,
432                                           0); // create textbox wiht value spin, value and value range
433         table.attach(spinnerMinY, {2, 3, 1, 2}); // insert spinbox into table
434
435         label = ui::Label("mapcoordsmaxs"); // create label
436         gtk_label_set_justify(GTK_LABEL(label), GTK_JUSTIFY_LEFT); // align text to the left side
437         table.attach(label, {0, 1, 2, 3}); // insert label into table
438
439         auto spinnerMaxX = ui::SpinButton(spinner_adj_MaxX, 1.0,
440                                           0); // create textbox wiht value spin, value and value range
441         table.attach(spinnerMaxX, {1, 2, 2, 3}); // insert spinbox into table
442
443         auto spinnerMaxY = ui::SpinButton(spinner_adj_MaxY, 1.0,
444                                           0); // create textbox wiht value spin, value and value range
445         table.attach(spinnerMaxY, {2, 3, 2, 3}); // insert spinbox into table
446
447         // put the references to the spinboxes and the worldspawn into the global exchange
448         msp.spinner1 = spinnerMinX;
449         msp.spinner2 = spinnerMinY;
450         msp.spinner3 = spinnerMaxX;
451         msp.spinner4 = spinnerMaxY;
452         msp.worldspawn = theWorldspawn;
453
454         button = ui::Button("Set"); // create button with text
455         button.connect("clicked", G_CALLBACK(set_coordinates), NULL); // connect button with callback function
456         table.attach(button, {1, 2, 3, 4}); // insert button into table
457
458         button = ui::Button("Cancel"); // create button with text
459         button.connect("clicked", G_CALLBACK(close_window), NULL); // connect button with callback function
460         table.attach(button, {2, 3, 3, 4}); // insert button into table
461     } else {
462         globalOutputStream() << "SunPlug: no worldspawn found!\n"; // output error to console
463
464         auto label = ui::Label(
465                 "ERROR: No worldspawn was found in the map!\nIn order to use this tool the map must have at least one brush in the worldspawn. "); // create a label
466         gtk_label_set_justify(GTK_LABEL(label), GTK_JUSTIFY_LEFT); // text align left
467         vbox.pack_start(label, FALSE, FALSE, 2); // insert the label in the box
468
469         auto button = ui::Button("OK"); // create a button with text
470         button.connect("clicked", G_CALLBACK(close_window), NULL); // connect the click event to close the window
471         vbox.pack_start(button, FALSE, FALSE, 2); // insert the button in the box
472     }
473
474     gtk_window_set_position(window, GTK_WIN_POS_CENTER); // center the window
475     gtk_widget_show_all(window); // show the window and all subelements
476 }