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