2 Sunplug plugin for GtkRadiant
3 Copyright (C) 2004 Topsun
4 Thanks to SPoG for help!
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.
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.
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
23 #include "debugging/debugging.h"
27 #include "string/string.h"
28 #include "modulesystem/singletonmodule.h"
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
34 #include "scenelib.h" // declaration of datastructure of the map
35 #include "qerplugin.h" // declaration to use other interfaces as a plugin
37 #include <gtk/gtk.h> // to display something with gtk (windows, buttons etc.), the whole package might not be necessary
39 void about_plugin_window();
40 void MapCoordinator();
43 // linux itoa implementation
44 char* itoa( int value, char* result, int base )
46 // check that the base if valid
47 if (base < 2 || base > 16)
58 *out = "0123456789abcdef"[abs(quotient % base)];
64 // Only apply negative sign for base 10
65 if( value < 0 && base == 10)
68 std::reverse(result, out);
75 typedef struct _mapcoord_setting_packet {
76 GtkSpinButton *spinner1, *spinner2, *spinner3, *spinner4;
78 } mapcoord_setting_packet;
80 static int map_minX, map_maxX, map_minY, map_maxY;
81 static int minX, maxX, minY, maxY;
82 mapcoord_setting_packet msp;
84 // **************************
85 // ** find entities by class ** from radiant/map.cpp
86 // **************************
87 class EntityFindByClassname : public scene::Graph::Walker
92 EntityFindByClassname(const char* name, Entity*& entity) : m_name(name), m_entity(entity)
96 bool pre(const scene::Path& path, scene::Instance& instance) const
100 Entity* entity = Node_getEntity(path.top());
102 && string_equal(m_name, entity->getKeyValue("classname")))
111 Entity* Scene_FindEntityByClass(const char* name)
114 GlobalSceneGraph().traverse(EntityFindByClassname(name, entity));
118 // **************************
119 // ** GTK callback functions **
120 // **************************
122 static gboolean delete_event(GtkWidget *widget, GdkEvent *event, gpointer data)
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?'
133 // destroy widget if destroy signal is passed to widget
134 static void destroy(GtkWidget *widget, gpointer data)
136 gtk_widget_destroy(widget);
139 // function for close button to destroy the toplevel widget
140 static void close_window(GtkWidget *widget, gpointer data)
142 gtk_widget_destroy(gtk_widget_get_toplevel(widget));
145 // callback function to assign the optimal mapcoords to the spinboxes
146 static void input_optimal(GtkWidget *widget, gpointer data)
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);
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);
159 // write the values of the Spinner-Boxes to the worldspawn
160 static void set_coordinates(GtkWidget *widget, gpointer data)
162 //Str str_min, str_max;
163 char buffer[10], str_min[20], str_max[20];
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);
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);
178 close_window(widget, NULL);
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
188 SunPlugPluginDependencies() :
189 GlobalEntityModuleRef(GlobalRadiant().getRequiredGameDescriptionKeyValue("entities"))//,
194 // *************************
195 // ** standard plugin stuff **
196 // *************************
199 GtkWindow* main_window;
200 char MenuList[100] = "";
202 const char* init(void* hApp, void* pMainWidget)
204 main_window = GTK_WINDOW(pMainWidget);
205 return "Initializing SunPlug for GTKRadiant";
207 const char* getName()
209 return "SunPlug"; // name that is shown in the menue
211 const char* getCommandList()
213 const char about[] = "About...";
214 const char etMapCoordinator[] = ";ET-MapCoordinator";
216 strcat(MenuList, about);
217 if (strncmp(GlobalRadiant().getGameName(), "etmain", 6) == 0) strcat(MenuList, etMapCoordinator);
218 return (const char*)MenuList;
220 const char* getCommandTitleList()
224 void dispatch(const char* command, float* vMin, float* vMax, bool bSingleBrush) // message processing
226 if(string_equal(command, "About..."))
228 about_plugin_window();
230 if(string_equal(command, "ET-MapCoordinator"))
237 class SunPlugModule : public TypeSystemRef
239 _QERPluginTable m_plugin;
241 typedef _QERPluginTable Type;
242 STRING_CONSTANT(Name, "SunPlug");
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;
252 _QERPluginTable* getTable()
258 typedef SingletonModule<SunPlugModule, SunPlugPluginDependencies> SingletonSunPlugModule;
260 SingletonSunPlugModule g_SunPlugModule;
263 extern "C" void RADIANT_DLLEXPORT Radiant_RegisterModules(ModuleServer& server)
265 initialiseModule(server);
267 g_SunPlugModule.selfRegister();
275 void about_plugin_window()
277 GtkWidget *window, *vbox, *label, *button;
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
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
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
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
299 gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER); // center the window on screen
301 gtk_widget_show_all(window); // show the window and all subelements
304 // get the current bounding box and return the optimal coordinates
305 void GetOptimalCoordinates(AABB *levelBoundingBox)
307 int half_width, half_heigth, center_x, center_y;
309 half_width = levelBoundingBox->extents.x();
310 half_heigth = levelBoundingBox->extents.y();
311 center_x = levelBoundingBox->origin.x();
312 center_y = levelBoundingBox->origin.y();
314 if (half_width > 175 || half_heigth > 175) // the square must be at least 350x350 units
316 // the wider side is the indicator for the square
317 if (half_width >= half_heigth)
319 minX = center_x - half_width;
320 maxX = center_x + half_width;
321 minY = center_y - half_width;
322 maxY = center_y + half_width;
324 minX = center_x - half_heigth;
325 maxX = center_x + half_heigth;
326 minY = center_y - half_heigth;
327 maxY = center_y + half_heigth;
330 minX = center_x - 175;
331 maxX = center_x + 175;
332 minY = center_y - 175;
333 maxY = center_y + 175;
337 // MapCoordinator dialog window
338 void MapCoordinator()
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;
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
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
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
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
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
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
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
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
399 gtk_box_pack_start(GTK_BOX(vbox), gtk_hseparator_new(), FALSE, FALSE, 2); // insert separator into vbox
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
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
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
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
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
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
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
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
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
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;
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
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
449 globalOutputStream() << "SunPlug: no worldspawn found!\n"; // output error to console
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
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
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