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
22 #include "globaldefs.h"
24 #include "debugging/debugging.h"
28 #include "string/string.h"
29 #include "modulesystem/singletonmodule.h"
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
35 #include "scenelib.h" // declaration of datastructure of the map
36 #include "qerplugin.h" // declaration to use other interfaces as a plugin
38 #include <gtk/gtk.h> // to display something with gtk (windows, buttons etc.), the whole package might not be necessary
40 void about_plugin_window();
42 void MapCoordinator();
46 // linux itoa implementation
47 char *itoa(int value, char *result, int base)
49 // check that the base if valid
50 if (base < 2 || base > 16) {
59 *out = "0123456789abcdef"[abs(quotient % base)];
65 // Only apply negative sign for base 10
66 if (value < 0 && base == 10) {
70 std::reverse(result, out);
78 typedef struct _mapcoord_setting_packet {
79 ui::SpinButton spinner1{ui::null}, spinner2{ui::null}, spinner3{ui::null}, spinner4{ui::null};
81 } mapcoord_setting_packet;
83 static int map_minX, map_maxX, map_minY, map_maxY;
84 static int minX, maxX, minY, maxY;
85 mapcoord_setting_packet msp;
87 // **************************
88 // ** find entities by class ** from radiant/map.cpp
89 // **************************
90 class EntityFindByClassname : public scene::Graph::Walker {
94 EntityFindByClassname(const char *name, Entity *&entity) : m_name(name), m_entity(entity)
99 bool pre(const scene::Path &path, scene::Instance &instance) const
102 Entity *entity = Node_getEntity(path.top());
104 && string_equal(m_name, entity->getKeyValue("classname"))) {
112 Entity *Scene_FindEntityByClass(const char *name)
115 GlobalSceneGraph().traverse(EntityFindByClassname(name, entity));
119 // **************************
120 // ** GTK callback functions **
121 // **************************
123 static gboolean delete_event(ui::Widget widget, GdkEvent *event, gpointer data)
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?'
134 // destroy widget if destroy signal is passed to widget
135 static void destroy(ui::Widget widget, gpointer data)
140 // function for close button to destroy the toplevel widget
141 static void close_window(ui::Widget widget, gpointer data)
143 widget.window().destroy();
146 // callback function to assign the optimal mapcoords to the spinboxes
147 static void input_optimal(ui::Widget widget, gpointer data)
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);
155 // Spinner return value function
156 gint grab_int_value(ui::SpinButton a_spinner, gpointer user_data)
158 return gtk_spin_button_get_value_as_int(a_spinner);
161 // write the values of the Spinner-Boxes to the worldspawn
162 static void set_coordinates(ui::Widget widget, gpointer data)
164 //Str str_min, str_max;
165 char buffer[10], str_min[20], str_max[20];
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);
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);
180 close_window(widget, NULL);
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
190 SunPlugPluginDependencies() :
191 GlobalEntityModuleRef(GlobalRadiant().getRequiredGameDescriptionKeyValue("entities"))
196 // *************************
197 // ** standard plugin stuff **
198 // *************************
200 ui::Window main_window{ui::null};
201 char MenuList[100] = "";
203 const char *init(void *hApp, void *pMainWidget)
205 main_window = ui::Window::from(pMainWidget);
206 return "Initializing SunPlug for GTKRadiant";
209 const char *getName()
211 return "SunPlug"; // name that is shown in the menue
214 const char *getCommandList()
216 const char about[] = "About...";
217 const char etMapCoordinator[] = ";ET-MapCoordinator";
219 strcat(MenuList, about);
220 if (strncmp(GlobalRadiant().getGameName(), "etmain", 6) == 0) {
221 strcat(MenuList, etMapCoordinator);
223 return (const char *) MenuList;
226 const char *getCommandTitleList()
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();
236 if (string_equal(command, "ET-MapCoordinator")) {
242 class SunPlugModule : public TypeSystemRef {
243 _QERPluginTable m_plugin;
245 typedef _QERPluginTable Type;
247 STRING_CONSTANT(Name, "SunPlug");
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;
258 _QERPluginTable *getTable()
264 typedef SingletonModule<SunPlugModule, SunPlugPluginDependencies> SingletonSunPlugModule;
266 SingletonSunPlugModule g_SunPlugModule;
269 extern "C" void RADIANT_DLLEXPORT Radiant_RegisterModules(ModuleServer &server)
271 initialiseModule(server);
273 g_SunPlugModule.selfRegister();
281 void about_plugin_window()
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
292 auto vbox = ui::VBox(FALSE, 10); // create a box to arrange new objects vertically
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
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
304 gtk_window_set_position(window, GTK_WIN_POS_CENTER); // center the window on screen
306 gtk_widget_show_all(window); // show the window and all subelements
309 // get the current bounding box and return the optimal coordinates
310 void GetOptimalCoordinates(AABB *levelBoundingBox)
312 int half_width, half_heigth, center_x, center_y;
314 half_width = levelBoundingBox->extents.x();
315 half_heigth = levelBoundingBox->extents.y();
316 center_x = levelBoundingBox->origin.x();
317 center_y = levelBoundingBox->origin.y();
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;
327 minX = center_x - half_heigth;
328 maxX = center_x + half_heigth;
329 minY = center_y - half_heigth;
330 maxY = center_y + half_heigth;
333 minX = center_x - 175;
334 maxX = center_x + 175;
335 minY = center_y - 175;
336 maxY = center_y + 175;
340 // MapCoordinator dialog window
341 void MapCoordinator()
343 Entity *theWorldspawn = NULL;
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
357 auto vbox = ui::VBox(FALSE, 10); // create a box to arrange new objects vertically
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
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
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
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
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
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
408 vbox.pack_start(ui::Widget::from(gtk_hseparator_new()), FALSE, FALSE, 2); // insert separator into vbox
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
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
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
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
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
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
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
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
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
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;
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
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
462 globalOutputStream() << "SunPlug: no worldspawn found!\n"; // output error to console
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
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
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