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