+/*\r
+Sunplug plugin for GtkRadiant\r
+Copyright (C) 2004 Topsun\r
+Thanks to SPoG for help!\r
+\r
+This library is free software; you can redistribute it and/or\r
+modify it under the terms of the GNU Lesser General Public\r
+License as published by the Free Software Foundation; either\r
+version 2.1 of the License, or (at your option) any later version.\r
+\r
+This library is distributed in the hope that it will be useful,\r
+but WITHOUT ANY WARRANTY; without even the implied warranty of\r
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\r
+Lesser General Public License for more details.\r
+\r
+You should have received a copy of the GNU Lesser General Public\r
+License along with this library; if not, write to the Free Software\r
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA\r
+*/\r
+\r
+#include "sunplug.h"\r
+\r
+#include "debugging/debugging.h"\r
+\r
+#include "iplugin.h"\r
+\r
+#include "string/string.h"\r
+#include "modulesystem/singletonmodule.h"\r
+\r
+#include "iundo.h" // declaration of undo system\r
+#include "ientity.h" // declaration of entity system\r
+#include "iscenegraph.h" // declaration of datastructure of the map\r
+\r
+#include "scenelib.h" // declaration of datastructure of the map\r
+#include "qerplugin.h" // declaration to use other interfaces as a plugin\r
+\r
+#include <gtk/gtk.h> // to display something with gtk (windows, buttons etc.), the whole package might not be necessary\r
+\r
+void about_plugin_window();\r
+void MapCoordinator();\r
+\r
+#ifdef __linux__\r
+// linux itoa implementation\r
+char* itoa( int value, char* result, int base )\r
+{ \r
+ // check that the base if valid\r
+ if (base < 2 || base > 16)\r
+ {\r
+ *result = 0;\r
+ return result;\r
+ }\r
+ \r
+ char* out = result;\r
+ int quotient = value;\r
+ \r
+ do\r
+ {\r
+ *out = "0123456789abcdef"[abs(quotient % base)];\r
+ ++out;\r
+ \r
+ quotient /= base;\r
+ } while (quotient);\r
+ \r
+ // Only apply negative sign for base 10\r
+ if( value < 0 && base == 10)\r
+ *out++ = '-';\r
+ \r
+ std::reverse(result, out);\r
+ \r
+ *out = 0;\r
+ return result;\r
+}\r
+#endif\r
+\r
+typedef struct _mapcoord_setting_packet {\r
+ GtkSpinButton *spinner1, *spinner2, *spinner3, *spinner4;\r
+ Entity* worldspawn;\r
+} mapcoord_setting_packet;\r
+\r
+static int map_minX, map_maxX, map_minY, map_maxY;\r
+static int minX, maxX, minY, maxY;\r
+mapcoord_setting_packet msp;\r
+\r
+// **************************\r
+// ** find entities by class ** from radiant/map.cpp\r
+// **************************\r
+class EntityFindByClassname : public scene::Graph::Walker\r
+{\r
+ const char* m_name;\r
+ Entity*& m_entity;\r
+public:\r
+ EntityFindByClassname(const char* name, Entity*& entity) : m_name(name), m_entity(entity)\r
+ {\r
+ m_entity = 0;\r
+ }\r
+ bool pre(const scene::Path& path, scene::Instance& instance) const\r
+ {\r
+ if(m_entity == 0)\r
+ {\r
+ Entity* entity = Node_getEntity(path.top());\r
+ if(entity != 0\r
+ && string_equal(m_name, entity->getKeyValue("classname")))\r
+ {\r
+ m_entity = entity;\r
+ }\r
+ }\r
+ return true;\r
+ }\r
+};\r
+\r
+Entity* Scene_FindEntityByClass(const char* name)\r
+{\r
+ Entity* entity;\r
+ GlobalSceneGraph().traverse(EntityFindByClassname(name, entity));\r
+ return entity;\r
+}\r
+\r
+// **************************\r
+// ** GTK callback functions **\r
+// **************************\r
+\r
+static gboolean delete_event(GtkWidget *widget, GdkEvent *event, gpointer data)\r
+{\r
+ /* If you return FALSE in the "delete_event" signal handler,\r
+ * GTK will emit the "destroy" signal. Returning TRUE means\r
+ * you don't want the window to be destroyed.\r
+ * This is useful for popping up 'are you sure you want to quit?'\r
+ * type dialogs. */\r
+\r
+ return FALSE;\r
+}\r
+\r
+// destroy widget if destroy signal is passed to widget\r
+static void destroy(GtkWidget *widget, gpointer data)\r
+{\r
+ gtk_widget_destroy(widget);\r
+}\r
+\r
+// function for close button to destroy the toplevel widget\r
+static void close_window(GtkWidget *widget, gpointer data)\r
+{\r
+ gtk_widget_destroy(gtk_widget_get_toplevel(widget));\r
+}\r
+\r
+// callback function to assign the optimal mapcoords to the spinboxes\r
+static void input_optimal(GtkWidget *widget, gpointer data)\r
+{\r
+ gtk_spin_button_set_value(msp.spinner1, minX);\r
+ gtk_spin_button_set_value(msp.spinner2, maxY);\r
+ gtk_spin_button_set_value(msp.spinner3, maxX);\r
+ gtk_spin_button_set_value(msp.spinner4, minY);\r
+}\r
+\r
+// Spinner return value function\r
+gint grab_int_value(GtkSpinButton *a_spinner, gpointer user_data) {\r
+ return gtk_spin_button_get_value_as_int(a_spinner);\r
+}\r
+\r
+// write the values of the Spinner-Boxes to the worldspawn\r
+static void set_coordinates(GtkWidget *widget, gpointer data)\r
+{\r
+ //Str str_min, str_max;\r
+ char buffer[10], str_min[20], str_max[20];\r
+\r
+ itoa(gtk_spin_button_get_value_as_int(msp.spinner1), str_min, 10);\r
+ itoa(gtk_spin_button_get_value_as_int(msp.spinner2), buffer, 10);\r
+ strcat(str_min, " ");\r
+ strcat(str_min, buffer);\r
+ msp.worldspawn->setKeyValue("mapcoordsmins", str_min);\r
+\r
+ itoa(gtk_spin_button_get_value_as_int(msp.spinner3), str_max, 10);\r
+ itoa(gtk_spin_button_get_value_as_int(msp.spinner4), buffer, 10);\r
+ strcat(str_max, " ");\r
+ strcat(str_max, buffer);\r
+ UndoableCommand undo("SunPlug.entitySetMapcoords");\r
+ msp.worldspawn->setKeyValue("mapcoordsmaxs", str_max);\r
+\r
+ close_window(widget, NULL);\r
+}\r
+\r
+class SunPlugPluginDependencies :\r
+ public GlobalRadiantModuleRef, // basic class for all other module refs\r
+ public GlobalUndoModuleRef, // used to say radiant that something has changed and to undo that\r
+ public GlobalSceneGraphModuleRef, // necessary to handle data in the mapfile (change, retrieve data)\r
+ public GlobalEntityModuleRef // to access and modify the entities\r
+{\r
+public:\r
+ SunPlugPluginDependencies() :\r
+ GlobalEntityModuleRef(GlobalRadiant().getRequiredGameDescriptionKeyValue("entities"))//,\r
+ {\r
+ }\r
+};\r
+\r
+// *************************\r
+// ** standard plugin stuff **\r
+// *************************\r
+namespace SunPlug\r
+{\r
+ GtkWindow* main_window;\r
+ char MenuList[100] = "";\r
+\r
+ const char* init(void* hApp, void* pMainWidget)\r
+ {\r
+ main_window = GTK_WINDOW(pMainWidget);\r
+ return "Initializing SunPlug for GTKRadiant";\r
+ }\r
+ const char* getName()\r
+ {\r
+ return "SunPlug"; // name that is shown in the menue\r
+ }\r
+ const char* getCommandList()\r
+ {\r
+ const char about[] = "About...";\r
+ const char etMapCoordinator[] = ";ET-MapCoordinator";\r
+\r
+ strcat(MenuList, about);\r
+ if (strncmp(GlobalRadiant().getGameName(), "etmain", 6) == 0) strcat(MenuList, etMapCoordinator);\r
+ return (const char*)MenuList;\r
+ }\r
+ const char* getCommandTitleList()\r
+ {\r
+ return "";\r
+ }\r
+ void dispatch(const char* command, float* vMin, float* vMax, bool bSingleBrush) // message processing\r
+ {\r
+ if(string_equal(command, "About..."))\r
+ {\r
+ about_plugin_window();\r
+ }\r
+ if(string_equal(command, "ET-MapCoordinator"))\r
+ {\r
+ MapCoordinator();\r
+ }\r
+ }\r
+} // namespace\r
+\r
+class SunPlugModule : public TypeSystemRef\r
+{\r
+ _QERPluginTable m_plugin;\r
+public:\r
+ typedef _QERPluginTable Type;\r
+ STRING_CONSTANT(Name, "SunPlug");\r
+\r
+ SunPlugModule()\r
+ {\r
+ m_plugin.m_pfnQERPlug_Init = &SunPlug::init;\r
+ m_plugin.m_pfnQERPlug_GetName = &SunPlug::getName;\r
+ m_plugin.m_pfnQERPlug_GetCommandList = &SunPlug::getCommandList;\r
+ m_plugin.m_pfnQERPlug_GetCommandTitleList = &SunPlug::getCommandTitleList;\r
+ m_plugin.m_pfnQERPlug_Dispatch = &SunPlug::dispatch;\r
+ }\r
+ _QERPluginTable* getTable()\r
+ {\r
+ return &m_plugin;\r
+ }\r
+};\r
+\r
+typedef SingletonModule<SunPlugModule, SunPlugPluginDependencies> SingletonSunPlugModule;\r
+\r
+SingletonSunPlugModule g_SunPlugModule;\r
+\r
+\r
+extern "C" void RADIANT_DLLEXPORT Radiant_RegisterModules(ModuleServer& server)\r
+{\r
+ initialiseModule(server);\r
+\r
+ g_SunPlugModule.selfRegister();\r
+}\r
+\r
+// ************\r
+// ** my stuff **\r
+// ************\r
+\r
+// About dialog\r
+void about_plugin_window()\r
+{\r
+ GtkWidget *window, *vbox, *label, *button;\r
+\r
+ window = gtk_window_new(GTK_WINDOW_TOPLEVEL); // create a window\r
+ gtk_window_set_transient_for(GTK_WINDOW(window), SunPlug::main_window); // make the window to stay in front of the main window\r
+ g_signal_connect(G_OBJECT(window), "delete_event", G_CALLBACK(delete_event), NULL); // connect the delete event\r
+ g_signal_connect(G_OBJECT(window), "destroy", G_CALLBACK(destroy), NULL); // connect the destroy event for the window\r
+ gtk_window_set_title(GTK_WINDOW(window), "About SunPlug"); // set the title of the window for the window\r
+ gtk_window_set_resizable(GTK_WINDOW(window), FALSE); // don't let the user resize the window\r
+ gtk_window_set_modal(GTK_WINDOW(window), TRUE); // force the user not to do something with the other windows\r
+ gtk_container_set_border_width(GTK_CONTAINER(window), 10); // set the border of the window\r
+\r
+ vbox = gtk_vbox_new(FALSE, 10); // create a box to arrange new objects vertically\r
+ gtk_container_add(GTK_CONTAINER(window), vbox); // add the box to the window\r
+\r
+ label = gtk_label_new("SunPlug v1.0 for GtkRadiant 1.5\nby Topsun"); // create a label\r
+ gtk_label_set_justify(GTK_LABEL(label), GTK_JUSTIFY_LEFT); // text align left\r
+ gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 2); // insert the label in the box\r
+\r
+ button = gtk_button_new_with_label("OK"); // create a button with text\r
+ g_signal_connect_swapped(G_OBJECT(button), "clicked", G_CALLBACK (gtk_widget_destroy), window); // connect the click event to close the window\r
+ gtk_box_pack_start(GTK_BOX(vbox), button, FALSE, FALSE, 2); // insert the button in the box\r
+\r
+ gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER); // center the window on screen\r
+\r
+ gtk_widget_show_all(window); // show the window and all subelements\r
+}\r
+\r
+// get the current bounding box and return the optimal coordinates\r
+void GetOptimalCoordinates(AABB *levelBoundingBox)\r
+{\r
+ int half_width, half_heigth, center_x, center_y;\r
+\r
+ half_width = levelBoundingBox->extents.x();\r
+ half_heigth = levelBoundingBox->extents.y();\r
+ center_x = levelBoundingBox->origin.x();\r
+ center_y = levelBoundingBox->origin.y();\r
+\r
+ if (half_width > 175 || half_heigth > 175) // the square must be at least 350x350 units\r
+ {\r
+ // the wider side is the indicator for the square\r
+ if (half_width >= half_heigth)\r
+ {\r
+ minX = center_x - half_width;\r
+ maxX = center_x + half_width;\r
+ minY = center_y - half_width;\r
+ maxY = center_y + half_width;\r
+ } else {\r
+ minX = center_x - half_heigth;\r
+ maxX = center_x + half_heigth;\r
+ minY = center_y - half_heigth;\r
+ maxY = center_y + half_heigth;\r
+ }\r
+ } else {\r
+ minX = center_x - 175;\r
+ maxX = center_x + 175;\r
+ minY = center_y - 175;\r
+ maxY = center_y + 175;\r
+ }\r
+}\r
+\r
+// MapCoordinator dialog window\r
+void MapCoordinator()\r
+{\r
+ GtkWidget *window, *vbox, *table, *label, *spinnerMinX, *spinnerMinY, *spinnerMaxX, *spinnerMaxY, *button;\r
+ GtkAdjustment *spinner_adj_MinX, *spinner_adj_MinY, *spinner_adj_MaxX, *spinner_adj_MaxY;\r
+ Entity *theWorldspawn = NULL;\r
+ const char *buffer;\r
+ char line[20];\r
+\r
+ // in any case we need a window to show the user what to do\r
+ window = gtk_window_new(GTK_WINDOW_TOPLEVEL); // create the window\r
+ gtk_window_set_transient_for(GTK_WINDOW(window), SunPlug::main_window); // make the window to stay in front of the main window\r
+ g_signal_connect(G_OBJECT(window), "delete_event", G_CALLBACK(delete_event), NULL); // connect the delete event for the window\r
+ g_signal_connect(G_OBJECT(window), "destroy", G_CALLBACK(destroy), NULL); // connect the destroy event for the window\r
+ gtk_window_set_title(GTK_WINDOW(window), "ET-MapCoordinator"); // set the title of the window for the window\r
+ gtk_window_set_resizable(GTK_WINDOW(window), FALSE); // don't let the user resize the window\r
+ gtk_window_set_modal(GTK_WINDOW(window), TRUE); // force the user not to do something with the other windows\r
+ gtk_container_set_border_width(GTK_CONTAINER(window), 10); // set the border of the window\r
+\r
+ vbox = gtk_vbox_new(FALSE, 10); // create a box to arrange new objects vertically\r
+ gtk_container_add(GTK_CONTAINER(window), vbox); // add the box to the window\r
+\r
+ scene::Path path = makeReference(GlobalSceneGraph().root()); // get the path to the root element of the graph\r
+ scene::Instance* instance = GlobalSceneGraph().find(path); // find the instance to the given path\r
+ AABB levelBoundingBox = instance->worldAABB(); // get the bounding box of the level\r
+\r
+ theWorldspawn = Scene_FindEntityByClass("worldspawn"); // find the entity worldspawn\r
+ if (theWorldspawn != 0) { // need to have a worldspawn otherwise setting a value crashes the radiant\r
+ // next two if's: get the current values of the mapcoords\r
+ buffer = theWorldspawn->getKeyValue("mapcoordsmins"); // upper left corner\r
+ if (strlen(buffer) > 0) {\r
+ strncpy(line, buffer, 19);\r
+ map_minX = atoi(strtok(line, " ")); // minimum of x value\r
+ map_minY = atoi(strtok(NULL, " ")); // maximum of y value\r
+ } else {\r
+ map_minX = 0;\r
+ map_minY = 0;\r
+ }\r
+ buffer = theWorldspawn->getKeyValue("mapcoordsmaxs"); // lower right corner\r
+ if (strlen(buffer) > 0) {\r
+ strncpy(line, buffer, 19);\r
+ map_maxX = atoi(strtok(line, " ")); // maximum of x value\r
+ map_maxY = atoi(strtok(NULL, " ")); // minimum of y value\r
+ } else {\r
+ map_maxX = 0;\r
+ map_maxY = 0;\r
+ }\r
+\r
+ globalOutputStream() << "SunPlug: calculating optimal coordinates\n"; // write to console that we are calculating the coordinates\r
+ GetOptimalCoordinates(&levelBoundingBox); // calculate optimal mapcoords with the dimensions of the level bounding box\r
+ globalOutputStream() << "SunPlug: adviced mapcoordsmins=" << minX << " " << maxY << "\n"; // console info about mapcoordsmins\r
+ globalOutputStream() << "SunPlug: adviced mapcoordsmaxs=" << maxX << " " << minY << "\n"; // console info about mapcoordsmaxs\r
+\r
+ spinner_adj_MinX = (GtkAdjustment *)gtk_adjustment_new(map_minX, -65536.0, 65536.0, 1.0, 5.0, 5.0); // create adjustment for value and range of minimum x value\r
+ spinner_adj_MinY = (GtkAdjustment *)gtk_adjustment_new(map_minY, -65536.0, 65536.0, 1.0, 5.0, 5.0); // create adjustment for value and range of minimum y value\r
+ spinner_adj_MaxX = (GtkAdjustment *)gtk_adjustment_new(map_maxX, -65536.0, 65536.0, 1.0, 5.0, 5.0); // create adjustment for value and range of maximum x value\r
+ spinner_adj_MaxY = (GtkAdjustment *)gtk_adjustment_new(map_maxY, -65536.0, 65536.0, 1.0, 5.0, 5.0); // create adjustment for value and range of maximum y value\r
+ \r
+ button = gtk_button_new_with_label("Get optimal mapcoords"); // create button with text\r
+ g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(input_optimal), NULL); // connect button with callback function\r
+ gtk_box_pack_start(GTK_BOX(vbox), button, FALSE, FALSE, 2); // insert button into vbox\r
+\r
+ gtk_box_pack_start(GTK_BOX(vbox), gtk_hseparator_new(), FALSE, FALSE, 2); // insert separator into vbox\r
+\r
+ table = gtk_table_new(4, 3, TRUE); // create table\r
+ gtk_table_set_row_spacings(GTK_TABLE(table), 8); // set row spacings\r
+ gtk_table_set_col_spacings(GTK_TABLE(table), 8); // set column spacings\r
+ gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 2); // insert table into vbox\r
+\r
+ label = gtk_label_new("x"); // create label\r
+ gtk_label_set_justify(GTK_LABEL(label), GTK_JUSTIFY_LEFT); // align text to the left side\r
+ gtk_table_attach_defaults(GTK_TABLE(table), label, 1, 2, 0, 1); // insert label into table\r
+\r
+ label = gtk_label_new("y"); // create label\r
+ gtk_label_set_justify(GTK_LABEL(label), GTK_JUSTIFY_LEFT); // align text to the left side\r
+ gtk_table_attach_defaults(GTK_TABLE(table), label, 2, 3, 0, 1); // insert label into table\r
+\r
+ label = gtk_label_new("mapcoordsmins"); // create label\r
+ gtk_label_set_justify(GTK_LABEL(label), GTK_JUSTIFY_LEFT); // align text to the left side\r
+ gtk_table_attach_defaults(GTK_TABLE(table), label, 0, 1, 1, 2); // insert label into table\r
+\r
+ spinnerMinX = gtk_spin_button_new(spinner_adj_MinX, 1.0, 0); // create textbox wiht value spin, value and value range\r
+ gtk_table_attach_defaults(GTK_TABLE(table), spinnerMinX, 1, 2, 1, 2); // insert spinbox into table\r
+\r
+ spinnerMinY = gtk_spin_button_new(spinner_adj_MinY, 1.0, 0); // create textbox wiht value spin, value and value range\r
+ gtk_table_attach_defaults(GTK_TABLE(table), spinnerMinY, 2, 3, 1, 2); // insert spinbox into table\r
+\r
+ label = gtk_label_new("mapcoordsmaxs"); // create label\r
+ gtk_label_set_justify(GTK_LABEL(label), GTK_JUSTIFY_LEFT); // align text to the left side\r
+ gtk_table_attach_defaults(GTK_TABLE(table), label, 0, 1, 2, 3); // insert label into table\r
+\r
+ spinnerMaxX = gtk_spin_button_new(spinner_adj_MaxX, 1.0, 0); // create textbox wiht value spin, value and value range\r
+ gtk_table_attach_defaults(GTK_TABLE(table), spinnerMaxX, 1, 2, 2, 3); // insert spinbox into table\r
+\r
+ spinnerMaxY = gtk_spin_button_new(spinner_adj_MaxY, 1.0, 0); // create textbox wiht value spin, value and value range\r
+ gtk_table_attach_defaults(GTK_TABLE(table), spinnerMaxY, 2, 3, 2, 3); // insert spinbox into table\r
+\r
+ // put the references to the spinboxes and the worldspawn into the global exchange\r
+ msp.spinner1 = GTK_SPIN_BUTTON(spinnerMinX);\r
+ msp.spinner2 = GTK_SPIN_BUTTON(spinnerMinY);\r
+ msp.spinner3 = GTK_SPIN_BUTTON(spinnerMaxX);\r
+ msp.spinner4 = GTK_SPIN_BUTTON(spinnerMaxY);\r
+ msp.worldspawn = theWorldspawn;\r
+\r
+ button = gtk_button_new_with_label("Set"); // create button with text\r
+ g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(set_coordinates), NULL); // connect button with callback function\r
+ gtk_table_attach_defaults(GTK_TABLE(table), button, 1, 2, 3, 4); // insert button into table\r
+\r
+ button = gtk_button_new_with_label("Cancel"); // create button with text\r
+ g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(close_window), NULL); // connect button with callback function\r
+ gtk_table_attach_defaults(GTK_TABLE(table), button, 2, 3, 3, 4); // insert button into table\r
+ } else {\r
+ globalOutputStream() << "SunPlug: no worldspawn found!\n"; // output error to console\r
+ \r
+ 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
+ gtk_label_set_justify(GTK_LABEL(label), GTK_JUSTIFY_LEFT); // text align left\r
+ gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 2); // insert the label in the box\r
+\r
+ button = gtk_button_new_with_label("OK"); // create a button with text\r
+ g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(close_window), NULL); // connect the click event to close the window\r
+ gtk_box_pack_start(GTK_BOX(vbox), button, FALSE, FALSE, 2); // insert the button in the box\r
+ }\r
+\r
+ gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER); // center the window\r
+ gtk_widget_show_all(window); // show the window and all subelements\r
+}
\ No newline at end of file