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 #define CMD_ABOUT "About..."
42 void about_plugin_window();
43 void MapCoordinator();
46 // linux itoa implementation
47 char* itoa( int value, char* result, int base ){
48 // check that the base if valid
49 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 );
77 typedef struct _mapcoord_setting_packet {
78 ui::SpinButton spinner1{ui::null}, spinner2{ui::null}, spinner3{ui::null}, spinner4{ui::null};
80 } mapcoord_setting_packet;
82 static int map_minX, map_maxX, map_minY, map_maxY;
83 static int minX, maxX, minY, maxY;
84 mapcoord_setting_packet msp;
86 // **************************
87 // ** find entities by class ** from radiant/map.cpp
88 // **************************
89 class EntityFindByClassname : public scene::Graph::Walker
94 EntityFindByClassname( const char* name, Entity*& entity ) : m_name( name ), m_entity( entity ){
97 bool pre( const scene::Path& path, scene::Instance& instance ) const {
98 if ( m_entity == 0 ) {
99 Entity* entity = Node_getEntity( path.top() );
101 && string_equal( m_name, entity->getKeyValue( "classname" ) ) ) {
109 Entity* Scene_FindEntityByClass( const char* name ){
111 GlobalSceneGraph().traverse( EntityFindByClassname( name, entity ) );
115 // **************************
116 // ** GTK callback functions **
117 // **************************
119 static gboolean delete_event(ui::Widget widget, GdkEvent *event, gpointer data ){
120 /* If you return FALSE in the "delete_event" signal handler,
121 * GTK will emit the "destroy" signal. Returning TRUE means
122 * you don't want the window to be destroyed.
123 * This is useful for popping up 'are you sure you want to quit?'
129 // destroy widget if destroy signal is passed to widget
130 static void destroy( ui::Widget widget, gpointer data ){
134 // function for close button to destroy the toplevel widget
135 static void close_window( ui::Widget widget, gpointer data ){
136 widget.window().destroy();
139 // callback function to assign the optimal mapcoords to the spinboxes
140 static void input_optimal(ui::Widget widget, gpointer data ){
141 gtk_spin_button_set_value( msp.spinner1, minX );
142 gtk_spin_button_set_value( msp.spinner2, maxY );
143 gtk_spin_button_set_value( msp.spinner3, maxX );
144 gtk_spin_button_set_value( msp.spinner4, minY );
147 // Spinner return value function
148 gint grab_int_value(ui::SpinButton a_spinner, gpointer user_data ) {
149 return gtk_spin_button_get_value_as_int( a_spinner );
152 // write the values of the Spinner-Boxes to the worldspawn
153 static void set_coordinates( ui::Widget widget, gpointer data ){
154 //Str str_min, str_max;
155 char buffer[10], str_min[20], str_max[20];
157 itoa( gtk_spin_button_get_value_as_int( msp.spinner1 ), str_min, 10 );
158 itoa( gtk_spin_button_get_value_as_int( msp.spinner2 ), buffer, 10 );
159 strcat( str_min, " " );
160 strcat( str_min, buffer );
161 msp.worldspawn->setKeyValue( "mapcoordsmins", str_min );
163 itoa( gtk_spin_button_get_value_as_int( msp.spinner3 ), str_max, 10 );
164 itoa( gtk_spin_button_get_value_as_int( msp.spinner4 ), buffer, 10 );
165 strcat( str_max, " " );
166 strcat( str_max, buffer );
167 UndoableCommand undo( "SunPlug.entitySetMapcoords" );
168 msp.worldspawn->setKeyValue( "mapcoordsmaxs", str_max );
170 close_window( widget, NULL );
173 class SunPlugPluginDependencies :
174 public GlobalRadiantModuleRef, // basic class for all other module refs
175 public GlobalUndoModuleRef, // used to say radiant that something has changed and to undo that
176 public GlobalSceneGraphModuleRef, // necessary to handle data in the mapfile (change, retrieve data)
177 public GlobalEntityModuleRef // to access and modify the entities
180 SunPlugPluginDependencies() :
181 GlobalEntityModuleRef( GlobalRadiant().getRequiredGameDescriptionKeyValue( "entities" ) ){ //,
185 // *************************
186 // ** standard plugin stuff **
187 // *************************
190 ui::Window main_window{ui::null};
191 char MenuList[100] = "";
193 const char* init( void* hApp, void* pMainWidget ){
194 main_window = ui::Window::from(pMainWidget);
195 return "Initializing " PLUGIN_NAME " for " RADIANT_NAME;
197 const char* getName(){
198 return PLUGIN_NAME; // name that is shown in the menue
200 const char* getCommandList(){
201 const char about[] = CMD_ABOUT;
202 const char etMapCoordinator[] = ";ET-MapCoordinator";
204 strcat( MenuList, about );
205 if ( strncmp( GlobalRadiant().getGameName(), "etmain", 6 ) == 0 ) {
206 strcat( MenuList, etMapCoordinator );
208 return (const char*)MenuList;
210 const char* getCommandTitleList(){
213 void dispatch( const char* command, float* vMin, float* vMax, bool bSingleBrush ){ // message processing
214 if ( string_equal( command, CMD_ABOUT ) ) {
215 about_plugin_window();
217 if ( string_equal( command, "ET-MapCoordinator" ) ) {
223 class SunPlugModule : public TypeSystemRef
225 _QERPluginTable m_plugin;
227 typedef _QERPluginTable Type;
228 STRING_CONSTANT( Name, PLUGIN_NAME );
231 m_plugin.m_pfnQERPlug_Init = &SunPlug::init;
232 m_plugin.m_pfnQERPlug_GetName = &SunPlug::getName;
233 m_plugin.m_pfnQERPlug_GetCommandList = &SunPlug::getCommandList;
234 m_plugin.m_pfnQERPlug_GetCommandTitleList = &SunPlug::getCommandTitleList;
235 m_plugin.m_pfnQERPlug_Dispatch = &SunPlug::dispatch;
237 _QERPluginTable* getTable(){
242 typedef SingletonModule<SunPlugModule, SunPlugPluginDependencies> SingletonSunPlugModule;
244 SingletonSunPlugModule g_SunPlugModule;
247 extern "C" void RADIANT_DLLEXPORT Radiant_RegisterModules( ModuleServer& server ){
248 initialiseModule( server );
250 g_SunPlugModule.selfRegister();
258 void about_plugin_window(){
259 auto window = ui::Window( ui::window_type::TOP ); // create a window
260 gtk_window_set_transient_for( window, SunPlug::main_window ); // make the window to stay in front of the main window
261 window.connect( "delete_event", G_CALLBACK( delete_event ), NULL ); // connect the delete event
262 window.connect( "destroy", G_CALLBACK( destroy ), NULL ); // connect the destroy event for the window
263 gtk_window_set_title( window, "About " PLUGIN_NAME ); // set the title of the window for the window
264 gtk_window_set_resizable( window, FALSE ); // don't let the user resize the window
265 gtk_window_set_modal( window, TRUE ); // force the user not to do something with the other windows
266 gtk_container_set_border_width( GTK_CONTAINER( window ), 10 ); // set the border of the window
268 auto vbox = ui::VBox( FALSE, 10 ); // create a box to arrange new objects vertically
271 char const *label_text =
272 PLUGIN_NAME " " PLUGIN_VERSION " for "
273 RADIANT_NAME " " RADIANT_VERSION "\n\n"
274 "Written by Topsun\n\n"
276 RADIANT_NAME " " RADIANT_VERSION_STRING "\n"
279 auto label = ui::Label( label_text ); // create a label
280 gtk_label_set_justify( GTK_LABEL( label ), GTK_JUSTIFY_LEFT ); // text align left
281 vbox.pack_start( label, FALSE, FALSE, 2 ); // insert the label in the box
283 auto button = ui::Button( "OK" ); // create a button with text
284 g_signal_connect_swapped( G_OBJECT( button ), "clicked", G_CALLBACK( gtk_widget_destroy ), (void *) window ); // connect the click event to close the window
285 vbox.pack_start( button, FALSE, FALSE, 2 ); // insert the button in the box
287 gtk_window_set_position( window, GTK_WIN_POS_CENTER ); // center the window on screen
289 gtk_widget_show_all( window ); // show the window and all subelements
292 // get the current bounding box and return the optimal coordinates
293 void GetOptimalCoordinates( AABB *levelBoundingBox ){
294 int half_width, half_heigth, center_x, center_y;
296 half_width = levelBoundingBox->extents.x();
297 half_heigth = levelBoundingBox->extents.y();
298 center_x = levelBoundingBox->origin.x();
299 center_y = levelBoundingBox->origin.y();
301 if ( half_width > 175 || half_heigth > 175 ) { // the square must be at least 350x350 units
302 // the wider side is the indicator for the square
303 if ( half_width >= half_heigth ) {
304 minX = center_x - half_width;
305 maxX = center_x + half_width;
306 minY = center_y - half_width;
307 maxY = center_y + half_width;
310 minX = center_x - half_heigth;
311 maxX = center_x + half_heigth;
312 minY = center_y - half_heigth;
313 maxY = center_y + half_heigth;
317 minX = center_x - 175;
318 maxX = center_x + 175;
319 minY = center_y - 175;
320 maxY = center_y + 175;
324 // MapCoordinator dialog window
325 void MapCoordinator(){
326 Entity *theWorldspawn = NULL;
330 // in any case we need a window to show the user what to do
331 auto window = ui::Window( ui::window_type::TOP ); // create the window
332 gtk_window_set_transient_for( window, SunPlug::main_window ); // make the window to stay in front of the main window
333 window.connect( "delete_event", G_CALLBACK( delete_event ), NULL ); // connect the delete event for the window
334 window.connect( "destroy", G_CALLBACK( destroy ), NULL ); // connect the destroy event for the window
335 gtk_window_set_title( window, "ET-MapCoordinator" ); // set the title of the window for the window
336 gtk_window_set_resizable( window, FALSE ); // don't let the user resize the window
337 gtk_window_set_modal( window, TRUE ); // force the user not to do something with the other windows
338 gtk_container_set_border_width( GTK_CONTAINER( window ), 10 ); // set the border of the window
340 auto vbox = ui::VBox( FALSE, 10 ); // create a box to arrange new objects vertically
343 scene::Path path = makeReference( GlobalSceneGraph().root() ); // get the path to the root element of the graph
344 scene::Instance* instance = GlobalSceneGraph().find( path ); // find the instance to the given path
345 AABB levelBoundingBox = instance->worldAABB(); // get the bounding box of the level
347 theWorldspawn = Scene_FindEntityByClass( "worldspawn" ); // find the entity worldspawn
348 if ( theWorldspawn != 0 ) { // need to have a worldspawn otherwise setting a value crashes the radiant
349 // next two if's: get the current values of the mapcoords
350 buffer = theWorldspawn->getKeyValue( "mapcoordsmins" ); // upper left corner
351 if ( strlen( buffer ) > 0 ) {
352 strncpy( line, buffer, 19 );
353 map_minX = atoi( strtok( line, " " ) ); // minimum of x value
354 map_minY = atoi( strtok( NULL, " " ) ); // maximum of y value
360 buffer = theWorldspawn->getKeyValue( "mapcoordsmaxs" ); // lower right corner
361 if ( strlen( buffer ) > 0 ) {
362 strncpy( line, buffer, 19 );
363 map_maxX = atoi( strtok( line, " " ) ); // maximum of x value
364 map_maxY = atoi( strtok( NULL, " " ) ); // minimum of y value
371 globalOutputStream() << "SunPlug: calculating optimal coordinates\n"; // write to console that we are calculating the coordinates
372 GetOptimalCoordinates( &levelBoundingBox ); // calculate optimal mapcoords with the dimensions of the level bounding box
373 globalOutputStream() << "SunPlug: adviced mapcoordsmins=" << minX << " " << maxY << "\n"; // console info about mapcoordsmins
374 globalOutputStream() << "SunPlug: adviced mapcoordsmaxs=" << maxX << " " << minY << "\n"; // console info about mapcoordsmaxs
376 auto spinner_adj_MinX = ui::Adjustment( map_minX, -65536.0, 65536.0, 1.0, 5.0, 0 ); // create adjustment for value and range of minimum x value
377 auto spinner_adj_MinY = ui::Adjustment( map_minY, -65536.0, 65536.0, 1.0, 5.0, 0 ); // create adjustment for value and range of minimum y value
378 auto spinner_adj_MaxX = ui::Adjustment( map_maxX, -65536.0, 65536.0, 1.0, 5.0, 0 ); // create adjustment for value and range of maximum x value
379 auto spinner_adj_MaxY = ui::Adjustment( map_maxY, -65536.0, 65536.0, 1.0, 5.0, 0 ); // create adjustment for value and range of maximum y value
381 auto button = ui::Button( "Get optimal mapcoords" ); // create button with text
382 button.connect( "clicked", G_CALLBACK( input_optimal ), NULL ); // connect button with callback function
383 vbox.pack_start( button, FALSE, FALSE, 2 ); // insert button into vbox
385 vbox.pack_start( ui::Widget::from(gtk_hseparator_new()), FALSE, FALSE, 2 ); // insert separator into vbox
387 auto table = ui::Table( 4, 3, TRUE ); // create table
388 gtk_table_set_row_spacings(table, 8); // set row spacings
389 gtk_table_set_col_spacings(table, 8); // set column spacings
390 vbox.pack_start( table, FALSE, FALSE, 2 ); // insert table into vbox
392 auto label = ui::Label( "x" ); // create label
393 gtk_label_set_justify( GTK_LABEL( label ), GTK_JUSTIFY_LEFT ); // align text to the left side
394 table.attach(label, {1, 2, 0, 1}); // insert label into table
396 label = ui::Label( "y" ); // create label
397 gtk_label_set_justify( GTK_LABEL( label ), GTK_JUSTIFY_LEFT ); // align text to the left side
398 table.attach(label, {2, 3, 0, 1}); // insert label into table
400 label = ui::Label( "mapcoordsmins" ); // create label
401 gtk_label_set_justify( GTK_LABEL( label ), GTK_JUSTIFY_LEFT ); // align text to the left side
402 table.attach(label, {0, 1, 1, 2}); // insert label into table
404 auto spinnerMinX = ui::SpinButton(spinner_adj_MinX, 1.0, 0); // create textbox wiht value spin, value and value range
405 table.attach(spinnerMinX, {1, 2, 1, 2}); // insert spinbox into table
407 auto spinnerMinY = ui::SpinButton(spinner_adj_MinY, 1.0, 0); // create textbox wiht value spin, value and value range
408 table.attach(spinnerMinY, {2, 3, 1, 2}); // insert spinbox into table
410 label = ui::Label( "mapcoordsmaxs" ); // create label
411 gtk_label_set_justify( GTK_LABEL( label ), GTK_JUSTIFY_LEFT ); // align text to the left side
412 table.attach(label, {0, 1, 2, 3}); // insert label into table
414 auto spinnerMaxX = ui::SpinButton(spinner_adj_MaxX, 1.0, 0); // create textbox wiht value spin, value and value range
415 table.attach(spinnerMaxX, {1, 2, 2, 3}); // insert spinbox into table
417 auto spinnerMaxY = ui::SpinButton(spinner_adj_MaxY, 1.0, 0); // create textbox wiht value spin, value and value range
418 table.attach(spinnerMaxY, {2, 3, 2, 3}); // insert spinbox into table
420 // put the references to the spinboxes and the worldspawn into the global exchange
421 msp.spinner1 = spinnerMinX;
422 msp.spinner2 = spinnerMinY;
423 msp.spinner3 = spinnerMaxX;
424 msp.spinner4 = spinnerMaxY;
425 msp.worldspawn = theWorldspawn;
427 button = ui::Button( "Set" ); // create button with text
428 button.connect( "clicked", G_CALLBACK( set_coordinates ), NULL ); // connect button with callback function
429 table.attach(button, {1, 2, 3, 4}); // insert button into table
431 button = ui::Button( "Cancel" ); // create button with text
432 button.connect( "clicked", G_CALLBACK( close_window ), NULL ); // connect button with callback function
433 table.attach(button, {2, 3, 3, 4}); // insert button into table
436 globalOutputStream() << "SunPlug: no worldspawn found!\n"; // output error to console
438 auto label = ui::Label( "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
439 gtk_label_set_justify( GTK_LABEL( label ), GTK_JUSTIFY_LEFT ); // text align left
440 vbox.pack_start( label, FALSE, FALSE, 2 ); // insert the label in the box
442 auto button = ui::Button( "OK" ); // create a button with text
443 button.connect( "clicked", G_CALLBACK( close_window ), NULL ); // connect the click event to close the window
444 vbox.pack_start( button, FALSE, FALSE, 2 ); // insert the button in the box
447 gtk_window_set_position( window, GTK_WIN_POS_CENTER ); // center the window
448 gtk_widget_show_all( window ); // show the window and all subelements