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